From 0595d6b88d180bc83bb34a0a86704a37b11d5c56 Mon Sep 17 00:00:00 2001 From: Philippe Accorsi Date: Wed, 7 Sep 2022 13:25:18 +0000 Subject: [PATCH 001/682] :globe_with_meridians: Add translations for: French. Currently translated at 86.3% (1044 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/ --- frontend/translations/fr.po | 347 +++++++++++++++++++++++++++++++++++- 1 file changed, 342 insertions(+), 5 deletions(-) diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index debf7c8895..14901e1516 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-03 08:15+0000\n" -"Last-Translator: Aimee \n" -"Language-Team: French " -"\n" +"PO-Revision-Date: 2022-09-10 17:20+0000\n" +"Last-Translator: Philippe Accorsi \n" +"Language-Team: French \n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -3782,4 +3782,341 @@ msgid "workspace.updates.update" msgstr "Actualiser" msgid "workspace.viewport.click-to-close-path" -msgstr "Cliquez pour fermer le chemin" \ No newline at end of file +msgstr "Cliquez pour fermer le chemin" + +msgid "workspace.shape.menu.transform-to-path" +msgstr "Transformer en chemin d'accès" + +msgid "workspace.options.grid.params.color" +msgstr "Couleur" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-center" +msgstr "Au centre" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-right" +msgstr "En haut à droite" + +msgid "workspace.options.shadow-options.color" +msgstr "Couleur de l'ombre" + +msgid "workspace.options.width" +msgstr "Largeur" + +msgid "workspace.options.x" +msgstr "X" + +msgid "workspace.shape.menu.exclude" +msgstr "Exclure" + +msgid "workspace.shape.menu.path" +msgstr "Chemin d'accès" + +msgid "shortcuts.ungroup" +msgstr "Dégrouper" + +msgid "shortcuts.unmask" +msgstr "Démasquer" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "Quadrillage" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "Options avancées" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Redimensionnement de l'élément" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "au centre" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "En bas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Colonne" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "Colonne inversée" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "a droite" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "A gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "La marge" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "Tous les côtés" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Une marge simple" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "À droite" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.title" +msgstr "Mise en page" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "En haut" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Éditer" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Fichier" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-multiple" +msgstr "Exporter la sélection" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "L'export est terminé" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-action" +msgstr "Action" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-none" +msgstr "Aucune" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.retry" +msgstr "Réessayer" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "Couleurs sélectionnées" + +msgid "workspace.sidebar.layers.groups" +msgstr "Groupes" + +msgid "workspace.sidebar.layers.texts" +msgstr "Textes" + +msgid "workspace.sidebar.layers.masks" +msgstr "Les masques" + +msgid "common.publish" +msgstr "Publier" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Attention" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Inviter des membres dans l'équipe" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Suppression du fichier" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "L'export a échoué" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-slow" +msgstr "L'export est étonnamment lent" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-duration" +msgstr "Durée" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-out" +msgstr "Sortie" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-left" +msgstr "En bas à gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-center" +msgstr "Centrer en haut" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-left" +msgstr "En haut à gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-preserve-scroll" +msgstr "Conserver la position du défilement" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-url" +msgstr "URL" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Hauteur maximale" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Largeur maximale" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Hauteur minimale" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Largeur minimale" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Largeur max" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Hauteur min" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Largeur min" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "a gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "Tous les côtés" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "en bas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "en haut" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "Aucune" + +msgid "workspace.sidebar.layers.images" +msgstr "Images" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Supprimer le fichier" +msgstr[1] "Supprimer les fichiers" + +msgid "onboarding.team-modal.create-team" +msgstr "Créer une équipe" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Nombre de membres illimité" + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Nombre de projets et de fichiers illimité" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% gratuit !" + +msgid "workspace.options.height" +msgstr "Hauteur" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-trigger" +msgstr "Déclencheur" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-prev-screen" +msgstr "Écran précédent" + +msgid "workspace.options.y" +msgstr "Y" + +msgid "viewer.breaking-change.message" +msgstr "Désolé !" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-full-screen" +msgstr "Plein écran" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-none" +msgstr "(non définie)" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.add-interaction" +msgstr "Cliquer sur le bouton + pour ajouter des interactions." + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-url" +msgstr "Ouvrir l'url" + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Guide utilisateur" + +msgid "onboarding-v2.welcome.title" +msgstr "Bienvenu sur Penpot !" + +msgid "onboarding-v2.before-start.title" +msgstr "Avant de démarrer" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Êtes-vous sûr de vouloir supprimer ce fichier ?" +msgstr[1] "Êtes-vous sûr de vouloir supprimer ces fichiers ?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Suppression du fichier" +msgstr[1] "Suppression des fichiers" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Démarrer le tutoriel" + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Tutoriels videos" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-right" +msgstr "En bas à droite" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Hauteur max" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "au centre" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "Plus de couleurs" From d47d687b439aff6a44c0ccc61b0fec50c9fcdd13 Mon Sep 17 00:00:00 2001 From: Beeby Xia Date: Fri, 9 Sep 2022 01:35:35 +0000 Subject: [PATCH 002/682] :globe_with_meridians: Add translations for: Chinese (Simplified). Currently translated at 89.9% (1088 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/ --- frontend/translations/zh_CN.po | 555 ++++++++++++++++++++++++++++++++- 1 file changed, 542 insertions(+), 13 deletions(-) diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 8d52b2d708..2791abb9e4 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-06-19 04:19+0000\n" -"Last-Translator: Wang Jiaxiang \n" -"Language-Team: Chinese (Simplified) " -"\n" +"PO-Revision-Date: 2022-09-10 17:20+0000\n" +"Last-Translator: Beeby Xia \n" +"Language-Team: Chinese (Simplified) \n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Weblate 4.13.1-dev\n" +"X-Generator: Weblate 4.14.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -61,23 +61,23 @@ msgstr "很高兴又见到你!" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "GitHub" +msgstr "GitHub登录" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Gitlab" +msgstr "Gitlab登录" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" -msgstr "Google" +msgstr "Google登录" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "LDAP" +msgstr "LDAP登录" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "OpenID" +msgstr "OpenID登录" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -641,14 +641,14 @@ msgstr "无效的颜色" #: src/app/main/ui/auth/verify_token.cljs msgid "errors.invite-invalid" -msgstr "邀请无效" +msgstr "无效的邀请" msgid "errors.invite-invalid.info" msgstr "此邀请可能已取消或已过期。" #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" -msgstr "仅用了LDAP授权。" +msgstr "LDAP身份验证已禁用。" msgid "errors.media-format-unsupported" msgstr "不支持该图片格式(只能是svg、jpg或png)。" @@ -3527,4 +3527,533 @@ msgid "workspace.updates.update" msgstr "更新" msgid "workspace.viewport.click-to-close-path" -msgstr "单击以闭合路径" \ No newline at end of file +msgstr "单击以闭合路径" + +msgid "shortcuts.clear-undo" +msgstr "清除回退内容" + +msgid "common.share-link.all-users" +msgstr "所有Penpot用户" + +msgid "common.share-link.current-tag" +msgstr "(当前)" + +msgid "common.share-link.destroy-link" +msgstr "去除链接" + +msgid "common.share-link.permissions-can-comment" +msgstr "可评论" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1页已共享" +msgstr[1] "%s页已共享" + +msgid "common.share-link.manage-ops" +msgstr "权限管理" + +msgid "common.share-link.team-members" +msgstr "只团队成员" + +msgid "common.share-link.view-all" +msgstr "选择所有" + +msgid "common.share-link.permissions-pages" +msgstr "页面已共享" + +msgid "dashboard.download-binary-file" +msgstr "下载Penpot文件 (.penpot)" + +msgid "dashboard.export-binary-multi" +msgstr "下载 %s Penpot文件 (.penpot)" + +msgid "dashboard.export-standard-multi" +msgstr "下载 %s 标准文件 (.svg + .json)" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "我们很欢迎你的到来。请在发布帮助请求前搜索你所需要的帮助内容。" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "前往Penpot论坛" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Penpot社区" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "前往Twtter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "这里可以帮助您解决技术问题。" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-title" +msgstr "Twtter支持帐号" + +msgid "labels.continue-with-penpot" +msgstr "你可以使用Penpot帐号继续" + +msgid "labels.log-or-sign" +msgstr "登录或注册" + +msgid "labels.show-comments-list" +msgstr "显示评论列表" + +msgid "shortcuts.add-comment" +msgstr "评论" + +msgid "shortcuts.add-node" +msgstr "添加节点" + +msgid "shortcuts.align-bottom" +msgstr "底部对齐" + +msgid "shortcuts.align-hcenter" +msgstr "水平居中对齐" + +msgid "shortcuts.align-left" +msgstr "左对齐" + +msgid "shortcuts.align-top" +msgstr "顶部对齐" + +msgid "shortcuts.align-vcenter" +msgstr "垂直居中对齐" + +msgid "shortcuts.artboard-selection" +msgstr "以所选内容创建画板" + +msgid "shortcuts.bool-difference" +msgstr "布尔差" + +msgid "shortcuts.bool-exclude" +msgstr "布尔排除" + +msgid "shortcuts.bool-intersection" +msgstr "布尔交集" + +msgid "shortcuts.bool-union" +msgstr "布尔合并" + +msgid "shortcuts.bring-forward" +msgstr "移至上一层" + +msgid "shortcuts.bring-front" +msgstr "移至最上层" + +msgid "shortcuts.bring-backward" +msgstr "移至下一层" + +msgid "shortcuts.create-component" +msgstr "创建组件" + +msgid "shortcuts.next-frame" +msgstr "下个画板" + +msgid "shortcuts.not-found" +msgstr "没找到快捷方式" + +msgid "shortcuts.opacity-9" +msgstr "设置不透明度为90%" + +msgid "shortcuts.or" +msgstr " 或 " + +msgid "common.publish" +msgstr "发布" + +msgid "common.unpublish" +msgstr "未发布" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "团队管理" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "好" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "注意" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "没有配置身份认证服务源" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "社区" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "邀请成员加入团队" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "正在删除文件" + +msgid "onboarding-v2.before-start.desc1" +msgstr "有很多资源可以帮助你开始使用Penpot,如用户指南和我们的Youtube频道。" + +msgid "shortcut-subsection.shape" +msgstr "形状" + +msgid "shortcut-subsection.tools" +msgstr "工具" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "缩放" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "缩放" + +msgid "shortcuts.align-right" +msgstr "右对齐" + +msgid "shortcuts.bring-back" +msgstr "移至最下层" + +msgid "shortcuts.cut" +msgstr "剪切" + +msgid "shortcuts.decrease-zoom" +msgstr "缩小" + +msgid "shortcuts.delete" +msgstr "删除" + +msgid "shortcuts.delete-node" +msgstr "删除节点" + +msgid "shortcuts.detach-component" +msgstr "拆分组件" + +msgid "shortcuts.draw-curve" +msgstr "曲线" + +msgid "shortcuts.draw-ellipse" +msgstr "椭圆" + +msgid "shortcuts.draw-frame" +msgstr "画板" + +msgid "shortcuts.move-unit-up" +msgstr "上移" + +msgid "shortcuts.open-interactions" +msgstr "转往阅读器交互部分" + +msgid "shortcuts.open-handoff" +msgstr "前往阅读器切换部分" + +msgid "shortcuts.open-viewer" +msgstr "转往阅读器交互部分" + +msgid "shortcuts.open-workspace" +msgstr "前往工作区" + +msgid "shortcuts.paste" +msgstr "粘贴" + +msgid "shortcuts.prev-frame" +msgstr "前一画板" + +msgid "shortcuts.redo" +msgstr "重做" + +msgid "shortcuts.reset-zoom" +msgstr "重置缩放" + +msgid "shortcuts.search-placeholder" +msgstr "搜索快捷方式" + +msgid "shortcuts.show-pixel-grid" +msgstr "显示/隐藏像素网格" + +msgid "shortcuts.show-shortcuts" +msgstr "显示/隐藏快捷方式" + +msgid "shortcuts.snap-nodes" +msgstr "对齐到节点" + +msgid "common.share-link.permissions-can-inspect" +msgstr "可审查代码" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "组建团队!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "实践教程" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "浏览Penpot, 了解其主要功能。" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "开始浏览" + +msgid "dashboard.download-standard-file" +msgstr "下载标准文件(.svg + .json)" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "界面浏览" + +msgid "dashboard.libraries-and-templates" +msgstr "库和模板" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "导入模板时发生错误。模板未导入成功。" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "未发布库" + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "此文件已经在组件V2版本下使用。" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "你是否确认要删除这个文件?" +msgstr[1] "你是否确认要删除这些文件?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "此文件中使用了以下库:" +msgstr[1] "这些文件中使用了以下库:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "删除文件" +msgstr[1] "批量删除文件" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "如果取消发布,则其中的资源将成为此文件的一个库。" +msgstr[1] "如果取消发布,则其中的资源将成为这些文件的一个库。" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "你是否确认取消发布这个库?" +msgstr[1] "你是否确认取消发布这些库?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "在这个文件中被使用:" +msgstr[1] "在这些文件中被使用:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "取消发布库" +msgstr[1] "批量取消发布库" + +msgid "onboarding-v2.before-start.desc2" +msgstr "有关如何使用Penpot的详细信息。从原型设计到组织或共享设计。" + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "用户指南" + +msgid "onboarding-v2.before-start.desc3" +msgstr "您可以观看我们的官方教程以及社区制作的教程。" + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "视频教程" + +msgid "onboarding-v2.before-start.title" +msgstr "在开始之前" + +msgid "onboarding-v2.welcome.desc2" +msgstr "一个与整个社区和Penpot核心团队学习、分享和讨论Penpot及其现在和未来的公共空间" +"。" + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "参与到社区中" + +msgid "onboarding-v2.welcome.desc3" +msgstr "在这里,您将了解如何协作进行翻译、功能需求提出、核心代码贡献、BUG修复等…" + +msgid "onboarding.choice.team-up.create-later" +msgstr "稍后创建团队" + +msgid "onboarding.team-modal.create-team" +msgstr "创建一个团队" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "团队能够让你与其它Penpot用户协作处理相同的文件和项目。" + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "无限制的文件和项目" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "无限制成员" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "完全免费!" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "贡献指南" + +msgid "onboarding.choice.team-up.roles" +msgstr "邀请角色:" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "开始教程" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "Penpot是为团队协作而设计,邀请成员合作处理项目和文件" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "通过有趣的实践教程学习Penpot的基础知识。" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "探索更多内容,了解如何做出贡献" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "删除文件" +msgstr[1] "批量删除文件" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "这些文件中的库在此文件中使用:" +msgstr[1] "这些文件中的库在此批文件中使用:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "取消发布" + +msgid "onboarding-v2.welcome.desc1" +msgstr "Penpot是由Kaleidos及社区共同开发的开源软件,许多人已经在社区中互相帮助。每个" +"人都可以通过以下方式进行协作:" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "记得将开发人员、设计师、经理……等各类人员都加进来:)" + +msgid "onboarding-v2.welcome.title" +msgstr "欢迎来到Penpot!" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "多用户编辑" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "角色管理" + +msgid "shortcuts.copy" +msgstr "拷贝" + +msgid "shortcuts.create-new-project" +msgstr "创建新的" + +msgid "shortcuts.escape" +msgstr "取消" + +msgid "shortcuts.fit-all" +msgstr "缩放至适应所有" + +msgid "shortcuts.go-to-libs" +msgstr "前往共享库" + +msgid "shortcuts.draw-nodes" +msgstr "绘制路径" + +msgid "shortcuts.draw-path" +msgstr "路径" + +msgid "shortcuts.draw-rect" +msgstr "长方形" + +msgid "shortcuts.draw-text" +msgstr "文本" + +msgid "shortcuts.flip-horizontal" +msgstr "水平翻转" + +msgid "shortcuts.flip-vertical" +msgstr "垂直翻转" + +msgid "shortcuts.go-to-drafts" +msgstr "前往草稿" + +msgid "shortcuts.go-to-search" +msgstr "搜索" + +msgid "shortcuts.group" +msgstr "组" + +msgid "shortcuts.h-distribute" +msgstr "水平分布" + +msgid "shortcuts.opacity-3" +msgstr "设置不透明度为30%" + +msgid "shortcuts.open-color-picker" +msgstr "色彩拾取器" + +msgid "shortcuts.duplicate" +msgstr "复制" + +msgid "shortcuts.opacity-2" +msgstr "设置不透明度为20%" + +msgid "shortcuts.opacity-5" +msgstr "设置不透明度为50%" + +msgid "shortcuts.opacity-4" +msgstr "设置不透明度为40%" + +msgid "shortcuts.opacity-6" +msgstr "设置不透明度为60%" + +msgid "shortcuts.export-shapes" +msgstr "导出形状" + +msgid "shortcuts.move-unit-right" +msgstr "右移" + +msgid "shortcuts.opacity-0" +msgstr "设置不透明度为100%" + +msgid "shortcuts.opacity-8" +msgstr "设置不透明度为80%" + +msgid "shortcuts.move-unit-left" +msgstr "左移" + +msgid "shortcuts.opacity-1" +msgstr "设置不透明度为10%" + +msgid "shortcuts.opacity-7" +msgstr "设置不透明度为70%" + +msgid "shortcuts.open-comments" +msgstr "前往查阅者评论区" + +msgid "shortcuts.open-dashboard" +msgstr "前往看板" + +msgid "shortcuts.select-all" +msgstr "选择所有" + +msgid "shortcuts.separate-nodes" +msgstr "分离节点" From e4e0deeb1cea06f04156b5c5b1fdf11cf20595bf Mon Sep 17 00:00:00 2001 From: Stas Haas Date: Tue, 6 Sep 2022 07:46:18 +0000 Subject: [PATCH 003/682] :globe_with_meridians: Add translations for: German. Currently translated at 98.9% (1196 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 445 +++++++++++++++++++++++++++++++++++- 1 file changed, 441 insertions(+), 4 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index bcce76f7db..a247815764 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-06 06:05+0000\n" +"PO-Revision-Date: 2022-09-10 17:20+0000\n" "Last-Translator: Stas Haas \n" -"Language-Team: German " -"\n" +"Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -4328,4 +4328,441 @@ msgid "workspace.updates.update" msgstr "Aktualisieren" msgid "workspace.viewport.click-to-close-path" -msgstr "Klicken Sie, um den Pfad zu schließen" \ No newline at end of file +msgstr "Klicken Sie, um den Pfad zu schließen" + +msgid "shortcuts.opacity-8" +msgstr "Deckkraft auf 80% setzen" + +msgid "shortcuts.opacity-9" +msgstr "Deckkraft auf 90% setzen" + +msgid "shortcuts.open-color-picker" +msgstr "Farbwähler" + +msgid "shortcuts.not-found" +msgstr "Kein Tastenkürzel gefunden" + +msgid "shortcuts.open-handoff" +msgstr "Zur Übergabe im Ansichtsmodus" + +msgid "shortcuts.open-dashboard" +msgstr "Zum Dashboard" + +msgid "shortcuts.open-workspace" +msgstr "Zum Arbeitsbereich" + +msgid "shortcuts.or" +msgstr " oder " + +msgid "shortcuts.paste" +msgstr "Einfügen" + +msgid "shortcuts.prev-frame" +msgstr "Vorheriges Board" + +msgid "shortcuts.toggle-grid" +msgstr "Raster ein-/ausblenden" + +msgid "shortcuts.toggle-layers" +msgstr "Ebenen ein-/ausblenden" + +msgid "shortcuts.toggle-alignment" +msgstr "Dynamische Ausrichtung umschalten" + +msgid "shortcuts.toggle-assets" +msgstr "Assets einblenden" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Farbpalette ein-/ausblenden" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Fokusmodus umschalten" + +msgid "shortcuts.toggle-lock" +msgstr "Auswahl sperren" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "Mitte" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.packed" +msgstr "kompakt" + +msgid "common.unpublish" +msgstr "Veröffentlichung aufheben" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "OK" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Achtung" + +msgid "shortcuts.opacity-7" +msgstr "Deckkraft auf 70% setzen" + +msgid "shortcuts.redo" +msgstr "Wiederholen" + +msgid "shortcuts.snap-pixel-grid" +msgstr "Am Pixelraster ausrichten" + +msgid "shortcuts.search-placeholder" +msgstr "Tastenkürzel suchen" + +msgid "shortcuts.start-editing" +msgstr "Mit der Bearbeitung beginnen" + +msgid "shortcuts.open-comments" +msgstr "Zum Kommentarbereich im Ansichtsmodus" + +msgid "shortcuts.thumbnail-set" +msgstr "Miniaturansichten festlegen" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Tastatürkürzel" + +msgid "shortcuts.toggle-history" +msgstr "Verlauf ein-/ausblenden" + +msgid "shortcuts.start-measure" +msgstr "Mit der Vermessung beginnen" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Max.Höhe" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "im Leerraum verteilt" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot ist für Teams gedacht. Um gemeinsam an Projekten und Dateien zu " +"arbeiten, laden Sie Mitglieder ein." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Teamwork!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Tutorial starten" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Praktisches Tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Tour starten" + +msgid "dashboard.libraries-and-templates" +msgstr "Bibliotheken & Vorlagen" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "" +"Beim Importieren der Vorlage ist ein Problem aufgetreten. Die Vorlage wurde " +"nicht importiert." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Veröffentlichung der Bibliothek aufheben" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "" +"Erkunden Sie Penpot um mehr über die wichtigsten Funktionen zu erfahren." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Datei löschen" +msgstr[1] "Dateien löschen" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Möchten Sie diese Datei wirklich löschen?" +msgstr[1] "Möchten Sie diese Dateien wirklich löschen?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Datei löschen" +msgstr[1] "Dateien löschen" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Veröffentlichung aufheben" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Es wird in dieser Datei verwendet:" +msgstr[1] "Es wird in diesen Dateien verwendet:" + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Benutzerhandbuch" + +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"Sie können sich unsere Tutorials und die von unserer Community erstellten " +"Tutorials ansehen." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Video-Tutorials" + +msgid "onboarding-v2.before-start.title" +msgstr "Bevor Sie beginnen" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Diese Datei enthält Bibliotheken, die in dieser Datei verwendet werden:" +msgstr[1] "" +"Diese Datei enthält Bibliotheken, die in diesen Dateien verwendet werden:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "" +"Diese Dateien enthalten Bibliotheken, die in dieser Datei verwendet werden:" +msgstr[1] "" +"Diese Dateien enthalten Bibliotheken, die in diesen Dateien verwendet werden:" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot ist Open Source und wird sowohl von Kaleidos als auch von der " +"Community entwickelt, wo sich viele Leute bereits gegenseitig helfen. Jeder " +"kann mitmachen:" + +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Sie sollten wissen, dass es viele Ressourcen gibt, die Ihnen beim Einstieg " +"in Penpot erleichtern, wie z. B. das Benutzerhandbuch und unser Youtube-" +"Kanal." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "In der Community mitmachen" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Leitfaden für Mitwirkende" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Hier erfahren Sie, wie Sie bei Übersetzungen, Feature Requests, Core-" +"Entwicklung und der Fehlersuche helfen können…" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Team später erstellen" + +msgid "onboarding.team-modal.create-team" +msgstr "Ein Team erstellen" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"In einem Team können Sie mit anderen Penpot-Nutzern zusammenarbeiten, die an " +"denselben Dateien und Projekten arbeiten." + +msgid "onboarding.choice.team-up.roles" +msgstr "Einladen mit der Rolle:" + +msgid "workspace.assets.local-library" +msgstr "lokale Bibliothek" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Maximale Höhe" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Maximale Breite" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Mindesthöhe" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Mindestbreite" + +msgid "workspace.shape.menu.restore-main" +msgstr "Hauptkomponente wiederherstellen" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Lernen Sie die Grundlagen von Penpot und haben Sie Spaß mit diesem " +"praktischen Tutorial." + +msgid "common.publish" +msgstr "Veröffentlichen" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Teammanagement" + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Diese Datei wurde bereits mit aktivierten V2-Komponenten verwendet." + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Mitglieder in das Team einladen" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Datei löschen" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Veröffentlichung der Bibliothek aufheben" +msgstr[1] "Veröffentlichung der Bibliotheken aufheben" + +msgid "onboarding-v2.welcome.title" +msgstr "Willkommen bei Penpot!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Benutzeroberfläche erkunden" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Möchten Sie die Veröffentlichung dieser Bibliothek wirklich aufheben?" +msgstr[1] "Möchten Sie die Veröffentlichung dieser Bibliotheken wirklich aufheben?" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Denken Sie daran, alle einzubeziehen. Entwickler, Designer, Manager... die " +"Vielfalt macht's :)" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Unbegrenzte Anzahl von Mitgliedern" + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Unbegrenzte Anzahl von Dateien und Projekten" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Multiplayer-Edition" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Rollenverwaltung" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% kostenlos!" + +msgid "shortcuts.stop-measure" +msgstr "Mit der Vermessung abbrechen" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-push" +msgstr "Push" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Max.Breite" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Min.Höhe" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Min.Breite" + +msgid "shortcuts.bring-backward" +msgstr "Eins nach hinten" + +msgid "shortcuts.bring-forward" +msgstr "Eins nach vorne" + +msgid "shortcuts.bring-back" +msgstr "In den Hintergrund" + +msgid "shortcuts.make-corner" +msgstr "Zur Ecke umwandeln" + +msgid "shortcuts.next-frame" +msgstr "Nächstes Board" + +msgid "shortcuts.opacity-2" +msgstr "Deckkraft auf 20% setzen" + +msgid "shortcuts.opacity-3" +msgstr "Deckkraft auf 30% setzen" + +msgid "shortcuts.opacity-4" +msgstr "Deckkraft auf 40% setzen" + +msgid "shortcuts.opacity-5" +msgstr "Deckkraft auf 50% setzen" + +msgid "shortcuts.opacity-6" +msgstr "Deckkraft auf 60% setzen" + +msgid "shortcuts.opacity-0" +msgstr "Deckkraft auf 100% setzen" + +msgid "shortcuts.opacity-1" +msgstr "Deckkraft auf 10% setzen" + +msgid "shortcuts.select-all" +msgstr "Alles auswählen" + +msgid "shortcuts.separate-nodes" +msgstr "Punkte trennen" + +msgid "shortcuts.show-pixel-grid" +msgstr "Pixelraster ein-/ausblenden" + +msgid "shortcuts.show-shortcuts" +msgstr "Tastenkürzel ein-/ausblenden" + +msgid "shortcuts.reset-zoom" +msgstr "Zoom zurücksetzen" + +msgid "shortcuts.snap-nodes" +msgstr "An den Punkten ausrichten" + +msgid "shortcuts.toggle-rules" +msgstr "Lineale ein-/ausblenden" + +msgid "shortcuts.toggle-snap-grid" +msgstr "Am Raster ausrichten" + +msgid "shortcuts.toggle-snap-guide" +msgstr "An Hilfslinien ausrichten" + +msgid "shortcuts.toggle-lock-size" +msgstr "Seitenverhältnis sperren/entsperren" + +msgid "shortcuts.toggle-scale-text" +msgstr "Textskalierung aktivieren/deaktivieren" + +msgid "shortcuts.toggle-textpalette" +msgstr "Textpalette ein-/ausblenden" + +msgid "shortcuts.toggle-visibility" +msgstr "Elemente ein-/ausblenden" + +msgid "shortcuts.undo" +msgstr "Rückgängig" + +msgid "shortcuts.ungroup" +msgstr "Gruppierung aufheben" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Vollbild aktivieren/deaktivieren" + +msgid "shortcuts.unmask" +msgstr "Maske entfernen" + +msgid "shortcuts.zoom-selected" +msgstr "Zur Auswahl zoomen" From 5454cabf98082a0e28f411c3760e85f667175507 Mon Sep 17 00:00:00 2001 From: liimee Date: Wed, 7 Sep 2022 12:43:32 +0000 Subject: [PATCH 004/682] :globe_with_meridians: Add translations for: Indonesian. Currently translated at 7.8% (95 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/ --- frontend/translations/id.po | 59 ++++++++++++++++++++++++++++++++++--- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/frontend/translations/id.po b/frontend/translations/id.po index 68c25397f6..798169ac76 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-03 08:15+0000\n" +"PO-Revision-Date: 2022-09-10 17:20+0000\n" "Last-Translator: liimee \n" -"Language-Team: Indonesian " -"\n" +"Language-Team: Indonesian \n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -319,4 +319,55 @@ msgstr "Ekspor pustaka bersama" msgid "dashboard.export.options.detach.message" msgstr "" "Pustaka bersama tidak akan dimasukkan dalam hasil ekspor dan tidak ada aset " -"yang akan ditambahkan ke pustaka. " \ No newline at end of file +"yang akan ditambahkan ke pustaka. " + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Unggah semua" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "%s fon ditambahkan" + +msgid "dashboard.import" +msgstr "Impor berkas Penpot" + +msgid "dashboard.fonts.empty-placeholder" +msgstr "Tak ada fon khusus yang terpasang." + +msgid "dashboard.import.analyze-error" +msgstr "Aduh! Kami tidak dapat mengimpor berkas ini" + +msgid "dashboard.import.import-message" +msgstr "%s berkas telah berhasil diimpor." + +msgid "dashboard.import.progress.process-colors" +msgstr "Memproses warna" + +msgid "dashboard.import.progress.process-components" +msgstr "Memproses komponen" + +msgid "dashboard.import.progress.process-media" +msgstr "Memproses media" + +msgid "dashboard.import.progress.process-page" +msgstr "Memproses halaman: %s" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Mulai tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Mulai tur" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Panduan Antarmuka" + +msgid "dashboard.import.import-error" +msgstr "Terdapat masalah saat mengimpor berkas. Berkasnya tidak terimpor." + +msgid "dashboard.fonts.deleted-placeholder" +msgstr "Fon dihapus" From 5b92dca270fcd8ed3c38364264f8749b0cdc687e Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Tue, 6 Sep 2022 14:58:22 +0000 Subject: [PATCH 005/682] :globe_with_meridians: Add translations for: Hebrew. Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/ --- frontend/translations/he.po | 282 +++++++++++++++++++++++++++++++++++- 1 file changed, 276 insertions(+), 6 deletions(-) diff --git a/frontend/translations/he.po b/frontend/translations/he.po index 96e01bde7f..f115f246bf 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -1,16 +1,16 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-07-19 06:21+0000\n" +"PO-Revision-Date: 2022-09-10 17:20+0000\n" "Last-Translator: Yaron Shahrabani \n" -"Language-Team: Hebrew " -"\n" +"Language-Team: Hebrew \n" "Language: he\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && " "n % 10 == 0) ? 2 : 3));\n" -"X-Generator: Weblate 4.14-dev\n" +"X-Generator: Weblate 4.14.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -3148,7 +3148,11 @@ msgstr "ייצוא הבחירה" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "ייצוא רכיב" +msgid_plural "" +msgstr[0] "ייצוא רכיב" +msgstr[1] "ייצוא %s רכיבים" +msgstr[2] "ייצוא %s רכיבים" +msgstr[3] "ייצוא %s רכיבים" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -4464,4 +4468,270 @@ msgid "workspace.updates.update" msgstr "עדכון" msgid "workspace.viewport.click-to-close-path" -msgstr "לחיצה תסגור את הנתיב" \ No newline at end of file +msgstr "לחיצה תסגור את הנתיב" + +msgid "common.unpublish" +msgstr "ביטול פרסום" + +msgid "dashboard.libraries-and-templates" +msgstr "ספריות ותבניות" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "אישור" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "תשומת לב" + +msgid "onboarding.choice.team-up.create-later" +msgstr "ליצירת צוות מאוחר יותר" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "גובה מזערי" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "גובה מר.‏" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "רוחב מר.‏" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "גובה מז.‏" + +msgid "workspace.shape.menu.restore-main" +msgstr "שחזור הרכיב הראשי" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "התחלת הסיור" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "עיון ביותר כאלה והסברים כיצד לתרום להן" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "אירעה שגיאה בייבוא התבנית והיא לא ייובאה." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "בקובץ זה כבר נעשה שימוש עם גרסה 2 של Components." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "לקובץ זה יש ספריות שנעשה בהן שימוש בקובץ הזה:" +msgstr[1] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" +msgstr[2] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" +msgstr[3] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "ביטול הפרסום הופך את המשאבים לספרייה של הקובץ הזה." +msgstr[1] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." +msgstr[2] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." +msgstr[3] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "לבטל את פרסום הספרייה הזאת?" +msgstr[1] "לבטל את פרסום הספריות האלו?" +msgstr[2] "לבטל את פרסום הספריות האלו?" +msgstr[3] "לבטל את פרסום הספריות האלו?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "בשימוש בקובץ הזה:" +msgstr[1] "בשימוש בקבצים האלה:" +msgstr[2] "בשימוש בקבצים האלה:" +msgstr[3] "בשימוש בקבצים האלה:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "ביטול פרסום ספרייה" +msgstr[1] "ביטול פרסום ספריות" +msgstr[2] "ביטול פרסום ספריות" +msgstr[3] "ביטול פרסום ספריות" + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "השתתפות בפעילות הקהילתית" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"כאן אפשר למצוא מידע על כיצד לשתף פעולה בנושאי תרגום, בקשות יכולות, תרומות " +"ליבה, מצוד אחר תקלות…" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"מקום ציבורי ללמידה, שיתוף ודיון על Penpot, ההווה והעתיד שלו עם כל הקהילה " +"וצוות הליבה של Penpot." + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot הוא בקוד פתוח והוא נוצר על ידי Kaleidos וגם על ידי הקהילה בה מגוון " +"אנשים כבר מסייעים זה לזה. כל אחד יכול לתרום דרך:" + +msgid "onboarding-v2.before-start.title" +msgstr "לפני שמתחילים" + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "מדריכים מצולמים" + +msgid "onboarding-v2.before-start.desc3" +msgstr "אפשר לצפות במדריכים שלנו ובמדריכים שנוצרו על ידי חברי הקהילה שלנו." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "מדריך למשתמשים" + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"מידע מפורט על אופן השימוש ב־Penpot. החל מתכנון אבטיפוס ועד שיתוף עיצובים." + +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"רצוי לדעת שיש מגוון משאבים זמינים לך כדי לסייע לך להתחיל להשתמש ב־Penpot כמו " +"המדריך למשתמשים וערוץ ה־YouTube שלנו." + +msgid "workspace.assets.local-library" +msgstr "ספרייה מקומית" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "ניהול תפקידים" + +msgid "onboarding.team-modal.create-team" +msgstr "יצירת צוות" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "ניהול צוות" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "מחיקת קובץ" +msgstr[1] "מחיקת קבצים" +msgstr[2] "מחיקת קבצים" +msgstr[3] "מחיקת קבצים" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "Penpot מיועד לצוותים. אפשר להזמין חברים כדי לעבוד ביחד על מיזמים וקבצים" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "להתגבש כקבוצה!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "סיור בנבכי מנשק המשתמש" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקובץ הזה:" +msgstr[1] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" +msgstr[2] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" +msgstr[3] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "מחיקת קובץ" + +msgid "common.publish" +msgstr "פרסום" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "כאן נלמד את היסודות של Penpot תוך השתעשעות עם המדריך המעשי הזה." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "התחלת המדריך" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "מדריך מעשי" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "סיור במרחבי Penpot ועריכת היכרות עם יכולות המפתח שלו." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "ביטול פרסום ספרייה" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "למחוק את הקובץ?" +msgstr[1] "למחוק את הקבצים?" +msgstr[2] "למחוק את הקבצים?" +msgstr[3] "למחוק את הקבצים?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "מחיקת קובץ" +msgstr[1] "מחיקת קבצים" +msgstr[2] "מחיקת קבצים" +msgstr[3] "מחיקת קבצים" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "הזמנת חברים לצוות" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "רוחב מזערי" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "גובה מרבי" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "ביטול פרסום" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "ללא הגבלת משתמשים" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "רוחב מרבי" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "מהדורת ריבוי משתתפים" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% בחינם!" + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "אין הגבלה על קבצים או מיזמים" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"צוות מאפשר לך לשתף פעולה עם משתמשים אחרים ב־Penpot שעובדים על אותם קבצים " +"ומיזמים." + +msgid "onboarding.choice.team-up.roles" +msgstr "הזמנה עם התפקיד:" + +msgid "onboarding-v2.welcome.title" +msgstr "ברוך בואך ל־Penpot!" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "רצוי לזכור לכלול את כולם. מפתחים, מעצבים, מנהלים… גיוון מעשיר :)" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "מדריך למתנדבים" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "רוחב מז.‏" From 8e0e77fd3c3181f9e4fb78815cb683276a322e85 Mon Sep 17 00:00:00 2001 From: Ahmad HosseinBor <123hozeifeh@gmail.com> Date: Thu, 8 Sep 2022 12:25:28 +0000 Subject: [PATCH 006/682] :globe_with_meridians: Add translations for: Persian. Currently translated at 54.6% (661 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/ --- frontend/translations/fa.po | 49 +++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/frontend/translations/fa.po b/frontend/translations/fa.po index 75ca1776f9..0384f241b8 100644 --- a/frontend/translations/fa.po +++ b/frontend/translations/fa.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-08-25 13:21+0000\n" +"PO-Revision-Date: 2022-09-10 17:20+0000\n" "Last-Translator: Ahmad HosseinBor <123hozeifeh@gmail.com>\n" -"Language-Team: Persian " -"\n" +"Language-Team: Persian \n" "Language: fa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.14-dev\n" +"X-Generator: Weblate 4.14.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -2778,4 +2778,43 @@ msgid "workspace.updates.update" msgstr "به‌روزرسانی" msgid "workspace.viewport.click-to-close-path" -msgstr "برای بستن مسیر کلیک کنید" \ No newline at end of file +msgstr "برای بستن مسیر کلیک کنید" + +msgid "common.unpublish" +msgstr "لغو انتشار" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "خیلی خوب" + +msgid "common.publish" +msgstr "انتشار" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "شروع آموزش" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "در پنپات قدم بزنید و با ویژگی‌های اصلی آن آشنا شوید." + +#: src/app/main/ui/dashboard/projects.cljs +#, fuzzy +msgid "dasboard.walkthrough-hero.start" +msgstr "شروع تور" + +msgid "dashboard.libraries-and-templates" +msgstr "کتابخانه‌ها و قالب‌ها" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "مدیریت تیم" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"در حالی که با این آموزش سرگرم می‌شوید، اصول اولیه را در Penpot بیاموزید." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "لغو انتشار کتابخانه" From 8ac1dfce29ce1a4c72b99c33ee4f139ead4db3f3 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Wed, 7 Sep 2022 08:06:12 +0000 Subject: [PATCH 007/682] :globe_with_meridians: Add translations for: Basque. Currently translated at 97.4% (1178 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/ --- frontend/translations/eu.po | 160 +++++++++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 4 deletions(-) diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index 811cdf9e5c..a39f10addc 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-08-28 21:15+0000\n" +"PO-Revision-Date: 2022-09-10 17:20+0000\n" "Last-Translator: Mikel Larreategi \n" -"Language-Team: Basque " -"\n" +"Language-Team: Basque \n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -4379,4 +4379,156 @@ msgid "workspace.updates.update" msgstr "Eguneratu" msgid "workspace.viewport.click-to-close-path" -msgstr "Egin klik bidea ixteko" \ No newline at end of file +msgstr "Egin klik bidea ixteko" + +msgid "common.publish" +msgstr "Argitaratu" + +msgid "common.unpublish" +msgstr "Argitaraketa atzera bota" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Oharra" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Egin taldea!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Hasi tutoriala" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "" +"Eman begirada bat Penpoti bere oinarrizko funtzionalitateak zein diren " +"ezagutzeko." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Hasi" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Ezabatu fitxategia" +msgstr[1] "Ezabatu fitxategiak" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Ezabatu fitxategia" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Gonbidatu kideak taldera" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Arazo bat egon da txantiloia inportatzean. Ezin izan da inportatu." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Ikasi Penpoten oinarriak tutorial atsegin honekin." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Interfazea ezagutu" + +msgid "dashboard.libraries-and-templates" +msgstr "Liburutegi eta txantiloiak" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Taldearen kudeaketa" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial atsegina" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot taldeentzat sortuta dago. Gonbidatu beste pertsona batzuk proiektu " +"eta fitxategietan batera lan egiteko." + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Gehiago ikusi eta ikasi nola lagundu" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Atzera bota liburutegia argitaratzea" + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Fitxategi hau V2 Osagaiak aktibatuta erabili da." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Ezabatu fitxategia" +msgstr[1] "Ezabatu fitxategiak" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Benetan fitxategi hau ezabatu nahi duzu?" +msgstr[1] "Benetan fitxategi hauek ezabatu nahi dituzu?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Argitaratzea atzera bota" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "" +"Ezabatu nahi duzun fitxategiak, fitxategi honetan erabiltzen den liburutegi " +"bat du:" +msgstr[1] "" +"Ezabatu nahi duzuen fitxategiak, fitxategi hauetan erabiltzen den liburutegi " +"bat du:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "" +"Ezabatu nahi dituzun fitxategiek, fitxategi honetan erabiltzen den " +"liburutegi bat dute:" +msgstr[1] "" +"Ezabatu nahi dituzun fitxategiek, fitxategi hauetan erabiltzen den " +"liburutegi bat dute:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Argitaratzea atzera botatzen baduzu, elementuak fitxategiaren liburutegira " +"pasatuko dira." +msgstr[1] "" +"Argitaratzea atzera botatzen baduzu, elementuak fitxategien liburutegietara " +"pasatuko dira." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Benetan liburutegi honen argitaratzea atzera bota nahi duzu?" +msgstr[1] "Benetan liburutegi hauen argitaratzea atzera bota nahi duzu?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Fitxategi honetan erabiltzen ari da:" +msgstr[1] "Fitxategi hauetan erabiltzen ari da:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Liburutegiaren argitaratzea atzera bota" +msgstr[1] "Liburutegian argitaratzea atzera bota" + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Erabiltzailearen gida" From 4027241bc0c01be3210afe961a9e4fd197aa74de Mon Sep 17 00:00:00 2001 From: Stas Haas Date: Sun, 11 Sep 2022 13:02:32 +0000 Subject: [PATCH 008/682] :globe_with_meridians: Add translations for: German. Currently translated at 99.0% (1197 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index a247815764..5da7315991 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-10 17:20+0000\n" +"PO-Revision-Date: 2022-09-12 13:20+0000\n" "Last-Translator: Stas Haas \n" "Language-Team: German \n" @@ -4766,3 +4766,6 @@ msgstr "Maske entfernen" msgid "shortcuts.zoom-selected" msgstr "Zur Auswahl zoomen" + +msgid "shortcuts.toggle-zoom-style" +msgstr "Zoom-Optionen umschalten" From 81a4c6b3f1fce6f23df911adadc24b051f68edd3 Mon Sep 17 00:00:00 2001 From: Beeby Xia Date: Tue, 13 Sep 2022 13:16:20 +0000 Subject: [PATCH 009/682] :globe_with_meridians: Add translations for: Chinese (Simplified). Currently translated at 90.4% (1094 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/ --- frontend/translations/zh_CN.po | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 2791abb9e4..b77b2fd9ee 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-10 17:20+0000\n" +"PO-Revision-Date: 2022-09-13 13:19+0000\n" "Last-Translator: Beeby Xia \n" "Language-Team: Chinese (Simplified) \n" @@ -4057,3 +4057,22 @@ msgstr "选择所有" msgid "shortcuts.separate-nodes" msgstr "分离节点" + +msgid "shortcuts.snap-pixel-grid" +msgstr "对齐像素网格" + +msgid "shortcuts.stop-measure" +msgstr "停止测量" + +msgid "shortcuts.start-editing" +msgstr "启用编辑" + +msgid "shortcuts.start-measure" +msgstr "启用测量" + +msgid "shortcuts.thumbnail-set" +msgstr "设置缩略图" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "快捷键" From be5a232994e83994f91e7da81ad6a623a22a02f7 Mon Sep 17 00:00:00 2001 From: Beeby Xia Date: Tue, 13 Sep 2022 13:45:19 +0000 Subject: [PATCH 010/682] :globe_with_meridians: Add translations for: Chinese (Simplified). Currently translated at 92.4% (1118 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/ --- frontend/translations/zh_CN.po | 75 +++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index b77b2fd9ee..e20d0ca1c8 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-13 13:19+0000\n" +"PO-Revision-Date: 2022-09-14 14:15+0000\n" "Last-Translator: Beeby Xia \n" "Language-Team: Chinese (Simplified) \n" @@ -4076,3 +4076,76 @@ msgstr "设置缩略图" #: src/app/main/ui/workspace/sidebar/shortcuts.cljs msgid "shortcuts.title" msgstr "快捷键" + +msgid "shortcuts.toggle-assets" +msgstr "切换资产" + +msgid "shortcuts.toggle-alignment" +msgstr "切换动态对齐" + +msgid "shortcuts.zoom-selected" +msgstr "缩放到选定对象" + +msgid "shortcuts.ungroup" +msgstr "取消组合" + +msgid "shortcuts.unmask" +msgstr "取消遮罩" + +msgid "shortcuts.v-distribute" +msgstr "垂直分布" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "帮助和信息" + +msgid "shortcuts.toggle-colorpalette" +msgstr "切换调色板" + +msgid "shortcuts.toggle-focus-mode" +msgstr "切换焦点模式" + +msgid "shortcuts.toggle-grid" +msgstr "显示/隐藏网格" + +msgid "shortcuts.toggle-history" +msgstr "切换历史" + +msgid "shortcuts.toggle-layers" +msgstr "切换层" + +msgid "shortcuts.toggle-lock" +msgstr "锁定所选" + +msgid "shortcuts.toggle-lock-size" +msgstr "锁定比例" + +msgid "shortcuts.toggle-rules" +msgstr "显示/隐藏规则" + +msgid "shortcuts.toggle-scale-text" +msgstr "切换缩放文本" + +msgid "shortcuts.toggle-snap-grid" +msgstr "网络对齐" + +msgid "shortcuts.toggle-snap-guide" +msgstr "辅助线对齐" + +msgid "shortcuts.toggle-textpalette" +msgstr "切换文本调色板" + +msgid "shortcuts.toggle-visibility" +msgstr "切换可见度" + +msgid "shortcuts.toggle-zoom-style" +msgstr "切换缩放样式" + +msgid "shortcuts.toogle-fullscreen" +msgstr "切换全屏" + +msgid "shortcuts.undo" +msgstr "回退" + +msgid "workspace.assets.local-library" +msgstr "本地库" From 4497d8842a6342553c4c77a9dbb0322ae79f38e1 Mon Sep 17 00:00:00 2001 From: Ally Tiago Date: Thu, 15 Sep 2022 19:55:06 +0000 Subject: [PATCH 011/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 58.7% (710 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 769 ++++++++++++++++++++++++++++++++- 1 file changed, 751 insertions(+), 18 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index c4989fa09f..0e1f09f001 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-08-18 16:16+0000\n" -"Last-Translator: andy \n" -"Language-Team: Portuguese (Brazil) " -"\n" +"PO-Revision-Date: 2022-09-16 21:18+0000\n" +"Last-Translator: Ally Tiago \n" +"Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.14-dev\n" +"X-Generator: Weblate 4.14.1-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -214,7 +214,7 @@ msgstr "Páginas selecionadas" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" -msgstr "Adicionar como Biblioteca Compartilhada" +msgstr "Adicionar à Biblioteca Compartilhada" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.change-email" @@ -350,7 +350,7 @@ msgstr "Sair da equipe" #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" -msgstr "Bibliotecas Compartilhadas" +msgstr "Bibliotecas" #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.loading-files" @@ -440,7 +440,7 @@ msgstr "Quer remover sua conta?" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.remove-shared" -msgstr "Remover como Biblioteca Compartilhada" +msgstr "Remover da Biblioteca Compartilhada" #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.search-placeholder" @@ -1144,7 +1144,6 @@ msgid "labels.send" msgstr "Enviar" #: src/app/main/ui/settings/feedback.cljs -#, fuzzy msgid "labels.sending" msgstr "Enviando…" @@ -1162,7 +1161,7 @@ msgstr "Configurações" #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.shared-libraries" -msgstr "Bibliotecas compartilhadas" +msgstr "Bibliotecas" #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-all-comments" @@ -1207,7 +1206,7 @@ msgstr "Carregando imagem…" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.accept" -msgstr "Adicionar como biblioteca compartilhada" +msgstr "Adicionar à Biblioteca Compartilhada" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.hint" @@ -1218,7 +1217,7 @@ msgstr "" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.message" -msgstr "Adicionar “%s” como Biblioteca Compartilhada" +msgstr "Adicionar “%s” à Biblioteca Compartilhada" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.confirm-email" @@ -1367,7 +1366,7 @@ msgstr "Promover a proprietário" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.accept" -msgstr "Remover como Biblioteca Compartilhada" +msgstr "Remover da Biblioteca Compartilhada" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.hint" @@ -1378,7 +1377,7 @@ msgstr "" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.message" -msgstr "Remover “%s” como Biblioteca Compartilhada" +msgstr "Remover “%s” da Biblioteca Compartilhada" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.accept" @@ -1391,12 +1390,12 @@ msgstr "Cancelar" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" msgstr "" -"Você está prestes a atualizar um Componente em uma Biblioteca " -"Compartilhada. Isso pode afetar outros arquivos que a utilizam." +"Você está prestes a atualizar um componente em uma biblioteca compartilhada. " +"Isso pode afetar outros arquivos que a utilizam." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.message" -msgstr "Atualizar Componente em uma Biblioteca Compartilhada" +msgstr "Atualizar componente em uma biblioteca compartilhada" #: src/app/main/ui/dashboard/team.cljs msgid "notifications.invitation-email-sent" @@ -2088,4 +2087,738 @@ msgid "workspace.updates.update" msgstr "Atualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Clique para fechar o caminho" \ No newline at end of file +msgstr "Clique para fechar o caminho" + +msgid "labels.export" +msgstr "Exportar" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.your-account" +msgstr "Sua conta" + +msgid "errors.team-leave.insufficient-members" +msgstr "" +"Membros insuficientes para deixar a equipe, você provavelmente deseja " +"excluí-la." + +msgid "errors.team-leave.member-does-not-exists" +msgstr "O membro que você tentou atribuir não existe." + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Encaixar na grade" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Traço" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Excluir projeto" + +msgid "onboarding.contrib.link" +msgstr "projeto no github" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Aprenda o básico no Penpot enquanto se diverte com este tutorial prático." + +msgid "dashboard.export.options.detach.message" +msgstr "" +"Bibliotecas compartilhadas não serão incluídas na exportação e nenhum ativo " +"será adicionado à biblioteca. " + +msgid "dashboard.export.options.detach.title" +msgstr "Trate os ativos da biblioteca compartilhada como objetos básicos" + +msgid "errors.team-leave.owner-cant-leave" +msgstr "" +"O proprietário não pode sair da equipe, você deve reatribuir a função de " +"proprietário." + +msgid "labels.share-prototype" +msgstr "Compartilhar protótipo" + +msgid "labels.edit-file" +msgstr "Editar arquivo" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "Pendente" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "Reenviar convite" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.hint" +msgstr "" +"Se você transferir a propriedade, mudará sua função para Admin, perdendo " +"algumas permissões sobre essa equipe. " + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Tem certeza de que deseja cancelar a publicação desta biblioteca?" +msgstr[1] "Tem certeza de que deseja cancelar a publicação dessas bibliotecas?" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.message" +msgstr "Atualizar componentes em uma biblioteca compartilhada" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"O Penpot é Open Source e é feito pela Kaleidos e também pela comunidade, " +"onde muitas pessoas já se ajudam. Todos podem colaborar:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Um espaço público para aprender, compartilhar e discutir sobre o Penpot, seu " +"presente e futuro com toda a Comunidade e a equipe principal do Penpot." + +msgid "common.share-link.all-users" +msgstr "Todos os usuários do Penpot" + +msgid "common.share-link.current-tag" +msgstr "(atual)" + +msgid "common.share-link.manage-ops" +msgstr "Gerenciar Permissões" + +msgid "common.share-link.destroy-link" +msgstr "Destruir link" + +msgid "common.share-link.permissions-can-comment" +msgstr "Pode comentar" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 página compartilhada" +msgstr[1] "%s páginas compartilhadas" + +msgid "common.share-link.permissions-pages" +msgstr "Páginas compartilhadas" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Pode inspecionar o código" + +msgid "common.share-link.team-members" +msgstr "Apenas membros da equipe" + +msgid "common.share-link.view-all" +msgstr "Selecionar todos" + +msgid "dashboard.download-binary-file" +msgstr "Baixar arquivo Penpot (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Baixar arquivo padrão (.svg + .json)" + +msgid "dashboard.export-binary-multi" +msgstr "Baixar %s arquivos Penpot (.penpot)" + +msgid "dashboard.export-standard-multi" +msgstr "Baixar %s arquivos padrões (.svg + .json)" + +msgid "dashboard.import.progress.upload-media" +msgstr "Carregando arquivo: %s" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Provedor de autenticação não configurado." + +#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.profile-is-muted" +msgstr "" +"Seu perfil tem e-mails silenciados (relatos de spam ou altas devoluções)." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Vá para o fórum do Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"Estamos felizes em tê-lo aqui. Se precisar de ajuda, pesquise antes de " +"postar." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Comunidade Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "Vá para o Twitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "Aqui para ajuda com suas dúvidas técnicas." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-title" +msgstr "Conta de suporte no Twitter" + +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Atalhos" + +msgid "labels.continue" +msgstr "Continuar" + +msgid "labels.continue-with" +msgstr "Continue com" + +msgid "labels.continue-with-penpot" +msgstr "Você pode continuar com a conta Penpot" + +msgid "labels.log-or-sign" +msgstr "Entrar ou cadastrar-se" + +msgid "labels.next" +msgstr "Próximo" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Tutorial" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(você)" + +msgid "modals.delete-font-variant.message" +msgstr "" +"Tem certeza de que deseja excluir este estilo de fonte? Ele não será " +"carregado se for usado em um arquivo." + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Tem certeza de que deseja excluir esta página?" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Excluir página" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Tem certeza de que deseja excluir este projeto?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Excluir projeto" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "Pequeno deslocamento" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"Você está prestes a atualizar componentes em uma biblioteca compartilhada. " +"Isso pode afetar outros arquivos que o utilizam." + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Convide membros" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "" +"Depois de nomear sua equipe, você poderá convidar pessoas para participar." + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Crie uma equipe e envie convites" + +msgid "onboarding.choice.title" +msgstr "Bem-vindo ao Penpot" + +msgid "onboarding.contrib.desc1" +msgstr "" +"Penpot é Open Source, feito por e para a comunidade. Se quiser colaborar, é " +"mais que bem-vindo!" + +msgid "onboarding.contrib.desc2.1" +msgstr "Você pode acessar o" + +msgid "onboarding.contrib.desc2.2" +msgstr "e siga as instruções de contribuição :)" + +msgid "onboarding.contrib.title" +msgstr "Contribuidor Open Source?" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "" +"Sua solicitação de inscrição foi enviada, enviaremos um e-mail para " +"confirmá-la." + +msgid "onboarding.newsletter.decline" +msgstr "Não, obrigado" + +msgid "onboarding.newsletter.policy" +msgstr "Politica de privacidade." + +msgid "onboarding.newsletter.privacy1" +msgstr "Porque nos preocupamos com a privacidade, aqui está o nosso " + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Habilitar alinhamento dinâmico" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-scale-text" +msgstr "Ativar texto de escala" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-guides" +msgstr "Encaixar nos guias" + +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Habilitar encaixe por pixel" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Salvo" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Alterações não salvas" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "" +"Envie-me notícias, atualizações de produtos e recomendações sobre o Penpot." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Assinatura de Newsletter" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "Salvar configurações" + +msgid "errors.auth.unable-to-login" +msgstr "Parece que você não está autenticado ou a sessão expirou." + +msgid "errors.email-as-password" +msgstr "Você não pode usar seu e-mail como senha" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.email-has-permanent-bounces" +msgstr "O e-mail «%s» tem muitos relatórios de devolução permanentes." + +msgid "errors.email-spam-or-permanent-bounces" +msgstr "O e-mail «%s» foi denunciado como spam ou devolvido permanentemente." + +msgid "handoff.tabs.code.selected.mask" +msgstr "Máscara" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Excluir equipe" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Excluindo equipe" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.preferences" +msgstr "Preferências" + +msgid "common.unpublish" +msgstr "Cancelar publicação" + +msgid "dashboard.import.progress.process-components" +msgstr "Processando componentes" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot é destinado a equipes. Convide membros para trabalharem juntos em " +"projetos e arquivos" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Juntem-se!" + +msgid "dashboard.libraries-and-templates" +msgstr "Biblioteca & Modelos" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Explore mais deles e saiba como contribuir" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Ocorreu um problema ao importar o modelo. O modelo não foi importado." + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Atenção" + +#: src/app/main/ui/confirm.cljs +msgid "ds.component-subtitle" +msgstr "Componentes para atualizar:" + +#: src/app/main/ui/dashboard/team.cljs +msgid "errors.member-is-muted" +msgstr "" +"O perfil que você está convidando tem e-mails silenciados (relatos de spam " +"ou altas devoluções)." + +msgid "handoff.tabs.code.selected.component" +msgstr "Componente" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.about-penpot" +msgstr "Sobre Penpot" + +msgid "labels.close" +msgstr "Fechar" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Comunidade" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "Não há convites." + +msgid "labels.show-comments-list" +msgstr "Mostrar lista de comentários" + +msgid "labels.workspace" +msgstr "Área de trabalho" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Status" + +msgid "modals.delete-font-variant.title" +msgstr "Excluindo estilo de fonte" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Excluindo arquivo" +msgstr[1] "Excluindo arquivos" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Tem certeza de que deseja excluir esta equipe? Todos os projetos e arquivos " +"associados à equipe serão excluídos permanentemente." + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Excluir membro" + +msgid "modals.invite-member.emails" +msgstr "E-mails, separados por vírgulas" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Excluir arquivo" +msgstr[1] "Excluir arquivos" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Excluindo arquivo" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Crie uma equipe depois" + +msgid "onboarding.choice.team-up.roles" +msgstr "Convide com a função:" + +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Você deve saber que existem muitos recursos disponíveis para ajudá-lo a " +"começar a usar o Penpot, como o Guia do Usuário e nosso canal no Youtube." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Informações detalhadas sobre como usar o Penpot. Da prototipagem à " +"organização ou compartilhamento de projetos." + +msgid "onboarding-v2.welcome.title" +msgstr "Bem-vindo ao Penpot!" + +msgid "common.publish" +msgstr "Publicar" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Gerenciamento de equipe" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Começar o tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial prático" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Faça um passeio pelo Penpot e conheça suas principais características." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Comece o passeio" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Passo a passo da interface" + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Este arquivo já foi usado com componentes V2 habilitado." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "Convite inválido" + +msgid "errors.invite-invalid.info" +msgstr "Este convite pode ter sido cancelado ou pode ter expirado." + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.nudge-title" +msgstr "Quantidade de deslocamento" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Cancelar publicação da biblioteca" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Tem certeza de que deseja excluir este arquivo?" +msgstr[1] "Tem certeza de que deseja excluir estes arquivos?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Este arquivo possui bibliotecas que estão sendo usadas neste arquivo:" +msgstr[1] "Este arquivo possui bibliotecas que estão sendo usadas nestes arquivos:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "" +"Esses arquivos possuem bibliotecas que estão sendo usadas neste arquivo:" +msgstr[1] "Esses arquivos têm bibliotecas que estão sendo usadas nesses arquivos:" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Convite membros para a equipe" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Cancelar publicação" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Se você cancelar a publicação, os ativos nele se tornarão uma biblioteca " +"desse arquivo." +msgstr[1] "" +"Se você cancelar a publicação, os ativos nele se tornarão uma biblioteca " +"desses arquivos." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Está em uso neste arquivo:" +msgstr[1] "Está em uso nestes arquivos:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Cancelar publicação da biblioteca" +msgstr[1] "Cancelar publicação de bibliotecas" + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Guia do Usuário" + +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"Você pode assistir nossos tutoriais e os tutoriais feitos por nossa " +"comunidade." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Tutoriais em vídeo" + +msgid "onboarding-v2.before-start.title" +msgstr "Antes de começar" + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "Participando da Comunidade" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Onde você poderá colaborar com traduções, solicitações de recursos, " +"contribuições principais, caça a bugs…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Guia do contribuidor" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Lembre-se de incluir todos. Desenvolvedores, designers, gerentes... a " +"diversidade se soma :)" + +msgid "onboarding.newsletter.accept" +msgstr "Sim, assinar" + +msgid "onboarding.newsletter.desc" +msgstr "" +"Subscreva a nossa newsletter para se manter atualizado com o progresso e as " +"novidades do desenvolvimento de produtos." + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Enviaremos apenas e-mails relevantes para você. Você pode cancelar a " +"inscrição a qualquer momento em seu perfil de usuário ou por meio do link de " +"cancelamento de inscrição em qualquer um de nossos boletins informativos." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "Recusar tudo" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Carregar tudo" + +msgid "dashboard.import.import-error" +msgstr "Ocorreu um problema ao importar o arquivo. O arquivo não foi importado." + +msgid "dashboard.import.import-message" +msgstr "%s arquivos foram importados com sucesso." + +msgid "dashboard.import.import-warning" +msgstr "Alguns arquivos continham objetos inválidos que foram removidos." + +msgid "onboarding.contrib.alt" +msgstr "Open Source" + +msgid "labels.and" +msgstr "e" + +msgid "labels.back" +msgstr "Voltar" + +msgid "labels.default" +msgstr "padrão" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.delete-invitation" +msgstr "Excluir convite" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Expirado" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.help-center" +msgstr "Central de Ajuda" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Repositório Github" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.invitations" +msgstr "Convites" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.libraries-and-templates" +msgstr "Biblioteca & Modelos" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Membro" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "" +"Pressione o botão \"Convidar para equipe\" para convidar mais membros para " +"esta equipe." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "Remover membro" + +msgid "labels.skip" +msgstr "Pular" + +msgid "labels.start" +msgstr "Começar" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.big-nudge" +msgstr "Grande deslocamento" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"Você é o dono desta equipe. Selecione outro membro para promover a " +"proprietário antes de sair." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Como você é o único membro desta equipe, a equipe será excluída junto com " +"seus projetos e arquivos." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "Tem certeza de que deseja sair da equipe %s?" + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Digite o nome da equipe" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Crie a equipe e convide depois" + +msgid "onboarding.choice.team-up.create-team" +msgstr "O nome da sua equipe" + +msgid "dashboard.export.options.merge.message" +msgstr "" +"Seu arquivo será exportado com todos os ativos externos mesclados na " +"biblioteca de arquivos." + +msgid "dashboard.export.options.merge.title" +msgstr "Incluir ativos da biblioteca compartilhada na bibliotecas de arquivos" + +msgid "dashboard.export.title" +msgstr "Exportar arquivos" + +msgid "dashboard.import.analyze-error" +msgstr "Ops! Não foi possível importar este arquivo" + +msgid "dashboard.import.progress.process-colors" +msgstr "Processando cores" + +msgid "dashboard.import.progress.process-media" +msgstr "Processando mídia" + +msgid "dashboard.import.progress.process-page" +msgstr "Processando página: %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "Processando tipografia" + +msgid "dashboard.import.progress.upload-data" +msgstr "Carregando dados para o servidor (%s/%s)" + +msgid "labels.link" +msgstr "Link" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "1 fonte adicionada" +msgstr[1] "%s fontes adicionadas" From 516735cd0bca60a48ec7aa331a73ad9f76d8a022 Mon Sep 17 00:00:00 2001 From: Eranot Date: Thu, 15 Sep 2022 20:09:53 +0000 Subject: [PATCH 012/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 58.7% (710 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 0e1f09f001..bb3e21c9c1 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-16 21:18+0000\n" -"Last-Translator: Ally Tiago \n" +"Last-Translator: Eranot \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -148,7 +148,7 @@ msgstr "Criar uma conta" #: src/app/main/ui/auth/register.cljs msgid "auth.register-subtitle" -msgstr "É de graça, é código aberto" +msgstr "É de graça, é Open Source" #: src/app/main/ui/auth/register.cljs msgid "auth.register-title" From 9d63bc99bf3d51bc1c9577aaf3b6246831babb38 Mon Sep 17 00:00:00 2001 From: Mikel Larreategi Date: Thu, 15 Sep 2022 17:06:01 +0000 Subject: [PATCH 013/682] :globe_with_meridians: Add translations for: Basque. Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/eu/ --- frontend/translations/eu.po | 117 +++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index a39f10addc..da97b16140 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-10 17:20+0000\n" +"PO-Revision-Date: 2022-09-16 21:18+0000\n" "Last-Translator: Mikel Larreategi \n" "Language-Team: Basque \n" @@ -4532,3 +4532,118 @@ msgstr[1] "Liburutegian argitaratzea atzera bota" msgid "onboarding-v2.before-start.desc2.title" msgstr "Erabiltzailearen gida" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Gehieneko zabalera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Gutxieneko altuera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Gehieneko altuera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Gutxieneko altuera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Gutxieneko zabalera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Gehieneko altuera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Gehieneko zabalaera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Gutxieneko zabalera" + +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Penpotekin lanean hasteko hainbat eta hainbat baliabide daude, adibidez " +"erabiltzailearen gida eta gure Youtube kanala." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Penpot erabiltzeari buruko informazioa. Prototipoak egitetik, diseinuak " +"antolatu eta partekatzera." + +msgid "onboarding-v2.before-start.desc3" +msgstr "Gure eta komunitateak egindako tutorialak ikusi ditzakezu." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Bideo tutorialak" + +msgid "onboarding-v2.before-start.title" +msgstr "Hasi aurretik" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot Kode Irekikoa da eta Kaleidos eta komunitateak egindakoa da. " +"Laguntzeko erak:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Penpoten oraina eta etorkizunari buruz ikasi, partekatu eta eztabaidatzeko " +"tokia." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "Komunitatean parte hartzen" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Hemen izango duzu itzulpenekin laguntzeko informazioa, funtzionalitateak " +"eskatzeko modua, erroreak bilatzekoa…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Laguntzeko gida" + +msgid "onboarding-v2.welcome.title" +msgstr "Ongi etorri Penpotera!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Sortu taldea beranduago" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Ez ahaztu garapeneko, diseinuko, kudeaketako... pertsonak sartzea, " +"dibertsitatea ona da :)" + +msgid "onboarding.choice.team-up.roles" +msgstr "Gonbidatu rol honekin:" + +msgid "onboarding.team-modal.create-team" +msgstr "Sortu talde bat" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Talde batek Penpoten fitxategi eta proiektuetan elkarrekin lan egiteko " +"aukera ematen du." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Mugarik gabeko fitxategi eta proiektu kopurua" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Jokalari anitzeko edizioa" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Rolen kudeaketa" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Partehartzaile muga gabe" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "%100 doan!" + +msgid "workspace.assets.local-library" +msgstr "liburutegi lokala" + +msgid "workspace.shape.menu.restore-main" +msgstr "Berrezarri osagai nagusia" From 9af04c8fbba6c2c2cd96b4e821e8289add76b65f Mon Sep 17 00:00:00 2001 From: liimee Date: Sun, 18 Sep 2022 03:50:19 +0000 Subject: [PATCH 014/682] :globe_with_meridians: Add translations for: Indonesian. Currently translated at 9.3% (113 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/ --- frontend/translations/id.po | 70 +++++++++++++++++++++++++++++++++++-- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/frontend/translations/id.po b/frontend/translations/id.po index 798169ac76..0dae4b3feb 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-10 17:20+0000\n" +"PO-Revision-Date: 2022-09-19 04:15+0000\n" "Last-Translator: liimee \n" "Language-Team: Indonesian \n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=1; plural=0;\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -371,3 +371,69 @@ msgstr "Terdapat masalah saat mengimpor berkas. Berkasnya tidak terimpor." msgid "dashboard.fonts.deleted-placeholder" msgstr "Fon dihapus" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Undang ke tim" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Pustaka" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ Buat Berkas" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Pindahkan ke tim lain" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Buat Proyek" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Proyek Baru" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "Kirimkan saya berita, pembaruan produk dan rekomendasi tentang Penpot." + +msgid "dashboard.libraries-and-templates" +msgstr "Pustaka & Template" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Jelajahi lebih banyak dan pelajari cara berkontribusi" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Terjadi masalah saat mengimpor template. Template tidak diimpor." + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "Berkas Baru" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Tinggalkan tim" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "memuat berkas-berkasmu …" + +msgid "dashboard.loading-fonts" +msgstr "memuat fon-fon kamu …" + +msgid "dashboard.import.progress.upload-data" +msgstr "Mengunggah data ke server (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "Mengunggah berkas: %s" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Pindahkan ke" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Pindahkan %s berkas ke" From 1019a037d88bfcdca2c2687664795fb330ab4c0a Mon Sep 17 00:00:00 2001 From: Ahmad HosseinBor <123hozeifeh@gmail.com> Date: Sat, 17 Sep 2022 13:09:44 +0000 Subject: [PATCH 015/682] :globe_with_meridians: Add translations for: Persian. Currently translated at 54.9% (664 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/ --- frontend/translations/fa.po | 58 +++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/frontend/translations/fa.po b/frontend/translations/fa.po index 0384f241b8..25deeeb032 100644 --- a/frontend/translations/fa.po +++ b/frontend/translations/fa.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-10 17:20+0000\n" +"PO-Revision-Date: 2022-09-19 04:15+0000\n" "Last-Translator: Ahmad HosseinBor <123hozeifeh@gmail.com>\n" "Language-Team: Persian \n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -2818,3 +2818,57 @@ msgstr "" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.unpublish-shared" msgstr "لغو انتشار کتابخانه" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.blend-mode.hue" +msgstr "رنگ" + +#, fuzzy +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"اطلاعات دقیق در مورد نحوه استفاده از Penpot. از نمونه‌سازی تا سازماندهی یا " +"به اشتراک‌گذاری طرح‌ها." + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.select-a-shape" +msgstr "یک شکل، برد یا گروه را برای کشیدن اتصال به تابلوی دیگر انتخاب کنید." + +#, fuzzy +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"شما می‌توانید آموزش‌های ما و آموزش‌های ساخته شده توسط انجمن ما را تماشا کنید." + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.blend-mode.lighten" +msgstr "روشن کردن" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.blend-mode.luminosity" +msgstr "درخشندگی" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.blend-mode.multiply" +msgstr "تکثیر" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-animation-dissolve" +msgstr "حل کردن" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot برای تیم‌ها در نظر گرفته شده است. از اعضا دعوت کنید تا روی پروژه‌ها و " +"فایل‌ها با هم کار کنند" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "آیا مطمئن هستید که می‌خواهید این عضو را از تیم حذف کنید؟" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "پس از نامگذاری تیم خود، می‌توانید افراد را برای پیوستن دعوت کنید." From 9a4ad389579ee419c7e8b1ed6977827104c5c00d Mon Sep 17 00:00:00 2001 From: Semon Xue Date: Mon, 19 Sep 2022 09:16:52 +0000 Subject: [PATCH 016/682] :globe_with_meridians: Add translations for: Chinese (Simplified). Currently translated at 92.5% (1119 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/ --- frontend/translations/zh_CN.po | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index e20d0ca1c8..3595a9ce88 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-14 14:15+0000\n" -"Last-Translator: Beeby Xia \n" +"PO-Revision-Date: 2022-09-19 09:17+0000\n" +"Last-Translator: Semon Xue \n" "Language-Team: Chinese (Simplified) \n" "Language: zh_CN\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -4149,3 +4149,7 @@ msgstr "回退" msgid "workspace.assets.local-library" msgstr "本地库" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit" +msgstr "适合 - 缩小以适合" From f72b94ac9b833c732a5aa646ad12333cf636df3a Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 20 Sep 2022 12:34:09 +0200 Subject: [PATCH 017/682] :globe_with_meridians: Added translation for: Czech. --- frontend/translations/cs.po | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 frontend/translations/cs.po diff --git a/frontend/translations/cs.po b/frontend/translations/cs.po new file mode 100644 index 0000000000..4f8f6e6dec --- /dev/null +++ b/frontend/translations/cs.po @@ -0,0 +1,2 @@ +msgid "" +msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file From 449a6c91271d1e166e57d93a543036b6ff9b31f4 Mon Sep 17 00:00:00 2001 From: Swapnil C Date: Mon, 19 Sep 2022 20:47:30 +0000 Subject: [PATCH 018/682] :globe_with_meridians: Add translations for: French. Currently translated at 88.8% (1074 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fr/ --- frontend/translations/fr.po | 139 ++++++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 4 deletions(-) diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 14901e1516..3e67213a38 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-10 17:20+0000\n" -"Last-Translator: Philippe Accorsi \n" +"PO-Revision-Date: 2022-09-20 13:46+0000\n" +"Last-Translator: Swapnil C \n" "Language-Team: French \n" "Language: fr\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n!=1);\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -3031,7 +3031,9 @@ msgstr "Export" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "Exporter l'élément" +msgid_plural "" +msgstr[0] "Exporter l'élément" +msgstr[1] "Exporter les %s éléments" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -4120,3 +4122,132 @@ msgstr "au centre" #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Plus de couleurs" + +msgid "workspace.shape.menu.union" +msgstr "Union" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot est conçu pour les équipes. Invitez les membres pour collaborer sur " +"des projets et des fichiers" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Explorez Penpot et découvrir ses fonctionnalités." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutoriel pratique" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Faites une équipe  !" + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Ce fichier a été déjà utilisé avec Components V2 activé." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Ce fichier contient les bibliothèques utilisées dans ce fichier :" +msgstr[1] "Ce fichier contient les bibliothèques utilisées dans ces fichiers :" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Ces fichiers contiennent les bibliothèques utilisées dans ce fichier :" +msgstr[1] "" +"Ces fichiers contiennent les bibliothèques utilisées dans ces fichiers :" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Si vous le retirez, les ressources là-dedans deviennent une bibliothèque de " +"ce fichier." +msgstr[1] "" +"Si vous le retirez, les ressources là-dedans deviennent une bibliothèque de " +"ces fichiers." + +msgid "workspace.shape.menu.flatten" +msgstr "Aplatir" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-start" +msgstr "Début du flux" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-artboard-from-selection" +msgstr "Créer un plan de travail depuis la sélection" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "Détacher les instances" + +msgid "workspace.sidebar.layers.shapes" +msgstr "Formes" + +msgid "common.unpublish" +msgstr "Dépublier" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.add-flow-start" +msgstr "Indiquer le début du flux" + +msgid "workspace.shape.menu.hide-ui" +msgstr "Montrer/Masquer l'interface" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Gestion de l'équipe" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Commencer le guide" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Apprenez les bases de Penpot en s'amusant avec ce tutoriel pratique." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Démonstration de l'interface" + +msgid "dashboard.libraries-and-templates" +msgstr "Bibliothèques et Modèles" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "En explorez plus et découvrir comment contribuer" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "" +"Il y avait un problème pendant l'importation de la modèle. La modèle n'est " +"pas importé." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Retirer la Bibliothèque" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Vous êtes sûr de vouloir retirer cette bibliothèque ?" +msgstr[1] "Vous êtes sûr de vouloir retirer ces bibliothèques ?" + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"De l'information détaillée sur Penpot. De prototypage à l’organisation et le " +"partage des designs." + +msgid "workspace.shape.menu.difference" +msgstr "Difference" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Raccourcis (%s)" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RVBA" From 051a65c346a7bbb536dbcf7a2722469e1ebb4376 Mon Sep 17 00:00:00 2001 From: Vik Date: Mon, 19 Sep 2022 13:13:40 +0000 Subject: [PATCH 019/682] :globe_with_meridians: Add translations for: Russian. Currently translated at 62.2% (752 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/ --- frontend/translations/ru.po | 849 ++++++++++++++++++++++++++++++++---- 1 file changed, 753 insertions(+), 96 deletions(-) diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index e401fa7cd6..7bd861a330 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-08-18 16:16+0000\n" -"Last-Translator: andy \n" -"Language-Team: Russian " -"\n" +"PO-Revision-Date: 2022-09-20 13:46+0000\n" +"Last-Translator: Vik \n" +"Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Weblate 4.14-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -25,7 +25,7 @@ msgstr "Подтвердите пароль" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.create-demo-account" -msgstr "Хотите попробовать?" +msgstr "Создать демо-аккаунт" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.create-demo-profile" @@ -39,7 +39,7 @@ msgstr "" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs msgid "auth.email" -msgstr "Email" +msgstr "Эл. почта" #: src/app/main/ui/auth/login.cljs msgid "auth.forgot-password" @@ -95,11 +95,11 @@ msgstr "Неверный код восстановления." #: src/app/main/ui/auth/recovery.cljs msgid "auth.notifications.password-changed-successfully" -msgstr "Пароль изменен успешно" +msgstr "Пароль успешно изменён" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.profile-not-verified" -msgstr "Профиль не подтверждён: пожалуйста, проверьте почту." +msgstr "Профиль не подтверждён, пожалуйста, проверьте почту." #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.recovery-token-sent" @@ -126,7 +126,7 @@ msgstr "Восстановить пароль" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.recovery-request-subtitle" -msgstr "Письмо с инструкциями отправлено на почту." +msgstr "Мы отправим эл. письмо с инструкциями" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.recovery-request-title" @@ -167,7 +167,7 @@ msgstr "" #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" -msgstr "Мы отправили письмо с подтверждением на" +msgstr "Мы отправили эл. письмо с подтверждением на" msgid "common.share-link.confirm-deletion-link-description" msgstr "" @@ -198,7 +198,7 @@ msgstr "Добавить как общую библиотеку" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.change-email" -msgstr "Сменить email адрес" +msgstr "Изменить эл. почту" #: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs msgid "dashboard.copy-suffix" @@ -229,17 +229,17 @@ msgstr "Дублировать файлы (%s)" #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.empty-files" -msgstr "Файлов пока нет" +msgstr "Здесь пока нет файлов" #: src/app/main/ui/dashboard/grid.cljs #, markdown msgid "dashboard.empty-placeholder-drafts" msgstr "" -"Файлов не обнаружено. Вы можете испытать готовые шаблоны в разделе " -"[\"Libraries & templates\"](https://penpot.app/libraries-templates.html)" +"Ой! Файлов пока нет. Вы можете испытать готовые шаблоны в разделе [" +"Библиотеки и шаблоны](https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "Экспорт кадров в PDF..." +msgstr "Экспорт кадров в PDF…" msgid "dashboard.export-multi" msgstr "Экспорт файлов Penpot (%s)" @@ -299,18 +299,19 @@ msgstr[1] "Шрифты добавлены (%s)" #, markdown msgid "dashboard.fonts.hero-text1" msgstr "" -"Любой загружаемый сюда шрифт будет доступен в файлах, редактируемых " -"командой. Шрифты из одного и того же семейства будут сгруппированы по " -"**названию этого семейства**. Для загрузки допустимы следующие форматы: " -"**TTF, OTF, и WOFF** (используйте один из них)." +"Любой загружаемый сюда шрифт будет доступен в свойствах файлов, " +"редактируемых командой. Шрифты из одного и того же семейства будут " +"сгруппированы по **названию семейства шрифта**. Для загрузки допустимы " +"следующие форматы: **TTF, OTF и WOFF** (используйте один из них)." #, markdown msgid "dashboard.fonts.hero-text2" msgstr "" -"Вам следует загружать только те шрифты, которые покрыты лицензией на " -"использование в приложении вроде Penpot. Ознакомьтесь с пунктом \"Content " -"rights\" в [правилах использования Penpot](https://penpot.app/terms.html) и " -"с [лицензированием шрифтов](https://www.typography.com/faq), в целом." +"Вам следует загружать только собственные шрифты, или у которых есть лицензия " +"на использование в Penpot. Больше информации в разделе \"Content rights\" в [" +"Условиях использования Penpot](https://penpot.app/terms.html). Также можете " +"прочитать о [лицензированием шрифтов](https://www.typography.com/faq) в " +"целом." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -323,13 +324,13 @@ msgid "dashboard.import.analyze-error" msgstr "Ой! Не вышло импортировать этот файл" msgid "dashboard.import.import-error" -msgstr "Есть проблема с импортом файла." +msgstr "Есть проблема с импортом файла. Файл не был импортирован." msgid "dashboard.import.import-message" -msgstr "Файлы импортированы (%s)." +msgstr "Файлы успешно импортированы (%s)." msgid "dashboard.import.import-warning" -msgstr "Некоторые файлы содержали неверные объекты: таковые были удалены." +msgstr "Некоторые файлы содержали неверные объекты, которые были удалены." msgid "dashboard.import.progress.process-colors" msgstr "Обработка цветов" @@ -362,7 +363,7 @@ msgstr "Покинуть команду" #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" -msgstr "Общие библиотеки" +msgstr "Библиотеки" #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.loading-files" @@ -409,7 +410,7 @@ msgstr "Закреплённые проекты будут здесь" #: src/app/main/ui/auth/verify_token.cljs msgid "dashboard.notifications.email-changed-successfully" -msgstr "Ваш email адрес успешно обновлен" +msgstr "Ваш адрес эл. почты был успешно обновлён" #: src/app/main/ui/auth/verify_token.cljs msgid "dashboard.notifications.email-verified-successfully" @@ -456,11 +457,11 @@ msgstr "Снять статус общей библиотеки" #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.search-placeholder" -msgstr "Поиск " +msgstr "Поиск…" #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.searching-for" -msgstr "Ищу “%s“…" +msgstr "Поиск \"%s\"…" #: src/app/main/ui/settings/options.cljs msgid "dashboard.select-ui-language" @@ -476,7 +477,7 @@ msgstr "Показать все файлы" #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.success-delete-file" -msgstr "Ваш файл удалён" +msgstr "Ваш файл успешно удалён" #: src/app/main/ui/dashboard/project_menu.cljs msgid "dashboard.success-delete-project" @@ -484,7 +485,7 @@ msgstr "Ваш проект удалён" #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.success-duplicate-file" -msgstr "Ваш файл продублирован" +msgstr "Ваш файл успешно продублирован" #: src/app/main/ui/dashboard/project_menu.cljs msgid "dashboard.success-duplicate-project" @@ -492,11 +493,11 @@ msgstr "Ваш проект продублирован" #: src/app/main/ui/dashboard/grid.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.success-move-file" -msgstr "Ваш файл перемещён" +msgstr "Ваш файл успешно перемещён" #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.success-move-files" -msgstr "Ваши файлы перемещены" +msgstr "Ваши файлы успешно перемещены" #: src/app/main/ui/dashboard/project_menu.cljs msgid "dashboard.success-move-project" @@ -512,7 +513,7 @@ msgstr "О команде" #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.team-members" -msgstr "Члены команды" +msgstr "Участники команды" #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.team-projects" @@ -540,7 +541,7 @@ msgstr "Ваш аккаунт" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.your-email" -msgstr "Email" +msgstr "Эл. почта" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.your-name" @@ -560,7 +561,7 @@ msgstr "Отмена" #: src/app/main/ui/confirm.cljs msgid "ds.confirm-ok" -msgstr "Ok" +msgstr "Oк" #: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs msgid "ds.confirm-title" @@ -575,18 +576,18 @@ msgstr "Кажется, сеанс истёк. Войдите снова." #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" -msgstr "Ваш браузер не поддерживает этот метод вставки" +msgstr "Ваш браузер не поддерживает эту операцию" #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" -msgstr "Такой email уже используется" +msgstr "Такая эл. почта уже используется" #: src/app/main/ui/auth/verify_token.cljs msgid "errors.email-already-validated" -msgstr "Электронная почта уже подтверждена." +msgstr "Эл. почта уже подтверждена." msgid "errors.email-as-password" -msgstr "Нельзя в качестве пароля указывать адрес электронной почты" +msgstr "Нельзя указывать в качестве пароля адрес эл. почты" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.email-has-permanent-bounces" @@ -594,7 +595,7 @@ msgstr "Получатель «%s» постоянно не доступен." #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-invalid-confirmation" -msgstr "Email для подтверждения должен совпадать" +msgstr "Эл. почта для подтверждения должна совпадать" #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.generic" @@ -654,7 +655,8 @@ msgid "errors.team-leave.owner-cant-leave" msgstr "Нужно переназначить роль владельца перед тем, как покинуть команду." msgid "errors.terms-privacy-agreement-invalid" -msgstr "Вы должны принять наши правила использования и политику конфиденциальности." +msgstr "" +"Вы должны принять наши условия использования и политику конфиденциальности." #: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "errors.unexpected-error" @@ -662,7 +664,7 @@ msgstr "Произошла ошибка." #: src/app/main/ui/auth/verify_token.cljs msgid "errors.unexpected-token" -msgstr "Неверный токен" +msgstr "Неизвестный токен" #: src/app/main/ui/auth/login.cljs msgid "errors.wrong-credentials" @@ -678,11 +680,11 @@ msgstr "Войти в чат" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.chat-subtitle" -msgstr "Поговорим? Заходите в чат Gitter!" +msgstr "Хотите поговорить? Заходите в наш чат Gitter" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.description" -msgstr "Подробности" +msgstr "Описание" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" @@ -691,12 +693,12 @@ msgstr "Краткое описание" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"Пожалуйста, опишите причину обращения: проблема в работе, идея, или " -"сомнение. Человек из нашей команды даст ответ в ближайшее время." +"Пожалуйста, опишите причину обращения: проблема в работе, идея или сомнение. " +"Участник нашей команды даст ответ в ближайшее время." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" -msgstr "Email" +msgstr "Эл. почта" #: src/app/main/ui/settings/password.cljs msgid "generic.error" @@ -768,7 +770,7 @@ msgstr "Ширина" #: src/app/main/ui/handoff/attributes/shadow.cljs msgid "handoff.attributes.shadow" -msgstr "Тени" +msgstr "Тень" #: src/app/main/ui/handoff/attributes/shadow.cljs msgid "handoff.attributes.shadow.shorthand.blur" @@ -1047,7 +1049,7 @@ msgstr "Шрифты" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.give-feedback" -msgstr "Дать обратную связь" +msgstr "Оставить отзыв" msgid "labels.go-back" msgstr "Назад" @@ -1097,7 +1099,7 @@ msgstr "Управление шрифтами" #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.members" -msgstr "Люди" +msgstr "Участники" #: src/app/main/ui/dashboard/team.cljs msgid "labels.name" @@ -1179,7 +1181,7 @@ msgstr "Недавние" #: src/app/main/ui/settings/sidebar.cljs msgid "labels.release-notes" -msgstr "примечания к выпуску" +msgstr "Примечания к выпуску" #: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.remove" @@ -1205,7 +1207,7 @@ msgid "labels.save" msgstr "Сохранить" msgid "labels.search-font" -msgstr "Найти шрифт" +msgstr "Искать шрифт" #: src/app/main/ui/settings/feedback.cljs msgid "labels.send" @@ -1225,7 +1227,7 @@ msgstr "Сервис недоступен" #: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.settings" -msgstr "Параметры" +msgstr "Настройки" #: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.share-prototype" @@ -1233,7 +1235,7 @@ msgstr "Поделиться ссылкой" #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.shared-libraries" -msgstr "Общие библиотеки" +msgstr "Библиотеки" #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-all-comments" @@ -1287,7 +1289,7 @@ msgstr "Ваш аккаунт" #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" -msgstr "Загружаю изображение…" +msgstr "Загрузка изображения…" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.accept" @@ -1301,7 +1303,7 @@ msgstr "" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.message" -msgstr "Добавить “%s” как общую библиотеку" +msgstr "Добавить \"%s\" как общую библиотеку" #: src/app/main/ui/workspace/nudge.cljs msgid "modals.big-nudge" @@ -1309,23 +1311,25 @@ msgstr "Большой сдвиг" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.confirm-email" -msgstr "Подтвердить новый email адрес" +msgstr "Подтвердить новую эл. почту" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.info" -msgstr "Мы отправим письмо для подтверждения подлиности на текущий email адрес “%s”." +msgstr "" +"Мы отправим эл. письмо для подтверждения личности на текущую эл. почту \"%s\"" +"." #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.new-email" -msgstr "Новый email адрес" +msgstr "Новая эл. почта" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.submit" -msgstr "Сменить email адрес" +msgstr "Изменить эл. почту" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.title" -msgstr "Сменить email адрес" +msgstr "Изменить эл. почту" #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" @@ -1350,8 +1354,8 @@ msgstr "Удалить переписку" #: src/app/main/ui/comments.cljs msgid "modals.delete-comment-thread.message" msgstr "" -"Вы уверены, что хотите удалить это обсуждение со всеми входящими в него " -"комментариями?" +"Вы уверены, что хотите удалить это обсуждение? Все комментарии, входящие в " +"него будут удалены." #: src/app/main/ui/comments.cljs msgid "modals.delete-comment-thread.title" @@ -1375,20 +1379,24 @@ msgstr "Удалить файлы" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-file-multi-confirm.message" -msgstr "Точно хотите удалить несколько файлов (%s)?" +msgstr "Точно хотите удалить файлы (%s)?" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-file-multi-confirm.title" msgstr "Удаление файлов (%s)" msgid "modals.delete-font-variant.message" -msgstr "Вы действительно хотите удалить это начертание шрифта?" +msgstr "" +"Вы действительно хотите удалить это начертание шрифта? Оно не будет " +"загружаться, если используется в файле." msgid "modals.delete-font-variant.title" msgstr "Удаление начертания шрифта" msgid "modals.delete-font.message" -msgstr "Вы действительно хотите удалить этот шрифт?" +msgstr "" +"Вы действительно хотите удалить этот шрифт? Он не будет загружаться, если " +"используется в файле." msgid "modals.delete-font.title" msgstr "Удаление шрифта" @@ -1420,8 +1428,8 @@ msgstr "Удалить команду" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.message" msgstr "" -"Вы уверены, что хотите удалить эту команду? Все проекты и файлы из этой " -"команды также будут безвозвратно удалены." +"Вы уверены, что хотите удалить эту команду? Все проекты и файлы этой команды " +"также будут безвозвратно удалены." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.title" @@ -1450,6 +1458,8 @@ msgstr "" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" msgstr "" +"Вы собираетесь обновить компонент в общей библиотеке. Это может повлиять на " +"другие файлы, которые её используют." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.message" @@ -1457,11 +1467,11 @@ msgstr "" #: src/app/main/ui/settings/delete_account.cljs msgid "notifications.profile-deletion-not-allowed" -msgstr "Вы не можете удалить профиль. Сначала смените команду." +msgstr "Вы не можете удалить профиль. Сначала переназначьте команды." #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs msgid "notifications.profile-saved" -msgstr "Профиль успешно сохранен!" +msgstr "Профиль успешно сохранён!" msgid "onboarding.slide.1.desc1" msgstr "Создайте различные взаимодействия для имитации поведения продукта." @@ -1487,11 +1497,11 @@ msgstr "Общие библиотеки - %s - Penpot" #: src/app/main/ui/settings/feedback.cljs msgid "title.settings.feedback" -msgstr "Дать обратную связь - Penpot" +msgstr "Оставить отзыв - Penpot" #: src/app/main/ui/settings/options.cljs msgid "title.settings.options" -msgstr "Параметры - Penpot" +msgstr "Настройки - Penpot" #: src/app/main/ui/settings/password.cljs msgid "title.settings.password" @@ -1503,7 +1513,7 @@ msgstr "Профиль - Penpot" #: src/app/main/ui/dashboard/team.cljs msgid "title.team-settings" -msgstr "Параметры - %s - Penpot" +msgstr "Настройки - %s - Penpot" #: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs msgid "title.viewer" @@ -1545,7 +1555,7 @@ msgstr "Интерактив (%s)" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.copy-link" -msgstr "Копировать ссылку" +msgstr "Скопировать ссылку" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.create-link" @@ -1553,7 +1563,7 @@ msgstr "Создать ссылку" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.placeholder" -msgstr "Здесь будет ссылка для обмена" +msgstr "Здесь будет ссылка, чтобы поделиться" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.remove-link" @@ -1676,7 +1686,7 @@ msgstr "Переименовать группу" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.search" -msgstr "Найти ресурсы" +msgstr "Поиск ресурсов" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.selected-count" @@ -1742,15 +1752,15 @@ msgstr "Скрыть имена кадров" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-grid" -msgstr "Спрятать сетку" +msgstr "Скрыть сетки" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-palette" -msgstr "Спрятать палитру цветов" +msgstr "Скрыть палитру цветов" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-rules" -msgstr "Спрятать линейки" +msgstr "Скрыть линейки" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.select-all" @@ -1778,7 +1788,7 @@ msgstr "Режим просмотра (%s)" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.add" -msgstr "" +msgstr "Добавить" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.colors" @@ -1790,7 +1800,7 @@ msgstr "" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.file-library" -msgstr "" +msgstr "Библиотека файлов" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.graphics" @@ -1798,23 +1808,23 @@ msgstr "" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.in-this-file" -msgstr "" +msgstr "БИБЛИОТЕКИ В ЭТОМ ФАЙЛЕ" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.libraries" -msgstr "" +msgstr "БИБЛИОТЕКИ" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.library" -msgstr "" +msgstr "БИБЛИОТЕКА" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-libraries-need-sync" -msgstr "Общие библиотеки не нуждаются в обновлении" +msgstr "Нет общих библиотек, требующих обновления" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-matches-for" -msgstr "Совпадений для “%s“ не найдено" +msgstr "Совпадений для \"%s\" не найдено" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-shared-libraries-available" @@ -1842,11 +1852,11 @@ msgstr "Типографики (%s)" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.update" -msgstr "" +msgstr "Обновить" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.updates" -msgstr "" +msgstr "ОБНОВЛЕНИЯ" msgid "workspace.library.all" msgstr "Все библиотеки" @@ -1878,11 +1888,13 @@ msgstr "Экспорт" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "Экспорт фигуры" +msgid_plural "" +msgstr[0] "Экспорт элемента" +msgstr[1] "Экспорт %s элементов" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.exporting-object" -msgstr "Экспортирую…" +msgstr "Экспортирование…" #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.fill" @@ -2017,7 +2029,7 @@ msgstr "Вращение" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.select-a-shape" -msgstr "Выберите фигуру, кадр, или группу, для соединения с другим кадром." +msgstr "Выберите фигуру, кадр или группу для соединения с другим кадром." #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.select-artboard" @@ -2037,7 +2049,7 @@ msgstr "Размер" #: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs msgid "workspace.options.size-presets" -msgstr "Предустановки для размеров" +msgstr "Предустановки размеров" #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke" @@ -2298,4 +2310,649 @@ msgid "workspace.updates.update" msgstr "" msgid "workspace.viewport.click-to-close-path" -msgstr "Нажмите для замыкания контура" \ No newline at end of file +msgstr "Нажмите для замыкания контура" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Файл" + +msgid "workspace.options.y" +msgstr "Y" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Перейти к входу" + +msgid "workspace.options.shadow-options.color" +msgstr "Цвет тени" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgb-complementary" +msgstr "Дополнительный цвет RGB" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Скрыть" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Внутренняя тень" + +msgid "common.share-link.view-all" +msgstr "Выбрать все" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Управление командой" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot предназначен для команд. Приглашайте участников к совместной работе " +"над проектами и файлами" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Создать команду и отправить приглашения" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot — проект с открытым исходным кодом, созданный Kaleidos и сообществом, " +"где многие люди уже помогают друг другу. Каждый может начать сотрудничество:" + +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"Вы можете посмотреть наши руководства и руководства, созданные нашим " +"сообществом." + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Библиотека файлов" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Файл содержит библиотеки, которые используются в этом файле:" +msgstr[1] "Файл содержит библиотеки, которые используются в этих файлах:" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Размытие" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "Обновить основные компоненты" + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Неограниченное количество файлов и проектов" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Вы уверены, что хотите удалить этот файл?" +msgstr[1] "Вы уверены, что хотите удалить эти файлы?" + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Мы будем отправлять только акутальные эл. письма. Вы можете отказаться от " +"подписки в любое время в своём профиле пользователя или по ссылке отказа от " +"подписки в любом из наших писем информационной рассылки." + +msgid "shortcuts.toggle-history" +msgstr "Переключить историю" + +msgid "shortcuts.open-color-picker" +msgstr "Выбор цвета" + +msgid "common.share-link.all-users" +msgstr "Все пользователи Penpot" + +msgid "common.share-link.current-tag" +msgstr "(текущее)" + +msgid "common.share-link.destroy-link" +msgstr "Удалить ссылку" + +msgid "common.share-link.permissions-can-comment" +msgstr "Может комментировать" + +msgid "common.share-link.manage-ops" +msgstr "Управлять разрешениями" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Может проверять код" + +msgid "common.share-link.team-members" +msgstr "Только участники команды" + +msgid "dashboard.download-binary-file" +msgstr "Скачать файл Penpot (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Скачать стандартный файл (.svg + .json)" + +msgid "dashboard.export-binary-multi" +msgstr "Скачать файлы Penpot (.penpot) (%s)" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Информация о настройке экспорта в Penpot." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Нет элементов с настройками экспорта." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "Выбор экспорта" + +msgid "dashboard.export-standard-multi" +msgstr "Скачать стандартные файлы (.svg + .json) (%s)" + +msgid "errors.team-leave.member-does-not-exists" +msgstr "Участник, которого вы пытаетесь назначить, не существует." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "Перейти в Twitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Сообщество Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Перейти на форум Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"Мы рады вас здесь видеть. Если вам нужна помощь, пожалуйста, поищите ответ, " +"возможно он уже есть." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "Здесь, чтобы помочь с вашими техническими запросами." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-title" +msgstr "Аккаунт поддержки в Twitter" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Сообщество" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Репозиторий на Github" + +msgid "labels.show-comments-list" +msgstr "Показать список комментариев" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(вы)" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"Вы собираетесь обновить компоненты в общей библиотеке. Это может повлиять на " +"другие файлы, которые её используют." + +msgid "onboarding.slide.1.alt" +msgstr "Интерактивные прототипы" + +msgid "shortcut-subsection.main-menu" +msgstr "Главное меню" + +msgid "shortcut-subsection.modify-layers" +msgstr "Изменить слои" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Навигация" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Навигация" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Навигация" + +msgid "shortcut-subsection.panels" +msgstr "Панели" + +msgid "shortcuts.create-component" +msgstr "Создать компонент" + +msgid "shortcuts.delete" +msgstr "Удалить" + +msgid "shortcuts.go-to-libs" +msgstr "Перейти к общим библиотекам" + +msgid "shortcuts.go-to-search" +msgstr "Поиск" + +msgid "shortcuts.hide-ui" +msgstr "Показать/скрыть UI" + +msgid "shortcuts.select-all" +msgstr "Выбрать все" + +msgid "shortcuts.or" +msgstr " или " + +msgid "shortcuts.show-pixel-grid" +msgstr "Показать/скрыть сетку пикселей" + +msgid "shortcuts.open-workspace" +msgstr "Перейти к рабочей области" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Горячие клавиши" + +msgid "shortcuts.start-measure" +msgstr "Начать измерение" + +msgid "shortcuts.stop-measure" +msgstr "Остановить измерение" + +msgid "shortcuts.start-editing" +msgstr "Начать редактирование" + +msgid "shortcuts.toggle-grid" +msgstr "Показать/скрыть сетку" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Переключить режим фокуса" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Переключить палитру цветов" + +msgid "shortcuts.toggle-assets" +msgstr "Переключить ресурсы" + +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - Penpot" + +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Скрыть сетку пикселей" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.reset-zoom" +msgstr "Сброс" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Фон" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Слой" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "Экспорт завершён" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Цвет" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Слой" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Тень" + +msgid "workspace.options.show-in-viewer" +msgstr "Показать в режиме просмотра" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-height" +msgstr "Автовысота" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Фиксированно" + +msgid "workspace.options.x" +msgstr "X" + +msgid "workspace.shape.menu.hide-ui" +msgstr "Показать/скрыть UI" + +msgid "workspace.sidebar.layers.components" +msgstr "Компоненты" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-main" +msgstr "Обновить основной компонент" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Экспорт" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.hsv" +msgstr "HSV" + +msgid "common.unpublish" +msgstr "Снять с публикации" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Экспорт в PDF" + +msgid "errors.team-leave.insufficient-members" +msgstr "" +"Недостаточно участников, чтобы покинуть команду, вероятно, вы хотите её " +"удалить." + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ок" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Внимание" + +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Сочетания клавиш" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"Вы являетесь владельцем этой команды. Прежде чем уйти, выберите участника, " +"чтобы сделать его владельцем." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "Ожидание" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Руководства" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Вы уверены, что хотите снять с публикации эту библиотеку?" +msgstr[1] "Вы уверены, что хотите снять с публикации эти библиотеки?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Снять библиотеку с публикации" +msgstr[1] "Снять библиотеки с публикации" + +msgid "onboarding.welcome.title" +msgstr "Добро пожаловать в Penpot" + +msgid "shortcut-subsection.edit" +msgstr "Редактировать" + +msgid "shortcuts.show-shortcuts" +msgstr "Показать/скрыть горячие клавиши" + +msgid "shortcuts.paste" +msgstr "Вставить" + +msgid "shortcuts.opacity-9" +msgstr "Установить непрозрачность на 90%" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot — свобода в дизайне для команд" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.fonts" +msgstr "Шрифты - %s - Penpot" + +msgid "shortcuts.toggle-rules" +msgstr "Показать/скрыть линейки" + +msgid "shortcuts.toggle-layers" +msgstr "Переключить слои" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-invitations" +msgstr "Приглашения - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Участники - %s - Penpot" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Помощь и информация" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-textpalette" +msgstr "Скрыть палитру шрифтов" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Перейти к файлу библиотеки стилей для редактирования" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Недавние цвета" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Сохранить стиль цвета" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "Экспорт не удался" + +msgid "workspace.options.search-font" +msgstr "Искать шрифт" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Автоширина" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.preset" +msgstr "Предустановка" + +msgid "workspace.sidebar.layers.search" +msgstr "Искать слои" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Выбрать слой" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.go-main" +msgstr "Перейти к основному файлу компонента" + +msgid "common.publish" +msgstr "Опубликовать" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Вы можете добавить настройки экспорта элементам из свойств дизайна (в нижней " +"части правой боковой панели)." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Объединяйтесь!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Изучите основы в Penpot весело с этим практическим руководством." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Начать обучение" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Практическое руководство" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "" +"Прогуляйтесь по возможностям Penpot и познакомьтесь с основными функциями." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Начать тур" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Руководство по интерфейсу" + +msgid "dashboard.libraries-and-templates" +msgstr "Библиотеки и шаблоны" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "Сохранить настройки" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Удалить файл" +msgstr[1] "Удалить файлы" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Файлы содержат библиотеки, которые используются в этом файле:" +msgstr[1] "Файлы содержат библиотеки, которые используются в этих файлах:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Удаление файла" +msgstr[1] "Удаление файлов" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Удаление файла" + +msgid "onboarding.slide.2.desc1" +msgstr "" +"Все участники команды работают одновременно в режиме реального времени и " +"централизованно комментируют, отправляют идеи и отзывы напрямую в проект " +"дизайна." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Используется в этом файле:" +msgstr[1] "Используется в этих файлах:" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Неограниченное количество участников" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Управление ролями" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% бесплатно!" + +msgid "onboarding.templates.subtitle" +msgstr "Вот несколько шаблонов." + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Многопользовательская версия" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Команда позволяет вам сотрудничать с другими пользователями Penpot, " +"работающими над одними файлами и проектами." + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "История (%s)" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "Снова отправить приглашение" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "Удалить участника" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Состояние" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Так как вы единственный участник этой команды, она будет удалена вместе с " +"проектами и файлами." + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Пригласить участников" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Создать команду и пригласить позже" + +msgid "onboarding.templates.title" +msgstr "Заняться дизайном" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +msgid "shortcuts.copy" +msgstr "Скопировать" + +msgid "shortcuts.opacity-8" +msgstr "Установить непрозрачность на 80%" + +msgid "shortcuts.opacity-7" +msgstr "Установить непрозрачность на 70%" + +msgid "shortcuts.opacity-5" +msgstr "Установить непрозрачность на 50%" + +msgid "shortcuts.opacity-6" +msgstr "Установить непрозрачность на 60%" + +msgid "onboarding.contrib.link" +msgstr "проект на Github" + +msgid "onboarding.contrib.desc1" +msgstr "" +"Penpot — это проект с открытым исходным кодом, созданный сообществом и для " +"него. Если вы хотите сотрудничать, добро пожаловать!" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.font-providers" +msgstr "Поставщики шрифтов - %s - Penpot" + +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + +msgid "viewer.breaking-change.message" +msgstr "Извините!" + +msgid "viewer.breaking-change.description" +msgstr "" +"Эта общая ссылка больше не действительна. Создайте новую или попросите об " +"этом владельца." + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.google" +msgstr "Google" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "Слева направо" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "Справа налево" From 0dbefcc4011a1f98bb2d50933bcadf9384eb6051 Mon Sep 17 00:00:00 2001 From: Semon Xue Date: Mon, 19 Sep 2022 09:29:35 +0000 Subject: [PATCH 020/682] :globe_with_meridians: Add translations for: Chinese (Simplified). Currently translated at 99.9% (1208 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/zh_Hans/ --- frontend/translations/zh_CN.po | 357 +++++++++++++++++++++++++++++++-- 1 file changed, 343 insertions(+), 14 deletions(-) diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 3595a9ce88..98268a65aa 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-19 09:17+0000\n" +"PO-Revision-Date: 2022-09-20 13:46+0000\n" "Last-Translator: Semon Xue \n" "Language-Team: Chinese (Simplified) \n" @@ -264,7 +264,7 @@ msgstr "如何使用Penpot导出" #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.no-elements" -msgstr "导出设置中无任何元素" +msgstr "在导出设置中没找到任何元素" #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.title" @@ -280,7 +280,7 @@ msgid "dashboard.export.explain" msgstr "你想导出的一个或多个文件用到了共享库。你想怎么处理它们的素材?" msgid "dashboard.export.options.all.message" -msgstr "使用了共享库的文件将会在导出时保持引用" +msgstr "使用了共享库的文件将会在导出时保持引用关系" msgid "dashboard.export.options.all.title" msgstr "导出共享库" @@ -313,8 +313,8 @@ msgstr "您仍然没有已安装的字体。" #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.fonts-added" msgid_plural "dashboard.fonts.fonts-added" -msgstr[0] "添加字体成功" -msgstr[1] "添加字体成功" +msgstr[0] "1 个字体添加成功" +msgstr[1] "%s 个字体添加成功" #, markdown msgid "dashboard.fonts.hero-text1" @@ -342,7 +342,7 @@ msgid "dashboard.import.import-error" msgstr "文件导入过程中出现未知问题,导入失败。" msgid "dashboard.import.import-message" -msgstr "文件导入成功" +msgstr "%s 个文件导入成功" msgid "dashboard.import.import-warning" msgstr "一些包含无效对象的文档已被移除" @@ -599,7 +599,7 @@ msgid "ds.updated-at" msgstr "最后更新:%s" msgid "errors.auth.unable-to-login" -msgstr "你似乎还没有认证或会话已过期" +msgstr "你似乎还没有登录或会话已过期" #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" @@ -915,7 +915,7 @@ msgstr "大写" #: src/app/main/ui/handoff/right_sidebar.cljs msgid "handoff.tabs.code" -msgstr "码" +msgstr "代码" msgid "handoff.tabs.code.selected.circle" msgstr "圆" @@ -1211,7 +1211,7 @@ msgstr "可能该页面不存在,也可能你没有访问权限。" #: src/app/main/ui/static.cljs msgid "labels.not-found.main-message" -msgstr "嚯!" +msgstr "哎呀!" #: src/app/main/ui/dashboard/team.cljs msgid "labels.num-of-files" @@ -1270,7 +1270,7 @@ msgstr "最近" #: src/app/main/ui/settings/sidebar.cljs msgid "labels.release-notes" -msgstr "發行說明" +msgstr "发布说明" #: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.remove" @@ -1740,7 +1740,7 @@ msgid "onboarding.slide.0.alt" msgstr "创作设计" msgid "onboarding.slide.0.desc1" -msgstr "在协作项目中与团队成员们一起创作漂亮的用户界面" +msgstr "在协作项目中与团队成员们一起创作好看的用户界面" msgid "onboarding.slide.0.desc2" msgstr "保持组件、库和设计系统规模上的一致性。" @@ -1970,7 +1970,7 @@ msgid "viewer.breaking-change.description" msgstr "此共享的链接已失效。创建一个新的链接或向所有者索取一个新的链接。" msgid "viewer.breaking-change.message" -msgstr "Sorry!" +msgstr "对不起!" #: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs msgid "viewer.empty-state" @@ -2558,7 +2558,9 @@ msgstr "导出已选择" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "导出 %s 个元素" +msgid_plural "" +msgstr[0] "导出 1 个元素" +msgstr[1] "导出 %s 个元素" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -3683,7 +3685,7 @@ msgstr "注意" #: src/app/main/ui/auth/login.cljs msgid "errors.auth-provider-not-configured" -msgstr "没有配置身份认证服务源" +msgstr "没有配置身份认证服务源." #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.community" @@ -4153,3 +4155,330 @@ msgstr "本地库" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.zoom-fit" msgstr "适合 - 缩小以适合" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-duration" +msgstr "持续时间" + +msgid "workspace.options.stroke-width" +msgstr "线宽" + +msgid "workspace.shape.menu.union" +msgstr "相加" + +msgid "workspace.options.width" +msgstr "宽度" + +msgid "workspace.options.x" +msgstr "X" + +msgid "workspace.path.actions.make-curve" +msgstr "转圆角 (%s)" + +msgid "workspace.shape.menu.difference" +msgstr "差集" + +msgid "workspace.path.actions.snap-nodes" +msgstr "对接节点 (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "最大宽度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "最小高度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "最大高度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "最小宽度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "最小宽度" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "导出完成" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "最大高度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "最大宽度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "最小高度" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-artboard-from-selection" +msgstr "转为画板" + +msgid "workspace.options.show-in-viewer" +msgstr "在预览模式显示" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.triangle-arrow" +msgstr "三角箭头" + +msgid "workspace.options.clip-content" +msgstr "剪辑内容" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "网格" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "高级选项" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "调整大小" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "底部" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "列" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "行" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.right" +msgstr "倒排行" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "倒排列" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.gap" +msgstr "差距" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "居中" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "居左" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "居右" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "所有方向" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding" +msgstr "内边距" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "右" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.title" +msgstr "布局" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "间隔留空" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "周围留空" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "顶部" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "底部" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "居中" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "顶部" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.wrap" +msgstr "底部" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "已选颜色" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "无边框" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square" +msgstr "方头" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square-marker" +msgstr "方形标记" + +msgid "workspace.options.stroke-color" +msgstr "线条颜色" + +msgid "workspace.sidebar.layers.texts" +msgstr "文本" + +msgid "workspace.sidebar.layers.shapes" +msgstr "形状" + +msgid "workspace.sidebar.layers.search" +msgstr "搜索图层" + +msgid "workspace.shape.menu.transform-to-path" +msgstr "转换为路径" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "设为缩略图" + +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "移除缩略图" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "选择图层" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.line-arrow" +msgstr "箭头" + +msgid "workspace.options.y" +msgstr "Y" + +msgid "workspace.path.actions.make-corner" +msgstr "转锐角 (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "解绑实例" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flow-start" +msgstr "流程起点" + +msgid "workspace.sidebar.layers.components" +msgstr "组件" + +msgid "workspace.sidebar.layers.frames" +msgstr "画板" + +msgid "workspace.sidebar.layers.images" +msgstr "图片" + +msgid "workspace.sidebar.layers.masks" +msgstr "遮罩" + +msgid "workspace.sidebar.layers.groups" +msgstr "编组" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-selected" +msgstr "缩放到选定的位置" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "左" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "所有方向" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "简易外边距" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "外边距" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-simple" +msgstr "简易内边距" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "更多颜色" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-lib-colors" +msgstr "更多共享库颜色" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.no-wrap" +msgstr "不换行" + +msgid "workspace.shape.menu.exclude" +msgstr "相减" + +msgid "workspace.shape.menu.flatten" +msgstr "展平" + +msgid "workspace.shape.menu.hide-ui" +msgstr "显示/隐藏界面" + +msgid "workspace.shape.menu.intersection" +msgstr "差集" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "快捷键 (%s)" + +msgid "workspace.shape.menu.restore-main" +msgstr "恢复主要组件" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "更新主要组件" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.round" +msgstr "圆头" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.add-flow-start" +msgstr "增加流程起点" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-start" +msgstr "流程起点" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-starts" +msgstr "所有流程起点" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay-dest" +msgstr "关闭遮罩层: %s" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete-flow-start" +msgstr "删除流程起点" + +msgid "workspace.shape.menu.path" +msgstr "路径" From 4433c1136c12a207b97669072c76cb207c5b42fa Mon Sep 17 00:00:00 2001 From: Filipe Pessanha Date: Mon, 19 Sep 2022 14:00:54 +0000 Subject: [PATCH 021/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 59.3% (717 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 48 +++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index bb3e21c9c1..77882a9bfa 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-16 21:18+0000\n" -"Last-Translator: Eranot \n" +"PO-Revision-Date: 2022-09-20 13:46+0000\n" +"Last-Translator: Filipe Pessanha \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -31,7 +31,7 @@ msgstr "Criar conta de demonstração" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.create-demo-profile" -msgstr "Só quer experimentar?" +msgstr "Quer apenas experimentar?" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" @@ -89,11 +89,11 @@ msgstr "Digite uma nova senha" #: src/app/main/ui/auth/register.cljs msgid "auth.newsletter-subscription" -msgstr "Concordo em subscrever a lista de emails Penpot." +msgstr "Concordo em subscrever a lista de e-mails do Penpot." #: src/app/main/ui/auth/recovery.cljs msgid "auth.notifications.invalid-token-error" -msgstr "O token de recuperação é inválido." +msgstr "O código de recuperação é inválido." #: src/app/main/ui/auth/recovery.cljs msgid "auth.notifications.password-changed-successfully" @@ -895,7 +895,7 @@ msgstr "" #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.main-message" -msgstr "Bad Gateway" +msgstr "Erro do servidor (Bad Gateway)" #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.cancel" @@ -1056,7 +1056,7 @@ msgstr "Esta página não existe ou você não tem permissão para acessá-la." #: src/app/main/ui/static.cljs msgid "labels.not-found.main-message" -msgstr "Oops!" +msgstr "Ops!" #: src/app/main/ui/dashboard/team.cljs msgid "labels.num-of-files" @@ -1632,7 +1632,9 @@ msgstr "Exportar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "Exportar forma" +msgid_plural "" +msgstr[0] "Exportar 1 elemento" +msgstr[1] "Exportar %s elementos" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -2822,3 +2824,31 @@ msgid "dashboard.fonts.fonts-added" msgid_plural "dashboard.fonts.fonts-added" msgstr[0] "1 fonte adicionada" msgstr[1] "%s fontes adicionadas" + +msgid "onboarding.newsletter.title" +msgstr "Deseja receber novidades sobre o Penpot?" + +msgid "onboarding.slide.0.desc2" +msgstr "" +"Mantenha a consistência em escala com componentes, bibliotecas e sistemas de " +"design." + +msgid "onboarding.slide.0.title" +msgstr "Bibliotecas de design, estilos e componentes" + +msgid "onboarding.slide.1.alt" +msgstr "Protótipos interativos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.selected-count" +msgid_plural "workspace.assets.selected-count" +msgstr[0] "%s item selecionado" +msgstr[1] "%s itens selecionados" + +msgid "onboarding.slide.1.desc1" +msgstr "Crie interações completas para imitar o comportamento do produto." + +msgid "onboarding.slide.0.desc1" +msgstr "" +"Criar lindas interfaces de usuários em colaboração com todos os membros da " +"equipe." From dc7e252972b6c987a0e8f3806bee1a1d6a124f81 Mon Sep 17 00:00:00 2001 From: Shuaib Zahda Date: Tue, 20 Sep 2022 06:01:07 +0000 Subject: [PATCH 022/682] :globe_with_meridians: Add translations for: Arabic. Currently translated at 48.6% (588 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 164 ++++++++++++++++++++++++++++++++++-- 1 file changed, 156 insertions(+), 8 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 251b20a18c..3659af1a4c 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,16 +1,16 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-08-28 21:15+0000\n" -"Last-Translator: Amine Gdoura \n" -"Language-Team: Arabic " -"\n" +"PO-Revision-Date: 2022-09-20 13:46+0000\n" +"Last-Translator: Shuaib Zahda \n" +"Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 " "&& n%100<=10 ? 3 : n%100>=11 ? 4 : 5;\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -312,7 +312,7 @@ msgstr "استيراد ملفات" #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" -msgstr "قم بدعوة فريق" +msgstr "قم بدعوة للفريق" #: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.leave-team" @@ -2063,7 +2063,13 @@ msgstr "تصدير" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "تصدير الشكل" +msgid_plural "" +msgstr[0] "تصدير 0 عنصر" +msgstr[1] "تصدير عنصر واحد" +msgstr[2] "تصدير عنصرين" +msgstr[3] "تصدير عناصر" +msgstr[4] "تصدير عنصر" +msgstr[5] "تصدير عنصر" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -2186,4 +2192,146 @@ msgid "workspace.updates.update" msgstr "تحديث" msgid "workspace.viewport.click-to-close-path" -msgstr "انقر لإغلاق المسار" \ No newline at end of file +msgstr "انقر لإغلاق المسار" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "حمل الكل" + +msgid "dashboard.export.title" +msgstr "صدر الملفات" + +msgid "dashboard.import.progress.process-components" +msgstr "يتم معالجة العناصر" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "Penpot تم تصميمه للفرق. أدعُ زملاءك للعمل سوياَ على المشاريع والملفات" + +msgid "dashboard.import.progress.process-media" +msgstr "يتم معالجة الوسائط" + +msgid "common.publish" +msgstr "أنشر" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "دورة تعليمية عملية" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "إبدا الدورة التعليمية" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "إبدا الجولة" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "جولة في الواجهة" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "%s من %s عناصر مختارة" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "لا يوجد عناصر بإعدادت التصدير." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "إختيار التصدير" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "معلومات عن كيفية إعداد التصدير." + +msgid "dashboard.export.options.all.title" +msgstr "صدر المكتبات المشتركة" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "الغاء الكل" + +msgid "dashboard.import.progress.process-colors" +msgstr "يتم معالجة الألوان" + +msgid "dashboard.import.import-message" +msgstr "%s ملف تم ادراجهم بنجاح." + +msgid "dashboard.import.import-error" +msgstr "حصل خلل خلال إدراج الملف. لم يتم إدراج الملف على البرنامج." + +msgid "dashboard.import.progress.process-page" +msgstr "يتم معالجة الصفحة: %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "يتم معالجة الخطوط" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "حفظ الإعدادات" + +msgid "common.unpublish" +msgstr "إلغاء النشر" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "حسناَ" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "إنتباه" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "يوجد تحديثات في المكتبات المشتركة" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "إدارة الفريق" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "إعمل فريق!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "إذهب في جولة في البرنامج وتعرف على ميزاته." + +msgid "dashboard.export-standard-multi" +msgstr "تحميل %s ملفات أساسية (.svg + .json)" + +msgid "dashboard.export.detail" +msgstr "* ربما يحتوي على عناصر، رسومات، الوان، و/أو خطوط." + +msgid "dashboard.import.analyze-error" +msgstr "لم نستطع استيراد أو إدراج هذا الملف" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "تعلم أساسيات برنامج Penpot بينما تستمع بتجربته في هذه الدورة التعليمية." + +msgid "dashboard.libraries-and-templates" +msgstr "المكتبات & القوالب" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "استعرض المزيد منهم وتعلم كيف تساهم" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "لم يتم مشاركة الصفحة" +msgstr[1] "مشاركة صفحة واحدة" +msgstr[2] "مشاركة صفحتين" +msgstr[3] "مشاركة صفحات" +msgstr[4] "مشاركة صفحة" +msgstr[5] "مشاركة صفحة" + +msgid "dashboard.import.progress.upload-data" +msgstr "تحميل البيانات للخادم (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "تحميل الملف: %s" + +msgid "dashboard.export-frames" +msgstr "صدر اللوحة الفنية الى ملف PDF…" From 58d604a20aa40d055838f6ace9401d8dd326e1dd Mon Sep 17 00:00:00 2001 From: Jacopo Lodovico Trabia Date: Mon, 19 Sep 2022 15:42:13 +0000 Subject: [PATCH 023/682] :globe_with_meridians: Add translations for: Italian. Currently translated at 32.5% (394 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/ --- frontend/translations/it.po | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/frontend/translations/it.po b/frontend/translations/it.po index 6c102f611f..8595baff2a 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-06 06:05+0000\n" -"Last-Translator: Valentina Chapellu \n" -"Language-Team: Italian " -"\n" +"PO-Revision-Date: 2022-09-20 13:46+0000\n" +"Last-Translator: Jacopo Lodovico Trabia \n" +"Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -1508,4 +1508,25 @@ msgstr "Annulla e mantieni il mio account" #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.confirm" -msgstr "Sì, cancellare il mio account" \ No newline at end of file +msgstr "Sì, cancellare il mio account" + +msgid "common.publish" +msgstr "Pubblica" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Gestisci team" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot è studiato per i team. Invita membri per lavorare insieme a file e " +"progetti" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Inizia il tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Inizia il tour" From e2b39c0680372d0e6e7c0ef9ebca414fa1009d43 Mon Sep 17 00:00:00 2001 From: Valentina Chapellu Date: Mon, 19 Sep 2022 15:23:03 +0000 Subject: [PATCH 024/682] :globe_with_meridians: Add translations for: Italian. Currently translated at 32.5% (394 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/ --- frontend/translations/it.po | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend/translations/it.po b/frontend/translations/it.po index 8595baff2a..50cce4fa72 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-20 13:46+0000\n" -"Last-Translator: Jacopo Lodovico Trabia \n" +"Last-Translator: Valentina Chapellu \n" "Language-Team: Italian \n" "Language: it\n" @@ -1530,3 +1530,7 @@ msgstr "Inizia il tutorial" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.walkthrough-hero.start" msgstr "Inizia il tour" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Appunta/Rimuovi" From f1552e40911f372d7f3a951b20e03a38fedb63a5 Mon Sep 17 00:00:00 2001 From: andy Date: Thu, 22 Sep 2022 16:40:29 +0200 Subject: [PATCH 025/682] :globe_with_meridians: Added translation for: Finnish. --- frontend/translations/fi.po | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 frontend/translations/fi.po diff --git a/frontend/translations/fi.po b/frontend/translations/fi.po new file mode 100644 index 0000000000..4f8f6e6dec --- /dev/null +++ b/frontend/translations/fi.po @@ -0,0 +1,2 @@ +msgid "" +msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file From 2667e515f7759b59af6f2ad1f2262445dd0e01a2 Mon Sep 17 00:00:00 2001 From: andy Date: Thu, 22 Sep 2022 16:43:43 +0200 Subject: [PATCH 026/682] :globe_with_meridians: Added translation for: Portuguese (Portugal). --- frontend/translations/pt_PT.po | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 frontend/translations/pt_PT.po diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po new file mode 100644 index 0000000000..4f8f6e6dec --- /dev/null +++ b/frontend/translations/pt_PT.po @@ -0,0 +1,2 @@ +msgid "" +msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file From 691a9fa87703e0eb15b6a2a0bc805d1ed9bf0eeb Mon Sep 17 00:00:00 2001 From: Vik Date: Wed, 21 Sep 2022 20:18:53 +0000 Subject: [PATCH 027/682] :globe_with_meridians: Add translations for: Russian. Currently translated at 65.8% (796 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/ --- frontend/translations/ru.po | 180 ++++++++++++++++++++++++++++++++---- 1 file changed, 163 insertions(+), 17 deletions(-) diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index 7bd861a330..d40710b2e0 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-20 13:46+0000\n" +"PO-Revision-Date: 2022-09-22 21:21+0000\n" "Last-Translator: Vik \n" "Language-Team: Russian \n" @@ -299,10 +299,10 @@ msgstr[1] "Шрифты добавлены (%s)" #, markdown msgid "dashboard.fonts.hero-text1" msgstr "" -"Любой загружаемый сюда шрифт будет доступен в свойствах файлов, " -"редактируемых командой. Шрифты из одного и того же семейства будут " -"сгруппированы по **названию семейства шрифта**. Для загрузки допустимы " -"следующие форматы: **TTF, OTF и WOFF** (используйте один из них)." +"Любой загружаемый сюда шрифт будет добавлен в семейство шрифтов и доступен в " +"свойствах файлов, редактируемых командой. Шрифты из одного и того же " +"семейства будут сгруппированы по **названию семейства шрифта**. Для загрузки " +"допустимы следующие форматы: **TTF, OTF и WOFF** (используйте один из них)." #, markdown msgid "dashboard.fonts.hero-text2" @@ -981,7 +981,7 @@ msgstr "Создать новую команду" #: src/app/main/ui/dashboard/team_form.cljs msgid "labels.create-team.placeholder" -msgstr "Введите имя для новой команды" +msgstr "Введите название новой команды" msgid "labels.custom-fonts" msgstr "Произвольные шрифты" @@ -1026,17 +1026,17 @@ msgstr "Редактор" #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.email" -msgstr "Email" +msgstr "Эл. почта" msgid "labels.export" msgstr "Экспорт" #: src/app/main/ui/settings/feedback.cljs msgid "labels.feedback-disabled" -msgstr "Обратная связь недоступна" +msgstr "Обратная связь отключена" msgid "labels.font-family" -msgstr "Гарнитура" +msgstr "Семейство шрифтов" msgid "labels.font-providers" msgstr "Поставщики шрифтов" @@ -1092,7 +1092,7 @@ msgstr "Ссылка" #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" -msgstr "Выход" +msgstr "Выйти" msgid "labels.manage-fonts" msgstr "Управление шрифтами" @@ -1110,11 +1110,11 @@ msgid "labels.new-password" msgstr "Новый пароль" msgid "labels.next" -msgstr "След." +msgstr "Далее" #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs msgid "labels.no-comments-available" -msgstr "Новых комментариев пока нет" +msgstr "Уведомлений о новых комментариях нет" #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" @@ -1449,11 +1449,11 @@ msgstr "" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.accept" -msgstr "" +msgstr "Обновить" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.cancel" -msgstr "" +msgstr "Отменить" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" @@ -2166,7 +2166,7 @@ msgstr "Вертикальное выравнивание" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.use-play-button" -msgstr "Нажмите кнопку \"Запуск\" вверху для показа прототипа." +msgstr "Нажмите кнопку воспроизведения вверху для показа прототипа." msgid "workspace.path.actions.add-node" msgstr "Добавить узел (%s)" @@ -2299,7 +2299,7 @@ msgstr "контур" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.dismiss" -msgstr "" +msgstr "Отклонить" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.there-are-updates" @@ -2307,7 +2307,7 @@ msgstr "Обнаружены обновления общих библиотек" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.update" -msgstr "" +msgstr "Обновить" msgid "workspace.viewport.click-to-close-path" msgstr "Нажмите для замыкания контура" @@ -2956,3 +2956,149 @@ msgstr "Слева направо" #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.direction-rtl" msgstr "Справа налево" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Удалить" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Дублировать" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Редактировать" + +msgid "workspace.undo.entry.single.rect" +msgstr "прямоугольник" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 общая страница" +msgstr[1] "%s общих страниц" + +msgid "common.share-link.permissions-pages" +msgstr "Общие страницы" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Подписка на рассылку" + +msgid "errors.invite-invalid.info" +msgstr "Возможно, это приглашение отменено или истёк срок его действия." + +msgid "labels.continue-with-penpot" +msgstr "Вы можете продолжить с аккаунтом Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Истекло" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-sent" +msgstr "Отзыв отправлен" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.invitations" +msgstr "Приглашения" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Показать" + +msgid "workspace.shape.menu.exclude" +msgstr "Исключить" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Разгруппировать" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-main" +msgstr "Показать основной компонент" + +msgid "workspace.undo.entry.single.group" +msgstr "группа" + +msgid "labels.log-or-sign" +msgstr "Войти или зарегистрироваться" + +msgid "labels.back" +msgstr "Назад" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.delete-invitation" +msgstr "Удалить приглашение" + +msgid "workspace.shape.menu.difference" +msgstr "Разница" + +msgid "workspace.shape.menu.restore-main" +msgstr "Восстановить основной компонент" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.reset-overrides" +msgstr "Сбросить переопределения" + +msgid "workspace.undo.entry.multiple.component" +msgstr "компоненты" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.title" +msgstr "История" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Узнайте больше о них и о том, как внести свой вклад" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Возникла проблема с импортом шаблона. Шаблон не был импортирован." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "" +"Присылайте мне новости, информацию об обновлениях продуктов и рекомендации о " +"Penpot." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Снять библиотеку с публикации" + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Введите название команды" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "Сделать миниатюрой" + +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "Удалить миниатюру" + +msgid "labels.and" +msgstr "и" + +msgid "labels.continue-with" +msgstr "Продолжить с" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "Приглашений нет." + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Участник" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "" +"Нажмите кнопку «Пригласить в команду», чтобы пригласить в эту команду больше " +"участников." + +msgid "onboarding.choice.team-up.create-team" +msgstr "Название вашей команды" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Скопировать" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Создать компонент" From da5fabbc6611c0f3e94de5b2bcd32c350ef981b5 Mon Sep 17 00:00:00 2001 From: Mateus Muller Date: Tue, 20 Sep 2022 19:04:34 +0000 Subject: [PATCH 028/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 60.6% (733 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 77882a9bfa..44339a86c7 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-20 13:46+0000\n" -"Last-Translator: Filipe Pessanha \n" +"PO-Revision-Date: 2022-09-22 21:21+0000\n" +"Last-Translator: Mateus Muller \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -2852,3 +2852,14 @@ msgid "onboarding.slide.0.desc1" msgstr "" "Criar lindas interfaces de usuários em colaboração com todos os membros da " "equipe." + +msgid "onboarding.slide.1.desc2" +msgstr "" +"Compartilhe com stakeholders, apresente propostas ao seu time e comece a " +"testar seus designs com usuários, tudo isso em um só lugar." + +msgid "onboarding.slide.0.alt" +msgstr "Crie designs" + +msgid "onboarding.slide.1.title" +msgstr "Dê vida aos seus projetos com interações" From 48e6cc5a6b42fb0178ffdcbc13d473e7754dbe33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ruan=20Arag=C3=A3o?= Date: Tue, 20 Sep 2022 20:58:31 +0000 Subject: [PATCH 029/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 60.6% (733 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 50 +++++++++++++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 44339a86c7..4f4cecf3bd 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-22 21:21+0000\n" -"Last-Translator: Mateus Muller \n" +"Last-Translator: Ruan Aragão \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -2863,3 +2863,51 @@ msgstr "Crie designs" msgid "onboarding.slide.1.title" msgstr "Dê vida aos seus projetos com interações" + +msgid "onboarding.slide.2.alt" +msgstr "Obter feedback" + +msgid "onboarding.slide.2.desc1" +msgstr "" +"Todos os membros da equipe trabalhando simultaneamente com design " +"multiusuário em tempo real e comentários, ideias e feedback centralizados " +"diretamente sobre os designs." + +msgid "onboarding.slide.3.alt" +msgstr "Handoff e lowcode" + +msgid "onboarding.slide.3.desc2" +msgstr "" +"Obtenha e forneça especificações de código como marcação (SVG, HTML) ou " +"estilos (CSS, Less, Stylus…)." + +msgid "onboarding.slide.3.desc1" +msgstr "" +"Sincronize o design e o código de todos os seus componentes e estilos e " +"obtenha trechos de código." + +msgid "onboarding.slide.3.title" +msgstr "Uma fonte compartilhada de verdade" + +msgid "onboarding.slide.2.title" +msgstr "Obtenha feedback, apresente e compartilhe seu trabalho" + +msgid "onboarding.team-modal.create-team" +msgstr "Crie uma equipe" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Uma equipe permite que você colabore com outros usuários do Penpot " +"trabalhando nos mesmos arquivos e projetos." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Arquivos e projetos ilimitados" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Edição multiusuário" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Gerenciamento de funções" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Membros ilimitados" From 47ba8383e886a8caf8be478889868e1ec19da14c Mon Sep 17 00:00:00 2001 From: nautilusx Date: Tue, 20 Sep 2022 16:46:47 +0000 Subject: [PATCH 030/682] :globe_with_meridians: Add translations for: German. Currently translated at 99.6% (1205 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 57 ++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 5da7315991..201bcbeb2e 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-12 13:20+0000\n" -"Last-Translator: Stas Haas \n" +"PO-Revision-Date: 2022-09-22 21:21+0000\n" +"Last-Translator: nautilusx \n" "Language-Team: German \n" "Language: de\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -1935,7 +1935,7 @@ msgid "onboarding.contrib.desc2.1" msgstr "Sie können das Projekt" msgid "onboarding.contrib.desc2.2" -msgstr "und mehr über die Richtlinien zum beitragen erfahren :)" +msgstr "und mehr über die Richtlinien zum Beitragen erfahren :)" msgid "onboarding.contrib.link" msgstr "auf GitHub ansehen" @@ -3035,7 +3035,9 @@ msgstr "Auswahl exportieren" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "Exportieren" +msgid_plural "" +msgstr[0] "1 Element exportieren" +msgstr[1] "%s Elemente exportieren" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -3297,7 +3299,7 @@ msgstr "Overlay %s öffnen" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-open-url" -msgstr "Url öffnen" +msgstr "URL öffnen" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-out" @@ -4443,7 +4445,7 @@ msgstr "im Leerraum verteilt" msgid "dasboard.team-hero.text" msgstr "" "Penpot ist für Teams gedacht. Um gemeinsam an Projekten und Dateien zu " -"arbeiten, laden Sie Mitglieder ein." +"arbeiten, laden Sie Mitglieder ein" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.title" @@ -4543,9 +4545,8 @@ msgstr "" msgid "onboarding-v2.before-start.desc1" msgstr "" -"Sie sollten wissen, dass es viele Ressourcen gibt, die Ihnen beim Einstieg " -"in Penpot erleichtern, wie z. B. das Benutzerhandbuch und unser Youtube-" -"Kanal." +"Sie sollten wissen, dass es viele Ressourcen gibt, die Ihnen den Einstieg in " +"Penpot erleichtern, wie z. B. das Benutzerhandbuch und unseren Youtube-Kanal." msgid "onboarding-v2.welcome.desc2.title" msgstr "In der Community mitmachen" @@ -4769,3 +4770,39 @@ msgstr "Zur Auswahl zoomen" msgid "shortcuts.toggle-zoom-style" msgstr "Zoom-Optionen umschalten" + +msgid "shortcuts.open-interactions" +msgstr "Zur Zuschauerinteraktionen" + +msgid "shortcuts.open-viewer" +msgstr "Zum Ansichtsmodus" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Elementgröße ändern" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Entdecken Sie weitere und erfahren Sie, wie Sie beitragen können" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Wenn Sie die Veröffentlichung aufheben, werden die darin enthaltenen " +"Elemente zu einer Bibliothek dieser Datei." +msgstr[1] "" +"Wenn Sie die Veröffentlichung aufheben, werden die darin enthaltenen " +"Elemente zu einer Bibliothek dieser Dateien." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Detaillierte Informationen über die Verwendung von Penpot. Vom Prototyping " +"bis zum Organisieren und Teilen von Designs." + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Ein öffentlicher Raum zum Lernen, Teilen und Diskutieren über Penpot, seine " +"Gegenwart und Zukunft mit der gesamten Community und dem Penpot-Kernteam." + +msgid "shortcuts.v-distribute" +msgstr "Vertikal verteilen" From d5fea6100db32fffb3b2f536a3b899485673c484 Mon Sep 17 00:00:00 2001 From: Jacopo Lodovico Trabia Date: Tue, 20 Sep 2022 19:38:54 +0000 Subject: [PATCH 031/682] :globe_with_meridians: Add translations for: Italian. Currently translated at 37.7% (456 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/ --- frontend/translations/it.po | 235 +++++++++++++++++++++++++++++++++++- 1 file changed, 232 insertions(+), 3 deletions(-) diff --git a/frontend/translations/it.po b/frontend/translations/it.po index 50cce4fa72..9427b91090 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-20 13:46+0000\n" -"Last-Translator: Valentina Chapellu \n" +"PO-Revision-Date: 2022-09-22 21:21+0000\n" +"Last-Translator: Jacopo Lodovico Trabia \n" "Language-Team: Italian \n" "Language: it\n" @@ -1430,7 +1430,7 @@ msgstr "Tutorial" #: src/app/main/ui/settings/profile.cljs msgid "labels.update" -msgstr "Aggiornare" +msgstr "Aggiorna" msgid "labels.upload" msgstr "Caricare" @@ -1534,3 +1534,232 @@ msgstr "Inizia il tour" #: src/app/main/ui/dashboard/project_menu.cljs msgid "dashboard.pin-unpin" msgstr "Appunta/Rimuovi" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Eliminare questa pagina?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Elimina membro del team" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Eliminare questo file?" +msgstr[1] "Eliminare questi file?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Questo file contiene librerie usate nel file:" +msgstr[1] "Questo file contiene librerie usate nei file:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Questi file contengono librerie usate nel file:" +msgstr[1] "Questi file contengono librerie usate nei file:" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.info" +msgstr "" +"Cancellando il tuo account, perderai tutti i tuoi archivi e progetti attuali." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.title" +msgstr "Eliminare il proprio account?" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Elimina conversazione" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Eliminazione file" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.accept" +msgstr "Elimina files" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "Eliminare %s files?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "Eliminazione %s files" + +msgid "modals.delete-font-variant.title" +msgstr "Eliminazione stile del carattere" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Eliminare questo progetto?" + +msgid "modals.delete-font.title" +msgstr "Eliminazione carattere" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Elimina progetto" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Elimina progetto" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "" +"Sei il proprietario di questo team. Per favore seleziona un altro membro da " +"promuovere a proprietario prima di uscire." + +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"Non puoi lasciare il team se non c'è alcun membro da promuovere a " +"proprietario. Potresti voler cancellare il team." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.promote-and-leave" +msgstr "Promuovi e esci" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.nudge-title" +msgstr "Ampiezza scatto" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.title" +msgstr "Nuovo proprietario del team" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Trasforma Testo" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Aggiorna team" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Elimina file" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "Eliminare questo file?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Eliminare questo team? Tutti i progetti e i file associati con questo team " +"verranno cancellati permanentemente." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Elimina team" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Eliminazione del team in corso" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Elimina membro" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "Lasciare il team di %s?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.select-member-to-promote" +msgstr "Seleziona un membro da promuovere" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.title" +msgstr "Prima di uscire" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.accept" +msgstr "Lascia il team" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.message" +msgstr "Lasciare questo team?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.accept" +msgstr "Trasferisci proprietà" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.hint" +msgstr "" +"Trasferendo la proprietà, il tuo ruolo diverrà quello di Admin, perdendo " +"alcuni privilegi su questo team. " + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.message" +msgstr "" +"Sei l'attuale proprietario di questo team. Trasferire la proprietà del team " +"a %s?" + +msgid "modals.delete-font.message" +msgstr "Eliminare questo carattere? Se è usato in un file, non verrà caricato." + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Elimina pagina" + +msgid "modals.invite-member.emails" +msgstr "Email, separate da virgole" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Elimina file" +msgstr[1] "Elimina i file" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Eliminazione del file" +msgstr[1] "Eliminazione dei file" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Eliminazione file in corso" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Eliminare questo membro dal team?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Invita membri al team" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Poiché sei il solo membro di questo team, il team verrà eliminato insieme ai " +"sui file e progetti." + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Altezza Linea" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.message" +msgstr "" +"Eliminare questa conversazione? Tutti i commenti in questo thread saranno " +"cancellati." + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.title" +msgstr "Elimina conversazione" + +msgid "modals.delete-font-variant.message" +msgstr "" +"Eliminare questo stile del carattere? Se è usato in un file, non verrà " +"caricato." From 69ab9e96968e2e4f9f441659d87c7e9ec5ef64ec Mon Sep 17 00:00:00 2001 From: Valentina Chapellu Date: Wed, 21 Sep 2022 14:20:26 +0000 Subject: [PATCH 032/682] :globe_with_meridians: Add translations for: Italian. Currently translated at 37.7% (456 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/ --- frontend/translations/it.po | 49 ++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/frontend/translations/it.po b/frontend/translations/it.po index 9427b91090..9e517d124f 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-22 21:21+0000\n" -"Last-Translator: Jacopo Lodovico Trabia \n" +"Last-Translator: Valentina Chapellu \n" "Language-Team: Italian \n" "Language: it\n" @@ -77,11 +77,11 @@ msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Accedi con LDAP" +msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "Connettersi via OpenID (SSO)" +msgstr "OpenID" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -1763,3 +1763,46 @@ msgid "modals.delete-font-variant.message" msgstr "" "Eliminare questo stile del carattere? Se è usato in un file, non verrà " "caricato." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +msgstr "Autenticazione con Google disabilitata nel backend" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Attenzione" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Impara le basi di Penpot divertendoti con questo tutorial pratico." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial pratico" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Esplora Penpot e scopri le sue principali funzionalità." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Spiegazione dell'interfaccia passo per passo" + +msgid "dashboard.libraries-and-templates" +msgstr "Librerie e template" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Esplorane di più e scopri come contribuire" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "" +"Si è verificato un problema nell'importazione del template. Il template non " +"è stato importato." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Spubblicare la libreria" From fbaa19d405126cdda26bfb386b78d176b32d0da5 Mon Sep 17 00:00:00 2001 From: Vik Date: Thu, 22 Sep 2022 21:22:40 +0000 Subject: [PATCH 033/682] :globe_with_meridians: Add translations for: Russian. Currently translated at 66.5% (804 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/ --- frontend/translations/ru.po | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index d40710b2e0..02818a2112 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-22 21:21+0000\n" +"PO-Revision-Date: 2022-09-23 11:06+0000\n" "Last-Translator: Vik \n" "Language-Team: Russian \n" @@ -3102,3 +3102,34 @@ msgstr "Скопировать" #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.create-component" msgstr "Создать компонент" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Удалить участника" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Отправить приглашение" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Удалить участника команды" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Вы уверены, что хотите удалить этого участника из команды?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Пригласить участников в команду" + +msgid "modals.invite-member.emails" +msgstr "Эл. почты, разделённые запятой" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "Вы уверены, что хотите покинуть команду %s?" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Вырезать" From 706bf86c95d1bc40f8aff37671b0a89a7368a684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Fri, 23 Sep 2022 00:20:59 +0000 Subject: [PATCH 034/682] :globe_with_meridians: Add translations for: Portuguese (Portugal). Currently translated at 64.9% (785 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/ --- frontend/translations/pt_PT.po | 2969 +++++++++++++++++++++++++++++++- 1 file changed, 2968 insertions(+), 1 deletion(-) diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index 4f8f6e6dec..eb9d81c8fe 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -1,2 +1,2969 @@ msgid "" -msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file +msgstr "" +"PO-Revision-Date: 2022-09-23 11:07+0000\n" +"Last-Translator: Dário \n" +"Language-Team: Portuguese (Portugal) \n" +"Language: pt_PT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 4.14.1\n" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Confirmar palavra-passe" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Cria uma conta de demonstração" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Só queres experimentar?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Já tens uma conta?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"Verifica o teu e‑mail e clica no link de verificação para começares a " +"utilizar o Penpot." + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.email" +msgstr "E-mail" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Nome completo" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Iniciar sessão" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Esqueceste a tua palavra-passe?" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "OpenID" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Escreve uma nova palavra-passe" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "Concordo em subscrever à lista de correio do Penpot." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "O token de recuperação é inválido." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-successfully" +msgstr "Palavra-passe alterada com sucesso" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "O perfil não está verificado, por favor valida-o antes de continuar." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Link de recuperação da palavra-passe enviado para o teu e-mail." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Entraste na equipa com sucesso" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Palavra-passe" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Mínimo de 8 caracteres" + +msgid "auth.privacy-policy" +msgstr "Política de privacidade" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Recuperar palavra-passe" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "Enviámos um email de verificação para" + +msgid "common.publish" +msgstr "Publicar" + +msgid "common.share-link.all-users" +msgstr "Todos os utilizadores Penpot" + +msgid "common.share-link.confirm-deletion-link-description" +msgstr "" +"Tens a certeza de que queres remover este link? Se o fizeres, deixa de ficar " +"disponível para ninguém" + +msgid "common.share-link.permissions-can-comment" +msgstr "Pode comentar" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Gestão da equipa" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"O Penpot é destinado a equipas. Convida colegas para colaborarem em projetos " +"e ficheiros" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Trabalho de equipa!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Passo a passo na interface" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Adicionar como biblioteca partilhada" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Alterar e-mail" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(copiar)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Criar nova equipa" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "O teu Penpot" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Duplicar %s ficheiros" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Ainda não tens ficheiros aqui" + +msgid "dashboard.export-binary-multi" +msgstr "Descarrega %s ficheiros Penpot (.penpot)" + +msgid "dashboard.export-frames" +msgstr "Exportar pranchetas para PDF…" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Exportar para PDF" + +msgid "dashboard.export-multi" +msgstr "Exportar %s ficheiros Penpot" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "%s de %s elementos selecionados" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Exportar" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "Exportar seleção" + +msgid "dashboard.export-single" +msgstr "Exportar ficheiro Penpot" + +msgid "dashboard.export-standard-multi" +msgstr "Descarregar %s ficheiros standard (svg + json)" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Não existem elementos com definições de exportação." + +msgid "dashboard.export.options.all.message" +msgstr "" +"ficheiros com bibliotecas partilhadas serão incluídos na exportação, " +"mantendo as ligações." + +msgid "dashboard.export.options.all.title" +msgstr "Exportar bibliotecas partilhadas" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Convidar para a equipa" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Sair da equipa" + +msgid "dashboard.libraries-and-templates" +msgstr "Bibliotecas e Templates" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Explora mais e sabe como contribuir" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "" +"Ocorreu um problema com a importação do template. O template não foi " +"importado." + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Bibliotecas" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "a carregar os teus ficheiros …" + +msgid "dashboard.loading-fonts" +msgstr "a carregar as tuas fontes …" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Mover para" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Mover %s ficheiros para" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Mover para outra equipa" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ Novo Ficheiro" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "Novo Ficheiro" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "O teu endereço de e-mail foi atualizado com sucesso" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "O teu endereço de e-mail foi verificado com sucesso" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "Palavra-passe guardada com sucesso!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "%s membros" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "Abrir ficheiro num novo separador" + +msgid "dashboard.options" +msgstr "Opções" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Alterar palavra-passe" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Fixar/Desafixar" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Projetos" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "Promover para proprietário" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.remove-account" +msgstr "Queres remover a tua conta?" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "Guardar definições" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Pesquisar…" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "Pesquisar por \"%s\"…" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "Selecionar idioma da Interface" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "Selecionar tema" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "Mostrar todos os ficheiros" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "O teu ficheiro foi removido com sucesso" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "O teu projeto foi eliminado com sucesso" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "O teu ficheiro foi duplicado com sucesso" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "O teu projeto foi duplicado com sucesso" + +#: src/app/main/ui/dashboard/grid.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-file" +msgstr "O teu ficheiro foi movido com sucesso" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "Os teus ficheiros foram movidos com sucesso" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "O teu projeto foi movido com sucesso" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "Trocar de equipa" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "Informação da equipa" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "Membros da equipa" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "Projetos da equipa" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.theme-change" +msgstr "Tema da interface" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "Resultados da pesquisa" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "Escreve para pesquisar resultados" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Cancelar publicação da Biblioteca" + +#: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs +msgid "dashboard.update-settings" +msgstr "Atualizar definições" + +#: src/app/main/ui/settings.cljs +msgid "dashboard.your-account-title" +msgstr "A tua conta" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "E-mail" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "O teu nome" + +#: src/app/main/ui/dashboard/search.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/libraries.cljs, src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.your-penpot" +msgstr "O teu Penpot" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Atenção" + +#: src/app/main/ui/confirm.cljs +msgid "ds.component-subtitle" +msgstr "Componentes para atualizar:" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Cancelar" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "Ok" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "Tens a certeza?" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "ds.updated-at" +msgstr "Atualizado: %s" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Provedor de autenticação não configurado." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Este ficheiro já foi usado com Componentes V2 ativos." + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "E-mail já utilizado" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "E-mail já validado." + +msgid "errors.email-as-password" +msgstr "Não podes utilizar o teu e-mail como palavra-passe" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.email-has-permanent-bounces" +msgstr "O e-mail «%s» tem muitos relatórios de rejeição permanentes." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "O e-mail de confirmação deve combinar" + +msgid "errors.email-spam-or-permanent-bounces" +msgstr "O e-mail «%s» foi denunciado como spam ou devolvido permanentemente." + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.generic" +msgstr "Ocorreu algo inesperado." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +msgstr "Autenticação com o Google desativado no back-end" + +#: src/app/main/ui/components/color_input.cljs +msgid "errors.invalid-color" +msgstr "Cor inválida" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "Convite inválido" + +msgid "errors.invite-invalid.info" +msgstr "Este convite pode ter sido cancelado ou expirado." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "Autenticação LDAP está desativada." + +msgid "errors.media-format-unsupported" +msgstr "O formato de imagem não é suportado (deverá ser svg, jpg ou png)." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "A imagem é demasiado grande para ser inserida." + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "errors.media-type-mismatch" +msgstr "Parece que o conteúdo da imagem não corresponde à extensão do ficheiro." + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "errors.media-type-not-allowed" +msgstr "Parece que esta não é uma imagem válida." + +#: src/app/main/ui/dashboard/team.cljs +msgid "errors.member-is-muted" +msgstr "" +"O perfil que estás a convidar tem e-mails silenciados (relatórios de spam ou " +"devoluções altas)." + +msgid "errors.network" +msgstr "Não é possível conectar ao servidor do back-end." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "A palavra-passe de confirmação tem de corresponder" + +#: src/app/main/ui/auth/register.cljs +msgid "errors.registration-disabled" +msgstr "A criação de contas está atualmente desativada." + +msgid "errors.team-leave.insufficient-members" +msgstr "" +"Membros insuficientes para deixar a equipa, provavelmente queres eliminá-la." + +msgid "errors.team-leave.member-does-not-exists" +msgstr "O membro que tentas atribuir não existe." + +msgid "errors.team-leave.owner-cant-leave" +msgstr "" +"O proprietário não pode sair da equipa, deverás retribuir a função de " +"proprietário." + +msgid "errors.terms-privacy-agreement-invalid" +msgstr "Tens de aceitar os nossos termos de serviço e política de privacidade." + +#: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "errors.unexpected-error" +msgstr "Ocorreu um erro inesperado." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.unexpected-token" +msgstr "Token desconhecido" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.wrong-credentials" +msgstr "Utilizador ou palavra-passe parecem estar errados." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.wrong-old-password" +msgstr "Palavra-passe antiga está incorreta" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-start" +msgstr "Participa na conversa" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "Sentes vontade de falar? Conversa connosco no Gitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "Descrição" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"Estamos felizes em ter-te aqui. Se precisares de ajuda, por favor pesquisa " +"antes de publicar." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Comunidade Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +msgstr "Assunto" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" +"Por favor descreve o motivo do teu e-mail, especificando se é um problema, " +"uma ideia, ou uma dúvida. Um membro da nossa equipa tentará responder o mais " +"rápido possível." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Ir para o fórum Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "E-mail" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "Ir para o Twitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "Aqui para ajudar com as tuas dúvidas técnicas." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-title" +msgstr "Conta de suporte no Twitter" + +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +msgstr "Ocorreu um erro" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Desfoque" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "Valor" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.hex" +msgstr "HEX" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.hsla" +msgstr "HSLA" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.rgba" +msgstr "RGBA" + +#: src/app/main/ui/handoff/attributes/fill.cljs +msgid "handoff.attributes.fill" +msgstr "Preencher" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "Descarregar imagem original" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "Altura" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Largura" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout" +msgstr "Layout" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Raio" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Rotação" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Topo" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Largura" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Sombra" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.blur" +msgstr "B" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.offset-x" +msgstr "X" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.offset-y" +msgstr "Y" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.spread" +msgstr "S" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Traço" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Centro" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Interior" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Exterior" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Pontos" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Misto" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Nenhum" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Sólido" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Largura" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografia" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-family" +msgstr "Família da Fonte" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Tamanho da Fonte" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Estilo da Fonte" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Espaço entre caracteres" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Altura da Linha" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Decoração de Texto" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Nenhum" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Rasurado" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Sublinhado" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Transformação de Texto" + +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "Minúsculo" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Nenhum" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Capitalização de Título" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "Maiúsculo" + +msgid "handoff.tabs.code.selected.component" +msgstr "Componente" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Curva" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Prancha" + +msgid "handoff.tabs.code.selected.path" +msgstr "Caminho" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Rectângulo" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.text" +msgstr "Texto" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Informação" + +msgid "history.alert-message" +msgstr "Estás a visualizar a versão %s" + +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Atalhos" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.about-penpot" +msgstr "Sobre o Penpot" + +msgid "labels.accept" +msgstr "Aceitar" + +msgid "labels.add-custom-font" +msgstr "Adicionar fonte personalizada" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.admin" +msgstr "Administração" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.all" +msgstr "Todos" + +msgid "labels.and" +msgstr "e" + +msgid "labels.back" +msgstr "Voltar" + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.main-message" +msgstr "Error de Servidor (Bad Gateway)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "Cancelar" + +msgid "labels.centered" +msgstr "Centrar" + +msgid "labels.close" +msgstr "Fechar" + +#: src/app/main/ui/dashboard/comments.cljs +msgid "labels.comments" +msgstr "Comentários" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.confirm-password" +msgstr "Confirmar palavra-passe" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Comunidade" + +msgid "labels.content" +msgstr "Conteúdo" + +msgid "labels.continue" +msgstr "Continuar" + +msgid "labels.continue-with" +msgstr "Continuar com" + +msgid "labels.continue-with-penpot" +msgstr "Podes continuar com uma conta Penpot" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "labels.create" +msgstr "Criar" + +#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs +msgid "labels.create-team" +msgstr "Criar equipa nova" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.create-team.placeholder" +msgstr "Introduz o nome da nova equipa" + +msgid "labels.custom-fonts" +msgstr "Fonte personalizada" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.dashboard" +msgstr "Painel" + +msgid "labels.default" +msgstr "pré-definido" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete" +msgstr "Eliminar" + +#: src/app/main/ui/comments.cljs +msgid "labels.delete-comment" +msgstr "Eliminar comentário" + +#: src/app/main/ui/comments.cljs +msgid "labels.delete-comment-thread" +msgstr "Eliminar tópico" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.delete-invitation" +msgstr "Eliminar convite" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete-multi-files" +msgstr "Eliminar %s ficheiros" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.drafts" +msgstr "Rascunhos" + +msgid "labels.edit-file" +msgstr "Editar ficheiro" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.editor" +msgstr "Editor" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "E-mail" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Expirado" + +msgid "labels.export" +msgstr "Exportar" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-disabled" +msgstr "Feedback desativado" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-sent" +msgstr "Feedback enviado" + +msgid "labels.font-family" +msgstr "Família da Fonte" + +msgid "labels.font-providers" +msgstr "Provedores de fontes" + +msgid "labels.go-back" +msgstr "Voltar atrás" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.help-center" +msgstr "Centro de Ajuda" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.hide-resolved-comments" +msgstr "Esconder comentários resolvidos" + +msgid "labels.icons" +msgstr "Ícones" + +msgid "labels.images" +msgstr "Imagens" + +msgid "labels.installed-fonts" +msgstr "Fontes instaladas" + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.desc-message" +msgstr "" +"Ocorreu algo inesperado. Tenta novamente a operação e se o problema " +"persistir, entra em contacto com o suporte." + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.main-message" +msgstr "Erro interno" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.invitations" +msgstr "Convites" + +#: src/app/main/ui/settings/options.cljs +msgid "labels.language" +msgstr "Idioma" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.libraries-and-templates" +msgstr "Bibliotecas e Templates" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.new-password" +msgstr "Palavra-passe nova" + +msgid "labels.link" +msgstr "Link" + +msgid "labels.log-or-sign" +msgstr "Iniciar sessão ou registar" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +msgstr "Sair" + +msgid "labels.manage-fonts" +msgstr "Gerir fontes" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.name" +msgstr "Nome" + +msgid "labels.next" +msgstr "Próximo" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs +msgid "labels.no-comments-available" +msgstr "Não tens notificações de comentários pendentes" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "Não há convites." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "" +"Clica no botão \"Convidar para a equipa\" para convidar mais membros para " +"esta equipa." + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.auth-info" +msgstr "Estás autenticado como" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.old-password" +msgstr "Palavra-passe antiga" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.desc-message" +msgstr "Esta página não existe ou não tens permissões para a aceder." + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.main-message" +msgstr "Ups!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-files" +msgid_plural "labels.num-of-files" +msgstr[0] "1 ficheiro" +msgstr[1] "%s ficheiros" + +msgid "labels.num-of-frames" +msgid_plural "labels.num-of-frames" +msgstr[0] "1 prancha" +msgstr[1] "%s pranchas" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-projects" +msgid_plural "labels.num-of-projects" +msgstr[0] "1 projeto" +msgstr[1] "%s projetos" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Palavra-passe" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.only-yours" +msgstr "Apenas teu" + +msgid "labels.or" +msgstr "ou" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.owner" +msgstr "Proprietário" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "Pendente" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.permissions" +msgstr "Permissões" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.profile" +msgstr "Perfil" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.projects" +msgstr "Projetos" + +msgid "labels.recent" +msgstr "Recente" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.release-notes" +msgstr "Notas de versões" + +#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.remove" +msgstr "Remover" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "Remover membro" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.rename" +msgstr "Renomear" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "Reenviar convite" + +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +msgid "labels.retry" +msgstr "Tentar novamente" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.role" +msgstr "Cargo" + +msgid "labels.save" +msgstr "Salvar" + +msgid "labels.search-font" +msgstr "Pesquisar font" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.send" +msgstr "Enviar" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.sending" +msgstr "Enviando…" + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.main-message" +msgstr "Serviço Indisponível" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "Bibliotecas" + +msgid "labels.share-prototype" +msgstr "Partilhar protótipo" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-all-comments" +msgstr "Mostrar todos os comentários" + +msgid "labels.show-comments-list" +msgstr "Mostrar lista de comentários" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-your-comments" +msgstr "Mostrar apenas os teus comentários" + +#: src/app/main/ui/static.cljs +msgid "labels.sign-out" +msgstr "Sair" + +msgid "labels.skip" +msgstr "Saltar" + +msgid "labels.start" +msgstr "Início" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Estado" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Tutoriais" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +msgstr "Atualizar" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Atualizar equipa" + +msgid "labels.upload" +msgstr "Carregar" + +msgid "labels.upload-custom-fonts" +msgstr "Carregar fontes personalizadas" + +msgid "labels.uploading" +msgstr "Carregando…" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Visualizador" + +msgid "labels.workspace" +msgstr "Área de trabalho" + +#: src/app/main/ui/comments.cljs +msgid "labels.write-new-comment" +msgstr "Escrever novo comentário" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.your-account" +msgstr "A tua conta" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.accept" +msgstr "Adicionar como Biblioteca Partilhada" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.hint" +msgstr "" +"Uma vez adicionados como Biblioteca Partilhada, os ativos na biblioteca " +"deste ficheiro estarão disponíveis com o resto dos teus ficheiros." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.message" +msgstr "Adicionar \"%s\" como Biblioteca Partilhada" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.big-nudge" +msgstr "Grande deslocamento" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.confirm-email" +msgstr "Verificar o novo e-mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.info" +msgstr "" +"Vamos enviar um e‑mail para o teu endereço atual \"%s\" para verificar a tua " +"identidade." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Novo e-mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "Alterar e-mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "Altera o teu e-mail" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.cancel" +msgstr "Cancelar e manter a minha conta" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.confirm" +msgstr "Sim, elimina a minha conta" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.info" +msgstr "Ao remover a tua conta vais perder todos os projetos e ficheiros." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.title" +msgstr "Tens a certeza de que queres eliminar a tua conta?" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Eliminar conversa" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.message" +msgstr "" +"Tens a certeza de que pretender eliminar esta conversa? Todos os comentários " +"neste tópico serão eliminados." + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.title" +msgstr "Eliminar conversa" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Eliminar ficheiro" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "Tens a certeza de que pretendes eliminar este ficheiro?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Eliminando ficheiro" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.accept" +msgstr "Eliminar ficheiros" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "Tens a certeza de que pretendes eliminar %s ficheiros?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "Eliminando %s ficheiros" + +msgid "modals.delete-font-variant.title" +msgstr "Eliminando estilo de fonte" + +msgid "modals.delete-font-variant.message" +msgstr "" +"Tens a certeza de que pretendes eliminar este estilo de fonte? Não carregará " +"se estiver a ser utilizado num ficheiro." + +msgid "modals.delete-font.title" +msgstr "Eliminando fonte" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Tens a certeza de que pretendes eliminar esta página?" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Eliminar página" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Eliminar projeto" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Tens a certeza de que pretendes eliminar este projeto?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Eliminar projeto" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Este ficheiro tem bibliotecas utilizadas neste ficheiro:" +msgstr[1] "Este ficheiro tem bibliotecas utilizadas nestes ficheiros:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Eliminar ficheiro" +msgstr[1] "Eliminar ficheiros" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Tens a certeza de que pretendes eliminar este ficheiro?" +msgstr[1] "Tens a certeza de que pretendes eliminar estes ficheiros?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Eliminando ficheiro" +msgstr[1] "Eliminando ficheiros" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Eliminando ficheiro" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Eliminar equipa" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Tens a certeza de que pretendes eliminar esta equipa? Todos os projetos e " +"ficheiros associados com a equipa serão eliminados permanentemente." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Eliminando equipa" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Eliminar membro" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Eliminar membro da equipa" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Tens a certeza de que pretendes eliminar este membro da equipa?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Enviar convite" + +msgid "modals.invite-member.emails" +msgstr "E-mails, separados por vírgulas" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Convidar membros para a equipa" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Como és o único membro desta equipa, a equipa será eliminado juntamente com " +"os projetos e ficheiros." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "Tens a certeza de que pretendes sair da equipa %s?" + +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"Não podes sair da equipa se não tiveres outros membros para promover para " +"proprietário. Poderás querer eliminar a equipa." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "" +"És o proprietário desta equipa. Por favor seleciona outro membro para " +"promover para proprietário antes de saíres." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.promote-and-leave" +msgstr "Promover e sair" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.select-member-to-promote" +msgstr "Seleciona um membro para promover" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.title" +msgstr "Antes de saires" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.accept" +msgstr "Sair da equipa" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.message" +msgstr "Tens a certeza de que pretendes sair desta equipa?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.title" +msgstr "Saindo da equipa" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.nudge-title" +msgstr "Montante de deslocamento" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.accept" +msgstr "Transferir propriedade" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.hint" +msgstr "" +"Se transferires a propriedade, vais alterar a tua função para Administrador, " +"perdendo algumas permissões sobre esta equipa. " + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.message" +msgstr "" +"És o proprietário atual desta equipa. Tens a certeza de que pretendes " +"promover %s a proprietário da equipa?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.title" +msgstr "Novo proprietário de equipa" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.accept" +msgstr "Remover como Biblioteca Partilhada" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Uma vez removida como Biblioteca Partilhada, a Biblioteca de Ficheiros deste " +"ficheiro deixarão de estar disponíveis para serem utilizados com o resto dos " +"teus ficheiros." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.message" +msgstr "Remover \"%s\" como Biblioteca Partilhada" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "Pequeno deslocamento" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Cancelar publicação" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Se cancelares a publicação, os ativos nele tornam-se uma biblioteca deste " +"ficheiro." +msgstr[1] "" +"Se cancelares a publicação, os ativos nele tornam-se uma biblioteca destes " +"ficheiros." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Está em uso neste ficheiro:" +msgstr[1] "Está em uso nestes ficheiros:" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"Estás prestes a atualizar componentes numa biblioteca partilhada. Pode " +"afetar outros ficheiros que o utilizam." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.message" +msgstr "Atualizar componentes numa biblioteca partilhada" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.cancel" +msgstr "Cancelar" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.hint" +msgstr "" +"Estás prestes a atualizar componentes numa biblioteca partilhada. Pode " +"afetar outros ficheiros que o utilizam." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.message" +msgstr "Atualizar componente numa biblioteca partilhada" + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Convite enviado com sucesso" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "" +"Não podes eliminar o teu perfil. Atribui um proprietário às tuas equipas " +"antes de avançar." + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Perfil salvo com sucesso!" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "E-mail de verificação enviado para %s. Verifica o teu e-mail!" + +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Deves saber que existem muitos recursos disponíveis para ajudar a começar a " +"usar o Penpot, como o Guia de Utilizador e no nosso canal de YouTube." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Informações detalhadas sobre como utilizar o Penpot. Desde o protótipo à " +"organização ou partilha de designs." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Guia de Utilizador" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"O Penpot é um software de código aberto criado pela Kaleidos e também pela " +"comunidade, onde muitas pessoas já colaboram. Todos podem colaborar:" + +msgid "onboarding-v2.before-start.desc3" +msgstr "Poderás ver os nossos tutoriais e os criados pela nossa comunidade." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Tutoriais em vídeo" + +msgid "onboarding-v2.before-start.title" +msgstr "Antes de começares" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Um espaço público para aprender, partilhar e discutir sobre o Penpot, o " +"presente e futuro com toda a Comunidade e com a equipa principal do Penpot." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "Participando na Comunidade" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Onde vais encontrar como colaborar com traduções, solicitações de " +"funcionalidades, contribuições core, caça a bugs…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Guia de Contribuição" + +msgid "onboarding-v2.welcome.title" +msgstr "Bem-vindo ao Penpot!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Criar uma equipa mais tarde" + +msgid "onboarding.choice.team-up.create-team" +msgstr "O nome da tua equipa" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "Depois de nomeares a tua equipa, poderás convidar pessoas para entrar." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Escreve o nome da equipa" + +msgid "onboarding.contrib.alt" +msgstr "Código aberto" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Convida membros" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Lembra-te em incluir todos. Programadores, designers, gestores... acrescenta " +"diversidade :)" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Criar equipa e convidar mais tarde" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Criar equipa e enviar convites" + +msgid "onboarding.choice.team-up.roles" +msgstr "Convidar com a função:" + +msgid "onboarding.choice.title" +msgstr "Bem-vindo ao Penpot" + +msgid "onboarding.contrib.desc1" +msgstr "" +"O Penpot é de código aberto, feito por e para a comunidade. Se queres " +"colaborar, és mais que bem-vindo!" + +msgid "onboarding.contrib.title" +msgstr "Contribuidor Open Source?" + +msgid "onboarding.contrib.desc2.1" +msgstr "Podes aceder o" + +msgid "onboarding.contrib.desc2.2" +msgstr "e seguir as instruções de contribuição :)" + +msgid "onboarding.contrib.link" +msgstr "projeto no GitHub" + +msgid "onboarding.newsletter.accept" +msgstr "Sim, subscreve" + +msgid "onboarding.newsletter.decline" +msgstr "Não, obrigado" + +msgid "onboarding.newsletter.desc" +msgstr "" +"Subscreve a nossa newsletter para estares atualizado com o progresso e as " +"novidades do desenvolvimento de produto." + +msgid "onboarding.newsletter.policy" +msgstr "Política de Privacidade." + +msgid "onboarding.newsletter.privacy1" +msgstr "Porque nos preocupamos com a privacidade, aqui está o nosso " + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Enviaremos apenas e-mail relevantes para ti. Podes cancelar a subscrição a " +"qualquer momento no teu perfil ou através do link de cancelamento em " +"qualquer uma das nossas newsletters." + +msgid "onboarding.newsletter.title" +msgstr "Queres receber as novidades do Penpot?" + +msgid "onboarding.slide.0.alt" +msgstr "Cria designs" + +msgid "onboarding.slide.0.desc1" +msgstr "" +"Cria interfaces maravilhosas em colaboração com todos os membros da equipa." + +msgid "onboarding.slide.0.desc2" +msgstr "" +"Mantém a consistência em escala com componentes, bibliotecas e sistemas de " +"design." + +msgid "onboarding.slide.0.title" +msgstr "Bibliotecas de design, estilos e componentes" + +msgid "onboarding.slide.1.alt" +msgstr "Protótipos interativos" + +msgid "onboarding.slide.1.desc1" +msgstr "Cria interações ricas para simular o comportamento do produto." + +msgid "onboarding.slide.1.desc2" +msgstr "" +"Partilha com stakeholders, apresenta propostas à tua equipa e começa a " +"testar os designs com utilizadores, tudo em um só lugar." + +msgid "onboarding.slide.1.title" +msgstr "Dá vida aos teus designs com interações" + +msgid "onboarding.slide.2.alt" +msgstr "Recebe feedback" + +msgid "onboarding.slide.2.desc1" +msgstr "" +"Todos os membros da equipa a colaborar em tempo real, com comentários, " +"ideias e feedback centralizados diretamente nos designs." + +msgid "onboarding.slide.2.title" +msgstr "Recebe feedback, apresenta e partilha o teu trabalho" + +msgid "onboarding.slide.3.alt" +msgstr "Handoff e low code" + +msgid "onboarding.slide.3.desc1" +msgstr "" +"Sincroniza o design e código de todos os teus componentes e estilos, e obtém " +"os snippets de código." + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Uma equipa permite que colabores com outros utilizadores do Penpot " +"trabalhando nos mesmos ficheiros e projetos." + +msgid "onboarding.slide.3.desc2" +msgstr "" +"Recebe e fornece especificações de código como markup (SVG, HTML) ou estilos " +"(CSS, Less, Stylus...)." + +msgid "onboarding.slide.3.title" +msgstr "Uma origem partilhada de verdade" + +msgid "onboarding.team-modal.create-team" +msgstr "Cria uma equipa" + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Ficheiros e projetos ilimitados" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Edição Multiplayer" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Membros ilimitados" + +msgid "onboarding.templates.subtitle" +msgstr "Aqui tens alguns templates." + +msgid "onboarding.templates.title" +msgstr "Começa a desenhar" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Ir para login" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +msgid "onboarding.welcome.title" +msgstr "Bem-vindo ao Penpot" + +#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "settings.multiple" +msgstr "Misturado" + +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Básicos" + +msgid "shortcut-section.dashboard" +msgstr "Painel" + +msgid "shortcut-section.viewer" +msgstr "Visualizador" + +msgid "shortcut-section.workspace" +msgstr "Área de Trabalho" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Alinhamento" + +msgid "shortcut-subsection.edit" +msgstr "Editar" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Genérico" + +msgid "shortcut-subsection.general-viewer" +msgstr "Genérico" + +msgid "shortcut-subsection.main-menu" +msgstr "Menu Principal" + +msgid "shortcut-subsection.modify-layers" +msgstr "Modificar camadas" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Navegação" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Navegação" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Navegação" + +msgid "shortcut-subsection.panels" +msgstr "Painéis" + +msgid "shortcut-subsection.path-editor" +msgstr "Caminhos" + +msgid "shortcut-subsection.shape" +msgstr "Formas" + +msgid "shortcuts.add-comment" +msgstr "Comentários" + +msgid "shortcuts.add-node" +msgstr "Adicionar nó" + +msgid "shortcuts.align-bottom" +msgstr "Align abaixo" + +msgid "shortcuts.align-hcenter" +msgstr "Alinhar ao centro horizontalmente" + +msgid "shortcuts.align-left" +msgstr "Alinhar à esquerda" + +msgid "shortcuts.align-right" +msgstr "Alinhas à direita" + +msgid "shortcuts.align-top" +msgstr "Alinhar topo" + +msgid "shortcuts.align-vcenter" +msgstr "Alinhar ao centro verticalmente" + +msgid "shortcuts.artboard-selection" +msgstr "Criar prancha a partir da seleção" + +msgid "shortcuts.bool-difference" +msgstr "Diferença booleana" + +msgid "shortcuts.bool-exclude" +msgstr "Exclusão Booleana" + +msgid "shortcuts.bool-intersection" +msgstr "Intersecção Booleana" + +msgid "shortcuts.bool-union" +msgstr "União Booleana" + +msgid "shortcuts.bring-back" +msgstr "Enviar para trás" + +msgid "shortcuts.bring-forward" +msgstr "Mover para a frente" + +msgid "shortcuts.bring-backward" +msgstr "Mover para trás" + +msgid "shortcuts.bring-front" +msgstr "Enviar para a frente" + +msgid "shortcuts.clear-undo" +msgstr "Limpar historial" + +msgid "shortcuts.copy" +msgstr "Copiar" + +msgid "shortcuts.create-component" +msgstr "Criar componente" + +msgid "shortcuts.create-new-project" +msgstr "Criar novo" + +msgid "shortcuts.cut" +msgstr "Cortar" + +msgid "shortcuts.decrease-zoom" +msgstr "Reduzir zoom" + +msgid "shortcuts.delete" +msgstr "Eliminar" + +msgid "shortcuts.delete-node" +msgstr "Eliminar nó" + +msgid "shortcuts.detach-component" +msgstr "Desanexar componente" + +msgid "shortcuts.draw-curve" +msgstr "Curva" + +msgid "shortcuts.draw-ellipse" +msgstr "Elipse" + +msgid "shortcuts.draw-frame" +msgstr "Quadro" + +msgid "shortcuts.draw-nodes" +msgstr "Desenhar caminho" + +msgid "shortcuts.draw-path" +msgstr "Caminho" + +msgid "shortcuts.draw-rect" +msgstr "Rectângulo" + +msgid "shortcuts.draw-text" +msgstr "Texto" + +msgid "shortcuts.duplicate" +msgstr "Duplicar" + +msgid "shortcuts.escape" +msgstr "Cancelar" + +msgid "shortcuts.export-shapes" +msgstr "Exportar formas" + +msgid "shortcuts.fit-all" +msgstr "Zoom para caber tudo" + +msgid "shortcuts.flip-horizontal" +msgstr "Virar horizontalmente" + +msgid "shortcuts.go-to-drafts" +msgstr "Ir para rascunhos" + +msgid "shortcuts.go-to-search" +msgstr "Pesquisa" + +msgid "shortcuts.group" +msgstr "Grupo" + +msgid "shortcuts.h-distribute" +msgstr "Distribuir horizontalmente" + +msgid "shortcuts.increase-zoom" +msgstr "Aumentar zoom" + +msgid "shortcuts.insert-image" +msgstr "Inserir imagem" + +msgid "shortcuts.join-nodes" +msgstr "Juntar nós" + +msgid "shortcuts.make-corner" +msgstr "Fazer canto" + +msgid "shortcuts.make-curve" +msgstr "Fazer curva" + +msgid "shortcuts.mask" +msgstr "Máscara" + +msgid "shortcuts.merge-nodes" +msgstr "Fundir nós" + +msgid "shortcuts.move" +msgstr "Mover" + +msgid "shortcuts.move-fast-left" +msgstr "Mover para a esquerda rápido" + +msgid "shortcuts.move-fast-right" +msgstr "Mover para a direita rápido" + +msgid "shortcuts.move-fast-up" +msgstr "Mover para cima rápido" + +msgid "shortcuts.move-nodes" +msgstr "Mover nó" + +msgid "shortcuts.move-unit-down" +msgstr "Mover para baixo" + +msgid "shortcuts.move-unit-left" +msgstr "Mover para a esquerda" + +msgid "shortcuts.move-unit-right" +msgstr "Mover para a direita" + +msgid "shortcuts.move-unit-up" +msgstr "Mover para cima" + +msgid "shortcuts.next-frame" +msgstr "Próximo quadro" + +msgid "shortcuts.not-found" +msgstr "Nenhum atalho encontrado" + +msgid "shortcuts.opacity-0" +msgstr "Definir opacidade a 100%" + +msgid "shortcuts.open-comments" +msgstr "Ir para secção de comentários" + +msgid "shortcuts.opacity-1" +msgstr "Definir opacidade a 10%" + +msgid "shortcuts.opacity-2" +msgstr "Definir opacidade a 20%" + +msgid "shortcuts.opacity-3" +msgstr "Definir opacidade a 30%" + +msgid "shortcuts.opacity-4" +msgstr "Definir opacidade a 40%" + +msgid "shortcuts.opacity-5" +msgstr "Definir opacidade a 50%" + +msgid "shortcuts.opacity-6" +msgstr "Definir opacidade a 60%" + +msgid "shortcuts.opacity-7" +msgstr "Definir opacidade a 70%" + +msgid "shortcuts.opacity-8" +msgstr "Definir opacidade a 80%" + +msgid "shortcuts.opacity-9" +msgstr "Definir opacidade a 90%" + +msgid "shortcuts.open-color-picker" +msgstr "Selector de cores" + +msgid "shortcuts.open-dashboard" +msgstr "Ir para painel" + +msgid "shortcuts.open-handoff" +msgstr "Ir para secção de transferências" + +msgid "shortcuts.open-interactions" +msgstr "Ir para secção de interações" + +msgid "shortcuts.open-workspace" +msgstr "Ir para a área de trabalho" + +msgid "shortcuts.or" +msgstr " ou " + +msgid "shortcuts.paste" +msgstr "Colar" + +msgid "shortcuts.prev-frame" +msgstr "Quadro anterior" + +msgid "shortcuts.redo" +msgstr "Refazer" + +msgid "shortcuts.reset-zoom" +msgstr "Redefinir zoom" + +msgid "shortcuts.search-placeholder" +msgstr "Pesquisar atalhos" + +msgid "shortcuts.separate-nodes" +msgstr "Separar nós" + +msgid "shortcuts.show-pixel-grid" +msgstr "Mostrar/esconder grelha de pixels" + +msgid "shortcuts.show-shortcuts" +msgstr "Mostrar/esconder atalhos" + +msgid "shortcuts.snap-nodes" +msgstr "Encaixar nos nós" + +msgid "shortcuts.snap-pixel-grid" +msgstr "Encaixar na grelha de pixels" + +msgid "shortcuts.start-editing" +msgstr "Iniciar edição" + +msgid "shortcuts.start-measure" +msgstr "Iniciar medição" + +msgid "shortcuts.stop-measure" +msgstr "Parar medição" + +msgid "shortcuts.thumbnail-set" +msgstr "Definir imagem destaque" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Atalhos do teclado" + +msgid "shortcuts.toggle-alignment" +msgstr "Alternar alinhamento dinâmico" + +msgid "shortcuts.toggle-assets" +msgstr "Alternar ativos" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Alternar selector de cores" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Alternar modo de foco" + +msgid "shortcuts.toggle-grid" +msgstr "Mostrar/esconder grelha" + +msgid "shortcuts.toggle-history" +msgstr "Alternar histórico" + +msgid "shortcuts.toggle-layers" +msgstr "Alternar camadas" + +msgid "shortcuts.toggle-lock" +msgstr "Bloquear selecionado" + +msgid "shortcuts.toggle-lock-size" +msgstr "Bloquear proporções" + +msgid "shortcuts.toggle-rules" +msgstr "Mostrar/esconder regras" + +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - Penpot" + +msgid "shortcuts.toggle-scale-text" +msgstr "Alternar escala de texto" + +msgid "shortcuts.toggle-snap-grid" +msgstr "Encaixar na grelha" + +msgid "shortcuts.toggle-snap-guide" +msgstr "Encaixar nas guias" + +msgid "shortcuts.toggle-textpalette" +msgstr "Alternar paleta de texto" + +msgid "shortcuts.toggle-visibility" +msgstr "Alternar visibilidade" + +msgid "shortcuts.toggle-zoom-style" +msgstr "Alternar estilo de zoom" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Alternar tela cheia" + +msgid "shortcuts.undo" +msgstr "desfazer" + +msgid "shortcuts.ungroup" +msgstr "Desagrupar" + +msgid "shortcuts.unmask" +msgstr "Desmascarar" + +msgid "shortcuts.v-distribute" +msgstr "Distribuir verticalmente" + +msgid "shortcuts.zoom-selected" +msgstr "Zoom para selecionados" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.font-providers" +msgstr "Provedores de fonte - %s - Penpot" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.fonts" +msgstr "Fontes - %s - Penpot" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Bibliotecas partilhadas - %s - Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Dá feedback - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Palavra-passe - Penpot" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Projetos - %s - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Pesquisa - %s - Penpot" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot - Liberdade de Design para Equipas" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Perfil - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Definições - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-invitations" +msgstr "Convites - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Membros - %s - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Modo visualizador - Penpot" + +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + +msgid "viewer.breaking-change.description" +msgstr "" +"Este link partilhável já não é válido. Cria uma nova ou pede ao proprietário " +"para um novo." + +msgid "viewer.breaking-change.message" +msgstr "Desculpa!" + +msgid "viewer.header.comments-section" +msgstr "Comentários (%s)" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "Não mostrar interações" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "Tela Cheia" + +msgid "viewer.header.handoff-section" +msgstr "Handoff (%s)" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interações" + +msgid "viewer.header.interactions-section" +msgstr "Interações (%s)" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "Copiar link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Criar link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "Links partilhados aparecerão aqui" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions-on-click" +msgstr "Mostrar interações com click" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Mapa do site" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hcenter" +msgstr "Alinhar horizontal ao centro (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "Distribuir espaçamento horizontal (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hleft" +msgstr "Alinhar à esquerda (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hright" +msgstr "Alinhar à direita (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vbottom" +msgstr "Alinhar a baixo (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vcenter" +msgstr "Alinhar ao centro verticalmente (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vdistribute" +msgstr "Distribuir espaçamento vertical (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vtop" +msgstr "Alinhar ao topo (%s)" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Ativos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Todos os ativos" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Gráficos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Cores" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Componentes" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "Criar grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group-hint" +msgstr "" +"Os teus itens serão nomeados automaticamente como \"nome do grupo / nome do " +"item\"" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.delete" +msgstr "Eliminar" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Duplicar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.edit" +msgstr "Editar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Gráficos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group-name" +msgstr "Nome do grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Bibliotecas" + +msgid "workspace.assets.local-library" +msgstr "biblioteca local" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "Ativos não encontrados" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename-group" +msgstr "Renomear grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Pesquisar ativos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.selected-count" +msgid_plural "workspace.assets.selected-count" +msgstr[0] "%s item selecionado" +msgstr[1] "%s itens selecionados" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "PARTILHADO" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografias" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Fonte" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Tamanho" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Variante" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Ir para ficheiro da biblioteca de estilos para editar" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Espaço entre letras" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Entrelinhamento" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +msgstr "Transformar Texto" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.ungroup" +msgstr "Desagrupar" + +msgid "workspace.focus.focus-mode" +msgstr "Modo de foco" + +msgid "workspace.focus.focus-off" +msgstr "Foco desativado" + +msgid "workspace.focus.focus-on" +msgstr "Foco ativo" + +msgid "workspace.focus.selection" +msgstr "Seleção" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Gradiente linear" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Desativar alinhamento dinâmico" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Desativar encaixar na grelha" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-guides" +msgstr "Desativar encaixe de guias" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "BIBLIOTECAS PARTILHADAS" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.go-main" +msgstr "Ir para ficheiro do componente principal" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Este é um serviço de DEMONSTRAÇÃO, NÃO UTILIZES para trabalhos reais. Os " +"projetos serão eliminados periodicamente." + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Iniciar sessão" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Que bom voltar a ver-te!" + +msgid "common.share-link.destroy-link" +msgstr "Eliminar link" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Github" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "GitLab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Google" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "A solução código aberto para design e prototipar." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Altera a palavra-passe" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Não tens conta?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Criar conta" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "É gratuito, é Open Source" + +msgid "auth.terms-of-service" +msgstr "Termos de serviço" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Ao criar uma nova conta, concordas com os nossos termos de serviço e " +"política de privacidade." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Cria uma conta" + +msgid "common.share-link.current-tag" +msgstr "(atual)" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Vamos enviar-te um e-mail com as instruções" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Não te lembras da tua palavra-passe?" + +msgid "common.share-link.get-link" +msgstr "Obter link" + +msgid "common.share-link.link-copied-success" +msgstr "Link copiado com sucesso" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 página partilhada" +msgstr[1] "%s páginas partilhadas" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Pode inspecionar o código" + +msgid "common.share-link.link-deleted-success" +msgstr "Link eliminado com sucesso" + +msgid "common.share-link.permissions-pages" +msgstr "Páginas partilhadas" + +msgid "common.share-link.manage-ops" +msgstr "Gerir permissões" + +msgid "common.share-link.permissions-hint" +msgstr "Qualquer pessoa com o link terá acesso" + +msgid "common.share-link.view-all" +msgstr "Seleciona tudo" + +msgid "common.share-link.placeholder" +msgstr "O link partilhável será apresentado aqui" + +msgid "common.share-link.team-members" +msgstr "Apenas membros da equipa" + +msgid "common.share-link.title" +msgstr "Partilha protótipos" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Aprende os básicos no Penpot enquanto divertes-te a praticar neste tutorial." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial prático" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Explora o Penpot e conhece as suas principais características." + +msgid "common.unpublish" +msgstr "Cancelar publicação" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Iniciar tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Inicia a tour" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Eliminar equipa" + +msgid "dashboard.download-standard-file" +msgstr "Descarregar ficheiro standard (svg + json)" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplicar" + +msgid "dashboard.download-binary-file" +msgstr "Descarregar ficheiro Penpot (.penpot)" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Podes adicionar definições de exportação em elementos a partir das " +"propriedades de design (na parte inferior da barra lateral direita)." + +msgid "dashboard.draft-title" +msgstr "Rascunho" + +msgid "dashboard.export.detail" +msgstr "* Pode incluir componentes, gráficos, cores e/ou tipografia." + +msgid "dashboard.export.explain" +msgstr "" +"Um ou mais ficheiros que queres exportar estão a utilizar bibliotecas " +"partilhadas. O que queres fazer com os seus ativos*?" + +#: src/app/main/ui/dashboard/grid.cljs +#, markdown +msgid "dashboard.empty-placeholder-drafts" +msgstr "" +"Oh não! Ainda não tens ficheiros! Se quiseres experimentar podes começar com " +"os nossos templates em [Libraries & templates](https://penpot.app/libraries-" +"templates.html)" + +msgid "dashboard.export.options.merge.message" +msgstr "" +"Os teus ficheiros serão exportados com todos os ativos externos incorporados " +"na biblioteca de ficheiros." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Informações sobre como definir exportações no Penpot." + +msgid "dashboard.export.options.detach.message" +msgstr "" +"Bibliotecas partilhadas não serão incluídas na exportação e nenhum ativo " +"será adicionado à biblioteca. " + +msgid "dashboard.export.options.detach.title" +msgstr "Trata os ativos da biblioteca partilhada como objetos básicos" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-scale-text" +msgstr "Desativar escala de texto" + +msgid "dashboard.export.options.merge.title" +msgstr "Incluir ativos da biblioteca partilhada em bibliotecas de ficheiros" + +msgid "dashboard.fonts.deleted-placeholder" +msgstr "Tipo de letra eliminado" + +msgid "shortcuts.select-all" +msgstr "Selecionar tudo" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Qualquer pessoa com o link terá acesso" + +msgid "dashboard.export.title" +msgstr "Exportar ficheiros" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Remover link" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "Ignorar todas" + +msgid "dashboard.fonts.empty-placeholder" +msgstr "Ainda não tens tipos de letra personalizados instalados." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "1 tipo de letra adicionado" +msgstr[1] "%s tipos de letra adicionados" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "Não foram encontrados quadros na página." + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "Mostrar interações" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs +msgid "workspace.assets.typography.sample" +msgstr "Ag" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename" +msgstr "Renomear" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Gradiente radial" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Estes ficheiros têm bibliotecas utilizadas neste ficheiro:" +msgstr[1] "Estes ficheiros têm bibliotecas utilizadas nestes ficheiros:" + +msgid "dashboard.import.progress.process-components" +msgstr "Processando componentes" + +msgid "shortcuts.go-to-libs" +msgstr "Ir para bibliotecas partilhadas" + +msgid "dashboard.import.import-message" +msgstr "%s ficheiros importados com sucesso." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Tens a certeza de que queres cancelar a publicação desta biblioteca?" +msgstr[1] "Tens a certeza de que queres cancelar a publicação destas bibliotecas?" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Pesquisar bibliotecas partilhadas" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "Existem atualizações nas bibliotecas partilhadas" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Cancelar publicação da biblioteca" +msgstr[1] "Cancelar publicação das bibliotecas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "Não há bibliotecas partilhadas que precisem de atualização" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "Não há bibliotecas partilhadas disponíveis" + +#, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"Qualquer web font que carregares aqui será adicionada à família de fontes " +"disponível nas propriedades de texto dos ficheiros desta equipa. Tipos de " +"letra com a mesma família serão agrupadas como uma **única família**. Podes " +"carregar tipos de letra com os seguintes formatos: **TTF, OTF e WOFF** (" +"apenas um será necessário)." + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"Deves carregar tipos de letra que possuas or tenhas licença para utilizar no " +"Penpot. Sabe mais na secção de Direitos de Conteúdos dos [Termos de serviço " +"do Penpot](https://penpot.app/terms.html). Podes também ler mais sobre [" +"licenciamento de fontes](https://www.typography.com/faq)." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Carregar tudo" + +msgid "dashboard.import.analyze-error" +msgstr "Oops! Não conseguimos importar este ficheiro" + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "O teu browser não pode fazer esta operação" + +msgid "dashboard.import" +msgstr "Importar ficheiros Penpot" + +msgid "dashboard.import.import-error" +msgstr "" +"Ocorreu um problema na importação do ficheiro. O ficheiro não foi importado." + +msgid "dashboard.import.progress.upload-media" +msgstr "A carregar ficheiro: %s" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Novo projeto" + +msgid "errors.auth.unable-to-login" +msgstr "Parece que não estás autenticado ou a sessão expirou." + +msgid "shortcuts.open-viewer" +msgstr "Ir para secção de interações" + +msgid "dashboard.import.import-warning" +msgstr "Alguns ficheiros continham objetos inválidos que foram removidos." + +msgid "dashboard.import.progress.process-colors" +msgstr "Processando cores" + +msgid "dashboard.import.progress.process-media" +msgstr "Processando media" + +msgid "dashboard.import.progress.process-page" +msgstr "Processando página: %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "Processando tipografias" + +msgid "dashboard.import.progress.upload-data" +msgstr "A carregar dados para o servidor (%s/%s)" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Novo Projeto" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "" +"Envia-me notícias, atualizações de produto e recomendações sobre o Penpot." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Subscrição de Newsletter" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "Não há resultados para \"%s\"" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.remove-shared" +msgstr "Remover como Biblioteca Partilhada" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "Projetos fixos aparecerão aqui" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "A palavra-passe deverá conter no mínimo 8 caracteres" + +#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.profile-is-muted" +msgstr "" +"O teu perfil tem e-mails silenciados (relatórios de spam ou devoluções " +"altas)." + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Altura" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.give-feedback" +msgstr "Dá feedback" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Esquerda" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.settings" +msgstr "Definições" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Definições - Penpot" + +msgid "handoff.tabs.code.selected.image" +msgstr "Imagem" + +msgid "handoff.tabs.code.selected.mask" +msgstr "Máscara" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s Selecionados" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Círculo" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Código" + +msgid "handoff.tabs.code.selected.group" +msgstr "Grupo" + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.desc-message" +msgstr "" +"Parece que tens de aguardar um pouco e tentar novamente; estamos a realizar " +"pequenas manutenções nos nossos servidores." + +msgid "labels.fonts" +msgstr "Fontes" + +#: src/app/main/ui/comments.cljs +msgid "labels.edit" +msgstr "Editar" + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.desc-message" +msgstr "Estamos numa manutenção programada dos nossos sistemas." + +msgid "labels.font-variants" +msgstr "Variantes" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Repositório Github" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Membro" + +msgid "modals.delete-font.message" +msgstr "" +"Tens a certeza de que pretendes eliminar esta fonte? Não carregará se " +"estiver a ser utilizada num ficheiro." + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.members" +msgstr "Membros" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.rename-team" +msgstr "Renomear equipa" + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "A carregar imagem…" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"És o proprietário desta equipa. Seleciona outro membro para promover para " +"proprietário antes de saíres." + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(tu)" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.accept" +msgstr "Atualizar" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "" +"A tua solicitação de inscrição foi enviada, iremos enviar-te um e-mail para " +"confirmá-la." + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Gestão de funções" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% grátis!" + +msgid "shortcuts.flip-vertical" +msgstr "Virar verticalmente" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.frame-not-found" +msgstr "Quadro não encontrado." + +msgid "shortcut-subsection.tools" +msgstr "Ferramentas" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Zoom" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Zoom" + +msgid "shortcuts.hide-ui" +msgstr "Mostrar/Esconder UI" + +msgid "shortcuts.move-fast-down" +msgstr "Mover para baixo rápido" From 920cb86849e4efdb11f993287791dac4c13b296e Mon Sep 17 00:00:00 2001 From: Hugo Figueira Date: Fri, 23 Sep 2022 14:05:21 +0000 Subject: [PATCH 035/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 62.7% (759 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 84 +++++++++++++++++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 4f4cecf3bd..868084765e 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-22 21:21+0000\n" -"Last-Translator: Ruan Aragão \n" +"PO-Revision-Date: 2022-09-23 14:27+0000\n" +"Last-Translator: Hugo Figueira \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -2911,3 +2911,83 @@ msgstr "Gerenciamento de funções" msgid "onboarding.team-modal.create-team-feature-4" msgstr "Membros ilimitados" + +msgid "onboarding.welcome.title" +msgstr "Bem-vindo ao Penpot" + +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Fundamentos" + +msgid "onboarding.templates.subtitle" +msgstr "Aqui estão alguns templates." + +msgid "onboarding.templates.title" +msgstr "Começar a desenhar" + +msgid "shortcut-subsection.main-menu" +msgstr "Menu principal" + +msgid "shortcut-subsection.modify-layers" +msgstr "Modificar camadas" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Navegação" + +msgid "shortcut-subsection.shape" +msgstr "Formas" + +msgid "shortcut-subsection.tools" +msgstr "Ferramentas" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Zoom" + +msgid "shortcut-section.workspace" +msgstr "Espaço de trabalho" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Alinhamento" + +msgid "shortcut-subsection.edit" +msgstr "Editar" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Navegação" + +msgid "shortcut-subsection.panels" +msgstr "Painéis" + +msgid "shortcuts.align-right" +msgstr "Alinhar à direita" + +msgid "shortcuts.align-top" +msgstr "Alinhar ao topo" + +msgid "shortcuts.align-vcenter" +msgstr "Alinhar ao centro vertical" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Navegação" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% gratuito!" + +msgid "shortcuts.add-comment" +msgstr "Comentários" + +msgid "shortcuts.align-left" +msgstr "Alinhar à esquerda" + +msgid "shortcuts.align-bottom" +msgstr "Alinhar à base" + +msgid "shortcuts.add-node" +msgstr "Adicionar nó" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Zoom" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" From 0fa8f54ce4ef3e9be271d6d1f684d750f446d77e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Fri, 23 Sep 2022 13:29:29 +0000 Subject: [PATCH 036/682] :globe_with_meridians: Add translations for: Portuguese (Portugal). Currently translated at 99.0% (1198 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/ --- frontend/translations/pt_PT.po | 1641 +++++++++++++++++++++++++++++++- 1 file changed, 1607 insertions(+), 34 deletions(-) diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index eb9d81c8fe..1dada5b591 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-23 11:07+0000\n" +"PO-Revision-Date: 2022-09-23 14:27+0000\n" "Last-Translator: Dário \n" "Language-Team: Portuguese (Portugal) \n" @@ -698,7 +698,7 @@ msgid "handoff.attributes.stroke.alignment.outer" msgstr "Exterior" msgid "handoff.attributes.stroke.style.dotted" -msgstr "Pontos" +msgstr "Pontilhado" msgid "handoff.attributes.stroke.style.mixed" msgstr "Misto" @@ -755,7 +755,7 @@ msgid "handoff.attributes.typography.text-transform" msgstr "Transformação de Texto" msgid "handoff.attributes.typography.text-transform.lowercase" -msgstr "Minúsculo" +msgstr "Minúsculas" msgid "handoff.attributes.typography.text-transform.none" msgstr "Nenhum" @@ -764,7 +764,7 @@ msgid "handoff.attributes.typography.text-transform.titlecase" msgstr "Capitalização de Título" msgid "handoff.attributes.typography.text-transform.uppercase" -msgstr "Maiúsculo" +msgstr "Maiúsculas" msgid "handoff.tabs.code.selected.component" msgstr "Componente" @@ -773,7 +773,7 @@ msgid "handoff.tabs.code.selected.curve" msgstr "Curva" msgid "handoff.tabs.code.selected.frame" -msgstr "Prancha" +msgstr "Prancheta" msgid "handoff.tabs.code.selected.path" msgstr "Caminho" @@ -947,7 +947,7 @@ msgstr "Centro de Ajuda" #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" -msgstr "Esconder comentários resolvidos" +msgstr "Ocultar comentários resolvidos" msgid "labels.icons" msgstr "Ícones" @@ -1042,8 +1042,8 @@ msgstr[1] "%s ficheiros" msgid "labels.num-of-frames" msgid_plural "labels.num-of-frames" -msgstr[0] "1 prancha" -msgstr[1] "%s pranchas" +msgstr[0] "1 prancheta" +msgstr[1] "%s pranchetas" #: src/app/main/ui/dashboard/team.cljs msgid "labels.num-of-projects" @@ -1813,7 +1813,7 @@ msgid "shortcuts.add-node" msgstr "Adicionar nó" msgid "shortcuts.align-bottom" -msgstr "Align abaixo" +msgstr "Alinhar abaixo" msgid "shortcuts.align-hcenter" msgstr "Alinhar ao centro horizontalmente" @@ -1831,7 +1831,7 @@ msgid "shortcuts.align-vcenter" msgstr "Alinhar ao centro verticalmente" msgid "shortcuts.artboard-selection" -msgstr "Criar prancha a partir da seleção" +msgstr "Criar prancheta a partir da seleção" msgid "shortcuts.bool-difference" msgstr "Diferença booleana" @@ -1870,10 +1870,10 @@ msgid "shortcuts.create-new-project" msgstr "Criar novo" msgid "shortcuts.cut" -msgstr "Cortar" +msgstr "Recortar" msgid "shortcuts.decrease-zoom" -msgstr "Reduzir zoom" +msgstr "Menos zoom" msgid "shortcuts.delete" msgstr "Eliminar" @@ -1915,7 +1915,7 @@ msgid "shortcuts.export-shapes" msgstr "Exportar formas" msgid "shortcuts.fit-all" -msgstr "Zoom para caber tudo" +msgstr "Ajustar tudo à janela" msgid "shortcuts.flip-horizontal" msgstr "Virar horizontalmente" @@ -1933,13 +1933,13 @@ msgid "shortcuts.h-distribute" msgstr "Distribuir horizontalmente" msgid "shortcuts.increase-zoom" -msgstr "Aumentar zoom" +msgstr "Mais zoom" msgid "shortcuts.insert-image" msgstr "Inserir imagem" msgid "shortcuts.join-nodes" -msgstr "Juntar nós" +msgstr "Unir nós" msgid "shortcuts.make-corner" msgstr "Fazer canto" @@ -2056,16 +2056,16 @@ msgid "shortcuts.separate-nodes" msgstr "Separar nós" msgid "shortcuts.show-pixel-grid" -msgstr "Mostrar/esconder grelha de pixels" +msgstr "Mostrar/ocultar grade de píxeis" msgid "shortcuts.show-shortcuts" -msgstr "Mostrar/esconder atalhos" +msgstr "Mostrar/ocultar atalhos" msgid "shortcuts.snap-nodes" -msgstr "Encaixar nos nós" +msgstr "Ajustar aos nós" msgid "shortcuts.snap-pixel-grid" -msgstr "Encaixar na grelha de pixels" +msgstr "Ajustar à grade de píxeis" msgid "shortcuts.start-editing" msgstr "Iniciar edição" @@ -2096,7 +2096,7 @@ msgid "shortcuts.toggle-focus-mode" msgstr "Alternar modo de foco" msgid "shortcuts.toggle-grid" -msgstr "Mostrar/esconder grelha" +msgstr "Mostrar/ocultar grade" msgid "shortcuts.toggle-history" msgstr "Alternar histórico" @@ -2111,7 +2111,7 @@ msgid "shortcuts.toggle-lock-size" msgstr "Bloquear proporções" msgid "shortcuts.toggle-rules" -msgstr "Mostrar/esconder regras" +msgstr "Mostrar/ocultar regras" #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" @@ -2121,10 +2121,10 @@ msgid "shortcuts.toggle-scale-text" msgstr "Alternar escala de texto" msgid "shortcuts.toggle-snap-grid" -msgstr "Encaixar na grelha" +msgstr "Ajustar à grade" msgid "shortcuts.toggle-snap-guide" -msgstr "Encaixar nas guias" +msgstr "Ajustar às guias" msgid "shortcuts.toggle-textpalette" msgstr "Alternar paleta de texto" @@ -2139,13 +2139,13 @@ msgid "shortcuts.toogle-fullscreen" msgstr "Alternar tela cheia" msgid "shortcuts.undo" -msgstr "desfazer" +msgstr "Desfazer" msgid "shortcuts.ungroup" msgstr "Desagrupar" msgid "shortcuts.unmask" -msgstr "Desmascarar" +msgstr "Retirar máscara" msgid "shortcuts.v-distribute" msgstr "Distribuir verticalmente" @@ -2276,7 +2276,7 @@ msgstr "Alinhar à direita (%s)" #: src/app/main/ui/workspace/sidebar/align.cljs msgid "workspace.align.vbottom" -msgstr "Alinhar a baixo (%s)" +msgstr "Alinhar abaixo (%s)" #: src/app/main/ui/workspace/sidebar/align.cljs msgid "workspace.align.vcenter" @@ -2426,11 +2426,11 @@ msgstr "Desativar alinhamento dinâmico" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.disable-snap-grid" -msgstr "Desativar encaixar na grelha" +msgstr "Desativar ajuste à grade" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.disable-snap-guides" -msgstr "Desativar encaixe de guias" +msgstr "Desativar ajuste às guias" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.shared-libraries" @@ -2604,7 +2604,7 @@ msgstr "* Pode incluir componentes, gráficos, cores e/ou tipografia." msgid "dashboard.export.explain" msgstr "" "Um ou mais ficheiros que queres exportar estão a utilizar bibliotecas " -"partilhadas. O que queres fazer com os seus ativos*?" +"partilhadas. O que queres fazer com os recursos*?" #: src/app/main/ui/dashboard/grid.cljs #, markdown @@ -2616,8 +2616,8 @@ msgstr "" msgid "dashboard.export.options.merge.message" msgstr "" -"Os teus ficheiros serão exportados com todos os ativos externos incorporados " -"na biblioteca de ficheiros." +"Os teus ficheiros serão exportados com todos os recursos externos " +"incorporados na biblioteca de ficheiros." #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.how-to-link" @@ -2625,11 +2625,11 @@ msgstr "Informações sobre como definir exportações no Penpot." msgid "dashboard.export.options.detach.message" msgstr "" -"Bibliotecas partilhadas não serão incluídas na exportação e nenhum ativo " +"Bibliotecas partilhadas não serão incluídas na exportação e nenhum recurso " "será adicionado à biblioteca. " msgid "dashboard.export.options.detach.title" -msgstr "Trata os ativos da biblioteca partilhada como objetos básicos" +msgstr "Trata os recursos da biblioteca partilhada como objetos básicos" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.disable-scale-text" @@ -2963,7 +2963,1580 @@ msgid "shortcut-subsection.zoom-workspace" msgstr "Zoom" msgid "shortcuts.hide-ui" -msgstr "Mostrar/Esconder UI" +msgstr "Mostrar/ocultar interface" msgid "shortcuts.move-fast-down" msgstr "Mover para baixo rápido" + +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Desativar ajuste ao pixel" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Ativar alinhamento dinâmico" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-scale-text" +msgstr "Ativar escalar texto" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Ajustar à grade" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-guides" +msgstr "Ajustar às guias" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-artboard-names" +msgstr "Ocultar nome das pranchetas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-grid" +msgstr "Ocultar grades" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Ocultar paleta de cor" + +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Ocultar grade de píxeis" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Ocultar regras" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-textpalette" +msgstr "Ocultar paleta de texto" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Editar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.view" +msgstr "Visualização" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.select-all" +msgstr "Selecionar tudo" + +msgid "workspace.header.menu.show-pixel-grid" +msgstr "Mostrar grade de píxeis" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Erro ao salvar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.reset-zoom" +msgstr "Ampliar em 100%" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-textpalette" +msgstr "Mostrar paleta de texto" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Guardado" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "A salvar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fill" +msgstr "Ajustar para preencher" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit" +msgstr "Ajustar para encaixar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit-all" +msgstr "Ajustar para mostrar tudo" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-full-screen" +msgstr "Tela cheia" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Alterações não guardadas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Modo de visualização (%s)" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-selected" +msgstr "Aumentar para seleção" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Adicionar" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s cores" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Miniaturas grandes" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Biblioteca de ficheiros" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.hsv" +msgstr "HSV" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Guardar estilo de cor" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Cores recentes" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgb-complementary" +msgstr "RGB Complementar" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Miniaturas pequenas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s componentes" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "Biblioteca de ficheiros" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s gráficos" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "BIBLIOTECAS NESTE FICHEIRO" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "BIBLIOTECAS" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "BIBLIOTECA" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "Não há resultados para \"%s\"" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography" +msgstr "Múltiplas tipografias" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Desassociar todas as tipografias" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s tipografias" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Atualizar" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "ATUALIZAÇÕES" + +msgid "workspace.library.all" +msgstr "Todas as bibliotecas" + +msgid "workspace.library.libraries" +msgstr "Bibliotecas" + +msgid "workspace.library.own" +msgstr "As minhas bibliotecas" + +msgid "workspace.library.store" +msgstr "Bibliotecas de lojas" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.add-interaction" +msgstr "Clica no botão + para adicionar interações." + +msgid "workspace.options.blur-options.background-blur" +msgstr "Fundo" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Camada" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Desfoque" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.group" +msgstr "Desfoque de grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.multiple" +msgstr "Desfoque de seleção" + +#: src/app/main/ui/workspace/sidebar/options/page.cljs +msgid "workspace.options.canvas-background" +msgstr "Plano de fundo" + +msgid "workspace.options.clip-content" +msgstr "Recorte do conteúdo" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Componente" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints" +msgstr "Restrições" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.bottom" +msgstr "Abaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.leftright" +msgstr "Esquerda e Direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.right" +msgstr "Direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.scale" +msgstr "Escala" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.topbottom" +msgstr "Topo e Abaixo" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.design" +msgstr "Design" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export" +msgstr "Exportar" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-multiple" +msgstr "Exportar seleção" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgid_plural "workspace.options.export-object" +msgstr[0] "Exportar 1 elemento" +msgstr[1] "Exportar %s elementos" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +msgid "workspace.options.export.suffix" +msgstr "Sufixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "Exportação completa" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object" +msgstr "A exportar…" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "A exportação falhou" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-slow" +msgstr "Exportação inesperadamente lenta" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Preenchimento" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.add-flow-start" +msgstr "Adicionar início de fluxo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-start" +msgstr "Início de fluxo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-starts" +msgstr "Início de fluxo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Automático" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Colunas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "Grade" + +msgid "workspace.options.grid.params.color" +msgstr "Cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Colunas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.gutter" +msgstr "Espaço" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.height" +msgstr "Altura" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Margem" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.rows" +msgstr "Linhas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.set-default" +msgstr "Definir como padrão" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Utilizar padrão" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.stretch" +msgstr "Esticar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.width" +msgstr "Largura" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.row" +msgstr "Linhas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.square" +msgstr "Quadrado" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-action" +msgstr "Ação" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-after-delay" +msgstr "Após atraso" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.group-fill" +msgstr "Preenchimento de grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.group-stroke" +msgstr "Traço de grupo" + +msgid "workspace.options.height" +msgstr "Altura" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation" +msgstr "Animação" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-dissolve" +msgstr "Dissolver" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-none" +msgstr "Nenhum" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-push" +msgstr "Empurrar" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-slide" +msgstr "Deslizar" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-background" +msgstr "Adicionar sobreposição de fundo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-outside" +msgstr "Fechar ao clicar no exterior" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay" +msgstr "Fechar sobreposição" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay-dest" +msgstr "Fechar sobreposição: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-delay" +msgstr "Atraso" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-destination" +msgstr "Destino" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-duration" +msgstr "Duração" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease-in" +msgstr "Ease in" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease" +msgstr "Ease" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing" +msgstr "Easing" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease-in-out" +msgstr "Ease in out" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease-out" +msgstr "Ease out" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-ms" +msgstr "ms" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to-dest" +msgstr "Navegar para: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-offset-effect" +msgstr "Efeito Offset" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-on-click" +msgstr "No Clique" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-left" +msgstr "Inferior esquerdo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-right" +msgstr "Inferior direito" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-center" +msgstr "Inferior centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-out" +msgstr "Fora" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-manual" +msgstr "Manual" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-left" +msgstr "Superior esquerdo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-center" +msgstr "Superior centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-right" +msgstr "Superior direito" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-position" +msgstr "Posição" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-preserve-scroll" +msgstr "Preservar posição do scroll" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-prev-screen" +msgstr "Ecrã anterior" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-self" +msgstr "auto" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-toggle-overlay" +msgstr "Alternar a sobreposição" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-toggle-overlay-dest" +msgstr "Alternar a sobreposição: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-trigger" +msgstr "Ativador" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-url" +msgstr "URL" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-while-hovering" +msgstr "Durante o hover" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-while-pressing" +msgstr "Durante a premir" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interactions" +msgstr "Interações" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-burn" +msgstr "Superexposição de cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hard-light" +msgstr "Luz direta" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hue" +msgstr "Matiz" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.luminosity" +msgstr "Luminosidade" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.multiply" +msgstr "Multiplicação" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.normal" +msgstr "Normal" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.overlay" +msgstr "Sobrepor" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.saturation" +msgstr "Saturação" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title.multiple" +msgstr "Camadas selecionadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "Opções avançadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Altura.Min" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Largura.Min" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Altura mínima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "Linha" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.no-wrap" +msgstr "sem envolver" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.packed" +msgstr "juntos" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding" +msgstr "Padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "Todos os lados" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-simple" +msgstr "Padding simples" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "Direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "espaço à volta" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.title" +msgstr "Layout" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.wrap" +msgstr "envolver" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "Mais cores" + +msgid "workspace.options.opacity" +msgstr "Opacidade" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Protótipo" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.single-corners" +msgstr "Cantos individuais" + +msgid "workspace.options.recent-fonts" +msgstr "Recente" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.retry" +msgstr "Repetir" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Rotação" + +msgid "workspace.options.search-font" +msgstr "Pesquisar fonte" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.selection-stroke" +msgstr "Traço da seleção" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-artboard" +msgstr "Selecionar prancheta" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.offsety" +msgstr "Y" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.spread" +msgstr "Difundir" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Sombra" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.group" +msgstr "Grupo de sombras" + +msgid "workspace.options.show-in-viewer" +msgstr "Mostrar no modo de visualização" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.size" +msgstr "Tamanho" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +msgid "workspace.options.size-presets" +msgstr "Tamanho pré-definido" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "Nenhum" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.round" +msgstr "Arredondado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square-marker" +msgstr "Marcador quadrado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.triangle-arrow" +msgstr "Seta triangular" + +msgid "workspace.options.stroke-width" +msgstr "Largura do traço" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Tracejado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Misto" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.outer" +msgstr "Exterior" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Alinhar abaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-center" +msgstr "Alinhar ao centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-middle" +msgstr "Alinhar ao meio" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-right" +msgstr "Alinhar à direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-top" +msgstr "Alinhar ao topo" + +msgid "workspace.options.text-options.decoration" +msgstr "Decoração" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "LTR" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "RTL" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.google" +msgstr "Google" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-height" +msgstr "Altura automática" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Largura automática" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Altura entre linhas" + +msgid "workspace.options.text-options.text-case" +msgstr "Capitalizar" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.titlecase" +msgstr "Capitalizar iniciais" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Texto" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Grupo de texto" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-selection" +msgstr "Texto selecionado" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Sublinhado" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Maiúsculas" + +msgid "workspace.options.text-options.vertical-align" +msgstr "Alinhamento vertical" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.use-play-button" +msgstr "" +"Utiliza o botão de play no cabeçalho para iniciar a visualização do " +"protótipo." + +msgid "workspace.options.width" +msgstr "Largura" + +msgid "workspace.path.actions.make-curve" +msgstr "Em curvas (%s)" + +msgid "workspace.path.actions.snap-nodes" +msgstr "Ajustar nós (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "Enviar para trás" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Criar componente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Recortar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete-flow-start" +msgstr "Eliminar início de fluxo" + +msgid "workspace.shape.menu.difference" +msgstr "Diferença" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Duplicar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Editar" + +msgid "workspace.shape.menu.exclude" +msgstr "Excluir" + +msgid "workspace.shape.menu.flatten" +msgstr "Achatar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Virar na horizontal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-vertical" +msgstr "Virar na vertical" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flow-start" +msgstr "Início de fluxo" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Ocultar" + +msgid "workspace.shape.menu.hide-ui" +msgstr "Mostrar/Ocultar interface" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.forward" +msgstr "Mover para a frente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.front" +msgstr "Enviar para a frente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.group" +msgstr "Agrupar" + +msgid "workspace.shape.menu.intersection" +msgstr "Interseção" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Colar" + +msgid "workspace.shape.menu.path" +msgstr "Caminho" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.reset-overrides" +msgstr "Anular alterações" + +msgid "workspace.shape.menu.transform-to-path" +msgstr "Transformar em vector" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Desagrupar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unmask" +msgstr "Retirar máscara" + +msgid "workspace.sidebar.layers.frames" +msgstr "Pranchetas" + +msgid "workspace.sidebar.layers.groups" +msgstr "Grupos" + +msgid "workspace.sidebar.layers.masks" +msgstr "Máscaras" + +msgid "workspace.sidebar.layers.images" +msgstr "Imagens" + +msgid "workspace.sidebar.layers.search" +msgstr "Pesquisar camadas" + +msgid "workspace.sidebar.layers.shapes" +msgstr "Formas" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "workspace.sidebar.sitemap" +msgstr "Páginas" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.color-palette" +msgstr "Paleta de cores (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.curve" +msgstr "Curvas (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "Elipse (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "%s eliminado" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.modify" +msgstr "%s modificado" + +msgid "workspace.undo.entry.multiple.circle" +msgstr "círculos" + +msgid "workspace.undo.entry.multiple.color" +msgstr "ativos de cor" + +msgid "workspace.undo.entry.multiple.group" +msgstr "grupos" + +msgid "workspace.undo.entry.single.component" +msgstr "componente" + +msgid "workspace.undo.entry.single.curve" +msgstr "curva" + +msgid "workspace.undo.entry.single.frame" +msgstr "prancheta" + +msgid "workspace.undo.entry.single.group" +msgstr "grupo" + +msgid "workspace.undo.entry.single.page" +msgstr "página" + +msgid "workspace.undo.entry.single.path" +msgstr "caminho" + +msgid "workspace.undo.entry.single.rect" +msgstr "rectângulo" + +msgid "workspace.undo.entry.single.shape" +msgstr "forma" + +msgid "workspace.undo.entry.single.text" +msgstr "texto" + +msgid "workspace.undo.entry.single.typography" +msgstr "recurso tipográfico" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.unknown" +msgstr "Operação sobre %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.title" +msgstr "Histórico" + +msgid "workspace.undo.entry.multiple.page" +msgstr "páginas" + +msgid "workspace.undo.entry.multiple.rect" +msgstr "rectângulos" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.empty" +msgstr "Não há histórico de mudanças até agora" + +msgid "workspace.undo.entry.multiple.multiple" +msgstr "objectos" + +msgid "workspace.undo.entry.multiple.path" +msgstr "caminhos" + +msgid "workspace.undo.entry.multiple.media" +msgstr "recursos gráficos" + +msgid "workspace.undo.entry.multiple.shape" +msgstr "formas" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.move" +msgstr "Objectos movidos" + +msgid "workspace.undo.entry.multiple.text" +msgstr "textos" + +msgid "workspace.undo.entry.multiple.typography" +msgstr "recursos tipográficos" + +msgid "workspace.undo.entry.multiple.component" +msgstr "componentes" + +msgid "workspace.undo.entry.multiple.curve" +msgstr "curvas" + +msgid "workspace.undo.entry.single.color" +msgstr "recurso de cor" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.new" +msgstr "Novo %s" + +msgid "workspace.undo.entry.single.circle" +msgstr "círculo" + +msgid "workspace.undo.entry.single.image" +msgstr "imagem" + +msgid "workspace.undo.entry.single.media" +msgstr "recurso gráfico" + +msgid "workspace.undo.entry.single.multiple" +msgstr "objecto" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "prancheta" + +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Ativar ajuste ao pixel" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-grid" +msgstr "Mostrar grade" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Mostrar paleta de cor" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-rules" +msgstr "Mostrar regras" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Ficheiro" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Ajuda e Informações" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.preferences" +msgstr "Preferências" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-artboard-names" +msgstr "Mostrar nomes das pranchetas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-lib-colors" +msgstr "Mais bibliotecas de cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Abaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.fix-when-scrolling" +msgstr "Fixar no scroll" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Desfoque" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.top" +msgstr "Topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.size" +msgstr "Tamanho" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type" +msgstr "Tipo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Coluna" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "abaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-mouse-enter" +msgstr "Cursor entra" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to" +msgstr "Navegar para" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-url" +msgstr "Abrir url" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Abaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.circle-marker" +msgstr "Marcador circular" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-mouse-leave" +msgstr "Cursor sai" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke" +msgstr "Traço" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.solid" +msgstr "Sólido" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.diamond-marker" +msgstr "Marcador em diamante" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.line-arrow" +msgstr "Seta de linha" + +msgid "workspace.options.stroke-color" +msgstr "Cor do traço" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square" +msgstr "Quadrado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Pontilhado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Interior" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-linear" +msgstr "Linear" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-overlay-dest" +msgstr "Abrir sobreposição: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-in" +msgstr "Dentro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-none" +msgstr "(indefinido)" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-overlay" +msgstr "Abrir sobreposição" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-dodge" +msgstr "Subexposição Linear (Adicionar)" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.darken" +msgstr "Escurecer" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.difference" +msgstr "Diferença" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.exclusion" +msgstr "Exclusão" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.screen" +msgstr "Tela" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.soft-light" +msgstr "Luz indireta" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title.group" +msgstr "Grupo de camadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.lighten" +msgstr "Clarear" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Camada" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Altura.Máx" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Largura.Máx" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Redimensionar elementos" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.selection-fill" +msgstr "Preenchimento de seleção" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Altura máxima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.right" +msgstr "Linha inversa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Margem" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Margem simples" + +msgid "workspace.options.shadow-options.color" +msgstr "Cor da sombra" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Largura máxima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "Coluna inversa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "Todos os lados" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Largura mínima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.gap" +msgstr "Espaço" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "espaço entre" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Topo" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.position" +msgstr "Posição" + +msgid "workspace.options.radius" +msgstr "Raio" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Todos os cantos" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Seleção de sombras" + +msgid "workspace.path.actions.draw-nodes" +msgstr "Desenhar nós (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instance" +msgstr "Desprender instância" + +msgid "workspace.path.actions.join-nodes" +msgstr "Unir nós (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-a-shape" +msgstr "" +"Selecionar a forma, prancheta ou grupo para arrastar uma conexão para outra " +"prancheta." + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "Cores selecionadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-left" +msgstr "Alinhar à esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Sombra" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.offsetx" +msgstr "X" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Sombra interna" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.show-fill-on-export" +msgstr "Mostrar na exportação" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Fixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Minúsculas" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-justify" +msgstr "Justificar" + +msgid "workspace.path.actions.make-corner" +msgstr "Em cantos (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.letter-spacing" +msgstr "Espaço entre letras" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.none" +msgstr "Nenhum" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.preset" +msgstr "Pré-definido" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Rasurado" + +msgid "workspace.options.y" +msgstr "Y" + +msgid "workspace.path.actions.merge-nodes" +msgstr "Fundir nós (%s)" + +msgid "workspace.path.actions.delete-node" +msgstr "Eliminar nó (%s)" + +msgid "workspace.options.x" +msgstr "X" + +msgid "workspace.path.actions.add-node" +msgstr "Adicionar nó (%s)" + +msgid "workspace.path.actions.move-nodes" +msgstr "Mover nós (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Copiar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-artboard-from-selection" +msgstr "Seleção para a prancheta" + +msgid "workspace.path.actions.separate-nodes" +msgstr "Separar nós (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "Mover para trás" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Eliminar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Selecionar camada" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "Desprender instâncias" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Bloquear" + +msgid "workspace.shape.menu.restore-main" +msgstr "Restaurar componente principal" + +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "Remover miniatura" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unlock" +msgstr "Desbloquear" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "Atualizar componentes principais" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "Histórico (%s)" + +msgid "workspace.sidebar.layers.texts" +msgstr "Textos" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-main" +msgstr "Mostrar componente principal" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Máscara" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Mostrar" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "Definir como miniatura" + +msgid "workspace.shape.menu.union" +msgstr "União" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-main" +msgstr "Atualizar componente principal" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.layers" +msgstr "Camadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs +msgid "workspace.sidebar.options.svg-attrs.title" +msgstr "Importar atributos do SVG" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.sitemap" +msgstr "Mapa do site" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Recursos" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Comentários (%s)" + +msgid "workspace.sidebar.layers.components" +msgstr "Componentes" From f4264e47f0e00ead9793e6a99062239018ee4ae7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Fri, 23 Sep 2022 14:44:05 +0000 Subject: [PATCH 037/682] :globe_with_meridians: Add translations for: Spanish. Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/ --- frontend/translations/es.po | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 741e14a6cf..9970401c81 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-06-23 12:19+0000\n" -"Last-Translator: andy \n" -"Language-Team: Spanish " -"\n" +"PO-Revision-Date: 2022-09-23 20:54+0000\n" +"Last-Translator: Dário \n" +"Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.13.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -242,8 +242,8 @@ msgstr "Gestión del equipo" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.text" msgstr "" -" Penpot está dirigido a equipos. Invita a personas para trabajar " -"conjuntamente en proyectos y archivos." +"Penpot está dirigido a equipos. Invita a personas para trabajar " +"conjuntamente en proyectos y archivos" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.title" @@ -265,7 +265,7 @@ msgstr "Tutorial práctico" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.walkthrough-hero.info" -msgstr " Da un paseo por Penpot para conocer sus principales funcionalidades." +msgstr "Da un paseo por Penpot para conocer sus principales funcionalidades." #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.walkthrough-hero.start" @@ -333,7 +333,7 @@ msgid "dashboard.export-binary-multi" msgstr "Descargar %s archivos Penpot (.penpot)" msgid "dashboard.export-frames" -msgstr "Exportar tableros a PDF..." +msgstr "Exportar tableros a PDF…" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" @@ -2069,7 +2069,7 @@ msgstr "" msgid "onboarding-v2.welcome.desc2" msgstr "" "Un espacio público donde aprender, compatir y discutir sobre el presente y " -"futuro de Penpot con toda la Comunidad y el el equipo de Penpot." +"futuro con toda la Comunidad y el equipo de Penpot." msgid "onboarding-v2.welcome.desc2.title" msgstr "Participantdo en la Comunidad" @@ -3867,19 +3867,19 @@ msgstr "Opciones avanzadas" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.layout-max-h" -msgstr "AlturaMax." +msgstr "Altura.Max" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.layout-max-w" -msgstr "AnchoMax." +msgstr "Ancho.Max" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.layout-min-h" -msgstr "AlturaMin." +msgstr "Altura.Min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.layout-min-w" -msgstr "AnchoMin." +msgstr "Ancho.Min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title" @@ -3963,7 +3963,7 @@ msgstr "juntar" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.padding" -msgstr "Padding" +msgstr "Distancia interna" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.padding-all" @@ -4755,4 +4755,8 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" \ No newline at end of file +msgstr "Pulsar para cerrar la ruta" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Proveedor de autenticación no configurado." From 9d02bbcc1c149df63e9227c27ffe68a1237b293b Mon Sep 17 00:00:00 2001 From: Stas Haas Date: Fri, 23 Sep 2022 20:05:32 +0000 Subject: [PATCH 038/682] :globe_with_meridians: Add translations for: Russian. Currently translated at 66.5% (805 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/ --- frontend/translations/ru.po | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index 02818a2112..f7b3c9d4f6 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-23 11:06+0000\n" -"Last-Translator: Vik \n" +"PO-Revision-Date: 2022-09-23 20:54+0000\n" +"Last-Translator: Stas Haas \n" "Language-Team: Russian \n" "Language: ru\n" @@ -3133,3 +3133,7 @@ msgstr "Вы уверены, что хотите покинуть команду #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.cut" msgstr "Вырезать" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "Выбрано %s из %s элементов" From 83f734977f2131dd1fdd24f99482acd6bd3d15c3 Mon Sep 17 00:00:00 2001 From: Hugo Figueira Date: Fri, 23 Sep 2022 14:36:22 +0000 Subject: [PATCH 039/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 99.1% (1199 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 1401 +++++++++++++++++++++++++++++++- 1 file changed, 1397 insertions(+), 4 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 868084765e..89f923b8fe 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-23 14:27+0000\n" +"PO-Revision-Date: 2022-09-23 20:54+0000\n" "Last-Translator: Hugo Figueira \n" "Language-Team: Portuguese (Brazil) \n" @@ -1616,7 +1616,7 @@ msgstr "Camada" #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs msgid "workspace.options.blur-options.title" -msgstr "" +msgstr "Desfoque" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs msgid "workspace.options.component" @@ -2966,7 +2966,7 @@ msgid "shortcuts.align-top" msgstr "Alinhar ao topo" msgid "shortcuts.align-vcenter" -msgstr "Alinhar ao centro vertical" +msgstr "Alinhar ao centro verticalmente" msgid "shortcut-subsection.navigation-workspace" msgstr "Navegação" @@ -2984,10 +2984,1403 @@ msgid "shortcuts.align-bottom" msgstr "Alinhar à base" msgid "shortcuts.add-node" -msgstr "Adicionar nó" +msgstr "Adicionar ponto" msgid "shortcut-subsection.zoom-viewer" msgstr "Zoom" msgid "onboarding.welcome.alt" msgstr "Penpot" + +msgid "shortcuts.bring-backward" +msgstr "Mover para trás" + +msgid "shortcuts.bool-union" +msgstr "União boleana" + +msgid "shortcuts.bring-back" +msgstr "Enviar para trás" + +msgid "shortcuts.move-nodes" +msgstr "Mover ponto" + +msgid "shortcuts.open-handoff" +msgstr "Ir para seção de entrega" + +msgid "shortcuts.toggle-snap-guide" +msgstr "Encaixar nos guias" + +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Ocultar grade de pixel" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-dissolve" +msgstr "Dissolver" + +msgid "workspace.path.actions.move-nodes" +msgstr "Mover pontos (%s)" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Procurar arquivos" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Imagens de destaque grandes" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.difference" +msgstr "Diferença" + +msgid "workspace.path.actions.join-nodes" +msgstr "Unir pontos (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Remover" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.go-main" +msgstr "Ir para o ficheiro do componente principal" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-grid" +msgstr "Ocultar grade" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Esconder paleta de cores" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Ocultar réguas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.select-all" +msgstr "Selecionar tudo" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-rules" +msgstr "Mostrar réguas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Erro ao salvar" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Adicionar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Modo de visualização (%s)" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Pré-visualizações pequenas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "BIBLIOTECAS" + +msgid "workspace.options.grid.params.color" +msgstr "Cor" + +msgid "workspace.options.height" +msgstr "Altura" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-preserve-scroll" +msgstr "Preservar posição do scroll" + +msgid "workspace.options.opacity" +msgstr "Opacidade" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.multiply" +msgstr "Multiplicação" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Cortar" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Máscara" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Mostrar paleta de cor" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s componentes" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Imagens" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.ungroup" +msgstr "Desagrupar" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Alinhar à base" + +msgid "shortcut-section.viewer" +msgstr "Espectador" + +msgid "shortcut-section.dashboard" +msgstr "Painel" + +msgid "shortcut-subsection.general-viewer" +msgstr "Geral" + +msgid "shortcut-subsection.path-editor" +msgstr "Curvas" + +msgid "shortcuts.bool-intersection" +msgstr "Intersecção Booleana" + +msgid "shortcuts.draw-frame" +msgstr "Prancheta" + +msgid "shortcuts.draw-nodes" +msgstr "Desenhar caminho" + +msgid "shortcuts.draw-path" +msgstr "Caminho" + +msgid "shortcuts.move-unit-left" +msgstr "Mover para a esquerda" + +msgid "shortcuts.flip-horizontal" +msgstr "Refletir horizontalmente" + +msgid "shortcuts.move-unit-up" +msgstr "Mover para cima" + +msgid "shortcuts.next-frame" +msgstr "Próximo quadro" + +msgid "shortcuts.not-found" +msgstr "Não foram encontrados atalhos" + +msgid "shortcuts.opacity-0" +msgstr "Definir opacidade para 100%" + +msgid "shortcuts.opacity-3" +msgstr "Definir opacidade para 10%" + +msgid "shortcuts.open-color-picker" +msgstr "Selector de cores" + +msgid "shortcuts.open-comments" +msgstr "Ir para secção de comentários" + +msgid "shortcuts.open-dashboard" +msgstr "Ir para o painel" + +msgid "shortcuts.open-viewer" +msgstr "Ir para seção de interação" + +msgid "shortcuts.opacity-1" +msgstr "Definir opacidade para 100%" + +msgid "shortcuts.opacity-2" +msgstr "Definir opacidade para 20%" + +msgid "shortcuts.open-workspace" +msgstr "Ir para espaço de trabalho" + +msgid "shortcuts.or" +msgstr " ou " + +msgid "shortcuts.prev-frame" +msgstr "Quadro anterior" + +msgid "shortcuts.toggle-history" +msgstr "Alternar histórico" + +msgid "shortcuts.show-shortcuts" +msgstr "Mostrar/ocultar atalhos" + +msgid "shortcuts.toggle-scale-text" +msgstr "Alternar escala de texto" + +msgid "shortcuts.toggle-snap-grid" +msgstr "Encaixar na grade" + +msgid "shortcuts.toggle-visibility" +msgstr "Alternar visibilidade" + +msgid "shortcuts.toggle-zoom-style" +msgstr "Alternar estilo de zoom" + +msgid "shortcuts.unmask" +msgstr "Retirar máscara" + +msgid "shortcuts.v-distribute" +msgstr "Distribuir verticalmente" + +msgid "shortcuts.ungroup" +msgstr "Desagrupar" + +msgid "shortcuts.zoom-selected" +msgstr "Ajustar zoom à seleção" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Modo de visualização - Penpot" + +msgid "viewer.breaking-change.description" +msgstr "" +"Este link compartilhável não é mais válido. Crie ou peça ao proprietário um " +"novo." + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "Não foram encontrados quadros na página." + +msgid "viewer.breaking-change.message" +msgstr "Desculpe!" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Todos os recursos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Bibliotecas" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "Não foram encontrados arquivos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografias" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Fonte" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename-group" +msgstr "Renomear grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Espaçamento de letra" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Tamanho" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Altura entrelinha" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +msgstr "Transformar Texto" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Gradiente linear" + +msgid "workspace.focus.focus-on" +msgstr "Foco ligado" + +msgid "workspace.focus.selection" +msgstr "Seleção" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Gradiente radial" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Desabilitar encaixar à grade" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Desabilitar alinhamento dinâmico" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-scale-text" +msgstr "Desabilitar escala de texto" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-guides" +msgstr "Desativar ajuste às guias" + +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Desativar ajuste ao pixel" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-textpalette" +msgstr "Ocultar palete de fontes" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Editar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Ficheiro" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Ajuda & informação" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.view" +msgstr "Visualizar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.reset-zoom" +msgstr "Redefenir" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-full-screen" +msgstr "Tela cheia" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fill" +msgstr "Ajustar para preencher" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Biblioteca de ficheiros" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Salvar estilo de cor" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-selected" +msgstr "Zoom até selecionados" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s cores" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.hsv" +msgstr "HSV" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Cores recentes" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "Biblioteca de ficheiros" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "BIBLIOTECA" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s imagens" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "BIBLIOTECAS PARTILHADAS" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Desassociar todas as tipografias" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s tipografias" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.multiple" +msgstr "Desfoque selecionado" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.fix-when-scrolling" +msgstr "Fixar no scroll" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.right" +msgstr "Direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-starts" +msgstr "Início de fluxo" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.group-fill" +msgstr "Preenchimento de grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.group-stroke" +msgstr "Traço do grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-action" +msgstr "Ação" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-slide" +msgstr "Deslizar" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-background" +msgstr "Adicionar sobreposição de fundo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-after-delay" +msgstr "Após atraso" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-duration" +msgstr "Duração" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing" +msgstr "Easing" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease" +msgstr "Ease" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-mouse-enter" +msgstr "Mouse entra" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-mouse-leave" +msgstr "Mouse sai" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-ms" +msgstr "ms" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-out" +msgstr "Sair" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-right" +msgstr "Superior direito" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interactions" +msgstr "Interações" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-self" +msgstr "próprio" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.darken" +msgstr "Escurecer" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.exclusion" +msgstr "Exclusão" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.lighten" +msgstr "Clarear" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.luminosity" +msgstr "Luminusidade" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hue" +msgstr "Matiz" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.normal" +msgstr "Normal" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "Opções avançadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Redimensionar elemento" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Inferior" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "Linha" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.right" +msgstr "Linha inversa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "Coluna inversa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.gap" +msgstr "Espaço" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Margem simples" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "Todos os lados" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.title" +msgstr "Layout" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.retry" +msgstr "Tentar de novo" + +msgid "workspace.options.search-font" +msgstr "Procurar fonte" + +msgid "workspace.options.text-options.text-case" +msgstr "Capitalização" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete-flow-start" +msgstr "Remover início de fluxo" + +msgid "workspace.shape.menu.difference" +msgstr "Diferença" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.front" +msgstr "Trazer para a frente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.group" +msgstr "Grupo" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Bloquear" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Atalhos (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "Removido %s" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Recursos" + +msgid "viewer.header.handoff-section" +msgstr "Entrega (%s)" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-grid" +msgstr "Mostrar grade" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-multiple" +msgstr "Exportar seleção" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-slow" +msgstr "Exportação inesperadamente lenta" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-offset-effect" +msgstr "Efeito de offset" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-in" +msgstr "Dentro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-center" +msgstr "Centro inferior" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-while-hovering" +msgstr "Durante o hover" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-while-pressing" +msgstr "Durante o premir" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Copiar" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "Quebrar instâncias" + +msgid "workspace.sidebar.layers.frames" +msgstr "Painéis" + +msgid "workspace.sidebar.layers.groups" +msgstr "Grupos" + +msgid "workspace.sidebar.layers.images" +msgstr "Imagens" + +msgid "workspace.sidebar.layers.masks" +msgstr "Máscaras" + +msgid "workspace.sidebar.layers.shapes" +msgstr "Formas" + +msgid "workspace.sidebar.layers.search" +msgstr "Procurar camadas" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text-palette" +msgstr "Tipografias (%s)" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit" +msgstr "Ajustar para encaixar" + +msgid "shortcuts.artboard-selection" +msgstr "Criar quadro da seleção" + +msgid "shortcuts.bool-exclude" +msgstr "Excluir seleção" + +msgid "shortcuts.bool-difference" +msgstr "Subtrair seleção" + +msgid "shortcuts.bring-front" +msgstr "Trazer para a frente" + +msgid "shortcuts.bring-forward" +msgstr "Mover para a frente" + +msgid "shortcuts.decrease-zoom" +msgstr "Reduzir zoom" + +msgid "shortcuts.delete" +msgstr "Apagar" + +msgid "shortcuts.draw-curve" +msgstr "Curva" + +msgid "shortcuts.draw-ellipse" +msgstr "Elipse" + +msgid "shortcuts.fit-all" +msgstr "Ajustar à janela" + +msgid "shortcuts.go-to-drafts" +msgstr "Ir para rascunhos" + +msgid "shortcuts.go-to-libs" +msgstr "Ir para bibliotecas partilhadas" + +msgid "shortcuts.delete-node" +msgstr "Apagar ponto" + +msgid "shortcuts.make-curve" +msgstr "Criar curva" + +msgid "shortcuts.clear-undo" +msgstr "Limpar desfazer" + +msgid "shortcuts.move-unit-down" +msgstr "Mover para baixo" + +msgid "shortcuts.select-all" +msgstr "Selecionar todos" + +msgid "shortcuts.separate-nodes" +msgstr "Separar pontos" + +msgid "shortcuts.show-pixel-grid" +msgstr "Mostrar/ocultar grelha de píxeis" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Geral" + +msgid "shortcuts.open-interactions" +msgstr "Ir para seção de interação" + +msgid "shortcuts.reset-zoom" +msgstr "Redefenir zoom" + +msgid "shortcuts.toggle-lock" +msgstr "Bloquear selecionado" + +msgid "shortcuts.toggle-lock-size" +msgstr "Bloquear proporções" + +msgid "shortcuts.toggle-layers" +msgstr "Alternar camadas" + +msgid "shortcuts.toggle-textpalette" +msgstr "Alternar palete de texto" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-invitations" +msgstr "Convites - %s - Penpot" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "Partilhado" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Variante" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Ir para biblioteca de estilo para editar" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs +msgid "workspace.assets.typography.sample" +msgstr "Ag" + +msgid "workspace.assets.local-library" +msgstr "biblioteca local" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-artboard-names" +msgstr "Esconder nomes das pranchetas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "BIBILIOTECAS NESTE FICHEIRO" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-artboard-names" +msgstr "Mostrar nomes das pranchetas" + +msgid "workspace.header.menu.show-pixel-grid" +msgstr "Mostrar grade de pixel" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-textpalette" +msgstr "Mostrar palete de fontes" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "Não há Bibliotecas Partilhadas por atualizar" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Atualizar" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.add-interaction" +msgstr "Clique no botão + para adicionar interações." + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "ATUALIZAÇÔES" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Altura máxima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Largura máxima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Altura mínima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Largura mínima" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-outside" +msgstr "Fechar quando clicar fora do diálogo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-burn" +msgstr "Queimar cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Camada" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-manual" +msgstr "Manual" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Margem" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-position" +msgstr "Posição" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-prev-screen" +msgstr "Ecrã anterior" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-center" +msgstr "Superior centro" + +msgid "workspace.path.actions.separate-nodes" +msgstr "Separar pontos (%s)" + +msgid "workspace.path.actions.snap-nodes" +msgstr "Ajustar ao ponto (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "Cores selecionadas" + +msgid "workspace.sidebar.layers.components" +msgstr "Componentes" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Esconder" + +msgid "workspace.shape.menu.intersection" +msgstr "Interseção" + +msgid "workspace.sidebar.layers.texts" +msgstr "Textos" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit-all" +msgstr "Ajustar tudo à janela" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "Não foram encontrados resultados para “%s“" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Procurar bibliotecas partilhadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.top" +msgstr "Topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease-in-out" +msgstr "Ease in out" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to-dest" +msgstr "Navegar para: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-toggle-overlay" +msgstr "Alternar sobreposição" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.gutter" +msgstr "Goteira" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.soft-light" +msgstr "Luz difusa" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease-in" +msgstr "Ease in" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-none" +msgstr "(não especificado)" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-on-click" +msgstr "No clique" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease-out" +msgstr "Ease out" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to" +msgstr "Navegar para" + +msgid "shortcuts.create-component" +msgstr "Criar componente" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-left" +msgstr "Superior esquerdo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-url" +msgstr "URL" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hard-light" +msgstr "Luz direta" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.overlay" +msgstr "Sobreposição" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.saturation" +msgstr "Saturação" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "topo" + +msgid "shortcuts.copy" +msgstr "Copiar" + +msgid "shortcuts.create-new-project" +msgstr "Criar novo" + +msgid "shortcuts.cut" +msgstr "Cortar" + +msgid "shortcuts.draw-rect" +msgstr "Retângulo" + +msgid "shortcuts.detach-component" +msgstr "Quebrar componente" + +msgid "shortcuts.duplicate" +msgstr "Duplicar" + +msgid "shortcuts.draw-text" +msgstr "Texto" + +msgid "shortcuts.escape" +msgstr "Cancelar" + +msgid "shortcuts.export-shapes" +msgstr "Exportar formas" + +msgid "shortcuts.flip-vertical" +msgstr "Refletir verticalmente" + +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + +msgid "shortcuts.toggle-rules" +msgstr "Mostrar/ocultar réguas" + +msgid "shortcuts.move-unit-right" +msgstr "Mover para a direita" + +msgid "shortcuts.redo" +msgstr "Refazer" + +msgid "shortcuts.paste" +msgstr "Colar" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Dispensar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Altura.Min" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Altura.Máx" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Largura.Máx" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Altura.Min" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay" +msgstr "Fechar diálogo" + +msgid "workspace.options.clip-content" +msgstr "Cortar conteúdo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay-dest" +msgstr "Fechar diálogo: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-left" +msgstr "Inferior esquerdo" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Criar componente" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.add-flow-start" +msgstr "Adicionar início de fluxo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "Todos os lados" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.bottom" +msgstr "Inferior" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "abaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-right" +msgstr "Inferior direito" + +#: src/app/main/ui/workspace/sidebar/options/page.cljs +msgid "workspace.options.canvas-background" +msgstr "Fundo da tela" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.forward" +msgstr "Trazer para a frente" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-destination" +msgstr "Destino" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instance" +msgstr "Quebrar instância" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Duplicar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "Grade" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Coluna" + +msgid "viewer.header.comments-section" +msgstr "Comentários (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints" +msgstr "Restrições" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Editar" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "Exportação falhada" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flow-start" +msgstr "Início do fluxo" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgb-complementary" +msgstr "RGB Complementar" + +msgid "viewer.header.interactions-section" +msgstr "Interações (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Altura de linha" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Alternar tela cheia" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.scale" +msgstr "Escala" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.topbottom" +msgstr "Topo & Fundo" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "Não há Bibliotecas Partilhadas disponíveis" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography" +msgstr "Tipografias múltiplas" + +msgid "workspace.shape.menu.restore-main" +msgstr "Restaurar componente principal" + +msgid "workspace.focus.focus-mode" +msgstr "Modo de foco" + +msgid "workspace.shape.menu.exclude" +msgstr "Excluir" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "Exportação completa" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation" +msgstr "Animação" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-none" +msgstr "Nenhuma" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-push" +msgstr "Empurrar" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-url" +msgstr "Abrir url" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-linear" +msgstr "Linear" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-overlay" +msgstr "Abrir diálogo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-overlay-dest" +msgstr "Abrir diálogo: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-toggle-overlay-dest" +msgstr "Alternar sobreposição: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-trigger" +msgstr "Ativador" + +msgid "workspace.options.recent-fonts" +msgstr "Recente" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.circle-marker" +msgstr "" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "Esquerda para a direita" + +msgid "shortcuts.search-placeholder" +msgstr "Procurar atalhos" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Atalhos do teclado" + +msgid "shortcuts.align-hcenter" +msgstr "Alinhar ao centro horizontalmente" + +msgid "shortcuts.join-nodes" +msgstr "Unir pontos" + +msgid "shortcuts.go-to-search" +msgstr "Pesquisa" + +msgid "shortcuts.group" +msgstr "Agrupar" + +msgid "shortcuts.h-distribute" +msgstr "Distribuir horizontalmente" + +msgid "shortcuts.hide-ui" +msgstr "Mostrar/ocultar interface" + +msgid "shortcuts.increase-zoom" +msgstr "Mais zoom" + +msgid "shortcuts.insert-image" +msgstr "Inserir imagem" + +msgid "shortcuts.make-corner" +msgstr "Criar canto" + +msgid "shortcuts.move-fast-right" +msgstr "Mover para a direita rápido" + +msgid "shortcuts.move-fast-up" +msgstr "Mover para cima rápido" + +msgid "shortcuts.merge-nodes" +msgstr "Unir pontos" + +msgid "shortcuts.mask" +msgstr "Máscara" + +msgid "shortcuts.move" +msgstr "Mover" + +msgid "shortcuts.move-fast-down" +msgstr "Mover para baixo rápido" + +msgid "shortcuts.move-fast-left" +msgstr "Mover para a esquerda rápido" + +msgid "shortcuts.opacity-4" +msgstr "Definir opacidade para 40%" + +msgid "shortcuts.opacity-5" +msgstr "Definir opacidade para 50%" + +msgid "shortcuts.opacity-6" +msgstr "Definir opacidade para 60%" + +msgid "shortcuts.opacity-8" +msgstr "Definir opacidade para 80%" + +msgid "shortcuts.opacity-9" +msgstr "Definir opacidade para 90%" + +msgid "shortcuts.opacity-7" +msgstr "Definir opacidade para 70%" + +msgid "shortcuts.snap-nodes" +msgstr "Ajustar ao ponto" + +msgid "shortcuts.snap-pixel-grid" +msgstr "Ajustar à grelha" + +msgid "shortcuts.start-editing" +msgstr "Começar a editar" + +msgid "shortcuts.start-measure" +msgstr "Iniciar medição" + +msgid "shortcuts.stop-measure" +msgstr "Parar medição" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Alternar paleta de cor" + +msgid "shortcuts.thumbnail-set" +msgstr "Definir imagem de destaque" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Alternar modo de foco" + +msgid "shortcuts.toggle-grid" +msgstr "Mostrar/ocultar grelha" + +msgid "shortcuts.toggle-alignment" +msgstr "Alternar alinhamento dinâmico" + +msgid "shortcuts.toggle-assets" +msgstr "Alternar recursos" + +msgid "shortcuts.undo" +msgstr "Desfazer" + +msgid "workspace.focus.focus-off" +msgstr "Foco desligado" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group-name" +msgstr "Nome do grupo" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename" +msgstr "Renomear" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.group" +msgstr "Desfoque agrupado" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Salvando" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.leftright" +msgstr "Esquerda & Direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-start" +msgstr "Início do fluxo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-delay" +msgstr "Atraso" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-dodge" +msgstr "Subexposição de cores" + +msgid "workspace.path.actions.add-node" +msgstr "Adicionar ponto (%s)" + +msgid "workspace.path.actions.delete-node" +msgstr "Remover ponto (%s)" + +msgid "workspace.path.actions.draw-nodes" +msgstr "Desenhar ponto (%s)" + +msgid "workspace.path.actions.merge-nodes" +msgstr "Mesclar pontos (%s)" From 96ef9a3c525825c10964d58d2bab63bc904650c2 Mon Sep 17 00:00:00 2001 From: Jaziel Cavalcante Date: Fri, 23 Sep 2022 20:48:22 +0000 Subject: [PATCH 040/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 99.1% (1199 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 214 ++++++++++++++++++++++++++++++++- 1 file changed, 213 insertions(+), 1 deletion(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 89f923b8fe..2f1547e94c 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-23 20:54+0000\n" -"Last-Translator: Hugo Figueira \n" +"Last-Translator: Jaziel Cavalcante \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -4384,3 +4384,215 @@ msgstr "Desenhar ponto (%s)" msgid "workspace.path.actions.merge-nodes" msgstr "Mesclar pontos (%s)" + +msgid "workspace.options.width" +msgstr "Largura" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.selection-stroke" +msgstr "Selecionar traçado" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Sublinhado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square" +msgstr "Quadrado" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Tachado" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Texto" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.packed" +msgstr "packed" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.no-wrap" +msgstr "sem wrap" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding" +msgstr "Padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "Direito" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-simple" +msgstr "Padding simples" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "space between" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "space around" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "Mais cores" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.wrap" +msgstr "wrap" + +msgid "workspace.options.shadow-options.color" +msgstr "Cor da sombra" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.show-fill-on-export" +msgstr "Mostrar nas exportações" + +msgid "workspace.options.show-in-viewer" +msgstr "Mostrar no modo de visualização" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.line-arrow" +msgstr "Seta de linha" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "Nenhum" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.round" +msgstr "Redondo" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Minúsculo" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Texto do grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-selection" +msgstr "Texto de seleção" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.none" +msgstr "Nenhum" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "Enviar para trás" + +msgid "workspace.shape.menu.flatten" +msgstr "Achatar" + +msgid "workspace.shape.menu.hide-ui" +msgstr "Mostrar/ocultar UI" + +msgid "workspace.shape.menu.path" +msgstr "Caminho" + +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "Remover miniatura" + +msgid "workspace.options.x" +msgstr "X" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Colar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.screen" +msgstr "Tela" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Maiúsculo" + +msgid "workspace.options.text-options.vertical-align" +msgstr "Alinhamento vertical" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Selecionar camada" + +msgid "workspace.shape.menu.union" +msgstr "Unir" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-lib-colors" +msgstr "Mais cores da biblioteca" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.reset-overrides" +msgstr "Redefinir substituições" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke" +msgstr "Traçado" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.preset" +msgstr "Predefinição" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "Enviar para trás" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "Definir como miniatura" + +msgid "workspace.shape.menu.transform-to-path" +msgstr "Transformar em caminho" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "Atualizar componentes principais" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square-marker" +msgstr "Marcador quadrado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.triangle-arrow" +msgstr "Seta triangular" + +msgid "workspace.options.stroke-color" +msgstr "Cor do traçado" + +msgid "workspace.options.stroke-width" +msgstr "Largura do traçado" + +msgid "workspace.options.y" +msgstr "Y" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "Direita para a esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.spread" +msgstr "Espalhar" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.use-play-button" +msgstr "" +"Use o botão play no cabeçalho para executar a visualização do protótipo." + +msgid "workspace.path.actions.make-curve" +msgstr "Para curvar (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Virar horizontalmente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-vertical" +msgstr "Virar verticalmente" From 71f2e4cabe62f432d1db3c1c1e6e4ac038a7bcb7 Mon Sep 17 00:00:00 2001 From: Filipe Pessanha Date: Fri, 23 Sep 2022 16:58:16 +0000 Subject: [PATCH 041/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 99.1% (1199 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 2f1547e94c..8642abd37e 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-23 20:54+0000\n" -"Last-Translator: Jaziel Cavalcante \n" +"Last-Translator: Filipe Pessanha \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -826,7 +826,7 @@ msgid "handoff.attributes.typography.text-transform.none" msgstr "Nenhum" msgid "handoff.attributes.typography.text-transform.titlecase" -msgstr "Title Case" +msgstr "Iniciais em maiúsculas" msgid "handoff.attributes.typography.text-transform.uppercase" msgstr "Maiúsculo" @@ -2859,7 +2859,7 @@ msgstr "" "testar seus designs com usuários, tudo isso em um só lugar." msgid "onboarding.slide.0.alt" -msgstr "Crie designs" +msgstr "Criar designs" msgid "onboarding.slide.1.title" msgstr "Dê vida aos seus projetos com interações" From 4faa3db6f8ba03afa76cbb46e3a23d818cc24851 Mon Sep 17 00:00:00 2001 From: Stas Haas Date: Fri, 23 Sep 2022 19:40:16 +0000 Subject: [PATCH 042/682] :globe_with_meridians: Add translations for: German. Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 201bcbeb2e..ee6892f3dd 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-22 21:21+0000\n" -"Last-Translator: nautilusx \n" +"PO-Revision-Date: 2022-09-23 20:54+0000\n" +"Last-Translator: Stas Haas \n" "Language-Team: German \n" "Language: de\n" @@ -2190,7 +2190,7 @@ msgid "shortcuts.artboard-selection" msgstr "Zeichenfläche aus Auswahl erstellen" msgid "shortcuts.bool-difference" -msgstr "Subtrahieren (Booleschen Operation)" +msgstr "Subtrahieren (Boolesche Operation)" msgid "shortcuts.bring-front" msgstr "In den Vordergrund" @@ -4806,3 +4806,16 @@ msgstr "" msgid "shortcuts.v-distribute" msgstr "Vertikal verteilen" + +msgid "shortcuts.bool-union" +msgstr "Vereinigung" + +msgid "shortcuts.bool-exclude" +msgstr "Ausschluss" + +msgid "shortcuts.bool-intersection" +msgstr "Schnittmenge" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "Abstand zwischen" From 84760f940cfe43e08162b8cc7e97ce684f840ad3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Fri, 23 Sep 2022 14:46:06 +0000 Subject: [PATCH 043/682] :globe_with_meridians: Add translations for: Indonesian. Currently translated at 9.3% (113 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/ --- frontend/translations/id.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/translations/id.po b/frontend/translations/id.po index 0dae4b3feb..ad8713a567 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-19 04:15+0000\n" -"Last-Translator: liimee \n" +"PO-Revision-Date: 2022-09-23 20:54+0000\n" +"Last-Translator: Dário \n" "Language-Team: Indonesian \n" "Language: id\n" @@ -311,7 +311,7 @@ msgstr "" "Apa yang ingin kamu lakukan dengan asetnya*?" msgid "dashboard.export.options.all.message" -msgstr "berkas dengan pustaka bersama akan dimasukkan dalam hasil ekspor" +msgstr "berkas dengan pustaka bersama akan dimasukkan dalam hasil ekspor." msgid "dashboard.export.options.all.title" msgstr "Ekspor pustaka bersama" From f6792ce67ff829eb295d5d010ad907303231b443 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Fri, 23 Sep 2022 14:52:54 +0000 Subject: [PATCH 044/682] :globe_with_meridians: Add translations for: Portuguese (Portugal). Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/ --- frontend/translations/pt_PT.po | 75 ++++++++++++++++++++++++++-------- 1 file changed, 59 insertions(+), 16 deletions(-) diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index 1dada5b591..c884ccb922 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-23 14:27+0000\n" +"PO-Revision-Date: 2022-09-23 20:54+0000\n" "Last-Translator: Dário \n" "Language-Team: Portuguese (Portugal) \n" @@ -55,7 +55,7 @@ msgstr "LDAP" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-oidc-submit" -msgstr "OpenID" +msgstr "OpenID Connect" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" @@ -1206,7 +1206,7 @@ msgstr "Adicionar como Biblioteca Partilhada" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.hint" msgstr "" -"Uma vez adicionados como Biblioteca Partilhada, os ativos na biblioteca " +"Uma vez adicionados como Biblioteca Partilhada, os recursos na biblioteca " "deste ficheiro estarão disponíveis com o resto dos teus ficheiros." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs @@ -1485,10 +1485,10 @@ msgstr "Cancelar publicação" msgid "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint" msgstr[0] "" -"Se cancelares a publicação, os ativos nele tornam-se uma biblioteca deste " +"Se cancelares a publicação, os recursos nele tornam-se uma biblioteca deste " "ficheiro." msgstr[1] "" -"Se cancelares a publicação, os ativos nele tornam-se uma biblioteca destes " +"Se cancelares a publicação, os recursos nele tornam-se uma biblioteca destes " "ficheiros." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs @@ -2087,7 +2087,7 @@ msgid "shortcuts.toggle-alignment" msgstr "Alternar alinhamento dinâmico" msgid "shortcuts.toggle-assets" -msgstr "Alternar ativos" +msgstr "Alternar recursos" msgid "shortcuts.toggle-colorpalette" msgstr "Alternar selector de cores" @@ -2229,7 +2229,7 @@ msgid "viewer.header.fullscreen" msgstr "Tela Cheia" msgid "viewer.header.handoff-section" -msgstr "Handoff (%s)" +msgstr "Transferência (%s)" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.interactions" @@ -2292,11 +2292,11 @@ msgstr "Alinhar ao topo (%s)" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.assets" -msgstr "Ativos" +msgstr "Recursos" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.box-filter-all" -msgstr "Todos os ativos" +msgstr "Todos os recursos" msgid "workspace.assets.box-filter-graphics" msgstr "Gráficos" @@ -2348,7 +2348,7 @@ msgstr "biblioteca local" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.not-found" -msgstr "Ativos não encontrados" +msgstr "Recursos não encontrados" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.rename-group" @@ -2356,7 +2356,7 @@ msgstr "Renomear grupo" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.search" -msgstr "Pesquisar ativos" +msgstr "Pesquisar recursos" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.selected-count" @@ -2394,7 +2394,7 @@ msgstr "Espaço entre letras" #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.assets.typography.line-height" -msgstr "Entrelinhamento" +msgstr "Altura da Linha" #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.assets.typography.text-transform" @@ -2636,7 +2636,7 @@ msgid "workspace.header.menu.disable-scale-text" msgstr "Desativar escala de texto" msgid "dashboard.export.options.merge.title" -msgstr "Incluir ativos da biblioteca partilhada em bibliotecas de ficheiros" +msgstr "Incluir recursos da biblioteca partilhada em bibliotecas de ficheiros" msgid "dashboard.fonts.deleted-placeholder" msgstr "Tipo de letra eliminado" @@ -2678,7 +2678,7 @@ msgstr "Mostrar interações" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.group" -msgstr "Grupo" +msgstr "Agrupar" #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs msgid "workspace.assets.typography.sample" @@ -3616,7 +3616,7 @@ msgstr "juntos" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.padding" -msgstr "Padding" +msgstr "Distância interna" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.padding-all" @@ -3972,7 +3972,7 @@ msgid "workspace.undo.entry.multiple.circle" msgstr "círculos" msgid "workspace.undo.entry.multiple.color" -msgstr "ativos de cor" +msgstr "recursos de cor" msgid "workspace.undo.entry.multiple.group" msgstr "grupos" @@ -4540,3 +4540,46 @@ msgstr "Comentários (%s)" msgid "workspace.sidebar.layers.components" msgstr "Componentes" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Mover (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Rectângulo (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Texto (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text-palette" +msgstr "Tipografias (%s)" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Ignorar" + +msgid "workspace.viewport.click-to-close-path" +msgstr "Clica para fechar o caminho" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Atalhos (%s)" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.update" +msgstr "Atualizar" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.path" +msgstr "Caminho (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Prancheta (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Imagem (%s)" From 043683775f4283be762c6c085921b1fbca16c9db Mon Sep 17 00:00:00 2001 From: Hugo Figueira Date: Fri, 23 Sep 2022 14:27:47 +0000 Subject: [PATCH 045/682] :globe_with_meridians: Add translations for: Portuguese (Portugal). Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/ --- frontend/translations/pt_PT.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index c884ccb922..e2bddbc42d 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-23 20:54+0000\n" -"Last-Translator: Dário \n" +"Last-Translator: Hugo Figueira \n" "Language-Team: Portuguese (Portugal) \n" "Language: pt_PT\n" @@ -3909,7 +3909,7 @@ msgid "workspace.shape.menu.paste" msgstr "Colar" msgid "workspace.shape.menu.path" -msgstr "Caminho" +msgstr "Curvas" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.reset-overrides" From 836511f5c793da0bb0d95f4b70108320e3cdfa2c Mon Sep 17 00:00:00 2001 From: Jaziel Cavalcante Date: Fri, 23 Sep 2022 20:55:29 +0000 Subject: [PATCH 046/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 8642abd37e..5d458be174 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-23 20:54+0000\n" -"Last-Translator: Filipe Pessanha \n" +"PO-Revision-Date: 2022-09-24 08:30+0000\n" +"Last-Translator: Jaziel Cavalcante \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -13,7 +13,7 @@ msgstr "" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" -msgstr "Já tem uma conta?" +msgstr "Já possui uma conta?" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" @@ -2441,7 +2441,7 @@ msgid "dasboard.team-hero.title" msgstr "Juntem-se!" msgid "dashboard.libraries-and-templates" -msgstr "Biblioteca & Modelos" +msgstr "Biblioteca e Modelos" msgid "dashboard.libraries-and-templates.explore" msgstr "Explore mais deles e saiba como contribuir" @@ -4223,7 +4223,7 @@ msgstr "Recente" #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke-cap.circle-marker" -msgstr "" +msgstr "Marcador de círculo" #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.direction-ltr" @@ -4414,9 +4414,8 @@ msgid "workspace.options.layout.packed" msgstr "packed" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -#, fuzzy msgid "workspace.options.layout.no-wrap" -msgstr "sem wrap" +msgstr "no wrap" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.padding" @@ -4596,3 +4595,22 @@ msgstr "Virar horizontalmente" #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.flip-vertical" msgstr "Virar verticalmente" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.titlecase" +msgstr "Title Case" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.diamond-marker" +msgstr "Diamond marker" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.unknown" +msgstr "Operação em %s" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-artboard-from-selection" +msgstr "Selecionar o board" + +msgid "workspace.undo.entry.single.typography" +msgstr "recurso de tipografia" From 26b32634f79a98eaa845417499fe19cc6f2b56cd Mon Sep 17 00:00:00 2001 From: Hugo Figueira Date: Sat, 24 Sep 2022 08:12:20 +0000 Subject: [PATCH 047/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 5d458be174..b71ba3b265 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-24 08:30+0000\n" -"Last-Translator: Jaziel Cavalcante \n" +"Last-Translator: Hugo Figueira \n" "Language-Team: Portuguese (Brazil) \n" "Language: pt_BR\n" @@ -1169,7 +1169,7 @@ msgstr "Mostrar todos os comentários" #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-your-comments" -msgstr "Mostrar apenas os seus comentários" +msgstr "Mostrar apenas seus comentários" #: src/app/main/ui/static.cljs msgid "labels.sign-out" @@ -3728,7 +3728,7 @@ msgid "shortcuts.separate-nodes" msgstr "Separar pontos" msgid "shortcuts.show-pixel-grid" -msgstr "Mostrar/ocultar grelha de píxeis" +msgstr "Mostrar/ocultar grade de pixeis" msgid "shortcut-subsection.general-dashboard" msgstr "Geral" @@ -4306,7 +4306,7 @@ msgid "shortcuts.snap-nodes" msgstr "Ajustar ao ponto" msgid "shortcuts.snap-pixel-grid" -msgstr "Ajustar à grelha" +msgstr "Ajustar à grade" msgid "shortcuts.start-editing" msgstr "Começar a editar" @@ -4327,7 +4327,7 @@ msgid "shortcuts.toggle-focus-mode" msgstr "Alternar modo de foco" msgid "shortcuts.toggle-grid" -msgstr "Mostrar/ocultar grelha" +msgstr "Mostrar/ocultar grade" msgid "shortcuts.toggle-alignment" msgstr "Alternar alinhamento dinâmico" @@ -4409,9 +4409,8 @@ msgid "workspace.options.text-options.title" msgstr "Texto" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -#, fuzzy msgid "workspace.options.layout.packed" -msgstr "packed" +msgstr "embalado" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.no-wrap" @@ -4442,9 +4441,8 @@ msgid "workspace.options.more-colors" msgstr "Mais cores" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -#, fuzzy msgid "workspace.options.layout.wrap" -msgstr "wrap" +msgstr "quebrado" msgid "workspace.options.shadow-options.color" msgstr "Cor da sombra" @@ -4614,3 +4612,6 @@ msgstr "Selecionar o board" msgid "workspace.undo.entry.single.typography" msgstr "recurso de tipografia" + +msgid "workspace.path.actions.make-corner" +msgstr "Em cantos (%s)" From aadc3c25db308b1908753a70a9bb2ebcd11eef54 Mon Sep 17 00:00:00 2001 From: Hugo Figueira Date: Sat, 24 Sep 2022 08:32:50 +0000 Subject: [PATCH 048/682] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index b71ba3b265..1c6b513d84 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-24 08:30+0000\n" +"PO-Revision-Date: 2022-09-24 23:03+0000\n" "Last-Translator: Hugo Figueira \n" "Language-Team: Portuguese (Brazil) \n" @@ -3453,7 +3453,7 @@ msgstr "Duração" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing" -msgstr "Easing" +msgstr "Atenuação" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing-ease" @@ -4430,11 +4430,11 @@ msgstr "Padding simples" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.space-between" -msgstr "space between" +msgstr "espaço interno" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.space-around" -msgstr "space around" +msgstr "espaço em volta" #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" @@ -4596,11 +4596,11 @@ msgstr "Virar verticalmente" #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.options.text-options.titlecase" -msgstr "Title Case" +msgstr "Capitalização de Título" #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke-cap.diamond-marker" -msgstr "Diamond marker" +msgstr "Marcador diamante" #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.unknown" From 407423b480b4441c215e4f0b89e47b90c8ea4320 Mon Sep 17 00:00:00 2001 From: Shuaib Zahda Date: Sat, 24 Sep 2022 11:19:38 +0000 Subject: [PATCH 049/682] :globe_with_meridians: Add translations for: Arabic. Currently translated at 52.6% (637 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 181 +++++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 1 deletion(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 3659af1a4c..03d0f2c79e 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-20 13:46+0000\n" +"PO-Revision-Date: 2022-09-24 23:03+0000\n" "Last-Translator: Shuaib Zahda \n" "Language-Team: Arabic \n" @@ -2335,3 +2335,182 @@ msgstr "تحميل الملف: %s" msgid "dashboard.export-frames" msgstr "صدر اللوحة الفنية الى ملف PDF…" + +msgid "errors.team-leave.insufficient-members" +msgstr "أعضاء غير كافيين لمغادرة الفريق ، ربما تريد حذفه." + +msgid "errors.team-leave.member-does-not-exists" +msgstr "العضو الذي تحاول تعيينه غير موجود." + +msgid "labels.next" +msgstr "التالي" + +msgid "labels.share-prototype" +msgstr "مشاركة النموذج الأولي" + +msgid "dashboard.export.options.detach.title" +msgstr "عامل أصول المكتبة المشتركة كعناصر بسيطة" + +msgid "dashboard.export.options.merge.title" +msgstr "تضمين أصول المكتبة المشتركة في مكتبات الملفات" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "الاشتراك في النشرة الإخبارية" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "موفر المصادقة غير معد ومسجل." + +msgid "errors.email-spam-or-permanent-bounces" +msgstr "تم الإبلاغ عن البريد الإلكتروني «٪ s» كبريد عشوائي أو مرتد بشكل دائم." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "يسعدنا وجودك هنا. إذا كنت بحاجة إلى مساعدة، يرجى البحث أولا قبل النشر." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "انتقل إلى منتدى Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "مجتمع Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "اذهب إلى Twitter" + +msgid "labels.default" +msgstr "إفتراضي" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "مستودع Github" + +msgid "labels.log-or-sign" +msgstr "تسجيل الدخول أو الاشتراك" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "عضو" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "أرسل لي الأخبار وتحديثات المنتجات والتوصيات حول Penpot." + +#: src/app/main/ui/confirm.cljs +msgid "ds.component-subtitle" +msgstr "عناصر سيتم تحديثها:" + +msgid "errors.auth.unable-to-login" +msgstr "يبدوا أنك غير مصرح لك أو أن الجلسة إنتهت." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "إزالة العضو" + +msgid "labels.skip" +msgstr "تخطي" + +msgid "labels.start" +msgstr "ابدأ" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.your-account" +msgstr "حسابك" + +msgid "modals.invite-member.emails" +msgstr "رسائل البريد الإلكتروني، مفصولة بفواصل" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.libraries-and-templates" +msgstr "المكتبات والقوالب" + +msgid "labels.link" +msgstr "رابط" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "قيد الانتظار" + +msgid "labels.show-comments-list" +msgstr "قائمة التعليقات" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.big-nudge" +msgstr "دفعة كبيرة" + +msgid "dashboard.export.explain" +msgstr "" +"ملف أو أكثر تريد تصديرهم يستخدمون مكتبات مشتركة. ماذا تريد أن تفعل في " +"أصولهم*؟" + +msgid "dashboard.export.options.all.message" +msgstr "" +"سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم." + +msgid "dashboard.import.import-warning" +msgstr "تحتوي بعض الملفات على كائنات غير صالحة تمت إزالتها." + +msgid "errors.email-as-password" +msgstr "لا يمكنك استخدام بريدك الإلكتروني ككلمة مرور" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "حذف الملف" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "ادعُ الأعضاء إلى الفريق" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "الغاء نشر المكتبة" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "دعوة غير صالحة" + +msgid "errors.invite-invalid.info" +msgstr "هذه الدعوة قد تلغى أو قد تنتهي." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "الحالة" + +msgid "errors.team-leave.owner-cant-leave" +msgstr "لا يمكن للمالك مغادرة الفريق ، يجب إعادة تعيين دور المالك." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "الدروس" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "هنا للمساعدة في استفساراتك التقنية." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "إعادة إرسال الدعوة" + +msgid "labels.workspace" +msgstr "مساحة العمل" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "لا توجد دعوات." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "" +"اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(أنت)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." From 161a13919402582e38f9a32af35a6f2e80e89c78 Mon Sep 17 00:00:00 2001 From: Hugo Figueira Date: Sat, 24 Sep 2022 23:01:48 +0000 Subject: [PATCH 050/682] :globe_with_meridians: Add translations for: Portuguese (Portugal). Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/ --- frontend/translations/pt_PT.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index e2bddbc42d..37a3dac765 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-23 20:54+0000\n" +"PO-Revision-Date: 2022-09-24 23:03+0000\n" "Last-Translator: Hugo Figueira \n" "Language-Team: Portuguese (Portugal) \n" @@ -3440,7 +3440,7 @@ msgstr "Ease" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing" -msgstr "Easing" +msgstr "Atenuação" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing-ease-in-out" From 494b08b97510c3645c480da158c6f476058b3d27 Mon Sep 17 00:00:00 2001 From: Eva Date: Tue, 27 Sep 2022 12:57:20 +0200 Subject: [PATCH 051/682] :sparkles: Add title to color bullets --- CHANGES.md | 1 + frontend/src/app/main/ui/components/color_bullet.cljs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index be5e6a75b4..772ab747f3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ ### :sparkles: New features +- Add title to color bullets [Taiga #4218](https://tree.taiga.io/project/penpot/task/4218) - Add team hero in projects dashboard [Taiga #3863](https://tree.taiga.io/project/penpot/us/3863) - Add zoom style to shared link [Taiga #3874](https://tree.taiga.io/project/penpot/us/3874) - Add dashboard creation button as placeholder [Taiga #3861](https://tree.taiga.io/project/penpot/us/3861) diff --git a/frontend/src/app/main/ui/components/color_bullet.cljs b/frontend/src/app/main/ui/components/color_bullet.cljs index 8db2af7a67..999e77cb6d 100644 --- a/frontend/src/app/main/ui/components/color_bullet.cljs +++ b/frontend/src/app/main/ui/components/color_bullet.cljs @@ -36,7 +36,7 @@ :is-not-library-color (nil? (:id color)) :is-gradient (some? (:gradient color))) :on-click on-click - :alt (or (:name color) (:color color) (gradient-type->string (:type (:gradient color))))} + :title (or (:name color) (:color color) (gradient-type->string (:type (:gradient color))))} (if (:gradient color) [:div.color-bullet-wrapper {:style {:background (uc/color->background color)}}] [:div.color-bullet-wrapper From c373b3741fa72baed6a6ed17826e28a75c7ee50d Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 27 Sep 2022 13:02:43 +0200 Subject: [PATCH 052/682] :paperclip: Increase version --- CHANGES.md | 11 +++++++++-- version.txt | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index be5e6a75b4..ffccfe0970 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,6 +2,15 @@ ## :rocket: Next +### :boom: Breaking changes & Deprecations +### :sparkles: New features +### :bug: Bugs fixed +### :arrow_up: Deps updates +### :heart: Community contributions by (Thank you!) + + +## :rocket: 1.16.0-beta + ### :boom: Breaking changes & Deprecations - Removed the support for v2 internal file data blob format. This @@ -26,8 +35,6 @@ - Print emails to console by default if smtp is disabled - Add `email-verification` flag for enable/disable email verification - - ### :bug: Bugs fixed - Fix unexpected removal of guides on copy&paste frames [Taiga #3887](https://tree.taiga.io/project/penpot/issue/3887) by @andrewzhurov diff --git a/version.txt b/version.txt index 50696337d7..2dc4f7c6fe 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -1.16.0-beta +1.17.0-beta From e1960b44727edd8589f6db455d20593d1240389e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 25 Sep 2022 14:08:28 +0000 Subject: [PATCH 053/682] :globe_with_meridians: Add translations for: Turkish. Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/ --- frontend/translations/tr.po | 275 +++++++++++++++++++++++++++++++++++- 1 file changed, 269 insertions(+), 6 deletions(-) diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 03dc39241b..0cfcbd71af 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-08-18 16:16+0000\n" +"PO-Revision-Date: 2022-09-27 15:18+0000\n" "Last-Translator: Oğuz Ersen \n" -"Language-Team: Turkish " -"\n" +"Language-Team: Turkish \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.14-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -3196,7 +3196,9 @@ msgstr "Seçimi dışa aktar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "1 ögeyi dışa aktar" +msgid_plural "" +msgstr[0] "1 ögeyi dışa aktar" +msgstr[1] "%s ögeyi dışa aktar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -4515,4 +4517,265 @@ msgid "workspace.updates.update" msgstr "Güncelle" msgid "workspace.viewport.click-to-close-path" -msgstr "Yolu kapatmak için tıklayın" \ No newline at end of file +msgstr "Yolu kapatmak için tıklayın" + +msgid "common.publish" +msgstr "Yayınla" + +msgid "common.unpublish" +msgstr "Yayından kaldır" + +msgid "dashboard.libraries-and-templates" +msgstr "Kütüphaneler ve Şablonlar" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Daha fazlasını keşfedin ve nasıl katkıda bulunacağınızı öğrenin" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Tamam" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Dikkat" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Herkesi dahil etmeyi unutmayın. Geliştiriciler, tasarımcılar, yöneticiler... " +"çeşitlilik iyidir :)" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Dosya siliniyor" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Daha sonra bir takım oluştur" + +msgid "onboarding.choice.team-up.roles" +msgstr "Rol ile davet et:" + +msgid "onboarding.team-modal.create-team" +msgstr "Bir takım oluştur" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Takım, aynı dosya ve projelerde çalışan diğer Penpot kullanıcılarıyla " +"işbirliği yapmanıza olanak tanır." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Sınırsız dosya ve proje" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Tüm topluluk ve Penpot çekirdek takımı ile Penpot, bugünü ve geleceği " +"hakkında bilgi edinmek, paylaşmak ve tartışmak için herkese açık bir alan." + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Çeviriler, özellik istekleri, temel katkılar, hata avı ile nasıl işbirliği " +"yapacağınızı bulacağınız yer…" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot açık kaynaklıdır ve Kaleidos'un yanı sıra birçok insanın birbirine " +"yardım ettiği topluluk tarafından yapılmıştır. Herkes işbirliğine " +"katılabilir:" + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "Topluluğa Katılım" + +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Kullanıcı Kılavuzu ve Youtube kanalımız gibi Penpot'u kullanmaya başlamanıza " +"yardımcı olacak birçok kaynak olduğunu bilmelisiniz." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Penpot'un nasıl kullanılacağı hakkında ayrıntılı bilgi. Prototiplemeden " +"tasarımları düzenlemeye veya paylaşmaya kadar." + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Asgari Genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Azami yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Asgari yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Asgari genişlik" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Takım yönetimi" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot takımlar içindir. Üyeleri projeler ve dosyalar üzerinde birlikte " +"çalışmaya davet edin" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Takım olun!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Bu uygulamalı öğretici ile biraz eğlenirken Penpot'taki temel bilgileri " +"öğrenin." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Öğreticiyi başlat" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Uygulamalı Öğretici" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Penpot'ta bir gezintiye çıkın ve temel özelliklerini öğrenin." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Gezintiyi başlat" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Arayüz İncelemesi" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Şablon içe aktarılırken bir sorun oluştu. Şablon içe aktarılmadı." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Bu dosya zaten Bileşenler V2 etkinken kullanıldı." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Dosyayı sil" +msgstr[1] "Dosyaları sil" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Üyeleri takıma davet et" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Yayından kaldır" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Yayından kaldırırsanız, içindeki varlıklar bu dosyanın bir kütüphanesi " +"haline gelir." +msgstr[1] "" +"Yayından kaldırırsanız, içindeki varlıklar bu dosyaların bir kütüphanesi " +"haline gelir." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Bu kütüphaneyi yayından kaldırmak istediğinizden emin misiniz?" +msgstr[1] "Bu kütüphaneleri yayından kaldırmak istediğinizden emin misiniz?" + +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"Bizim ve topluluğumuz tarafından hazırlanan öğreticileri izleyebilirsiniz." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Video öğreticiler" + +msgid "onboarding-v2.before-start.title" +msgstr "Başlamadan önce" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Katkıda bulunma kılavuzu" + +msgid "onboarding-v2.welcome.title" +msgstr "Penpot'a hoş geldiniz!" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Çok oyunculu sürüm" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Rol yönetimi" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Sınırsız üye" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "%100 özgür!" + +msgid "workspace.assets.local-library" +msgstr "yerel kütüphane" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Kütüphaneyi Yayından Kaldır" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Dosya siliniyor" +msgstr[1] "Dosyalar siliniyor" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Bu dosyada, şu dosyada kullanılmakta olan kütüphaneler var:" +msgstr[1] "Bu dosyada, şu dosyalarda kullanılmakta olan kütüphaneler var:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Bu dosyayı silmek istediğinizden emin misiniz?" +msgstr[1] "Bu dosyaları silmek istediğinizden emin misiniz?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Bu dosyalarda, şu dosyada kullanılmakta olan kütüphaneler var:" +msgstr[1] "Bu dosyalarda, şu dosyalarda kullanılmakta olan kütüphaneler var:" + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Kullanıcı kılavuzu" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Bu dosyada kullanılıyor:" +msgstr[1] "Bu dosyalarda kullanılıyor:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Kütüphaneyi yayından kaldır" +msgstr[1] "Kütüphaneleri yayından kaldır" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Asgari Yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Azami Genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Azami Yükseklik" + +msgid "workspace.shape.menu.restore-main" +msgstr "Ana bileşeni geri yükle" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Azami genişlik" From 4d56b5f1b90588b38bd1fd022064a09316731ca1 Mon Sep 17 00:00:00 2001 From: Youkho Date: Mon, 26 Sep 2022 14:16:35 +0000 Subject: [PATCH 054/682] :globe_with_meridians: Add translations for: Arabic. Currently translated at 53.1% (642 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 03d0f2c79e..c73f6b6636 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-24 23:03+0000\n" -"Last-Translator: Shuaib Zahda \n" +"PO-Revision-Date: 2022-09-27 15:18+0000\n" +"Last-Translator: Youkho \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -2514,3 +2514,24 @@ msgstr "(أنت)" msgid "modals.change-owner-and-leave-confirm.message" msgstr "" "أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"يمكنك إضافة إعدادات التصدير إلى العناصر من خصائص التصميم (أسفل الشريط " +"الجانبي الأيمن)." + +msgid "dashboard.export.options.detach.message" +msgstr "" +"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى المكتبة. " + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"نظرًا لأنك العضو الوحيد في هذا الفريق ، فسيتم حذف الفريق مع مشاريعه وملفاته." + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "حدثت مشكلة في استيراد النموذج. لم يتم استيراد النموذج." + +msgid "dashboard.export.options.merge.message" +msgstr "سيتم تصدير ملفك مع دمج جميع الأصول الخارجية في مكتبة الملفات." From b1edc53a1cb23244b6efcbbe73f2c4fd7a798575 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1rio?= Date: Sat, 24 Sep 2022 23:03:26 +0000 Subject: [PATCH 055/682] :globe_with_meridians: Add translations for: Portuguese (Portugal). Currently translated at 100.0% (1209 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_PT/ --- frontend/translations/pt_PT.po | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index 37a3dac765..dd58bfcf8d 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-24 23:03+0000\n" -"Last-Translator: Hugo Figueira \n" +"PO-Revision-Date: 2022-09-27 15:18+0000\n" +"Last-Translator: Dário \n" "Language-Team: Portuguese (Portugal) \n" "Language: pt_PT\n" @@ -3440,7 +3440,7 @@ msgstr "Ease" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing" -msgstr "Atenuação" +msgstr "Easing" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing-ease-in-out" From 2030e845bb9f1e5f15d326095d4fbe9d2bf4823a Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 27 Sep 2022 17:32:55 +0200 Subject: [PATCH 056/682] :globe_with_meridians: Added translation for: Tamil. --- frontend/translations/ta.po | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 frontend/translations/ta.po diff --git a/frontend/translations/ta.po b/frontend/translations/ta.po new file mode 100644 index 0000000000..4f8f6e6dec --- /dev/null +++ b/frontend/translations/ta.po @@ -0,0 +1,2 @@ +msgid "" +msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file From f2de69e1f3998a0752b4c771358f03857ff277f7 Mon Sep 17 00:00:00 2001 From: andy Date: Tue, 27 Sep 2022 17:35:28 +0200 Subject: [PATCH 057/682] :globe_with_meridians: Added translation for: Croatian. --- frontend/translations/hr.po | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 frontend/translations/hr.po diff --git a/frontend/translations/hr.po b/frontend/translations/hr.po new file mode 100644 index 0000000000..4f8f6e6dec --- /dev/null +++ b/frontend/translations/hr.po @@ -0,0 +1,2 @@ +msgid "" +msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file From 1a7583e6adce12c03fb8dcccff41a43d979b335c Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 28 Sep 2022 09:42:08 +0200 Subject: [PATCH 058/682] :bug: Fix some texts and a typo --- CHANGES.md | 3 +++ frontend/translations/ca.po | 2 +- frontend/translations/de.po | 2 +- frontend/translations/en.po | 8 ++++---- frontend/translations/es.po | 2 +- frontend/translations/eu.po | 2 +- frontend/translations/fa.po | 2 +- frontend/translations/fr.po | 2 +- frontend/translations/gl.po | 2 +- frontend/translations/he.po | 2 +- frontend/translations/id.po | 2 +- frontend/translations/it.po | 2 +- frontend/translations/jpn_JP.po | 2 +- frontend/translations/lt.po | 2 +- frontend/translations/ml.po | 2 +- frontend/translations/pl.po | 2 +- frontend/translations/pt_BR.po | 2 +- frontend/translations/ru.po | 2 +- frontend/translations/tr.po | 2 +- frontend/translations/zh_CN.po | 2 +- frontend/translations/zh_Hant.po | 2 +- 21 files changed, 26 insertions(+), 23 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ffccfe0970..c58255e54c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,9 @@ ### :boom: Breaking changes & Deprecations ### :sparkles: New features ### :bug: Bugs fixed + +- Fix some texts and a typo [Taiga #4215](https://tree.taiga.io/project/penpot/issue/4215) + ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index f8423e7372..505b04db40 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -283,7 +283,7 @@ msgid "dashboard.export-binary-multi" msgstr "Baixa %s fitxers Penpot (.penpot)" msgid "dashboard.export-frames" -msgstr "Exporta els taulers a PDF…" +msgstr "Exporta els taulers a PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/de.po b/frontend/translations/de.po index bcce76f7db..9d5fcaf58b 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -287,7 +287,7 @@ msgid "dashboard.export-binary-multi" msgstr "%s Penpot-Dateien herunterladen (.penpot)" msgid "dashboard.export-frames" -msgstr "Zeichenflächen als PDF exportieren…" +msgstr "Zeichenflächen als PDF exportieren" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 3c53e47546..96af1faaee 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -326,7 +326,7 @@ msgid "dashboard.export-binary-multi" msgstr "Download %s Penpot files (.penpot)" msgid "dashboard.export-frames" -msgstr "Export artboards to PDF…" +msgstr "Export boards to PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" @@ -2557,7 +2557,7 @@ msgid "shortcuts.toggle-lock-size" msgstr "Lock proportions" msgid "shortcuts.toggle-rules" -msgstr "Show/hide rules" +msgstr "Show/hide rulers" msgid "shortcuts.toggle-scale-text" msgstr "Toggle scale text" @@ -2959,7 +2959,7 @@ msgstr "Hide pixel grid" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-rules" -msgstr "Hide rules" +msgstr "Hide rulers" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-textpalette" @@ -3006,7 +3006,7 @@ msgstr "Show pixel grid" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-rules" -msgstr "Show rules" +msgstr "Show rulers" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-textpalette" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 85ee471b2b..65c7778e22 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -333,7 +333,7 @@ msgid "dashboard.export-binary-multi" msgstr "Descargar %s archivos Penpot (.penpot)" msgid "dashboard.export-frames" -msgstr "Exportar tableros a PDF..." +msgstr "Exportar tableros a PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index 811cdf9e5c..87657ca830 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -281,7 +281,7 @@ msgid "dashboard.export-binary-multi" msgstr "Deskargatu %s Penpot fitxategi (.penpot)" msgid "dashboard.export-frames" -msgstr "Esportatu arbelak PDFra…" +msgstr "Esportatu arbelak PDFra" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/fa.po b/frontend/translations/fa.po index 75ca1776f9..ab1d3a0199 100644 --- a/frontend/translations/fa.po +++ b/frontend/translations/fa.po @@ -293,7 +293,7 @@ msgid "dashboard.export-binary-multi" msgstr "دانلود %s فایل پنپات (.penpot)" msgid "dashboard.export-frames" -msgstr "خروجی آرت‌بوردها به پی‌دی‌اف…" +msgstr "خروجی آرت‌بوردها به پی‌دی‌اف" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index debf7c8895..027773e288 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -286,7 +286,7 @@ msgid "dashboard.export-binary-multi" msgstr "Télécharger %s fichiers Penpot (.penpot)" msgid "dashboard.export-frames" -msgstr "Exporter les plans de travail au format PDF…" +msgstr "Exporter les plans de travail au format PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/gl.po b/frontend/translations/gl.po index 7dac802cbc..b9b82d938b 100644 --- a/frontend/translations/gl.po +++ b/frontend/translations/gl.po @@ -255,7 +255,7 @@ msgstr "" "vai a [Bibliotecas e modelos] (https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "Exportar marcos a PDF…" +msgstr "Exportar marcos a PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/he.po b/frontend/translations/he.po index 96e01bde7f..2c44ca2637 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -280,7 +280,7 @@ msgid "dashboard.export-binary-multi" msgstr "הורדת %s קובצי Penpot‏ (‎.penpot)" msgid "dashboard.export-frames" -msgstr "ייצוא לוחות אומנות ל־PDF…" +msgstr "ייצוא לוחות אומנות ל־PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/id.po b/frontend/translations/id.po index 68c25397f6..3ed6c3d6d5 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -279,7 +279,7 @@ msgid "dashboard.export-binary-multi" msgstr "Unduh %s berkas Penpot (.penpot)" msgid "dashboard.export-frames" -msgstr "Ekspor papan-papan ke PDF…" +msgstr "Ekspor papan-papan ke PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/it.po b/frontend/translations/it.po index 6c102f611f..37c672f29f 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -278,7 +278,7 @@ msgid "dashboard.export-binary-multi" msgstr "Scarica %s file Penpot (.penpot)" msgid "dashboard.export-frames" -msgstr "Esportare le tavole da disegno in PDF…" +msgstr "Esportare le tavole da disegno in PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/jpn_JP.po b/frontend/translations/jpn_JP.po index dcb68686d7..8e5196d9df 100644 --- a/frontend/translations/jpn_JP.po +++ b/frontend/translations/jpn_JP.po @@ -270,7 +270,7 @@ msgstr "" "templates](https://penpot.app/libraries-templates.html) をチェックしてみてください。" msgid "dashboard.export-frames" -msgstr "PDFでエクスポート…" +msgstr "PDFでエクスポート" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/lt.po b/frontend/translations/lt.po index 13466d977b..a41a1ad2a5 100644 --- a/frontend/translations/lt.po +++ b/frontend/translations/lt.po @@ -244,7 +244,7 @@ msgstr "" "į [Bibliotekos ir šablonai] (https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "Eksportuokite darbalaukius į PDF..." +msgstr "Eksportuokite darbalaukius į PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/ml.po b/frontend/translations/ml.po index f53fdfdc3f..3990b0849c 100644 --- a/frontend/translations/ml.po +++ b/frontend/translations/ml.po @@ -246,7 +246,7 @@ msgstr "" "വിഭാഗത്തിലേക്ക്] (https://penpot.app/libraries-templates.html) പോകാവുന്നതാണ്" msgid "dashboard.export-frames" -msgstr "ആർട്ട്ബോർഡുകൾ പിഡിഎഫായി എക്സ്പോർട്ട് ചെയ്യുക...." +msgstr "ആർട്ട്ബോർഡുകൾ പിഡിഎഫായി എക്സ്പോർട്ട് ചെയ്യുക" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po index 54f4a1af98..ca185fab87 100644 --- a/frontend/translations/pl.po +++ b/frontend/translations/pl.po @@ -243,7 +243,7 @@ msgstr "" "szablony](https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "Eksportuj obszary kompozycji do PDF…" +msgstr "Eksportuj obszary kompozycji do PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index c4989fa09f..781aa41035 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -260,7 +260,7 @@ msgstr "" "modelos](https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "Exportar pranchetas para PDF…" +msgstr "Exportar pranchetas para PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index e401fa7cd6..dba5754392 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -239,7 +239,7 @@ msgstr "" "[\"Libraries & templates\"](https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "Экспорт кадров в PDF..." +msgstr "Экспорт кадров в PDF" msgid "dashboard.export-multi" msgstr "Экспорт файлов Penpot (%s)" diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 03dc39241b..19f1413310 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -286,7 +286,7 @@ msgid "dashboard.export-binary-multi" msgstr "%s Penpot dosyasını indir (.penpot)" msgid "dashboard.export-frames" -msgstr "Çalışma yüzeylerini PDF'ye aktar…" +msgstr "Çalışma yüzeylerini PDF'ye aktar" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 8d52b2d708..75582b769b 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -237,7 +237,7 @@ msgid "dashboard.empty-placeholder-drafts" msgstr "现在尚无文件。如果你想尝试一些模板,请点击[库和模板](https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "导出画板到PDF…" +msgstr "导出画板到PDF" #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" diff --git a/frontend/translations/zh_Hant.po b/frontend/translations/zh_Hant.po index 35ec4836f9..65e1322d10 100644 --- a/frontend/translations/zh_Hant.po +++ b/frontend/translations/zh_Hant.po @@ -238,7 +238,7 @@ msgstr "" "模板區段](https://penpot.app/libraries-templates.html)" msgid "dashboard.export-frames" -msgstr "將 artboards 匯出為 PDF..." +msgstr "將 boards 匯出為 PDF" msgid "dashboard.export-multi" msgstr "匯出 %s 個檔案" From 4ef876bf58d9bfc85a107133a91014b66b1b834c Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 27 Sep 2022 17:54:43 +0200 Subject: [PATCH 059/682] :sparkles: Fix elements-without-names accesibility issues --- frontend/src/app/main/ui/dashboard.cljs | 9 +++++---- frontend/src/app/main/ui/dashboard/projects.cljs | 13 +++++++++---- frontend/src/app/main/ui/dashboard/sidebar.cljs | 9 ++++++--- frontend/src/app/main/ui/workspace.cljs | 3 ++- frontend/src/app/main/ui/workspace/header.cljs | 1 + .../src/app/main/ui/workspace/left_toolbar.cljs | 12 ++++++++++++ frontend/src/app/main/ui/workspace/presence.cljs | 3 ++- frontend/src/app/main/ui/workspace/sidebar.cljs | 3 ++- frontend/translations/en.po | 6 ++++++ frontend/translations/es.po | 6 ++++++ 10 files changed, 51 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index a1154ec938..be423286fb 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -144,7 +144,7 @@ :section section})))] [:div.dashboard-templates-section {:class (when collapsed "collapsed")} - [:div.title + [:div.title [:div {:on-click toggle-collapse} [:span (tr "dashboard.libraries-and-templates")] [:span.icon (if collapsed i/arrow-up i/arrow-down)]]] @@ -156,7 +156,8 @@ :on-click #(import-template item)} [:div.template-card [:div.img-container - [:img {:src (:thumbnail-uri item)}]] + [:img {:src (:thumbnail-uri item) + :alt (:name item)}]] [:div.card-name [:span (:name item)] [:span.icon i/download]]]]) [:div.card-container @@ -195,8 +196,8 @@ (case section :dashboard-projects [:* - [:& projects-section {:team team - :projects projects + [:& projects-section {:team team + :projects projects :profile profile :default-project-id default-project-id}] [:& templates-section {:profile profile diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 08b507302e..8f10a151bc 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -54,7 +54,8 @@ :team team :origin :hero}))))] [:div.team-hero - [:img {:src "images/deco-team-banner.png" :border "0"}] + [:img {:src "images/deco-team-banner.png" :border "0" + :role "presentation"}] [:div.text [:div.title (tr "dasboard.team-hero.title")] [:div.info @@ -63,7 +64,9 @@ [:button.btn-primary.invite {:on-click invite-member} (tr "onboarding.choice.team-up.invite-members")] - [:button.close {:on-click close-banner} + [:button.close + {:on-click close-banner + :aria-label (tr "labels.close")} [:span i/close]]])) (def builtin-templates @@ -113,7 +116,8 @@ :success "")]] [:button.close - {:on-click close-tutorial} + {:on-click close-tutorial + :aria-label (tr "labels.close")} [:span.icon i/close]]])) (mf/defc interface-walkthrough @@ -132,7 +136,8 @@ [:a.btn-primary.action {:href " https://design.penpot.app/walkthrough" :target "_blank" :on-click handle-walkthrough-link} (tr "dasboard.walkthrough-hero.start")]] [:button.close - {:on-click close-walkthrough} + {:on-click close-walkthrough + :aria-label (tr "labels.close")} [:span.icon i/close]]])) (mf/defc project-item diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index eb11a8edab..4c87c6b80c 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -227,7 +227,8 @@ [:li.team-name {:on-click (partial team-selected (:id team)) :key (dm/str (:id team))} [:span.team-icon - [:img {:src (cf/resolve-team-photo-url team)}]] + [:img {:src (cf/resolve-team-photo-url team) + :alt (:name team)}]] [:span.team-text {:title (:name team)} (:name team)]]) [:hr] @@ -354,7 +355,8 @@ [:span.team-text (tr "dashboard.default-team-name")]] [:div.team-name [:span.team-icon - [:img {:src (cf/resolve-team-photo-url team)}]] + [:img {:src (cf/resolve-team-photo-url team) + :alt (:name team)}]] [:span.team-text {:title (:name team)} (:name team)]]) [:span.switch-icon @@ -487,7 +489,8 @@ [:div.profile-section [:div.profile {:on-click #(reset! show true) :data-test "profile-btn"} - [:img {:src photo}] + [:img {:src photo + :alt (:fullname profile)}] [:span (:fullname profile)]] [:& dropdown {:on-close #(reset! show false) diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index da4f644b34..cbb8ba694d 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -81,7 +81,8 @@ [:* [:& left-toolbar {:layout layout}] (if (:collapse-left-sidebar layout) - [:button.collapse-sidebar.collapsed {:on-click #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar))} + [:button.collapse-sidebar.collapsed {:on-click #(st/emit! (dw/toggle-layout-flag :collapse-left-sidebar)) + :aria-label (tr "workspace.sidebar.expand")} i/arrow-slide] [:& left-sidebar {:layout layout}]) [:& right-sidebar {:section options-mode diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 7657d319b0..f516785938 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -461,6 +461,7 @@ [:& export-progress-widget] [:button.document-history {:alt (tr "workspace.sidebar.history" (sc/get-tooltip :toggle-history)) + :aria-label (tr "workspace.sidebar.history" (sc/get-tooltip :toggle-history)) :class (when (contains? layout :document-history) "selected") :on-click #(st/emit! (-> (dw/toggle-layout-flag :document-history) (vary-meta assoc ::ev/origin "workspace-header")))} diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index 95435b53eb..458632e6b3 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -50,6 +50,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image)) + :aria-label (tr "workspace.toolbar.image" (sc/get-tooltip :insert-image)) :on-click on-click} [:* i/image @@ -72,6 +73,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.move" (sc/get-tooltip :move)) + :aria-label (tr "workspace.toolbar.move" (sc/get-tooltip :move)) :class (when (and (nil? selected-drawtool) (not edition)) "selected") :on-click #(st/emit! :interrupt)} @@ -79,6 +81,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame)) + :aria-label (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame)) :class (when (= selected-drawtool :frame) "selected") :on-click (partial select-drawtool :frame) :data-test "artboard-btn"} @@ -86,6 +89,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect)) + :aria-label (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect)) :class (when (= selected-drawtool :rect) "selected") :on-click (partial select-drawtool :rect) :data-test "rect-btn"} @@ -93,6 +97,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse)) + :aria-label (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse)) :class (when (= selected-drawtool :circle) "selected") :on-click (partial select-drawtool :circle) :data-test "ellipse-btn"} @@ -100,6 +105,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text)) + :aria-label (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text)) :class (when (= selected-drawtool :text) "selected") :on-click (partial select-drawtool :text)} i/text]] @@ -109,6 +115,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve)) + :aria-label (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve)) :class (when (= selected-drawtool :curve) "selected") :on-click (partial select-drawtool :curve) :data-test "curve-btn"} @@ -116,6 +123,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path)) + :aria-label (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path)) :class (when (= selected-drawtool :path) "selected") :on-click (partial select-drawtool :path) :data-test "path-btn"} @@ -124,6 +132,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment)) + :aria-label (tr "workspace.toolbar.comments" (sc/get-tooltip :add-comment)) :class (when (= selected-drawtool :comments) "selected") :on-click (partial select-drawtool :comments)} i/chat]]] @@ -132,6 +141,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette)) + :aria-label (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette)) :class (when (contains? layout :textpalette) "selected") :on-click (fn [] (r/set-resize-type! :bottom) @@ -144,6 +154,7 @@ [:li [:button.tooltip.tooltip-right {:alt (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette)) + :aria-label (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette)) :class (when (contains? layout :colorpalette) "selected") :on-click (fn [] (r/set-resize-type! :bottom) @@ -155,6 +166,7 @@ [:li [:button.tooltip.tooltip-right.separator {:alt (tr "workspace.toolbar.shortcuts" (sc/get-tooltip :show-shortcuts)) + :aria-label (tr "workspace.toolbar.shortcuts" (sc/get-tooltip :show-shortcuts)) :class (when (contains? layout :shortcuts) "selected") :on-click (fn [] (let [is-sidebar-closed? (contains? layout :collapse-left-sidebar)] diff --git a/frontend/src/app/main/ui/workspace/presence.cljs b/frontend/src/app/main/ui/workspace/presence.cljs index 19b1dcd222..16225650f1 100644 --- a/frontend/src/app/main/ui/workspace/presence.cljs +++ b/frontend/src/app/main/ui/workspace/presence.cljs @@ -16,7 +16,8 @@ [{:keys [session profile] :as props}] [:li.tooltip.tooltip-bottom {:alt (:fullname profile)} - [:img {:style {:border-color (:color session)} + [:img {:alt (:fullname profile) + :style {:border-color (:color session)} :src (cfg/resolve-profile-photo-url profile)}]]) (mf/defc active-sessions diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index e898b4e5a3..1d8fbafdea 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -56,7 +56,8 @@ [:& shortcuts-container] [:* [:button.collapse-sidebar - {:on-click handle-collapse} + {:on-click handle-collapse + :aria-label (tr "workspace.sidebar.collapse")} i/arrow-slide] [:& tab-container {:on-change-tab #(st/emit! (dw/go-to-layout %)) :selected section diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 374a9cc617..485cf15d09 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -4374,6 +4374,12 @@ msgstr "Imported SVG Attributes" msgid "workspace.sidebar.sitemap" msgstr "Pages" +msgid "workspace.sidebar.collapse" +msgstr "Collapse sidebar" + +msgid "workspace.sidebar.expand" +msgstr "Expand sidebar" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.sitemap" msgstr "Sitemap" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 84d0d8f295..3bef0ce741 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -4581,6 +4581,12 @@ msgstr "Atributos del SVG Importado" msgid "workspace.sidebar.sitemap" msgstr "Páginas" +msgid "workspace.sidebar.collapse" +msgstr "Cerrar barra lateral" + +msgid "workspace.sidebar.expand" +msgstr "Abrir barra lateral" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.sitemap" msgstr "Mapa del sitio" From 6a329fac2782a4dde496b22ae7c2def472800c5e Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Wed, 28 Sep 2022 13:16:36 +0200 Subject: [PATCH 060/682] :sparkles: Fix html-lang-missing accesibility issues --- frontend/src/app/util/dom.cljs | 4 ++++ frontend/src/app/util/i18n.cljs | 1 + 2 files changed, 5 insertions(+) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 26c361a32a..d24e681afe 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -53,6 +53,10 @@ [^string title] (set! (.-title globals/document) title)) +(defn set-html-lang! + [^string lang] + (.setAttribute (.querySelector js/document "html") "lang" lang)) + (defn set-html-theme-color [^string color scheme] (let [meta-node (.querySelector js/document "meta[name='theme-color']")] diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index c031ee9ecf..42984b4241 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -105,6 +105,7 @@ (add-watch locale ::browser-font (fn [_ _ _ locale] (log/info :hint "locale changed" :locale locale) + (dom/set-html-lang! locale) (let [node (dom/get-body)] (if (or (= locale "fa") (= locale "ar")) From f470efc9c7e30ec69429c9ec4e117b391d4aced6 Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Thu, 29 Sep 2022 09:28:47 +0200 Subject: [PATCH 061/682] :tada: Add new contributors to thankyou.md --- THANKYOU.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/THANKYOU.md b/THANKYOU.md index 118a498b1f..3e5effefa5 100644 --- a/THANKYOU.md +++ b/THANKYOU.md @@ -85,4 +85,13 @@ We want to thank to the amazing people that help us! Thank you! You're the best! * [Yaron](https://hosted.weblate.org/user/Yaron) * [yrd](https://hosted.weblate.org/user/yrd) * [YukiYuigishi](https://hosted.weblate.org/user/YukiYuigishi) -* [zcraber](https://hosted.weblate.org/user/zcraber) \ No newline at end of file +* [zcraber](https://hosted.weblate.org/user/zcraber) + +## Libraries & templates +* systxema +* plumilla +* victor crespo +* xtech +* candidexmedia +* merih güz +* klarr agency From 98f490703fc763ae5a02909099d578f72876bca8 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Fri, 30 Sep 2022 07:16:21 +0200 Subject: [PATCH 062/682] :globe_with_meridians: Added translation for: Ukrainian (ukr_UA). --- frontend/translations/ukr_UA.po | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 frontend/translations/ukr_UA.po diff --git a/frontend/translations/ukr_UA.po b/frontend/translations/ukr_UA.po new file mode 100644 index 0000000000..4f8f6e6dec --- /dev/null +++ b/frontend/translations/ukr_UA.po @@ -0,0 +1,2 @@ +msgid "" +msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file From a4bbb4355596a948bedf98c06642f9d86c597691 Mon Sep 17 00:00:00 2001 From: Eva Date: Fri, 30 Sep 2022 13:09:53 +0200 Subject: [PATCH 063/682] :bug: Fix shortcut texts alignment --- CHANGES.md | 1 + frontend/resources/styles/main/partials/sidebar.scss | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index ac370c5850..0419296894 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ ### :sparkles: New features ### :bug: Bugs fixed +- Fix shortcut texts alignment [Taiga #4275](https://tree.taiga.io/project/penpot/issue/4275) - Fix some texts and a typo [Taiga #4215](https://tree.taiga.io/project/penpot/issue/4215) ### :arrow_up: Deps updates diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 92c7cce166..f14aca961b 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -438,10 +438,14 @@ button.collapse-sidebar { margin-top: 4px; color: $color-white; font-size: $fs12; + .command-name { + display: flex; + align-items: center; + } .keys { flex-grow: 1; display: flex; - align-items: flex-start; + align-items: center; justify-content: flex-end; } .char-box { From 243e29fdb45e1b7f8d15e6dde7da655ba42cf3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 30 Sep 2022 14:24:18 +0200 Subject: [PATCH 064/682] :bug: Fix delete component from assets panel in v2 --- .../app/main/data/workspace/libraries.cljs | 24 +- .../data/workspace/libraries_helpers.cljs | 49 ++-- .../src/app/main/data/workspace/shapes.cljs | 241 +++++++++--------- 3 files changed, 167 insertions(+), 147 deletions(-) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 3888a2e805..9729dd89a3 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -16,6 +16,7 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.color :as ctc] + [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] [app.common.types.pages-list :as ctpl] @@ -29,6 +30,7 @@ [app.main.data.workspace.groups :as dwg] [app.main.data.workspace.libraries-helpers :as dwlh] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.main.features :as features] @@ -402,13 +404,16 @@ (ptk/reify ::delete-component ptk/WatchEvent (watch [it state _] - (let [data (get state :workspace-data) - components-v2 (features/active-feature? state :components-v2) - changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) - (pcb/delete-component id components-v2))] - - (rx/of (dch/commit-changes changes)))))) + (let [data (get state :workspace-data)] + (if (features/active-feature? state :components-v2) + (let [component (ctkl/get-component data id) + page (ctpl/get-page data (:main-instance-page component)) + shape (ctn/get-shape page (:main-instance-id component))] + (rx/of (dwsh/delete-shapes (:id page) #{(:id shape)}))) + (let [changes (-> (pcb/empty-changes it) + (pcb/with-library-data data) + (pcb/delete-component id false))] + (rx/of (dch/commit-changes changes)))))))) (defn restore-component "Restore a deleted component, with the given id, on the current file library." @@ -554,11 +559,14 @@ page-id (:current-page-id state) container (cph/get-container file :page page-id) + components-v2 + (features/active-feature? state :components-v2) + changes (-> (pcb/empty-changes it) (pcb/with-container container) (pcb/with-objects (:objects container)) - (dwlh/generate-sync-shape-direct libraries container id true))] + (dwlh/generate-sync-shape-direct libraries container id true components-v2))] (log/debug :msg "RESET-COMPONENT finished" :js/rchanges (log-changes (:redo-changes changes) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 2702226f3e..58326c7401 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -177,7 +177,8 @@ :file (pretty-file file-id state) :library (pretty-file library-id state)) - (let [file (wsh/get-file state file-id)] + (let [file (wsh/get-file state file-id) + components-v2 (get-in file [:options :components-v2])] (loop [pages (vals (get file :pages-index)) changes (pcb/empty-changes it)] (if-let [page (first pages)] @@ -189,7 +190,8 @@ asset-id library-id state - (cph/make-container page :page)))) + (cph/make-container page :page) + components-v2))) changes)))) (defn generate-sync-library @@ -198,7 +200,7 @@ the given library. If an asset id is given, only shapes linked to this particular asset will - be syncrhonized." + be synchronized." [it file-id asset-type asset-id library-id state] (s/assert #{:colors :components :typographies} asset-type) (s/assert (s/nilable ::us/uuid) asset-id) @@ -211,7 +213,8 @@ :file (pretty-file file-id state) :library (pretty-file library-id state)) - (let [file (wsh/get-file state file-id)] + (let [file (wsh/get-file state file-id) + components-v2 (get-in file [:options :components-v2])] (loop [local-components (vals (get file :components)) changes (pcb/empty-changes it)] (if-let [local-component (first local-components)] @@ -223,13 +226,14 @@ asset-id library-id state - (cph/make-container local-component :component)))) + (cph/make-container local-component :component) + components-v2))) changes)))) (defn- generate-sync-container "Generate changes to synchronize all shapes in a particular container (a page or a component) that use assets of the given type in the given library." - [it asset-type asset-id library-id state container] + [it asset-type asset-id library-id state container components-v2] (if (cph/page? container) (log/debug :msg "Sync page in local file" :page-id (:id container)) @@ -248,7 +252,8 @@ library-id state container - shape)) + shape + components-v2)) changes)))) (defmulti uses-assets? @@ -276,16 +281,16 @@ (defmulti generate-sync-shape "Generate changes to synchronize one shape from all assets of the given type that is using, in the given library." - (fn [asset-type _changes _library-id _state _container _shape] asset-type)) + (fn [asset-type _changes _library-id _state _container _shape _components-v2] asset-type)) (defmethod generate-sync-shape :components - [_ changes _library-id state container shape] + [_ changes _library-id state container shape components-v2] (let [shape-id (:id shape) libraries (wsh/get-libraries state)] - (generate-sync-shape-direct changes libraries container shape-id false))) + (generate-sync-shape-direct changes libraries container shape-id false components-v2))) (defmethod generate-sync-shape :colors - [_ changes library-id state _ shape] + [_ changes library-id state _ shape _] (log/debug :msg "Sync colors of shape" :shape (:name shape)) ;; Synchronize a shape that uses some colors of the library. The value of the @@ -296,7 +301,7 @@ #(ctc/sync-shape-colors % library-id library-colors)))) (defmethod generate-sync-shape :typographies - [_ changes library-id state container shape] + [_ changes library-id state container shape _] (log/debug :msg "Sync typographies of shape" :shape (:name shape)) ;; Synchronize a shape that uses some typographies of the library. The attributes @@ -442,7 +447,7 @@ (defn generate-sync-shape-direct "Generate changes to synchronize one shape that the root of a component instance, and all its children, from the given component." - [changes libraries container shape-id reset?] + [changes libraries container shape-id reset? components-v2] (log/debug :msg "Sync shape direct" :shape (str shape-id) :reset? reset?) (let [shape-inst (ctn/get-shape container shape-id) component (cph/get-component libraries @@ -466,20 +471,25 @@ root-inst root-main reset? - initial-root?) + initial-root? + components-v2) ; If the component is not found, because the master component has been - ; deleted or the library unlinked, detach the instance. - (generate-detach-instance changes container shape-id)))) + ; deleted or the library unlinked, do nothing in v2 or detach in v1. + (if components-v2 + changes + (generate-detach-instance changes container shape-id))))) (defn- generate-sync-shape-direct-recursive - [changes container shape-inst component shape-main root-inst root-main reset? initial-root?] + [changes container shape-inst component shape-main root-inst root-main reset? initial-root? components-v2] (log/debug :msg "Sync shape direct recursive" :shape (str (:name shape-inst)) :component (:name component)) (if (nil? shape-main) ;; This should not occur, but protect against it in any case - (generate-detach-instance changes container (:id shape-inst)) + (if components-v2 + changes + (generate-detach-instance changes container (:id shape-inst))) (let [omit-touched? (not reset?) clear-remote-synced? (and initial-root? reset?) set-remote-synced? (and (not initial-root?) reset?) @@ -545,7 +555,8 @@ root-inst root-main reset? - initial-root?)) + initial-root? + components-v2)) moved (fn [changes child-inst child-main] (move-shape diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index abeafaf673..e7c8e67057 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -131,141 +131,142 @@ (rx/empty)))))) (defn delete-shapes - [ids] - (us/assert ::us/set-of-uuid ids) - (ptk/reify ::delete-shapes - ptk/WatchEvent - (watch [it state _] - (let [file-id (:current-file-id state) - page-id (:current-page-id state) - file (wsh/get-file state file-id) - page (wsh/lookup-page state page-id) - objects (wsh/lookup-page-objects state page-id) + ([ids] (delete-shapes nil ids)) + ([page-id ids] + (us/assert ::us/set-of-uuid ids) + (ptk/reify ::delete-shapes + ptk/WatchEvent + (watch [it state _] + (let [file-id (:current-file-id state) + page-id (or page-id (:current-page-id state)) + file (wsh/get-file state file-id) + page (wsh/lookup-page state page-id) + objects (wsh/lookup-page-objects state page-id) - ids (cph/clean-loops objects ids) - lookup (d/getf objects) + ids (cph/clean-loops objects ids) + lookup (d/getf objects) - components-v2 (features/active-feature? state :components-v2) + components-v2 (features/active-feature? state :components-v2) - groups-to-unmask - (reduce (fn [group-ids id] - ;; When the shape to delete is the mask of a masked group, - ;; the mask condition must be removed, and it must be - ;; converted to a normal group. - (let [obj (lookup id) - parent (lookup (:parent-id obj))] - (if (and (:masked-group? parent) - (= id (first (:shapes parent)))) - (conj group-ids (:id parent)) - group-ids))) - #{} - ids) + groups-to-unmask + (reduce (fn [group-ids id] + ;; When the shape to delete is the mask of a masked group, + ;; the mask condition must be removed, and it must be + ;; converted to a normal group. + (let [obj (lookup id) + parent (lookup (:parent-id obj))] + (if (and (:masked-group? parent) + (= id (first (:shapes parent)))) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids) - interacting-shapes - (filter (fn [shape] - ;; If any of the deleted shapes is the destination of - ;; some interaction, this must be deleted, too. - (let [interactions (:interactions shape)] - (some #(and (ctsi/has-destination %) - (contains? ids (:destination %))) - interactions))) - (vals objects)) + interacting-shapes + (filter (fn [shape] + ;; If any of the deleted shapes is the destination of + ;; some interaction, this must be deleted, too. + (let [interactions (:interactions shape)] + (some #(and (ctsi/has-destination %) + (contains? ids (:destination %))) + interactions))) + (vals objects)) - ;; If any of the deleted shapes is a frame with guides - guides (into {} - (comp (map second) - (remove #(contains? ids (:frame-id %))) - (map (juxt :id identity))) - (dm/get-in page [:options :guides])) + ;; If any of the deleted shapes is a frame with guides + guides (into {} + (comp (map second) + (remove #(contains? ids (:frame-id %))) + (map (juxt :id identity))) + (dm/get-in page [:options :guides])) - starting-flows - (filter (fn [flow] - ;; If any of the deleted is a frame that starts a flow, - ;; this must be deleted, too. - (contains? ids (:starting-frame flow))) - (-> page :options :flows)) + starting-flows + (filter (fn [flow] + ;; If any of the deleted is a frame that starts a flow, + ;; this must be deleted, too. + (contains? ids (:starting-frame flow))) + (-> page :options :flows)) - all-parents - (reduce (fn [res id] - ;; All parents of any deleted shape must be resized. - (into res (cph/get-parent-ids objects id))) - (d/ordered-set) - ids) + all-parents + (reduce (fn [res id] + ;; All parents of any deleted shape must be resized. + (into res (cph/get-parent-ids objects id))) + (d/ordered-set) + ids) - all-children - (->> ids ;; Children of deleted shapes must be also deleted. - (reduce (fn [res id] - (into res (cph/get-children-ids objects id))) - []) - (reverse) - (into (d/ordered-set))) + all-children + (->> ids ;; Children of deleted shapes must be also deleted. + (reduce (fn [res id] + (into res (cph/get-children-ids objects id))) + []) + (reverse) + (into (d/ordered-set))) - find-all-empty-parents - (fn recursive-find-empty-parents [empty-parents] - (let [all-ids (into empty-parents ids) - contains? (partial contains? all-ids) - xform (comp (map lookup) - (filter cph/group-shape?) - (remove #(->> (:shapes %) (remove contains?) seq)) - (map :id)) - parents (into #{} xform all-parents)] - (if (= empty-parents parents) - empty-parents - (recursive-find-empty-parents parents)))) + find-all-empty-parents + (fn recursive-find-empty-parents [empty-parents] + (let [all-ids (into empty-parents ids) + contains? (partial contains? all-ids) + xform (comp (map lookup) + (filter cph/group-shape?) + (remove #(->> (:shapes %) (remove contains?) seq)) + (map :id)) + parents (into #{} xform all-parents)] + (if (= empty-parents parents) + empty-parents + (recursive-find-empty-parents parents)))) - empty-parents - ;; Any parent whose children are all deleted, must be deleted too. - (into (d/ordered-set) (find-all-empty-parents #{})) + empty-parents + ;; Any parent whose children are all deleted, must be deleted too. + (into (d/ordered-set) (find-all-empty-parents #{})) - components-to-delete - (if components-v2 - (reduce (fn [components id] - (let [shape (get objects id)] - (if (and (= (:component-file shape) file-id) ;; Main instances should exist only in local file - (:main-instance? shape)) ;; but check anyway - (conj components (:component-id shape)) - components))) - [] - (into ids all-children)) - []) + components-to-delete + (if components-v2 + (reduce (fn [components id] + (let [shape (get objects id)] + (if (and (= (:component-file shape) file-id) ;; Main instances should exist only in local file + (:main-instance? shape)) ;; but check anyway + (conj components (:component-id shape)) + components))) + [] + (into ids all-children)) + []) - changes (-> (pcb/empty-changes it page-id) - (pcb/with-page page) - (pcb/with-objects objects) - (pcb/with-library-data file) - (pcb/set-page-option :guides guides)) + changes (-> (pcb/empty-changes it page-id) + (pcb/with-page page) + (pcb/with-objects objects) + (pcb/with-library-data file) + (pcb/set-page-option :guides guides)) - changes (reduce (fn [changes component-id] - ;; It's important to delete the component before the main instance, because we - ;; need to store the instance position if we want to restore it later. - (pcb/delete-component changes component-id components-v2)) - changes - components-to-delete) + changes (reduce (fn [changes component-id] + ;; It's important to delete the component before the main instance, because we + ;; need to store the instance position if we want to restore it later. + (pcb/delete-component changes component-id components-v2)) + changes + components-to-delete) - changes (-> changes - (pcb/remove-objects all-children) - (pcb/remove-objects ids) - (pcb/remove-objects empty-parents) - (pcb/resize-parents all-parents) - (pcb/update-shapes groups-to-unmask - (fn [shape] - (assoc shape :masked-group? false))) - (pcb/update-shapes (map :id interacting-shapes) - (fn [shape] - (d/update-when shape :interactions - (fn [interactions] - (into [] - (remove #(and (ctsi/has-destination %) - (contains? ids (:destination %)))) - interactions))))) - (cond-> (seq starting-flows) - (pcb/update-page-option :flows (fn [flows] - (->> (map :id starting-flows) - (reduce ctp/remove-flow flows))))))] + changes (-> changes + (pcb/remove-objects all-children) + (pcb/remove-objects ids) + (pcb/remove-objects empty-parents) + (pcb/resize-parents all-parents) + (pcb/update-shapes groups-to-unmask + (fn [shape] + (assoc shape :masked-group? false))) + (pcb/update-shapes (map :id interacting-shapes) + (fn [shape] + (d/update-when shape :interactions + (fn [interactions] + (into [] + (remove #(and (ctsi/has-destination %) + (contains? ids (:destination %)))) + interactions))))) + (cond-> (seq starting-flows) + (pcb/update-page-option :flows (fn [flows] + (->> (map :id starting-flows) + (reduce ctp/remove-flow flows))))))] - (rx/of (dc/detach-comment-thread ids) - (dwsl/update-layout-positions all-parents) - (dch/commit-changes changes)))))) + (rx/of (dc/detach-comment-thread ids) + (dwsl/update-layout-positions all-parents) + (dch/commit-changes changes))))))) (defn- viewport-center [state] From 3c1ab1d58a1b49d764e6f425f36b291dd18db697 Mon Sep 17 00:00:00 2001 From: Youkho Date: Wed, 28 Sep 2022 23:51:13 +0000 Subject: [PATCH 065/682] :globe_with_meridians: Add translations for: Arabic. Currently translated at 58.3% (706 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 247 +++++++++++++++++++++++++++++++++++- 1 file changed, 243 insertions(+), 4 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index c73f6b6636..f9fcccc231 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-27 15:18+0000\n" +"PO-Revision-Date: 2022-10-01 14:17+0000\n" "Last-Translator: Youkho \n" "Language-Team: Arabic \n" @@ -1110,9 +1110,9 @@ msgstr "عفواً!" #: src/app/main/ui/dashboard/team.cljs msgid "labels.num-of-files" msgid_plural "labels.num-of-files" -msgstr[0] "0" -msgstr[1] "1" -msgstr[2] "2" +msgstr[0] "0 ملف" +msgstr[1] "ملفين" +msgstr[2] "3 ملفات" msgstr[3] "قليل" msgstr[4] "كثير" msgstr[5] "غير ذلك" @@ -2535,3 +2535,242 @@ msgstr "حدثت مشكلة في استيراد النموذج. لم يتم اس msgid "dashboard.export.options.merge.message" msgstr "سيتم تصدير ملفك مع دمج جميع الأصول الخارجية في مكتبة الملفات." + +msgid "onboarding.choice.title" +msgstr "مرحبًا بك في Penpot" + +msgid "onboarding.contrib.alt" +msgstr "مصدر مفتوح" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "هل أنت متأكد أنك تريد مغادرة فريق %s ؟" + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"سوف نرسل لك رسائل البريد الإلكتروني ذات الصلة فقط. يمكنك إلغاء الاشتراك في " +"أي وقت في ملف تعريف المستخدم الخاص بك أو عبر رابط إلغاء الاشتراك في أي من " +"رسائلنا الإخبارية." + +msgid "onboarding.slide.0.alt" +msgstr "خلق التصاميم" + +msgid "onboarding.slide.0.desc1" +msgstr "قم بإنشاء واجهات مستخدم جميلة بالتعاون مع جميع أعضاء الفريق." + +msgid "onboarding.slide.0.desc2" +msgstr "الحفاظ على الاتساق على نطاق واسع مع المكونات والمكتبات وأنظمة التصميم." + +msgid "onboarding.slide.0.title" +msgstr "مكتبات التصميم والأنماط والمكونات" + +msgid "onboarding.slide.1.alt" +msgstr "النماذج التفاعلية" + +msgid "onboarding.slide.2.alt" +msgstr "احصل على تعليقات" + +msgid "onboarding.slide.2.desc1" +msgstr "" +"يعمل جميع أعضاء الفريق في وقت واحد مع تصميمات متعددة اللاعبين في الوقت " +"الفعلي وتعليقات وأفكار وتعليقات مركزية مباشرة على التصميمات." + +msgid "onboarding.slide.2.title" +msgstr "الحصول على ردود الفعل ، وتقديم ومشاركة عملك" + +msgid "onboarding.slide.3.desc1" +msgstr "" +"قم بمزامنة التصميم والرمز لجميع المكونات والأنماط الخاصة بك واحصل على " +"مقتطفات التعليمات البرمجية." + +msgid "onboarding.slide.3.desc2" +msgstr "" +"احصل على مواصفات التعليمات البرمجية وقدمها مثل الترميز (SVG ، HTML) أو " +"الأنماط (CSS ، Less ، Stylus…)." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "لم يتم إضافة أي خط" +msgstr[1] "تمت إضافة خط واحد" +msgstr[2] "تمت إضافة خطين" +msgstr[3] "عدد قليل من الخطوط المضافة" +msgstr[4] "تمت إضافة العديد من الخطوط" +msgstr[5] "" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.hint" +msgstr "" +"إذا قمت بنقل الملكية ، فسوف تقوم بتغيير دورك إلى المسؤول ، وستفقد بعض " +"الأذونات على هذا الفريق. " + +msgid "onboarding.contrib.desc1" +msgstr "" +"Penpot هو برنامج مفتوح المصدر ، تم إنشاؤه بواسطة المجتمع ومن أجله. إذا كنت " +"ترغب في التعاون ، فأنت أكثر من موضع ترحيب!" + +msgid "onboarding.contrib.desc2.1" +msgstr "يمكنك الوصول إلى" + +msgid "onboarding.contrib.desc2.2" +msgstr "واتبع تعليمات المساهمة :)" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "" +"تم إرسال طلب الاشتراك الخاص بك ، وسوف نرسل لك بريدًا إلكترونيًا لتأكيده." + +msgid "onboarding.newsletter.decline" +msgstr "لا شكرا" + +msgid "onboarding.newsletter.desc" +msgstr "" +"اشترك في النشرة الإخبارية لدينا للبقاء على اطلاع دائم بتقدم تطوير المنتج " +"والأخبار." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "تم استخدام هذا الملف بالفعل مع تمكين المكونات V2." + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.nudge-title" +msgstr "كمية الدفع" + +msgid "onboarding-v2.before-start.desc3" +msgstr "يمكنك مشاهدة برامجنا التعليمية والبرامج التعليمية التي قدمها مجتمعنا." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "دروس الفيديو" + +msgid "onboarding-v2.before-start.title" +msgstr "قبل ان تبدا" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot هو برنامج مفتوح المصدر وهو من صنع Kaleidos وكذلك المجتمع ، حيث يساعد " +"الكثير من الناس بعضهم البعض بالفعل. يمكن للجميع التعاون من خلال:" + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "المشاركة في المجتمع" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "دفعة صغيرة" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "إلغاء النشر" + +msgid "onboarding.newsletter.policy" +msgstr "سياسة الخصوصية." + +msgid "onboarding.newsletter.privacy1" +msgstr "نظرًا لأننا نهتم بالخصوصية ، فإليك موقعنا " + +msgid "onboarding.slide.1.desc1" +msgstr "أنشئ تفاعلات غنية لتقليد سلوك المنتج." + +msgid "onboarding.slide.1.desc2" +msgstr "" +"شارك مع أصحاب المصلحة ، وقدم مقترحات إلى فريقك وابدأ في اختبار المستخدم " +"بتصميماتك ، كل ذلك في مكان واحد." + +msgid "onboarding.slide.1.title" +msgstr "اجعل تصميماتك تنبض بالحياة من خلال التفاعلات" + +msgid "onboarding.team-modal.create-team" +msgstr "أنشئ فريقًا" + +msgid "onboarding.choice.team-up.create-team" +msgstr "اسم فريقك" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "بعد تسمية فريقك ، ستتمكن من دعوة الأشخاص للانضمام." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "أدخل اسم الفريق" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "دعوة أعضاء" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "أنشئ فريقًا وادعُه لاحقًا" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "أنشئ فريقًا وأرسل الدعوات" + +msgid "onboarding.newsletter.title" +msgstr "هل تريد تلقي أخبار Penpot؟" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"أنت على وشك تحديث المكونات في مكتبة مشتركة. قد يؤثر هذا على الملفات الأخرى " +"التي تستخدمها." + +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"يجب أن تعلم أن هناك الكثير من الموارد المتاحة لمساعدتك في بدء استخدام Penpot " +"، مثل دليل المستخدم وقناة Youtube الخاصة بنا." + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"حيث ستجد كيفية التعاون في الترجمات وطلبات الميزات والمساهمات الأساسية والبحث " +"عن الأخطاء …" + +msgid "onboarding.contrib.title" +msgstr "مساهم في المصدر المفتوح؟" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.message" +msgstr "تحديث المكونات في مكتبة مشتركة" + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "دليل المستخدم" + +msgid "onboarding-v2.welcome.title" +msgstr "مرحبًا بك في Penpot!" + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"معلومات مفصلة حول كيفية استخدام Penpot. من النماذج الأولية إلى تنظيم أو " +"مشاركة التصاميم." + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "دليل المساهمة" + +msgid "onboarding.contrib.link" +msgstr "مشروع على github" + +msgid "onboarding.newsletter.accept" +msgstr "نعم ، اشترك" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"مساحة عامة للتعلم والمشاركة والمناقشة حول Penpot وحاضرها ومستقبلها مع " +"المجتمع بأكمله وفريق Penpot الأساسي." + +msgid "onboarding.choice.team-up.create-later" +msgstr "أنشئ فريقًا لاحقًا" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "تذكر أن تشمل الجميع. المطورين والمصممين والمديرين ... التنوع يضيف :)" + +msgid "onboarding.choice.team-up.roles" +msgstr "دعوة مع الدور:" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"يسمح لك الفريق بالتعاون مع مستخدمي Penpot الآخرين الذين يعملون في نفس " +"الملفات والمشاريع." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "ملفات ومشاريع غير محدودة" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "إدارة الأدوار" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "مجانا 100٪ !" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "عدد غير محدود من الأعضاء" From af640234b5bcb1e2f4d00ff4303ab4b942b4c0b0 Mon Sep 17 00:00:00 2001 From: Shuaib Zahda Date: Wed, 28 Sep 2022 23:59:03 +0000 Subject: [PATCH 066/682] :globe_with_meridians: Add translations for: Arabic. Currently translated at 58.3% (706 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index f9fcccc231..c4dc77b764 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-01 14:17+0000\n" -"Last-Translator: Youkho \n" +"Last-Translator: Shuaib Zahda \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -2356,7 +2356,7 @@ msgstr "تضمين أصول المكتبة المشتركة في مكتبات ا #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-title" -msgstr "الاشتراك في النشرة الإخبارية" +msgstr "الإشتراك في" #: src/app/main/ui/auth/login.cljs msgid "errors.auth-provider-not-configured" From cc68eaa9f7bb83435c804566f740effbba0ca5a4 Mon Sep 17 00:00:00 2001 From: Ahmad HosseinBor <123hozeifeh@gmail.com> Date: Wed, 28 Sep 2022 05:59:34 +0000 Subject: [PATCH 067/682] :globe_with_meridians: Add translations for: Persian. Currently translated at 55.1% (667 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fa/ --- frontend/translations/fa.po | 63 ++++++++++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 1 deletion(-) diff --git a/frontend/translations/fa.po b/frontend/translations/fa.po index 25deeeb032..31de8d8cd1 100644 --- a/frontend/translations/fa.po +++ b/frontend/translations/fa.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-19 04:15+0000\n" +"PO-Revision-Date: 2022-10-01 14:17+0000\n" "Last-Translator: Ahmad HosseinBor <123hozeifeh@gmail.com>\n" "Language-Team: Persian \n" @@ -2872,3 +2872,64 @@ msgstr "آیا مطمئن هستید که می‌خواهید این عضو را msgid "onboarding.choice.team-up.create-team-desc" msgstr "پس از نامگذاری تیم خود، می‌توانید افراد را برای پیوستن دعوت کنید." + +#: src/app/main/ui/auth/login.cljs +#, fuzzy +msgid "errors.auth-provider-not-configured" +msgstr "ارائه دهنده احراز هویت پیکربندی نشده است." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "به انجمن Penpot بروید" + +#: src/app/main/ui/settings/feedback.cljs +#, fuzzy +msgid "feedback.discourse-subtitle1" +msgstr "" +"ما خوشحالیم که شما اینجا هستید. اگر به کمک نیاز دارید، لطفا قبل از ارسال پست " +"جستجو کنید." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "انجمن Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "به توییتر بروید" + +#: src/app/main/ui/alert.cljs +#, fuzzy +msgid "ds.alert-title" +msgstr "توجه" + +#, fuzzy +msgid "errors.auth.unable-to-login" +msgstr "به نظر می‌رسد شما احراز هویت نشده‌اید یا جلسه منقضی شده است." + +#: src/app/main/ui/settings/feedback.cljs +#, fuzzy +msgid "feedback.twitter-title" +msgstr "حساب پشتیبانی در توییتر" + +#: src/app/main/data/workspace/persistence.cljs +#, fuzzy +msgid "errors.components-v2" +msgstr "این فایل قبلاً با فعال بودن Components V2 استفاده شده است." + +#, fuzzy +msgid "errors.team-leave.member-does-not-exists" +msgstr "عضوی که می‌خواهید اختصاص دهید وجود ندارد." + +#: src/app/main/ui/dashboard/search.cljs +#, fuzzy +msgid "dashboard.type-something" +msgstr "برای نمایش نتایج جستجو تایپ کنید" + +#, fuzzy +msgid "dashboard.libraries-and-templates.explore" +msgstr "بیشتر آنها را کاوش کنید و بدانید که چگونه مشارکت کنید" + +#: src/app/main/ui/settings/feedback.cljs +#, fuzzy +msgid "feedback.twitter-subtitle1" +msgstr "اینجا برای کمک به سوالات فنی شما." From f9b7235f8bc41a8c09f4f0ea699141e209e1a0c7 Mon Sep 17 00:00:00 2001 From: Jacopo Lodovico Trabia Date: Fri, 30 Sep 2022 12:01:05 +0000 Subject: [PATCH 068/682] :globe_with_meridians: Add translations for: Italian. Currently translated at 43.0% (520 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/ --- frontend/translations/it.po | 267 +++++++++++++++++++++++++++++++++++- 1 file changed, 264 insertions(+), 3 deletions(-) diff --git a/frontend/translations/it.po b/frontend/translations/it.po index 9e517d124f..db9b99a91c 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-22 21:21+0000\n" -"Last-Translator: Valentina Chapellu \n" +"PO-Revision-Date: 2022-10-01 14:17+0000\n" +"Last-Translator: Jacopo Lodovico Trabia \n" "Language-Team: Italian \n" "Language: it\n" @@ -1687,7 +1687,7 @@ msgstr "Lascia il team" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-confirm.message" -msgstr "Lasciare questo team?" +msgstr "Vuoi lasciare questo team?" #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.accept" @@ -1806,3 +1806,264 @@ msgstr "" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.unpublish-shared" msgstr "Spubblicare la libreria" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "Piccolo scatto" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.big-nudge" +msgstr "Grande scatto" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.message" +msgstr "Aggiorna componenti in una libreria condivisa" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.cancel" +msgstr "Cancella" + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Profilo salvato con successo!" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.accept" +msgstr "Aggiorna" + +msgid "onboarding.choice.team-up.create-team" +msgstr "Il nome del tuo team" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "" +"Dopo aver nominato il tuo team, potrai invitare persone ad unirsi ad esso." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Inserisci il nome del team" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Invita membri" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Crea un team e manda inviti" + +msgid "onboarding.choice.title" +msgstr "Benvenuti su Penpot" + +msgid "onboarding.contrib.alt" +msgstr "Open Source" + +msgid "onboarding.contrib.desc2.1" +msgstr "Puoi accedere al" + +msgid "onboarding.contrib.title" +msgstr "Contribuente Open Source?" + +msgid "onboarding.newsletter.title" +msgstr "Vuoi ricevere le news di Pentot?" + +msgid "onboarding.slide.1.alt" +msgstr "Prototipi interattivi" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Una volta eliminata come Libreria Condivisa, la Libreria dei File di questo " +"file smetterà di essere a disposizione per essere usata con il resto dei " +"tuoi file." + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Invito inviato con successo" + +msgid "onboarding.newsletter.accept" +msgstr "Si, iscrivimi" + +msgid "onboarding.newsletter.decline" +msgstr "No, grazie" + +msgid "onboarding.newsletter.desc" +msgstr "" +"Iscriviti alla nostra newsletter per rimanere aggiornato con le news e i " +"progressi di sviluppo del prodotto." + +msgid "onboarding.newsletter.acceptance-message" +msgstr "" +"La tua richiesta di iscrizione è stata invita, ti invieremo un'email di " +"conferma." + +msgid "onboarding.newsletter.policy" +msgstr "Condizioni sulla Privacy." + +msgid "onboarding.newsletter.privacy1" +msgstr "In quanto ci teniamo alla privacy, qui puoi vedere la nostra " + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Ti invieremo solamente email per te rilevanti. Puoi cancellare l'iscrizione " +"in qualsiasi momento nel tuo profilo utente o tramite il link presente in " +"qualsiasi delle nostre newsletter." + +msgid "onboarding.slide.1.desc1" +msgstr "Crea interazioni complete per imitare al meglio il prodotto finale." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "" +"Non puoi eliminare il tuo profilo. Riassegna i tuoi team prima di procedere." + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "Email di verifica inviata a %s. Controlla la tua email!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Invia invito" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.accept" +msgstr "Elimina come Libreria Condivisa" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.message" +msgstr "Elimina \"%s\" come Libreria Condivisa" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Annulla pubblicazione" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "È utilizzata in questo file:" +msgstr[1] "È utilizzata in questi file:" + +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Suggerimento: ci sono tantissime risorse disponibili per aiutarti nei tuoi " +"primi passi con Penpot, come la Guida Utenti e il nostro canale di Youtube." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Informazioni dettagliate su come usare Penpot. Dalla prototipazione " +"all'organizzazione o condivisione di design." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Guida utenti" + +msgid "onboarding-v2.before-start.desc3" +msgstr "Puoi guardare i nostri tutorial e quelli creati dalla nostra comunità." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Video tutorial" + +msgid "onboarding-v2.before-start.title" +msgstr "Prima di cominciare" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot è Open Source ed è prodotto da Kaleidos così come dalla comunità, in " +"cui già adesso tantissime persone si aiutano a vicenda. Chiunque può " +"collaborare:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Uno spazio pubblico dove imparare, condividere e discutere su Penpot, il suo " +"presente e futuro con l'intera Comunità e con il team di Penpot." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "Partecipando nella Comunità" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Qui troverai come collaborare con le traduzioni, richiedere funzionalità, " +"contribuire al codice, cercare bug…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Guida alla contribuzione" + +msgid "onboarding-v2.welcome.title" +msgstr "Benvenuti su Penpot!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Crea un team più tardi" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Non dimenticarti di includere ogni tipo di persona. Programmatori, " +"designers, responsabili... la diversità si somma :)" + +msgid "onboarding.choice.team-up.roles" +msgstr "Invita con il ruolo:" + +msgid "onboarding.contrib.desc1" +msgstr "" +"Penpot è Open Source, creato dalla comunità per la comunità. Se vuoi " +"collaborare, sei più che benvenuto/a!" + +msgid "onboarding.slide.0.alt" +msgstr "Crea design" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"Stai per aggiornare i componenti in una libreria condivisa. Questo potrebbe " +"causare modifiche nei file che la utilizzano." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Annulla pubblicazione libreria" +msgstr[1] "Annulla pubblicazione librerie" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.message" +msgstr "Aggiorna un componente in una libreria condivisa" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Annullare la pubblicazione di questa libreria?" +msgstr[1] "Annullare la pubblicazione di queste librerie?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Se annulli la pubblicazione, gli elementi diventeranno parte della libreria " +"di questo file." +msgstr[1] "" +"Se annulli la pubblicazione, gli elementi diventeranno parte della libreria " +"di questi file." + +msgid "onboarding.slide.0.desc2" +msgstr "" +"Mantieni in scala la consistenza utilizzando componenti, librerie e sistemi " +"di design." + +msgid "onboarding.slide.0.title" +msgstr "Librerie di design, stili e componenti" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Crea un team e invita più tardi" + +msgid "onboarding.contrib.desc2.2" +msgstr "e seguire le istruzioni di contribuzione :)" + +msgid "onboarding.contrib.link" +msgstr "progetto su github" + +msgid "onboarding.slide.0.desc1" +msgstr "" +"Crea splendide interfacce utente in collaborazione con tutti i membri del " +"team." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.hint" +msgstr "" +"Stai per aggiornare un componente in una libreria condivisa. Questo potrebbe " +"causare modifiche nei file che la utilizzano." + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-invitations" +msgstr "Inviti - %s - Penpot" From 44241ada56e1564d40eae7528fa24cff624bd849 Mon Sep 17 00:00:00 2001 From: "K.B.Dharun Krishna" Date: Tue, 27 Sep 2022 16:11:38 +0000 Subject: [PATCH 069/682] :globe_with_meridians: Add translations for: Tamil. Currently translated at 0.5% (7 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ta/ --- frontend/translations/ta.po | 44 ++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/frontend/translations/ta.po b/frontend/translations/ta.po index 4f8f6e6dec..f9289093cd 100644 --- a/frontend/translations/ta.po +++ b/frontend/translations/ta.po @@ -1,2 +1,44 @@ msgid "" -msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file +msgstr "" +"PO-Revision-Date: 2022-10-01 14:17+0000\n" +"Last-Translator: K.B.Dharun Krishna \n" +"Language-Team: Tamil \n" +"Language: ta\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.14.1\n" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "கடவுச்சொல்லை உறுதிப்படுத்தவும்" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "டெமோ கணக்கை உருவாக்கவும்" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "அதை முயற்சி செய்ய வேண்டுமா?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.email" +msgstr "மின்னஞ்சல்" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"இது ஒரு டெமோ சேவை, உண்மையான வேலைக்கு பயன்படுத்த வேண்டாம், திட்டங்கள் " +"அவ்வப்போது அழிக்கப்படும்." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"உங்கள் மின்னஞ்சலைச் சரிபார்த்து, இணைப்பைக் கிளிக் செய்து சரிபார்த்து, Penpot " +"ஐப் பயன்படுத்தத் தொடங்குங்கள்." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "ஏற்கனவே ஒரு கணக்கு உள்ளதா?" From dc863e8b9727711e48a0d5de7aff9ad1105cb719 Mon Sep 17 00:00:00 2001 From: Zvonimir Juranko Date: Fri, 30 Sep 2022 08:53:27 +0000 Subject: [PATCH 070/682] :globe_with_meridians: Add translations for: Croatian. Currently translated at 79.9% (967 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/ --- frontend/translations/hr.po | 4354 ++++++++++++++++++++++++++++++++++- 1 file changed, 4353 insertions(+), 1 deletion(-) diff --git a/frontend/translations/hr.po b/frontend/translations/hr.po index 4f8f6e6dec..e58da43f93 100644 --- a/frontend/translations/hr.po +++ b/frontend/translations/hr.po @@ -1,2 +1,4354 @@ msgid "" -msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file +msgstr "" +"PO-Revision-Date: 2022-10-01 14:17+0000\n" +"Last-Translator: Zvonimir Juranko \n" +"Language-Team: Croatian \n" +"Language: hr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.14.1\n" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-push" +msgstr "Gurni" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-ms" +msgstr "ms" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease" +msgstr "Ease" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to" +msgstr "Idi do" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-mouse-enter" +msgstr "Ulaz mišem" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-out" +msgstr "Vani" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay-dest" +msgstr "Zatvori preklapanje: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-on-click" +msgstr "Na klik" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-overlay" +msgstr "Otvoreno preklapanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-left" +msgstr "Dolje lijevo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-right" +msgstr "Dolje desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-center" +msgstr "Dolje sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-center" +msgstr "Gore sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-right" +msgstr "Gore desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-position" +msgstr "Pozicija" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay" +msgstr "Zatvori preklapanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-trigger" +msgstr "Okidač" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-url" +msgstr "URL" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-center" +msgstr "Sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-preserve-scroll" +msgstr "Sačuvaj položaj scrolanja" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-manual" +msgstr "Priručnik" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Označi layer" + +#: src/app/main/ui/workspace/left_toolbar.cljs +#, fuzzy +msgid "workspace.sidebar.layers" +msgstr "Layeri" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Svi kutevi" + +msgid "workspace.sidebar.layers.components" +msgstr "Komponente" + +msgid "workspace.sidebar.layers.images" +msgstr "Slike" + +msgid "workspace.sidebar.layers.masks" +msgstr "Maske" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Jednostavna margina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.padding" +msgstr "Padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.no-wrap" +msgstr "bez omota" + +msgid "workspace.options.opacity" +msgstr "Neprozirnost" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.packed" +msgstr "upakiran" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Vrh" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-lib-colors" +msgstr "Više boja iz biblioteke" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Prototip" + +msgid "workspace.options.radius" +msgstr "Radius" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "dno" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "vrh" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.wrap" +msgstr "omotaj" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.position" +msgstr "Pozicija" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "Više boja" + +msgid "workspace.options.stroke-width" +msgstr "Širina poteza" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.group" +msgstr "Grupiraj sjenu" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Unutarnja sjena" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.outer" +msgstr "Vani" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.size" +msgstr "Veličina" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Sjena" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "Token za oporavak je nevažeći." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-successfully" +msgstr "Lozinka uspješno promijenjena" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "Profil nije potvrđen, potvrdi profil prije nastavka." + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Lozinka" + +msgid "auth.privacy-policy" +msgstr "Pravila privatnosti" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Poslat ćemo ti e-mail sa uputama" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Zaboravljena lozinka?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Stvori račun" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "Open Source rješenje za dizajn i izradu prototipova." + +msgid "auth.terms-of-service" +msgstr "Uvjeti pružanja usluge" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Kada kreiraš novi račun, slažeš se s našim uvjetima pružanja usluge i " +"pravilima privatnosti." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "Poslali smo e-mail za potvrdu na" + +msgid "common.publish" +msgstr "Objavi" + +msgid "common.share-link.all-users" +msgstr "Svi Penpot korisnici" + +msgid "common.share-link.confirm-deletion-link-description" +msgstr "" +"Jesi li siguran/na da želiš ukloniti ovu vezu? Ako to učiniš, više nikome " +"neće biti dostupno" + +msgid "common.share-link.current-tag" +msgstr "(postojeći)" + +msgid "common.share-link.destroy-link" +msgstr "Poništi poveznicu" + +msgid "common.share-link.get-link" +msgstr "Izradi poveznicu" + +msgid "common.share-link.link-copied-success" +msgstr "Poveznica uspješno kopirana" + +msgid "common.share-link.permissions-pages" +msgstr "Stranice podijeljene" + +msgid "common.share-link.view-all" +msgstr "Označi sve" + +msgid "common.unpublish" +msgstr "Poništi objavu" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Udruži se!" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#, fuzzy +msgid "dashboard.add-shared" +msgstr "Dodaj kao zajedničku biblioteku" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Praktični vodič" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Promijeni e-mail" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Pregledaj sučelje" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Pokreni praktični vodič" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Tvoj Penpot" + +msgid "dashboard.download-binary-file" +msgstr "Preuzmi Penpot datoteku (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Preuzmi standardnu datoteku (.svg + .json)" + +msgid "dashboard.draft-title" +msgstr "Skica" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Kopija" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Ovdje još uvijek nemaš datoteka" + +#: src/app/main/ui/dashboard/grid.cljs +#, markdown +msgid "dashboard.empty-placeholder-drafts" +msgstr "" +"O ne! Još nemaš datoteka! Ako želiš isprobati neke predloške, idi na [" +"Biblioteke i predlošci](https://penpot.app/libraries-templates.html)" + +#: src/app/main/ui/dashboard/file_menu.cljs +#, fuzzy +msgid "dashboard.duplicate-multi" +msgstr "Kopiraj %s datoteka" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Izvezi" + +#, fuzzy +msgid "dashboard.export-multi" +msgstr "Izvezi %s Penpot datoteka" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "Izvezi odabir" + +msgid "dashboard.export-single" +msgstr "Izvezi Penpot datoteku" + +msgid "dashboard.export.detail" +msgstr "* Može uključivati komponente, grafike, boje i/ili tipografije." + +msgid "dashboard.export.options.all.title" +msgstr "Izvezi zajedničke biblioteke" + +#, fuzzy +msgid "dashboard.export.explain" +msgstr "" +"Jedna ili više datoteka koju želiš izvesti koriste zajedničke biblioteke. " +"Što želiš učiniti s njihovim stavkama*?" + +msgid "dashboard.export.options.detach.title" +msgstr "Tretiraj stavke zajedničke biblioteke kao osnovne objekte" + +#, fuzzy +msgid "dashboard.fonts.empty-placeholder" +msgstr "Još uvijek nemaš instalirane custom fontove." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "1 font dodan" +msgstr[1] "%s fontova dodano" +msgstr[2] "" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Učitaj sve" + +msgid "dashboard.import" +msgstr "Uvezi Penpot datoteke" + +msgid "dashboard.import.analyze-error" +msgstr "Ups! Nismo mogli uvesti ovu datoteku" + +msgid "dashboard.import.progress.process-typographies" +msgstr "Obrada tipografija" + +msgid "dashboard.import.progress.upload-data" +msgstr "Prijenos podataka na poslužitelj (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "Prijenos datoteke: %s" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Pozovi u tim" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Napusti tim" + +msgid "dashboard.libraries-and-templates" +msgstr "Biblioteke i predlošci" + +#, fuzzy +msgid "dashboard.libraries-and-templates.explore" +msgstr "Istraži više njih i saznaj kako doprinijeti" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Došlo je do problema prilikom uvoza predloška. Predložak nije uvezen." + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Biblioteke" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "učitavanje tvojih datoteka…" + +msgid "dashboard.loading-fonts" +msgstr "učitavanje tvojih fontova…" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Premijesti %s datoteke u" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Premijesti u drugi tim" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ Nova datoteka" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "Nova datoteka" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "Tvoja e-mail adresa je uspješno ažurirana" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "Lozinka je uspješno spremljena!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "%s članova" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "Otvori datoteku u novoj kartici" + +msgid "dashboard.options" +msgstr "Mogućnosti" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Promjeni lozinku" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Prikvači/Otkvači" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Projekti" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "Promakni u vlasnika" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.remove-account" +msgstr "Želiš li ukloniti svoj račun?" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "Odaberite jezik korisničkog sučelja" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "Odaberi temu" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "Prikaži sve datoteke" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "Tvoja datoteka je uspješno izbrisana" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "Tvoj projekt je uspješno dupliciran" + +#: src/app/main/ui/dashboard/grid.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-file" +msgstr "Tvoja datoteka je uspješno premještena" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "Tvoje datoteke su uspješno premještene" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "Promijeni tim" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "Informacije tima" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "Članovi tima" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "Projekti tima" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.theme-change" +msgstr "Tema korisničkog sučelja" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "Pretraži rezultate" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "Upiši za rezultate pretraživanja" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "E-mail se već koristi" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "E-mail je već potvrđen." + +msgid "errors.email-as-password" +msgstr "Ne možeš koristiti svoj e-mail kao lozinku" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.email-has-permanent-bounces" +msgstr "E-pmail «%s» ima mnogo trajnih izvješća o odbijanju." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "E-mail za potvrdu mora odgovarati" + +msgid "errors.email-spam-or-permanent-bounces" +msgstr "E-mail «%s» je prijavljen kao neželjena pošta ili je trajno odbijen." + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.generic" +msgstr "Dogodilo se nešto loše." + +#: src/app/main/ui/auth/login.cljs +#, fuzzy +msgid "errors.google-auth-not-enabled" +msgstr "Autentifikacija s Googleom onemogućena je na backendu" + +#: src/app/main/ui/components/color_input.cljs +msgid "errors.invalid-color" +msgstr "Pogrešna boja" + +#: src/app/main/ui/auth/verify_token.cljs +#, fuzzy +msgid "errors.invite-invalid" +msgstr "Pogrešna pozivnica" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "Onemogućena je LDAP provjera autentičnosti." + +msgid "errors.media-format-unsupported" +msgstr "Format slike nije podržan (mora biti svg, jpg ili png)." + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "errors.media-type-mismatch" +msgstr "Čini se da sadržaj slike ne odgovara ekstenziji datoteke." + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "errors.media-type-not-allowed" +msgstr "Čini se da ovo nije važeća slika." + +#: src/app/main/ui/dashboard/team.cljs +#, fuzzy +msgid "errors.member-is-muted" +msgstr "" +"Profil koji pozivaš ima isključen e-email (izvješća o neželjenoj pošti ili " +"veliki broj odbijanja)." + +#, fuzzy +msgid "errors.network" +msgstr "Nije moguće povezati se s backend poslužiteljem." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "Lozinka za potvrdu mora odgovarati" + +#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.profile-is-muted" +msgstr "" +"Tvoj profil ima isključen e-mail (izvješća o neželjenoj pošti ili veliki " +"broj odbijanja)." + +#: src/app/main/ui/auth/register.cljs +msgid "errors.registration-disabled" +msgstr "Registracija je trenutno onemogućena." + +msgid "errors.team-leave.insufficient-members" +msgstr "Nedovoljno članova za napuštanje tima, vjerojatno ga želiš izbrisati." + +msgid "errors.team-leave.member-does-not-exists" +msgstr "Član kojeg pokušavaš dodijeliti ne postoji." + +msgid "errors.terms-privacy-agreement-invalid" +msgstr "Moraš prihvatiti naše uvjete pružanja usluge i politiku privatnosti." + +#: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "errors.unexpected-error" +msgstr "Došlo je do neočekivane pogreške." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.unexpected-token" +msgstr "Nepoznati token" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.wrong-credentials" +msgstr "Čini se da su korisničko ime ili lozinka pogrešni." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.wrong-old-password" +msgstr "Stara lozinka je netočna" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-start" +msgstr "Pridruži se chatu" + +#: src/app/main/ui/settings/feedback.cljs +#, fuzzy +msgid "feedback.subject" +msgstr "Tema" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" +"Opiši razlog svojeg e-maila, navedi radi li se o problemu, ideji ili " +"nedoumici. Član našeg tima će odgovoriti u najkraćem mogućem roku." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "E-mail" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "Idi na Twitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-title" +msgstr "Twitter korisnički račun za podršku" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Zamućenje" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "Vrijednost" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.hex" +msgstr "HEX" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.hsla" +msgstr "HSLA" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.rgba" +msgstr "RGBA" + +#: src/app/main/ui/handoff/attributes/fill.cljs +msgid "handoff.attributes.fill" +msgstr "Ispuna" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "Preuzmi izvornu sliku" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "Visina" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Širina" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout" +msgstr "Raspored" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Visina" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Lijevo" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Radius" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Rotacija" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Širina" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Sjena" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.blur" +msgstr "B" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.offset-x" +msgstr "X" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.offset-y" +msgstr "Y" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.spread" +msgstr "S" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Potez" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Vani" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Unutra" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Sredina" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Nikakav" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Solidan" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Širina" + +#: src/app/main/ui/handoff/attributes/text.cljs +#, fuzzy +msgid "handoff.attributes.typography.font-family" +msgstr "Font" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Veličina fonta" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Stil fonta" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Razmak između slova" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Visina linije" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Dekoracija teksta" + +#, fuzzy +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Nikakav" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Precrtano" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Podcrtano" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Transformiraj tekst" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Krug" + +msgid "handoff.tabs.code.selected.component" +msgstr "Komponenta" + +#, fuzzy +msgid "handoff.tabs.code.selected.curve" +msgstr "Krivina" + +#, fuzzy +msgid "handoff.tabs.code.selected.frame" +msgstr "Tabla" + +msgid "handoff.tabs.code.selected.group" +msgstr "Grupa" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Pravokutnik" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.text" +msgstr "Tekst" + +msgid "history.alert-message" +msgstr "Vidiš verziju %s" + +msgid "labels.accept" +msgstr "Prihvati" + +#, fuzzy +msgid "labels.add-custom-font" +msgstr "Dodajte custom font" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.admin" +msgstr "Administrator" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.all" +msgstr "Sve" + +msgid "labels.and" +msgstr "i" + +msgid "labels.back" +msgstr "Povratak" + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.desc-message" +msgstr "" +"Čini se da moraš malo pričekati i pokušati ponovno; vršimo mala održavanja " +"naših servera." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "Odbaci" + +msgid "labels.close" +msgstr "Zatvori" + +#: src/app/main/ui/dashboard/comments.cljs +msgid "labels.comments" +msgstr "Komentari" + +msgid "labels.content" +msgstr "Sadržaj" + +msgid "labels.continue-with" +msgstr "Nastavi sa" + +msgid "labels.continue-with-penpot" +msgstr "Možeš nastaviti s Penpot računom" + +#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs +#, fuzzy +msgid "labels.create-team" +msgstr "Kreiraj novi tim" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.create-team.placeholder" +msgstr "Unesi ime novog tima" + +msgid "labels.custom-fonts" +msgstr "Custom fontovi" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.dashboard" +msgstr "Nadzorna ploča" + +msgid "labels.default" +msgstr "zadano" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete" +msgstr "Izbriši" + +#: src/app/main/ui/comments.cljs +msgid "labels.delete-comment" +msgstr "Izbriši komentar" + +#: src/app/main/ui/comments.cljs +#, fuzzy +msgid "labels.delete-comment-thread" +msgstr "Izbriši thread" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.delete-invitation" +msgstr "Izbriši pozivnicu" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete-multi-files" +msgstr "Izbriši %s datoteka" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.drafts" +msgstr "Nacrti" + +#: src/app/main/ui/comments.cljs +msgid "labels.edit" +msgstr "Uredi" + +msgid "labels.edit-file" +msgstr "Uredi datoteku" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.editor" +msgstr "Urednik" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Isteklo" + +msgid "labels.export" +msgstr "Izvezi" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-disabled" +msgstr "Povratne informacije onemogućene" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-sent" +msgstr "Povratne informacije poslane" + +#, fuzzy +msgid "labels.font-family" +msgstr "Font" + +msgid "labels.font-providers" +msgstr "Pružatelji fontova" + +msgid "labels.font-variants" +msgstr "Stilovi" + +msgid "labels.fonts" +msgstr "Fontovi" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Github repozitorij" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.give-feedback" +msgstr "Daj povratnu informaciju" + +msgid "labels.go-back" +msgstr "Povratak" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.help-center" +msgstr "Centar za pomoć" + +msgid "labels.images" +msgstr "Slike" + +msgid "labels.installed-fonts" +msgstr "Instalirani fontovi" + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.desc-message" +msgstr "" +"Nešto loše se dogodilo. Molimo pokušaj ponovno i ako problem potraje, " +"kontaktiraj podršku." + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.main-message" +msgstr "Interna pogreška" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.invitations" +msgstr "Pozivnice" + +#: src/app/main/ui/settings/options.cljs +msgid "labels.language" +msgstr "Jezik" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.libraries-and-templates" +msgstr "Biblioteke i predlošci" + +msgid "labels.link" +msgstr "Poveznica" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +msgstr "Odjava" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Član" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.name" +msgstr "Ime" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs +msgid "labels.no-comments-available" +msgstr "Nemaš obavijesti o komentarima na čekanju" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "Nema pozivnica." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "Pritisni gumb \"Pozovi u tim\" da pozoveš više članova u ovaj tim." + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.auth-info" +msgstr "Prijavljen/a si kao" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.desc-message" +msgstr "Ova stranica možda ne postoji ili nemaš dopuštenja za pristup." + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.main-message" +msgstr "Ups!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-files" +msgid_plural "labels.num-of-files" +msgstr[0] "1 datoteka" +msgstr[1] "%s datoteka" +msgstr[2] "" + +msgid "labels.num-of-frames" +msgid_plural "labels.num-of-frames" +msgstr[0] "1 ploča" +msgstr[1] "%s ploča" +msgstr[2] "" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.num-of-projects" +msgid_plural "labels.num-of-projects" +msgstr[0] "1 projekt" +msgstr[1] "%s projekata" +msgstr[2] "" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.old-password" +msgstr "Stara lozinka" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.only-yours" +msgstr "Samo tvoj" + +msgid "labels.or" +msgstr "ili" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.owner" +msgstr "Vlasnik" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.profile" +msgstr "Profil" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.projects" +msgstr "Projekti" + +msgid "labels.recent" +msgstr "Nedavno" + +#: src/app/main/ui/settings/sidebar.cljs +#, fuzzy +msgid "labels.release-notes" +msgstr "Release notes" + +#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.remove" +msgstr "Ukloni" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "Ukloni člana" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.rename-team" +msgstr "Preimenuj tim" + +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +msgid "labels.retry" +msgstr "Pokušaj ponovo" + +msgid "labels.save" +msgstr "Spremi" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.send" +msgstr "Pošalji" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.sending" +msgstr "Slanje…" + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.main-message" +msgstr "Usluga je nedostupna" + +msgid "labels.share-prototype" +msgstr "Podijeli prototip" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "Bibiloteke" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-all-comments" +msgstr "Prikaži sve komentare" + +msgid "labels.show-comments-list" +msgstr "Prikaži listu komentara" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.show-your-comments" +msgstr "Prikaži samo tvoje komentare" + +#: src/app/main/ui/static.cljs +msgid "labels.sign-out" +msgstr "Odjava" + +msgid "labels.skip" +msgstr "Preskoči" + +msgid "labels.start" +msgstr "Započni" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Status" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Upute" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Ažuriraj tim" + +msgid "labels.upload-custom-fonts" +msgstr "Prenesi custom fontove" + +#: src/app/main/ui/settings/profile.cljs +#, fuzzy +msgid "labels.update" +msgstr "Ažuriraj" + +msgid "labels.upload" +msgstr "Prenesi" + +msgid "labels.uploading" +msgstr "Prijenos…" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Promatrač" + +msgid "labels.workspace" +msgstr "Radni prostor" + +#: src/app/main/ui/comments.cljs +msgid "labels.write-new-comment" +msgstr "Napiši novi komentar" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.your-account" +msgstr "Tvoj korisnički račun" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.confirm-email" +msgstr "Potvrdite novi e-mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.info" +msgstr "" +"Poslat ćemo e-mail na tvoj trenutni e-mail \"%s\" kako bismo potvrdili tvoj " +"identitet." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#, fuzzy +msgid "modals.add-shared-confirm.accept" +msgstr "Dodaj kao zajedničku biblioteku" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.hint" +msgstr "" +"Nakon dodavanja kao zajedničku biblioteku, stavke ove biblioteke datoteka " +"bit će dostupni za korištenje među ostalim tvojim datotekama." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.message" +msgstr "Dodajte “%s” kao zajedničku biblioteku" + +#: src/app/main/ui/workspace/nudge.cljs +#, fuzzy +msgid "modals.big-nudge" +msgstr "Big nudge" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "Promijeni svoj e-mail" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"Ti si vlasnik ovog tima. Prije odlaska odaberi drugog člana kojeg želiš " +"promovirati u vlasnika." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.cancel" +msgstr "Otkaži i zadrži moj račun" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.confirm" +msgstr "Da, obriši moj račun" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.info" +msgstr "Uklanjanjem računa izgubit ćeš sve svoje trenutne projekte i arhive." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.title" +msgstr "Jeste li siguran/na da želiš izbrisati svoj račun?" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Obriši razgovor" + +#: src/app/main/ui/comments.cljs +#, fuzzy +msgid "modals.delete-comment-thread.message" +msgstr "" +"Jesi li siguran/na da želiš izbrisati ovaj razgovor? Svi komentari u ovoj " +"temi biti će izbrisani." + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.title" +msgstr "Izbriši razgovor" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Izbriši datoteku" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "Jesi li siguran/na da želiš izbrisati ovu datoteku?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Brisanje datoteke" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.accept" +msgstr "Izbriši datoteke" + +msgid "modals.delete-font-variant.title" +msgstr "Brisanje fonta" + +msgid "modals.delete-font.message" +msgstr "" +"Jesi li siguran/na da želiš izbrisati ovaj font? Neće se učitati ako se " +"koristi u datoteci." + +msgid "modals.delete-font.title" +msgstr "Brisanje fonta" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Jesi li siguran/na da želiš izbrisati ovu stranicu?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Jesi li siguran/na da želiš izbrisati ovaj projekt?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Brisanje projekta" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Izbriši datoteku" +msgstr[1] "Izbriši datoteke" +msgstr[2] "" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Ova datoteka ima biblioteke koje se koriste u ovoj datoteci:" +msgstr[1] "Ova datoteka ima biblioteke koje se koriste u ovim datotekama:" +msgstr[2] "" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Ove datoteke imaju biblioteke koje se koriste u ovoj datoteci:" +msgstr[1] "Ove datoteke imaju biblioteke koje se koriste u ovim datotekama:" +msgstr[2] "" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Brisanje datoteke" +msgstr[1] "Brisanje datoteka" +msgstr[2] "" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Brisanje datoteke" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Brisanje tima" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Jesi li siguran/na da želiš izbrisati ovaj tim? Svi projekti i datoteke " +"povezane s timom biti će trajno izbrisane." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Brisanje tima" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Brisanje člana" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Jesi li siguran/na da želiš izbrisati ovog člana iz tima?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Brisanje člana tima" + +msgid "modals.invite-member.emails" +msgstr "E-mail, odvojeno zarezom" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Pozovi članove u tim" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "Jesi li siguran/na da želiš napustiti tim %s?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "" +"Ti si vlasnik ovog tima. Prije odlaska odaberi drugog člana kojeg želiš " +"promovirati u vlasnika." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.promote-and-leave" +msgstr "Promoviraj i napusti" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.select-member-to-promote" +msgstr "Odaberi člana za promociju" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.title" +msgstr "Prije nego odeš" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.accept" +msgstr "Napusti tim" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.message" +msgstr "Jesi li siguran/na da želiš napustiti ovaj tim?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.title" +msgstr "Napuštanje tima" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.accept" +msgstr "Prenesi vlasništvo" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.message" +msgstr "" +"Ti si trenutni vlasnik ovog tima. Jesi li siguran/na da %s želiš postaviti " +"za novog vlasnika tima?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.title" +msgstr "Novi vlasnik tima" + +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "Mali pomak" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.accept" +msgstr "Ukloni kao zajedničku biblioteku" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.message" +msgstr "Ukloni “%s” kao zajedničku biblioteku" + +#: src/app/main/ui/workspace/nudge.cljs +#, fuzzy +msgid "modals.nudge-title" +msgstr "Nudge amount" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#, fuzzy +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Jesi li siguran/na da želiš poništiti objavu ove biblioteke?" +msgstr[1] "Jesi li siguran/na da želiš poništiti objavu ovih biblioteka?" +msgstr[2] "" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Koristi se u ovoj datoteci:" +msgstr[1] "Koristi se u ovim datotekama:" +msgstr[2] "" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Poništi objavu biblioteke" +msgstr[1] "Poništi objavu biblioteka" +msgstr[2] "" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"Upravo ćeš ažurirati komponente u zajedničkoj biblioteci. To može utjecati " +"na druge datoteke koje ga koriste." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.message" +msgstr "Ažuriraj komponente u zajedničkoj biblioteci" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.accept" +msgstr "Ažuriraj" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.hint" +msgstr "" +"Upravo ćeš ažurirati komponentu u zajedničkoj biblioteci. To može utjecati " +"na druge datoteke koje ga koriste." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.message" +msgstr "Ažuriraj komponentu u zajedničkoj biblioteci" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "" +"Ne možeš izbrisati svoj profil. Ponovno dodijeli svoje timove prije nastavka." + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Profil je uspješno spremljen!" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "E-mail za potvrdu poslana je na %s. Provjeri e-email!" + +#, fuzzy +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Postoji mnogo dostupnih resursa koji će ti pomoći da počneš koristiti " +"Penpot, poput korisničkog vodiča i našeg Youtube kanala." + +#, fuzzy +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"Možeš gledati naše tutorijale i tutorijale koje je napravila naša zajednica." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Video tutorijali" + +msgid "onboarding-v2.before-start.title" +msgstr "Prije nego počneš" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot je Open Source i izradio ga je Kaleidos kao i zajednica, gdje mnogi " +"ljudi već pomažu jedni drugima. Svi mogu surađivati na:" + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "Sudjelovanje u Community-u" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Stvori tim i pozovi kasnije" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Stvori tim i pošalji pozivnice" + +msgid "onboarding.choice.team-up.roles" +msgstr "Pozovi s ulogom:" + +msgid "onboarding.choice.title" +msgstr "Dobrodošli u Penpot" + +msgid "onboarding.contrib.alt" +msgstr "Open Source" + +msgid "onboarding.contrib.desc1" +msgstr "" +"Penpot je Open Source, napravljen od strane zajednice i za nju. Ako želiš " +"surađivati, više si nego dobrodošao/la!" + +msgid "onboarding.contrib.desc2.1" +msgstr "Možeš pristupiti" + +#, fuzzy +msgid "onboarding.contrib.desc2.2" +msgstr "i slijedi upute za doprinos :)" + +msgid "onboarding.contrib.link" +msgstr "projekt na githubu" + +#, fuzzy +msgid "onboarding.contrib.title" +msgstr "Open Source suradnik?" + +#, fuzzy +msgid "onboarding.slide.0.alt" +msgstr "Stvori dizajn" + +msgid "onboarding.slide.0.desc1" +msgstr "Stvaraj prekrasna korisnička sučelja u suradnji sa svim članovima tima." + +msgid "onboarding.slide.0.desc2" +msgstr "" +"Održavaj dosljednost na razini s komponentama, bibliotekama i sustavima " +"dizajna." + +msgid "onboarding.slide.0.title" +msgstr "Dizajniraj biblioteke, stilove i komponente" + +msgid "onboarding.slide.1.alt" +msgstr "Interaktivni prototipovi" + +#, fuzzy +msgid "onboarding.slide.1.desc1" +msgstr "Stvaraj bogate interakcije kako bi oponašao/la ponašanje proizvoda." + +#, fuzzy +msgid "onboarding.slide.1.desc2" +msgstr "" +"Podijeli sa stakeholderima, predstavi prijedloge svom timu i započni " +"korisničko testiranje sa svojim dizajnom, sve na jednom mjestu." + +msgid "onboarding.slide.1.title" +msgstr "Oživi vlastite dizajne interakcijama" + +msgid "onboarding.slide.2.alt" +msgstr "Sakupi povratne informacije" + +#, fuzzy +msgid "onboarding.slide.2.desc1" +msgstr "" +"Svi članovi tima rade istovremeno s multiplayer dizajnom u stvarnom vremenu " +"i centraliziranim komentarima, idejama i povratnim informacijama izravno " +"preko dizajna." + +#, fuzzy +msgid "onboarding.slide.3.alt" +msgstr "Handoff i lowcode" + +#, fuzzy +msgid "onboarding.slide.3.desc1" +msgstr "" +"Sinkroniziraj dizajn i kod svih svojih komponenti i stilova te dobij " +"snippets koda." + +msgid "onboarding.slide.3.desc2" +msgstr "" +"Dobij i pruži specifikacije koda kao što su oznake (SVG, HTML) ili stilovi (" +"CSS, Less, Stylus…)." + +msgid "onboarding.slide.3.title" +msgstr "Jedan zajednički izvor istine" + +msgid "onboarding.team-modal.create-team" +msgstr "Kreiraj tim" + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Neograničen broj datoteka i projekata" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Izdanje za više igrača" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Upravljanje ulogama" + +msgid "onboarding.templates.subtitle" +msgstr "Evo nekoliko predložaka." + +msgid "onboarding.templates.title" +msgstr "Počni dizajnirati" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +msgid "onboarding.welcome.title" +msgstr "Dobrodošli u Penpot" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Navigacija" + +msgid "shortcut-subsection.panels" +msgstr "Paneli" + +#, fuzzy +msgid "shortcut-subsection.path-editor" +msgstr "Paths" + +msgid "shortcut-subsection.shape" +msgstr "Oblici" + +msgid "shortcut-subsection.tools" +msgstr "Alati" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Zoom" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Zoom" + +msgid "shortcuts.add-comment" +msgstr "Komentari" + +msgid "shortcuts.add-node" +msgstr "Dodaj čvor" + +#, fuzzy +msgid "shortcuts.align-bottom" +msgstr "Poravnaj dolje" + +msgid "shortcuts.align-hcenter" +msgstr "Poravnaj sredinu vodoravno" + +msgid "shortcuts.align-left" +msgstr "Poravnaj lijevo" + +msgid "shortcuts.align-right" +msgstr "Poravnaj desno" + +#, fuzzy +msgid "shortcuts.align-top" +msgstr "Poravnaj gore" + +msgid "shortcuts.artboard-selection" +msgstr "Kreiraj ploču iz odabira" + +msgid "shortcuts.bool-difference" +msgstr "Boolean razlika" + +#, fuzzy +msgid "shortcuts.bool-exclude" +msgstr "Boolean isključenje" + +#, fuzzy +msgid "shortcuts.bool-intersection" +msgstr "Boolean intersection" + +msgid "shortcuts.bool-union" +msgstr "Boolean unija" + +msgid "shortcuts.bring-forward" +msgstr "Premijesti naprijed" + +msgid "shortcuts.bring-backward" +msgstr "Premiijesti nazad" + +msgid "shortcuts.bring-back" +msgstr "Premijesti u pozadinu" + +#, fuzzy +msgid "shortcuts.bring-front" +msgstr "Premijesti skroz naprijed" + +#, fuzzy +msgid "shortcuts.clear-undo" +msgstr "Obriši povrat" + +msgid "shortcuts.copy" +msgstr "Kopiraj" + +msgid "shortcuts.create-component" +msgstr "Kreiraj komponentu" + +msgid "shortcuts.create-new-project" +msgstr "Kreiraj novo" + +msgid "shortcuts.cut" +msgstr "Izreži" + +msgid "shortcuts.align-vcenter" +msgstr "Poravnaj sredinu okomito" + +msgid "shortcuts.decrease-zoom" +msgstr "Zoom out" + +msgid "shortcuts.delete" +msgstr "Izbriši" + +msgid "shortcuts.delete-node" +msgstr "Izbriši čvor" + +msgid "shortcuts.detach-component" +msgstr "Odvoji komponentu" + +#, fuzzy +msgid "shortcuts.draw-curve" +msgstr "Curve" + +msgid "shortcuts.draw-ellipse" +msgstr "Elipsa" + +msgid "shortcuts.draw-frame" +msgstr "Panel" + +msgid "shortcuts.draw-path" +msgstr "Put" + +msgid "shortcuts.draw-nodes" +msgstr "Ucrtaj put" + +msgid "shortcuts.draw-text" +msgstr "Tekst" + +msgid "shortcuts.duplicate" +msgstr "Dupliciraj" + +msgid "shortcuts.escape" +msgstr "Poništi" + +msgid "shortcuts.hide-ui" +msgstr "Prikaži/sakrij UI" + +msgid "shortcuts.increase-zoom" +msgstr "Zoom in" + +msgid "shortcuts.insert-image" +msgstr "Umetni sliku" + +msgid "shortcuts.join-nodes" +msgstr "Spoji čvorove" + +msgid "shortcuts.make-corner" +msgstr "Izradi rub" + +msgid "shortcuts.make-curve" +msgstr "Izradi krivinu" + +msgid "shortcuts.mask" +msgstr "Maskiraj" + +msgid "shortcuts.merge-nodes" +msgstr "Spoji čvorove" + +msgid "shortcuts.move" +msgstr "Premijesti" + +msgid "shortcuts.move-fast-down" +msgstr "Premijesti brzo dolje" + +msgid "shortcuts.move-fast-left" +msgstr "Premijesti brzo lijevo" + +msgid "shortcuts.move-fast-right" +msgstr "Premijesti brzo desno" + +msgid "shortcuts.move-fast-up" +msgstr "Premijesti brzo gore" + +msgid "shortcuts.move-nodes" +msgstr "Premijesti čvor" + +msgid "shortcuts.move-unit-down" +msgstr "Premijesti dolje" + +msgid "shortcuts.move-unit-left" +msgstr "Premijesti lijevo" + +msgid "shortcuts.move-unit-right" +msgstr "Premijesti desno" + +msgid "shortcuts.move-unit-up" +msgstr "Premijesti gore" + +msgid "shortcuts.h-distribute" +msgstr "Distribuiraj vodoravno" + +#, fuzzy +msgid "shortcuts.fit-all" +msgstr "Zumiraj da stane sve" + +msgid "shortcuts.open-dashboard" +msgstr "Idi na nadzornu ploču" + +#, fuzzy +msgid "shortcuts.open-handoff" +msgstr "Idi na odjeljak primopredaje gledatelja" + +msgid "shortcuts.or" +msgstr " ili " + +msgid "shortcuts.paste" +msgstr "Zaljepi" + +#, fuzzy +msgid "shortcuts.prev-frame" +msgstr "Prethodna ploča" + +msgid "shortcuts.redo" +msgstr "Ponovi" + +msgid "shortcuts.reset-zoom" +msgstr "Resetiraj zoom" + +#, fuzzy +msgid "shortcuts.search-placeholder" +msgstr "Pretraži prečace" + +msgid "shortcuts.select-all" +msgstr "Označi sve" + +msgid "shortcuts.separate-nodes" +msgstr "Posebni čvorovi" + +msgid "shortcuts.show-shortcuts" +msgstr "Prikaži/sakrij prečace" + +msgid "shortcuts.snap-nodes" +msgstr "Priključi na čvorove" + +msgid "shortcuts.start-editing" +msgstr "Počni uređivanje" + +msgid "shortcuts.start-measure" +msgstr "Počni mjerenje" + +#, fuzzy +msgid "shortcuts.show-pixel-grid" +msgstr "Prikaži/sakrij \"pixel grid\"" + +#, fuzzy +msgid "shortcuts.snap-pixel-grid" +msgstr "Priključi na \"pixel grid\"" + +msgid "shortcuts.stop-measure" +msgstr "Zaustavi mjerenje" + +#, fuzzy +msgid "shortcuts.thumbnail-set" +msgstr "Postavi sličice" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Prečaci na tipkovnici" + +msgid "shortcuts.toggle-alignment" +msgstr "Promijena dinamičkog poravnanja" + +msgid "shortcuts.toggle-assets" +msgstr "Promijena stavaka" + +msgid "shortcuts.toggle-lock" +msgstr "Zaključaj odabrano" + +msgid "shortcuts.toggle-lock-size" +msgstr "Zaključaj proporcije" + +msgid "shortcuts.toggle-layers" +msgstr "Promijena layera" + +msgid "shortcuts.toggle-history" +msgstr "Promijena povijesti" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Promijena fokus moda" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Promijena palete boja" + +#, fuzzy +msgid "shortcuts.toggle-grid" +msgstr "Promijena \"grida\"" + +msgid "shortcuts.toggle-scale-text" +msgstr "Promjena mjerila teksta" + +msgid "shortcuts.toggle-textpalette" +msgstr "Promijeni paletu teksta" + +msgid "shortcuts.toggle-visibility" +msgstr "Promijeni vidljivost" + +#, fuzzy +msgid "shortcuts.undo" +msgstr "Poništi" + +msgid "shortcuts.ungroup" +msgstr "Razgrupiraj" + +msgid "shortcuts.unmask" +msgstr "Makni masku" + +msgid "shortcuts.zoom-selected" +msgstr "Zoomiraj na selektirano" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.fonts" +msgstr "Fontovi - %s - Penpot" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Projekti - %s - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Traži - %s - Penpot" + +msgid "shortcuts.v-distribute" +msgstr "Distribuiraj okomito" + +#, fuzzy +msgid "shortcuts.toggle-snap-grid" +msgstr "Poravnanje s \"gridom\"" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Zajedničke biblioteke - %s - Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Pošalji povratnu informaciju - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Članovi - %s - Penpot" + +msgid "viewer.breaking-change.description" +msgstr "" +"Ova poveznica za dijeljenje više nije važeća. Napravi novu ili traži novu od " +"vlasnika." + +msgid "viewer.breaking-change.message" +msgstr "Oprosti!" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "Na stranici nisu pronađene ploče." + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.frame-not-found" +msgstr "Ploča nije pronađena." + +msgid "viewer.header.comments-section" +msgstr "Komentari (%s)" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "Ne prikazuj interakcije" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "Cijeli zaslon" + +#, fuzzy +msgid "viewer.header.handoff-section" +msgstr "Handoff (%s)" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interakcije" + +msgid "viewer.header.interactions-section" +msgstr "Interakcije (%s)" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "Kopiraj poveznicu" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "Ovdje će se pojaviti poveznica za dijeljenje" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Ukloni poveznicu" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Svatko s poveznicom imat će pristup" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "Prikaži interakcije" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions-on-click" +msgstr "Prikaži interakcije na klik" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Sitemap" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hcenter" +msgstr "Poravnaj vodoravno u sredinu (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hleft" +msgstr "Poravnaj lijevo (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hright" +msgstr "Poravnaj desno (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vbottom" +msgstr "Poravnaj dolje (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vdistribute" +msgstr "Distribuiraj okomiti razmak (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vcenter" +msgstr "Poravnaj okomito u sredinu (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "Distribuiraj vodoravni razmak (%s)" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vtop" +msgstr "Poravnaj gore (%s)" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Grafika" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Boje" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Komponente" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Stavke" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Sve stavke" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "Kreiraj grupu" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group-hint" +msgstr "" +"Tvoje stavke će se automatski imenovati kao \"naziv grupe / naziv stavke\"" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.delete" +msgstr "Izbriši" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Dupliciraj" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.edit" +msgstr "Uredi" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Grafika" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Grupa" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group-name" +msgstr "Ime grupe" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename" +msgstr "Preimenuj" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename-group" +msgstr "Preimenuj grupu" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Pretraži stavke" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Visina linije" + +msgid "workspace.focus.focus-mode" +msgstr "Fokus mode" + +msgid "workspace.focus.focus-off" +msgstr "Fokus isključen" + +msgid "workspace.focus.selection" +msgstr "Odabir" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Linearni gradijent" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Radijalni gradijent" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Onemogući dinamičko poravnanje" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-scale-text" +msgstr "Onemogući skaliranje teksta" + +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.enable-snap-guides" +msgstr "Snap to guides" + +#, fuzzy +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Omogući \"snap to pixel\"" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-artboard-names" +msgstr "Sakrij nazive ploča" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Sakrij paletu boja" + +#, fuzzy +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Sakrij \"pixel grid\"" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Sakrij \"rules\"" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-textpalette" +msgstr "Sakrij paletu boja" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Uredi" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Datoteka" + +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.hide-grid" +msgstr "Sakrij \"grid\"" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Pomoć i informacije" + +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.option.preferences" +msgstr "Preferencije" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.view" +msgstr "Pregled" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.select-all" +msgstr "Odaberi sve" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-artboard-names" +msgstr "Prikaži nazive ploča" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-grid" +msgstr "Prikaži \"grid\"" + +#, fuzzy +msgid "workspace.header.menu.show-pixel-grid" +msgstr "Prikaži \"pixel grid\"" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Prikaži paletu boja" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.reset-zoom" +msgstr "Resetiraj" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Greška kod spremanja" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Spremanje" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Nespremljene izmijene" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Način prikaza (%s)" + +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.zoom-fill" +msgstr "Ispuna - Skaliraj za popunjavanje" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit" +msgstr "Prilagodi - Smanji veličinu da pristane" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit-all" +msgstr "Zumiraj da stane sve" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-full-screen" +msgstr "Cijeli ekran" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-selected" +msgstr "Zumiraj na odabrano" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Dodaj" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s boje" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Velike sličice" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Biblioteka datoteka" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.hsv" +msgstr "HSV" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgb-complementary" +msgstr "RGB komplementarno" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Spremi u stil boja" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Male sličice" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s komponente" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "Biblioteka datoteka" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s grafike" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "BIBLIOTEKE U OVOJ DATOTECI" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "BIBLIOTEKE" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "BIBLIOTEKA" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "Nisu pronađeni rezultati za “%s”" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "Ne postoje zajedničke biblioteke koje je potrebno ažurirati" + +#: src/app/main/ui/workspace/libraries.cljs +#, fuzzy +msgid "workspace.libraries.search-shared-libraries" +msgstr "Pretraži zajedničke biblioteke" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "Nema dostupnih zajedničkih biblioteka" + +#: src/app/main/ui/workspace/libraries.cljs +#, fuzzy +msgid "workspace.libraries.shared-libraries" +msgstr "ZAJEDNIČKE BIBLIOTEKE" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +#, fuzzy +msgid "workspace.libraries.text.multiple-typography" +msgstr "Višestruke tipografije" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Prekini vezu svih tipografija" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s tipografije" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Ažuriranje" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "AŽURIRANJA" + +msgid "workspace.library.all" +msgstr "Sve biblioteke" + +msgid "workspace.library.libraries" +msgstr "Biblioiteke" + +msgid "workspace.library.own" +msgstr "Moje biblioteke" + +msgid "workspace.library.store" +msgstr "Pohrani biblioteke" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.add-interaction" +msgstr "Klikni gumb + za dodavanje interakcija." + +msgid "workspace.options.blur-options.background-blur" +msgstr "Pozadina" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Zamuti" + +#, fuzzy +msgid "workspace.options.blur-options.layer-blur" +msgstr "Layer" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.multiple" +msgstr "Odabir zamućenja" + +#: src/app/main/ui/workspace/sidebar/options/page.cljs +#, fuzzy +msgid "workspace.options.canvas-background" +msgstr "Pozadina canvasa" + +#, fuzzy +msgid "workspace.options.clip-content" +msgstr "Isjeci sadržaj" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Kolumne" + +msgid "workspace.options.grid.params.color" +msgstr "Boja" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Kolumne" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.height" +msgstr "Visina" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Margina" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.rows" +msgstr "Redovi" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Komponenta" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints" +msgstr "Ograničenja" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.center" +msgstr "Sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.fix-when-scrolling" +msgstr "Popravi prilikom scrolanja" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.left" +msgstr "Lijevo" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.top" +msgstr "Vrh" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +#, fuzzy +msgid "workspace.options.constraints.topbottom" +msgstr "Vrh i dno" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.design" +msgstr "Dizajn" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export" +msgstr "Izvoz" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-multiple" +msgstr "Izvezi selektirano" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgid_plural "workspace.options.export-object" +msgstr[0] "Izvezi 1 element" +msgstr[1] "Izvezi %s elemenata" +msgstr[2] "" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +msgid "workspace.options.export.suffix" +msgstr "Sufiks" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "Izvoz završen" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object" +msgstr "Izvoz…" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "Izvoz nije uspio" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-slow" +msgstr "Izvoz neočekivano spor" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Ispuni" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.flows.add-flow-start" +msgstr "Dodaj početak toka" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.flows.flow-start" +msgstr "Početak toka" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.flows.flow-starts" +msgstr "Tok započinje" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +#, fuzzy +msgid "workspace.options.grid.params.gutter" +msgstr "Gutter" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.set-default" +msgstr "Postavi kao zadano" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.size" +msgstr "Veličina" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Dno" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.stretch" +msgstr "Razvuci" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Vrh" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Koristi zadano" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.width" +msgstr "Širina" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.row" +msgstr "Redovi" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.square" +msgstr "Pravokutnik" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.group-fill" +msgstr "Popunjavanje grupe" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +#, fuzzy +msgid "workspace.options.group-stroke" +msgstr "Grupni potez" + +msgid "workspace.options.height" +msgstr "Visina" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-action" +msgstr "Akcija" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-after-delay" +msgstr "Nakon kašnjenja" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation" +msgstr "Animacija" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-animation-dissolve" +msgstr "Razriješi" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-animation-slide" +msgstr "Slide" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-outside" +msgstr "Zatvori kada klikneš izvana" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-background" +msgstr "Dodajte pozadinsko preklapanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-delay" +msgstr "Odgoda" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-destination" +msgstr "Odredište" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-duration" +msgstr "Trajanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing" +msgstr "Easing" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-easing-ease-in" +msgstr "Ease in" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-easing-ease-in-out" +msgstr "Ease in out" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-easing-ease-out" +msgstr "Ease out" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-linear" +msgstr "Linearno" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-in" +msgstr "U" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-mouse-leave" +msgstr "Izlaz mišem" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to-dest" +msgstr "Idi do: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-none" +msgstr "(nije spremno)" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-offset-effect" +msgstr "Offset učinak" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-url" +msgstr "Otvoreni url" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-overlay-dest" +msgstr "Otvoreno preklapanje: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-left" +msgstr "Gore lijevo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-prev-screen" +msgstr "Prethodni ekran" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-self" +msgstr "sebe" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-toggle-overlay" +msgstr "Uključi/isključi preklapanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-toggle-overlay-dest" +msgstr "Uključi/isključi preklapanje: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-while-hovering" +msgstr "Na hover" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-while-pressing" +msgstr "Dok pritisneš" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interactions" +msgstr "Interakcije" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Boja" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-burn" +msgstr "Izgaranje boje" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-dodge" +msgstr "Izbjegavanje boja" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.darken" +msgstr "Zatamni" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.difference" +msgstr "Razlika" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.exclusion" +msgstr "Ekskluzija" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hard-light" +msgstr "Tvrdo svjetlo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hue" +msgstr "Nijansa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.lighten" +msgstr "Posvijetli" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.luminosity" +msgstr "Svjetlost" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.multiply" +msgstr "Umnoži" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.overlay" +msgstr "Preklapanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.normal" +msgstr "Normalno" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.saturation" +msgstr "Saturacija" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.screen" +msgstr "Ekran" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.soft-light" +msgstr "Nježno svjetlo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.title" +msgstr "Layer" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.title.group" +msgstr "Grupiraj layere" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.title.multiple" +msgstr "Označeni layeri" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "Napredne opcije" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Min.visina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Max.širina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Max.visina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Promjena veličine elementa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Min.širina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Maksimalna visina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Maksimalna širina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Minimalna visina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Minimalna širina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Dno" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Kolumna" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "Red" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.right" +msgstr "Obrnuti red" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "Obrnuta kolumna" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.gap" +msgstr "Razmak" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "lijevo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "Lijevo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Margina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "Sve strane" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "Sve strane" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-simple" +msgstr "Jednostavni padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "Desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "prostor okolo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "prostor između" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.title" +msgstr "Layout" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +#, fuzzy +msgid "workspace.options.radius.single-corners" +msgstr "Jednostruki kutovi" + +msgid "workspace.options.recent-fonts" +msgstr "Nedavni" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.retry" +msgstr "Pokušaj ponovo" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Rotacija" + +msgid "workspace.options.search-font" +msgstr "Pretraži font" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-a-shape" +msgstr "Odaberi oblik, ploču ili grupu za povlačenje veze na drugu ploču." + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-artboard" +msgstr "Odaberi ploču" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "Odabrane boje" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.selection-fill" +msgstr "Ispuna odabira" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +#, fuzzy +msgid "workspace.options.selection-stroke" +msgstr "Potez selektirano" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Zamućeno" + +msgid "workspace.options.shadow-options.color" +msgstr "Boja sjene" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Spusti sjenu" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.offsetx" +msgstr "X" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.offsety" +msgstr "Y" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.spread" +msgstr "Proširi" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +#, fuzzy +msgid "workspace.options.size-presets" +msgstr "Unaprijed postavljena veličina" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke" +msgstr "Potez" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.circle-marker" +msgstr "Kružni marker" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.diamond-marker" +msgstr "Dijamantni marker" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.line-arrow" +msgstr "Strelica linije" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "Nijedan" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +#, fuzzy +msgid "workspace.options.stroke-cap.round" +msgstr "Krug" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square" +msgstr "Pravokutnik" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square-marker" +msgstr "Pravokutni marker" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.triangle-arrow" +msgstr "Trokutna strelica" + +msgid "workspace.options.stroke-color" +msgstr "Boja poteza" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.solid" +msgstr "Čvrsto" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-center" +msgstr "Poravnaj sredinu" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +#, fuzzy +msgid "workspace.options.text-options.align-justify" +msgstr "Složi u blok" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-left" +msgstr "Poravnaj lijevo" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-right" +msgstr "Poravnaj desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-top" +msgstr "Poravnaj vrh" + +msgid "workspace.options.text-options.decoration" +msgstr "Dekoracija" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "LTR" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "RTL" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.google" +msgstr "Google" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-height" +msgstr "Automatska visina" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Automatska širina" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +#, fuzzy +msgid "workspace.options.text-options.grow-fixed" +msgstr "Popravljeno" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.letter-spacing" +msgstr "Razmak između slova" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Visina linije" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Mala slova" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.none" +msgstr "Nijedan" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +#, fuzzy +msgid "workspace.options.text-options.preset" +msgstr "Unaprijed postavljeno" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Precrtanko" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Tekst" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Grupiraj tekst" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-selection" +msgstr "Selektiraj tekst" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +#, fuzzy +msgid "workspace.options.text-options.titlecase" +msgstr "Velika i mala slova" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Podcrtano" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Velika slova" + +msgid "workspace.options.text-options.vertical-align" +msgstr "Okomito poravnanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.use-play-button" +msgstr "" +"Upotrijebi gumb za reprodukciju u zaglavlju za pokretanje prikaza prototipa." + +msgid "workspace.options.width" +msgstr "Širina" + +msgid "workspace.options.x" +msgstr "X" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Okreni vodoravno" + +msgid "workspace.sidebar.layers.frames" +msgstr "Ploče" + +msgid "workspace.sidebar.layers.groups" +msgstr "Grupe" + +#, fuzzy +msgid "workspace.sidebar.layers.search" +msgstr "Traži layere" + +msgid "workspace.sidebar.layers.shapes" +msgstr "Oblici" + +msgid "workspace.sidebar.layers.texts" +msgstr "Tekstovi" + +msgid "workspace.undo.entry.multiple.color" +msgstr "stavke boja" + +msgid "workspace.undo.entry.multiple.media" +msgstr "grafičke stavke" + +msgid "workspace.undo.entry.multiple.typography" +msgstr "tipografske stavke" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Odabir sjena" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.show-fill-on-export" +msgstr "Prikaži u izvozu" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Crtkano" + +msgid "workspace.options.show-in-viewer" +msgstr "Prikaži u načinu pregleda" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Točkasto" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Unutra" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Miješano" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Poravnaj dno" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-middle" +msgstr "Poravnaj sredinu" + +msgid "workspace.options.y" +msgstr "Y" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Stvori račun" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Još nemaš račun?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Prijavi se ovdje" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Potvrdi lozinku" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Već imaš račun?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Kreiraj demo račun" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"Provjeri svoj e-mail i klikni na vezu da potvrdiš i počneš koristiti Penpot." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Veza za oporavak lozinke poslana je u tvoj inbox." + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Samo želiš isprobati?" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Drago nam je vidjeti te opet!" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "GitHub" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Google" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Unesi novu lozinku" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Ovo je DEMO usluga. NEMOJ KORISTITI za pravi rad. Projekti će se povremeno " +"brisati." + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Zaboravljena lozinka?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Puno ime" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.email" +msgstr "E-mail" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Prijava" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "GitLab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "OpenID" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "Želim se pretplatiti se na Penpot mailing listu." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Uspješno pridružen/a timu" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Obnovi lozinku" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Najmanje 8 znamenki" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Promjeni lozinku" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "Besplatno je, Open Source" + +msgid "common.share-link.link-deleted-success" +msgstr "Poveznica uspješno izbrisana" + +msgid "common.share-link.manage-ops" +msgstr "Upravljanje dopuštenjima" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Započni obilazak" + +msgid "common.share-link.permissions-can-comment" +msgstr "Dopušten komentar" + +#, fuzzy +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 stranica podijeljena" +msgstr[1] "%s stranica podijeljeno" +msgstr[2] "" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Dopušteno provjeriti kod" + +msgid "common.share-link.permissions-hint" +msgstr "Svatko sa poveznicom imat će pristup" + +msgid "common.share-link.placeholder" +msgstr "Ovdje će se pojaviti poveznica za dijeljenje" + +msgid "common.share-link.title" +msgstr "Podijeli prototip" + +msgid "common.share-link.team-members" +msgstr "Samo članovi tima" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Upravljanje timom" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot je namijenjen timovima. Pozovi članove da zajedno rade na projektima " +"i datotekama" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(kopiraj)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Kreiraj novi tim" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Obriši tim" + +msgid "dashboard.export-binary-multi" +msgstr "Preuzmi %s Penpot datoteke (.penpot)" + +msgid "dashboard.export-frames" +msgstr "Izvezi artboard u PDF…" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Izvezi u PDF" + +#: src/app/main/ui/export.cljs +#, fuzzy +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Postavke izvoza možeš dodati elementima iz svojstava dizajna (na dnu desne " +"bočne trake)." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Informacije kako postaviti izvoz na Penpot." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Nema elemenata s postavkama izvoza." + +msgid "dashboard.export.options.merge.message" +msgstr "" +"Tvoja će datoteka biti izvezena sa svim vanjskim stavkama spojenim u " +"biblioteku datoteka." + +msgid "dashboard.export.options.all.message" +msgstr "" +"datoteke sa zajedničkim bibliotekama bit će uključene u izvoz, održavajući " +"njihovu poveznicu." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "Odbaci sve" + +msgid "dashboard.export.title" +msgstr "Izvezi datoteke" + +msgid "dashboard.fonts.deleted-placeholder" +msgstr "Font izbrisan" + +#, fuzzy, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"Svaki web-font koji ovdje preneseš biti će dodan na popis fontova koji je " +"dostupan u tekstualnim svojstvima datoteka ovog tima. Fontovi s istim " +"nazivom fontova biti će grupirani kao **jedan font**. Možeš učitati fontove " +"sa sljedećim formatima: **TTF, OTF i WOFF** (biti će potreban samo jedan)." + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"Možeš učitavati samo fontove koje posjeduješ ili imaš licencu za korištenje " +"u Penpotu. Saznaj više u odjeljku Prava na sadržaj [Penpotovih uvjeta " +"pružanja usluge](https://penpot.app/terms.html). Također možeš pročitati o [" +"licenciranju fontova](https://www.typography.com/faq)." + +#, fuzzy +msgid "dashboard.import.progress.process-page" +msgstr "Obrada stranice: %s" + +msgid "dashboard.import.import-error" +msgstr "Došlo je do problema pri uvozu datoteke. Datoteka nije uvezena." + +#, fuzzy +msgid "dashboard.import.import-message" +msgstr "%s datoteka je uspješno uvezeno." + +msgid "dashboard.import.progress.process-colors" +msgstr "Obrada boja" + +msgid "dashboard.import.import-warning" +msgstr "Neke su datoteke sadržavale nevažeće objekte koji su uklonjeni." + +#: src/app/main/ui/export.cljs +#, fuzzy +msgid "dashboard.export-multiple.selected" +msgstr "%s - %s elementa označeno" + +msgid "dashboard.import.progress.process-components" +msgstr "Obrada komponenti" + +#, fuzzy +msgid "dashboard.import.progress.process-media" +msgstr "Obrada medija" + +msgid "dashboard.export-standard-multi" +msgstr "Preuzmi %s standardne datoteke (.svg + .json)" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Nauči osnove na Penpotu dok se zabavljaš uz ovaj praktični vodič." + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Novi projekt" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "Šaljite mi vijesti, ažuriranja proizvoda i preporuke o Penpotu." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Pretplata na newsletter" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Prošeći Penpotom i upoznaj glavne karakteristike." + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Premijesti u" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Novi projekt" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "Nisu pronađeni rezultati za “%s”" + +#: src/app/main/ui/dashboard/sidebar.cljs +#, fuzzy +msgid "dashboard.no-projects-placeholder" +msgstr "Prikvačeni projekti pojavit će se ovdje" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Pretraži…" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "U redu" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "Pretraga \"%s\"…" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "Tvoj projekt je uspješno izbrisan" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Pažnja" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "ds.updated-at" +msgstr "Ažurirano: %s" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "E-mail" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "Tvoja e-mail adresa je uspješno potvrđena" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "Spremi postavke" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "Tvoja datoteka je uspješno duplicirana" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "Tvoj projekt je uspješno premješten" + +#: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs +msgid "dashboard.update-settings" +msgstr "Ažuriraj postavke" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Poništi objavu biblioteke" + +#: src/app/main/ui/settings.cljs +msgid "dashboard.your-account-title" +msgstr "Tvoj korisnički račun" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "Jesi li siguran/na?" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "Ime" + +#: src/app/main/ui/dashboard/search.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/libraries.cljs, src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.your-penpot" +msgstr "Tvoj Penpot" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "U redu" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Poništi" + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "Slika je prevelika za umetanje." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "Opis" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografija" + +#: src/app/main/ui/confirm.cljs +msgid "ds.component-subtitle" +msgstr "Komponente za ažuriranje:" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Pružatelj autentifikacije nije konfiguriran." + +msgid "errors.invite-invalid.info" +msgstr "Ova pozivnica je možda otkazana ili je istekla." + +#, fuzzy +msgid "errors.auth.unable-to-login" +msgstr "Čini se da nisi autentificiran/a ili je sesija istekla." + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "Tvoj preglednik ne može izvršiti ovu operaciju" + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Ova datoteka je već korištena s omogućenim komponentama V2." + +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +msgstr "Došlo je do pogreške" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "Lozinka mora sadržavati najmanje 8 znakova" + +msgid "errors.team-leave.owner-cant-leave" +msgstr "Vlasnik ne može napustiti tim, moraš ponovno dodijeliti ulogu vlasnika." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "Imaš želju za razgovorom? Razgovaraj s nama na Gitteru" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Idi na Penpot forum" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"Sretni smo što si ovdje. Ako trebaš pomoć, pretraži prije objavljivanja." + +#: src/app/main/ui/settings/feedback.cljs +#, fuzzy +msgid "feedback.discourse-title" +msgstr "Penpot community" + +#: src/app/main/ui/settings/feedback.cljs +#, fuzzy +msgid "feedback.twitter-subtitle1" +msgstr "Ovdje za pomoć za tvoje tehničke upite." + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Vrh" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Točkasto" + +#, fuzzy +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Nikakav" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Miksano" + +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "Mala slova" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "Velika slova" + +msgid "handoff.tabs.code.selected.image" +msgstr "Slika" + +msgid "handoff.tabs.code.selected.mask" +msgstr "Maska" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s Označeno" + +#, fuzzy +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Velika i mala slova" + +msgid "labels.centered" +msgstr "Sredina" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Kod" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Informacija" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.about-penpot" +msgstr "O Penpotu" + +#, fuzzy +msgid "handoff.tabs.code.selected.path" +msgstr "Path" + +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Prečaci" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Tim ti omogućuje suradnju s drugim Penpot korisnicima koji rade na istim " +"datotekama i projektima." + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.main-message" +msgstr "Loš Gateway" + +#: src/app/main/ui/dashboard/sidebar.cljs +#, fuzzy +msgid "labels.community" +msgstr "Community" + +msgid "labels.continue" +msgstr "Nastavi" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.confirm-password" +msgstr "Potvrdi lozinku" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +#, fuzzy +msgid "labels.create" +msgstr "Kreiraj" + +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.hide-resolved-comments" +msgstr "Sakrij riješene komentare" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.members" +msgstr "Članovi" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "E-mail" + +msgid "labels.icons" +msgstr "Ikone" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.new-password" +msgstr "Nova lozinka" + +msgid "labels.log-or-sign" +msgstr "Prijava ili registracija" + +msgid "labels.manage-fonts" +msgstr "Uredi fontove" + +#, fuzzy +msgid "labels.next" +msgstr "Slijedeće" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.rename" +msgstr "Preimenuj" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.permissions" +msgstr "Dozvole" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "Ponovno pošalji pozivnicu" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Lozinka" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "U tijeku" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Novi e-mail" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.role" +msgstr "Uloga" + +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.desc-message" +msgstr "U programiranom smo održavanju naših sustava." + +msgid "labels.search-font" +msgstr "Pretraži font" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "Jesi li siguran/na da želiš izbrisati %s datoteke?" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.settings" +msgstr "Postavke" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(ti)" + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "Učitavanje slike…" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "Promijeni e-mail" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "Brisanje %s datoteka" + +msgid "modals.delete-font-variant.message" +msgstr "" +"Jesi li siguran/na da želiš izbrisati ovaj stil fonta? Neće se učitati ako " +"se koristi u datoteci." + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Brisanje stranice" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Brisanje projekta" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Jesi li siguran/na da želiš izbrisati ovu datoteku?" +msgstr[1] "Jesi li siguran/na da želiš izbrisati ove datoteke?" +msgstr[2] "" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.leftright" +msgstr "Lijevo i desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.group" +msgstr "Grupiraj zamućenje" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.bottom" +msgstr "Dno" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.right" +msgstr "Desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.scale" +msgstr "Skala" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Pošalji pozivnicu" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Budući da si jedini član ovog tima, tim će biti izbrisan zajedno sa svojim " +"projektima i datotekama." + +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"Ne možeš napustiti tim ako nema drugog člana kojeg možeš promovirati u " +"vlasnika. Možda želiš izbrisati tim." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "Ako poništiš objavu, stavke u njoj postaju biblioteka ove datoteke." +msgstr[1] "Ako poništiš objavu, stavke u njoj postaju biblioteka ovih datoteka." +msgstr[2] "" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.hint" +msgstr "" +"Ako preneseš vlasništvo, promijenit ćeš svoju ulogu u Administrator, čime " +"ćeš izgubiti neka dopuštenja za ovaj tim. " + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Poništi objavu" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.cancel" +msgstr "Poništi" + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Pozivnica je uspješno poslana" + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Detaljne informacije o tome kako koristiti Penpot. Od izrade prototipova do " +"organiziranja ili dijeljenja dizajna." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Korisnički priručnik" + +msgid "onboarding-v2.welcome.title" +msgstr "Dobrodošli u Penpot!" + +#, fuzzy +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Javni prostor za učenje, dijeljenje i raspravu o Penpotu, njegovoj " +"sadašnjosti i budućnosti s cijelim Community-em i glavnim timom Penpota." + +#, fuzzy +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Gdje ćete pronaći kako surađivati s prijevodima, zahtjevima za unapređenje, " +"temeljnim doprinosima, potragom za bugovima…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Vodič za doprinos" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Stvori tim kasnije" + +msgid "onboarding.choice.team-up.create-team" +msgstr "Ime tvojeg tima" + +#, fuzzy +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Ne zaboravi uključiti sve. Programere, dizajnere, menadžere... raznolikost " +"se isplati :)" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "Nakon što imenuješ svoj tim, moći ćeš pozvati ljude da se pridruže." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Unesi naziv tima" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Pozovi članove" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "" +"Tvoj zahtjev za pretplatu je poslan, poslat ćemo ti e-mail da ga potvrdiš." + +#, fuzzy +msgid "onboarding.newsletter.desc" +msgstr "" +"Pretplati se na naš newsletter kako bi bio/la u toku s razvojem proizvoda i " +"novostima." + +msgid "onboarding.newsletter.privacy1" +msgstr "Budući da brinemo o privatnosti, evo našeg " + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Poslat ćemo ti samo relevantne e-mailove. Pretplatu možeš odjaviti u bilo " +"kojem trenutku u svom korisničkom profilu ili putem linka za odjavu u bilo " +"kojem od naših newslettera." + +msgid "onboarding.newsletter.title" +msgstr "Želiš primati Penpot novostii?" + +msgid "onboarding.newsletter.policy" +msgstr "Politika privatnosti." + +msgid "onboarding.newsletter.accept" +msgstr "Da, pretplati se" + +msgid "onboarding.newsletter.decline" +msgstr "Ne, hvala" + +msgid "onboarding.slide.2.title" +msgstr "Sakupi povratne informacije, predstavi i podijeli svoj rad" + +msgid "shortcut-section.dashboard" +msgstr "Nadzorna ploča" + +msgid "shortcut-section.viewer" +msgstr "Gledatelj" + +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Osnove" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Idi na prijavu" + +#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "settings.multiple" +msgstr "Izmješano" + +msgid "shortcut-subsection.main-menu" +msgstr "Glavni meni" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Neograničen broj članova" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% besplatno!" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Poravnanje" + +msgid "shortcut-section.workspace" +msgstr "Radni prostor" + +msgid "shortcut-subsection.edit" +msgstr "Uredi" + +msgid "shortcuts.opacity-6" +msgstr "Postavi neprozirnost na 60%" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Generičko" + +msgid "shortcut-subsection.general-viewer" +msgstr "Generičko" + +#, fuzzy +msgid "shortcut-subsection.modify-layers" +msgstr "Izmijeni layere" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Navigacija" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Navigacija" + +#, fuzzy +msgid "shortcuts.open-comments" +msgstr "Idi na odjeljak s komentarima gledatelja" + +msgid "shortcuts.opacity-7" +msgstr "Postavi neprozirnost na 70%" + +msgid "shortcuts.export-shapes" +msgstr "Izvezi oblike" + +msgid "shortcuts.go-to-drafts" +msgstr "Idi na nacrte" + +msgid "shortcuts.flip-vertical" +msgstr "Okreni okomito" + +msgid "shortcuts.flip-horizontal" +msgstr "Okreni vodoravno" + +msgid "shortcuts.draw-rect" +msgstr "Pravokutnik" + +#, fuzzy +msgid "shortcuts.next-frame" +msgstr "Slijedeći board" + +msgid "shortcuts.opacity-3" +msgstr "Postavi neprozirnost na 30%" + +msgid "shortcuts.opacity-4" +msgstr "Postavi neprozirnost na 40%" + +msgid "shortcuts.opacity-5" +msgstr "Postavi neprozirnost na 50%" + +msgid "shortcuts.opacity-8" +msgstr "Postavi neprozirnost na 80%" + +msgid "shortcuts.opacity-9" +msgstr "Postavi neprozirnost na 90%" + +msgid "shortcuts.group" +msgstr "Grupiraj" + +msgid "shortcuts.go-to-libs" +msgstr "Idi na zajedničke biblioteke" + +msgid "shortcuts.go-to-search" +msgstr "Traži" + +msgid "shortcuts.not-found" +msgstr "Nema pronađenih prečaca" + +msgid "shortcuts.opacity-0" +msgstr "Postavi neprozirnost na 100%" + +msgid "shortcuts.opacity-1" +msgstr "Postavi neprozirnost na 10%" + +msgid "shortcuts.opacity-2" +msgstr "Postavi neprozirnost na 20%" + +msgid "shortcuts.open-color-picker" +msgstr "Birač boja" + +#, fuzzy +msgid "shortcuts.open-interactions" +msgstr "Idi na odjeljak interakcija gledatelja" + +msgid "shortcuts.open-workspace" +msgstr "Idi na radni prostor" + +msgid "shortcuts.open-viewer" +msgstr "Idi na odjeljak interakcija gledatelja" + +#, fuzzy +msgid "shortcuts.toggle-rules" +msgstr "Prikaži/sakrij \"rules\"" + +msgid "shortcuts.toggle-zoom-style" +msgstr "Promijeni stil zooma" + +#, fuzzy +msgid "shortcuts.toggle-snap-guide" +msgstr "Pričvrsti na vodilice" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Promijeni cijeli zaslon" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-none" +msgstr "Nijedan" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot - Sloboda dizajna za timove" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Varijanta" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Postavke - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Lozinka - Penpot" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Profil - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Postavke - %s - Penpot" + +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - Penpot" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.font-providers" +msgstr "Dobavljači fontova - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-invitations" +msgstr "Pozivnice - %s - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Način prikaza - Penpot" + +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Kreiraj poveznicu" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs +msgid "workspace.assets.typography.sample" +msgstr "Ag" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.selected-count" +msgid_plural "workspace.assets.selected-count" +msgstr[0] "%s odabrana stavka" +msgstr[1] "%s odabranih stavki" +msgstr[2] "" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografija" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Biblioteke" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Razmak između slova" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "Nisu pronađene stavke" + +msgid "workspace.assets.local-library" +msgstr "lokalna biblioteka" + +msgid "dashboard.export.options.merge.title" +msgstr "Uključi stavke zajedničke biblioteke u biblioteke datoteka" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "PODIJELJENO" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Font" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Veličina" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Idi na datoteku biblioteke stilova za uređivanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +msgstr "Transformiraj tekst" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +#, fuzzy +msgid "workspace.assets.ungroup" +msgstr "Razgrupiraj" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Stavke" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-scale-text" +msgstr "Omogući skaliranje teksta" + +msgid "workspace.focus.focus-on" +msgstr "Fokus uključen" + +msgid "workspace.undo.entry.single.color" +msgstr "stavka boja" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Omogući dinamičko poravnanje" + +msgid "workspace.undo.entry.single.media" +msgstr "grafička stavka" + +msgid "workspace.undo.entry.single.typography" +msgstr "tipografska stavka" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Onemogući \"snap to grid\"" + +#, fuzzy +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Onemogući \"snap to pixel\"" + +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.disable-snap-guides" +msgstr "Onemogući \"snap to guides\"" + +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Poravnanje s \"gridom\"" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Auto" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type" +msgstr "Tip" + +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.show-rules" +msgstr "Prikaži \"rules\"" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +#, fuzzy +msgid "workspace.options.grid.grid-title" +msgstr "Grid" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-textpalette" +msgstr "Prikaži paletu fontova" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Lijevo" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Spremljeno" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Nedavno korištene boje" + +#, fuzzy +msgid "dashboard.export.options.detach.message" +msgstr "" +"Zajedničke biblioteke neće biti uključene u izvoz i nikakve stavke neće biti " +"dodani u biblioteku. " + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.remove-shared" +msgstr "Ukloni kao zajedničku biblioteku" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#, fuzzy +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Nakon uklanjanja kao zajedničke biblioteke, biblioteka datoteka ove datoteke " +"više neće biti dostupna za korištenje među tvojim ostalim datotekama." From 8c20890c7b42147db33b9923df26e5568405fc0f Mon Sep 17 00:00:00 2001 From: Denys M Date: Fri, 30 Sep 2022 08:20:08 +0000 Subject: [PATCH 071/682] :globe_with_meridians: Add translations for: Ukrainian (ukr_UA). Currently translated at 7.5% (91 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/ --- frontend/translations/ukr_UA.po | 361 +++++++++++++++++++++++++++++++- 1 file changed, 360 insertions(+), 1 deletion(-) diff --git a/frontend/translations/ukr_UA.po b/frontend/translations/ukr_UA.po index 4f8f6e6dec..2689327c32 100644 --- a/frontend/translations/ukr_UA.po +++ b/frontend/translations/ukr_UA.po @@ -1,2 +1,361 @@ msgid "" -msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file +msgstr "" +"PO-Revision-Date: 2022-10-01 14:17+0000\n" +"Last-Translator: Denys M. \n" +"Language-Team: Ukrainian \n" +"Language: ukr_UA\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" +"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.14.1\n" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-successfully" +msgstr "Пароль успішно змінено" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.about-penpot" +msgstr "Про Penpot" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "Я хочу підписатися на розсилку Penpot." + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "Виділено: %s" + +msgid "labels.content" +msgstr "Вміст" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "Невірний код відновлення." + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.all" +msgstr "Всі" + +msgid "labels.back" +msgstr "Назад" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "Відміна" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Спільнота" + +msgid "labels.continue" +msgstr "Продовжити" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "labels.create" +msgstr "Створити" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Протерміновано" + +msgid "labels.export" +msgstr "Експорт" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Пароль" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "Опис" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Ширина" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Радіус" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +msgstr "Тема" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "Електронна пошта" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Висота" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Зліва" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Тінь" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.blur" +msgstr "Р" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.offset-x" +msgstr "X" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.offset-y" +msgstr "Y" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Уже маєте аккаунт?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.email" +msgstr "Електронна пошта" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Забули пароль?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Повне ім'я" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Ввійдіть тут" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Вхід" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Раді бачити Вас знову!" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "GitHub" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "GitLab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Google" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "OpenID" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Введіть новий пароль" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Пароль" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Щонайменше 8 символів" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Додати як Спільну Бібліотеку" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Створити нову команду" + +msgid "dashboard.download-binary-file" +msgstr "Завантажити файл Penpot (.penpot)" + +msgid "dashboard.export-multi" +msgstr "Експорт файлів Penpot (%s)" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "завантажую ваші файли…" + +msgid "dashboard.loading-fonts" +msgstr "завантажую ваші шрифти…" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Перемістити файли (%s)" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Перенести в іншу команду" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.remove-shared" +msgstr "Видалити Спільну Бібліотеку" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "Введіть для пошуку" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ок" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Увага" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Відміна" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Провайдер для автентифікації не налаштований." + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "Ок" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Розмивання" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "Значення" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.hex" +msgstr "HEX" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.hsla" +msgstr "HSLA" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.rgba" +msgstr "RGBA" + +#: src/app/main/ui/handoff/attributes/fill.cljs +msgid "handoff.attributes.fill" +msgstr "Заливка" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "Висота" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout" +msgstr "Розміщення" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Обертання" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Зверху" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Ширина" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.admin" +msgstr "Адміністратор" + +msgid "labels.and" +msgstr "і" + +msgid "labels.centered" +msgstr "По центру" + +msgid "labels.close" +msgstr "Закрити" + +#: src/app/main/ui/dashboard/comments.cljs +msgid "labels.comments" +msgstr "Коментарі" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete" +msgstr "Видалити" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.drafts" +msgstr "Чорновики" + +#: src/app/main/ui/comments.cljs +msgid "labels.edit" +msgstr "Редагувати" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.editor" +msgstr "Редактор" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "Електронна пошта" + +msgid "labels.fonts" +msgstr "Шрифти" + +msgid "labels.icons" +msgstr "Іконки" + +msgid "labels.images" +msgstr "Зображення" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.invitations" +msgstr "Запрошення" + +#: src/app/main/ui/settings/options.cljs +msgid "labels.language" +msgstr "Мова" + +msgid "labels.link" +msgstr "Посилання" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +msgstr "Вийти" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Учасник" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.members" +msgstr "Учасники" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.name" +msgstr "Ім'я" + +msgid "labels.next" +msgstr "Далі" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.main-message" +msgstr "Халепа!" + +msgid "labels.or" +msgstr "або" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.owner" +msgstr "Власник" From 9ae40b392f4e25bb30cc8c5e63b74209bbd1fc35 Mon Sep 17 00:00:00 2001 From: Adam Williams Date: Wed, 28 Sep 2022 10:12:11 +0100 Subject: [PATCH 072/682] :bug: Fix word-break on comments, changed to break-word --- CHANGES.md | 1 + frontend/resources/styles/main/partials/comments.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 0419296894..dba080c2f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -58,6 +58,7 @@ - To @andrewzhurov for many code contributions on this release. - UI improvements in Project section (by @Waishnav) [#2285](https://github.com/penpot/penpot/pull/2285) +- Fix fronted comments (by @lol768) [#2368](https://github.com/penpot/penpot/pull/2368) ## 1.15.4-beta diff --git a/frontend/resources/styles/main/partials/comments.scss b/frontend/resources/styles/main/partials/comments.scss index e3bd0904e4..fab594179c 100644 --- a/frontend/resources/styles/main/partials/comments.scss +++ b/frontend/resources/styles/main/partials/comments.scss @@ -191,7 +191,7 @@ margin: 0 $size-2 0 26px; white-space: pre-wrap; display: inline-block; - word-break: break-all; + word-break: break-word; } } } From 0680d25fd78f424d312303514794bce13b586068 Mon Sep 17 00:00:00 2001 From: Kevin Nowald Date: Sun, 2 Oct 2022 22:44:45 +0200 Subject: [PATCH 073/682] :bug: Fix twitter feedback link Refers to unexisting PenpotSupport Twitter account Signed-off-by: Kevin Nowald --- CHANGES.md | 4 +++- frontend/src/app/main/ui/settings/feedback.cljs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0419296894..17deacebe3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # CHANGELOG -## :rocket: 1.16.0-beta +## :rocket: Next (1.17) ### :boom: Breaking changes & Deprecations ### :sparkles: New features @@ -8,6 +8,8 @@ - Fix shortcut texts alignment [Taiga #4275](https://tree.taiga.io/project/penpot/issue/4275) - Fix some texts and a typo [Taiga #4215](https://tree.taiga.io/project/penpot/issue/4215) +- Fix twitter support account link [Taiga #4279](https://tree.taiga.io/project/penpot/issue/4279) + ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/frontend/src/app/main/ui/settings/feedback.cljs b/frontend/src/app/main/ui/settings/feedback.cljs index 5ad63a1f3e..10f1d88ac5 100644 --- a/frontend/src/app/main/ui/settings/feedback.cljs +++ b/frontend/src/app/main/ui/settings/feedback.cljs @@ -93,7 +93,7 @@ [:p (tr "feedback.twitter-subtitle1")] [:a.btn-secondary.btn-large - {:href "https://twitter.com/PenpotSupport" :target "_blank"} + {:href "https://twitter.com/penpotapp" :target "_blank"} (tr "feedback.twitter-go-to")] ])) From e30bea0b6f76c9564833a2f3e6e31ba78d00aac3 Mon Sep 17 00:00:00 2001 From: luz paz Date: Sun, 2 Oct 2022 14:00:19 -0400 Subject: [PATCH 074/682] :wrench: Fix typos in source code Found via `codespell -q 3 -S *.po,./frontend/yarn.lock -L childs,clen,fpr,inflight,ody,ot,ro,te,trys,ue` --- CHANGES.md | 18 +++++++++--------- CONTRIBUTING.md | 2 +- backend/resources/app/templates/debug.tmpl | 6 +++--- backend/src/app/cli/manage.clj | 2 +- backend/src/app/http.clj | 2 +- backend/src/app/loggers/audit.clj | 2 +- backend/src/app/loggers/database.clj | 2 +- backend/src/app/loggers/loki.clj | 2 +- backend/src/app/loggers/mattermost.clj | 2 +- backend/src/app/main.clj | 2 +- backend/src/app/media.clj | 2 +- backend/src/app/metrics.clj | 6 +++--- backend/src/app/msgbus.clj | 6 +++--- backend/src/app/rpc/commands/auth.clj | 2 +- backend/src/app/rpc/commands/binfile.clj | 18 +++++++++--------- backend/src/app/rpc/commands/demo.clj | 4 ++-- backend/src/app/rpc/mutations/files.clj | 2 +- backend/src/app/rpc/mutations/media.clj | 2 +- backend/src/app/rpc/mutations/profile.clj | 2 +- backend/src/app/rpc/queries/files.clj | 4 ++-- backend/src/app/rpc/rlimit.clj | 2 +- backend/src/app/rpc/semaphore.clj | 2 +- backend/src/app/setup.clj | 2 +- backend/src/app/setup/builtin_templates.clj | 2 +- backend/src/app/storage.clj | 2 +- backend/src/app/storage/tmp.clj | 4 ++-- backend/src/app/tasks/objects_gc.clj | 2 +- backend/src/app/util/time.clj | 2 +- backend/src/app/worker.clj | 4 ++-- backend/test/app/services_files_test.clj | 6 +++--- common/src/app/common/attrs.cljc | 2 +- common/src/app/common/data.cljc | 4 ++-- common/src/app/common/data/macros.cljc | 4 ++-- common/src/app/common/logging.cljc | 2 +- common/src/app/common/pages/changes.cljc | 2 +- common/src/app/common/spec.cljc | 4 ++-- common/test/app/common/pages_helpers_test.cljc | 2 +- exporter/src/app/core.cljs | 2 +- exporter/src/app/handlers/resources.cljs | 2 +- .../integration/02-onboarding/slides.spec.js | 2 +- frontend/scripts/jvm-repl | 2 +- frontend/src/app/config.cljs | 2 +- frontend/src/app/main/data/exports.cljs | 4 ++-- frontend/src/app/main/data/fonts.cljs | 4 ++-- frontend/src/app/main/data/users.cljs | 2 +- frontend/src/app/main/data/workspace.cljs | 4 ++-- .../src/app/main/data/workspace/common.cljs | 2 +- .../src/app/main/data/workspace/groups.cljs | 2 +- .../src/app/main/data/workspace/libraries.cljs | 2 +- .../main/data/workspace/libraries_helpers.cljs | 4 ++-- .../src/app/main/data/workspace/media.cljs | 2 +- .../app/main/data/workspace/notifications.cljs | 2 +- .../app/main/data/workspace/persistence.cljs | 2 +- .../src/app/main/data/workspace/selection.cljs | 2 +- .../app/main/data/workspace/transforms.cljs | 2 +- frontend/src/app/main/ui/hooks.cljs | 4 ++-- .../src/app/main/ui/onboarding/templates.cljs | 2 +- frontend/src/app/main/ui/shapes/path.cljs | 2 +- .../src/app/main/ui/shapes/text/svg_text.cljs | 2 +- .../app/main/ui/viewer/handoff/exports.cljs | 2 +- .../src/app/main/ui/viewer/interactions.cljs | 2 +- .../app/main/ui/workspace/shapes/frame.cljs | 2 +- .../shapes/frame/dynamic_modifiers.cljs | 2 +- .../shapes/frame/thumbnail_render.cljs | 2 +- .../shapes/text/viewport_texts_html.cljs | 2 +- .../sidebar/options/menus/exports.cljs | 2 +- .../sidebar/options/menus/measures.cljs | 4 ++-- frontend/src/app/render.cljs | 2 +- frontend/src/app/util/dom/dnd.cljs | 2 +- frontend/src/app/util/i18n.cljs | 2 +- frontend/src/app/util/path/tools.cljs | 2 +- frontend/src/app/util/snap_data.cljs | 4 ++-- 72 files changed, 110 insertions(+), 110 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6d4b640ef9..fe58596fbc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -21,7 +21,7 @@ - Removed the support for v2 internal file data blob format. This version has never been documented nor set as default value so - technicaly this is not a breaking change because we are removing + technically this is not a breaking change because we are removing a "private API". ### :sparkles: New features @@ -139,7 +139,7 @@ - The `PENPOT_LDAP_ATTRS_PHOTO` finally removed, it was unused for many versions. - If you are using social login (google, github, gitlab or generic OIDC) you - will need to ensure to add the following flags respectivelly to let them + will need to ensure to add the following flags respectively to let them enabled: `enable-login-with-google`, `enable-login-with-github`, `enable-login-with-gitlab` and `enable-login-with-oidc`. If not, they will remain disabled after application start independently if you set the client-id @@ -244,7 +244,7 @@ - Fix undo when drawing curves [Taiga #3523](https://tree.taiga.io/project/penpot/issue/3523) - Fix issue with text edition and certain fonts (WorkSans, Raleway, ...) and foreign objects [Taiga #3521](https://tree.taiga.io/project/penpot/issue/3521) - Fix thumbnail generation when concurrent edition [Taiga #3522](https://tree.taiga.io/project/penpot/issue/3522) -- Fix environment imporot for exporter in Docker +- Fix environment import for exporter in Docker - Fix auto scroll layers in Firefox [Taiga #3531](https://tree.taiga.io/project/penpot/issue/3531) - Fix base background not visible for imported SVG @@ -328,7 +328,7 @@ - Fix mouse leave in handoff close overlay animation breaks [Taiga #3173](https://tree.taiga.io/project/penpot/issue/3173) - Fix different behaviour during image drag [Taiga #2279](https://tree.taiga.io/project/penpot/issue/2279) - Fix hidden file name on import [Taiga #3172](https://tree.taiga.io/project/penpot/issue/3172) -- Fix unneccessary scrollbars at the color list [Taiga #3211](https://tree.taiga.io/project/penpot/issue/3211) +- Fix unnecessary scrollbars at the color list [Taiga #3211](https://tree.taiga.io/project/penpot/issue/3211) - "Show in exports" is showing in multiselections [Taiga #3194](https://tree.taiga.io/project/penpot/issue/3194) - Edit file name navigates to the file workspace [Taiga #3183](https://tree.taiga.io/project/penpot/issue/3183) - Fix scroll into view behind fixed element [Taiga #3170](https://tree.taiga.io/project/penpot/issue/3170) @@ -337,7 +337,7 @@ - Fix duplicate multi selected elements [Taiga #3155](https://tree.taiga.io/project/penpot/issue/3155) - Fix add fills to artboard modify children [Taiga #3151](https://tree.taiga.io/project/penpot/issue/3151) - Avoid numeric inputs to allow big numbers [Taiga #2858](https://tree.taiga.io/project/penpot/issue/2858) -- Fix component contex menu size [Taiga #2480](https://tree.taiga.io/project/penpot/issue/2480) +- Fix component context menu size [Taiga #2480](https://tree.taiga.io/project/penpot/issue/2480) - Add shadow to artboard make it lose the fill [Taiga #3139](https://tree.taiga.io/project/penpot/issue/3139) - Avoid numeric inputs to change its value without focusing them [Taiga #3140](https://tree.taiga.io/project/penpot/issue/3140) - Fix comments modal when changing pages [Taiga #2597](https://tree.taiga.io/project/penpot/issue/2508) @@ -466,7 +466,7 @@ - Fix issue on handling empty content on boolean shapes - Fix race condition issue on component renaming -- Handle EOF errors on writting streamed response +- Handle EOF errors on writing streamed response - Handle EOF errors on websocket send/ping methods - Disable parallel upload of file media on import (causes too much contention on the rlimit subsistem that does not works as expected @@ -578,7 +578,7 @@ ## 1.10.4-beta -### :sparkles: Enhacements +### :sparkles: Enhancements - Allow parametrice file snapshoting interval @@ -590,7 +590,7 @@ ## 1.10.3-beta -### :sparkles: Enhacements +### :sparkles: Enhancements - Make all logging asynchronous, this avoid some overhead on jetty threads at cost of logging latency. - Increase default session time to 15 days. @@ -926,7 +926,7 @@ - Add better auth module logging. - Add missing `email` scope to OIDC backend. -- Add missing cause prop on error loging. +- Add missing cause prop on error logging. - Fix empty font-family handling on custom fonts page. - Fix incorrect unicode code points handling on draft-to-penpot conversion. - Fix some problems with paths. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d28328cca7..5093f9021e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -99,7 +99,7 @@ Each commit should have: - An entry on the CHANGES.md file if applicable, referencing the github or taiga issue/user-story using the these same rules. -Examples of good commit messags: +Examples of good commit messages: - :bug: Fix unexpected error on launching modal - :bug: Set proper error message on generic error diff --git a/backend/resources/app/templates/debug.tmpl b/backend/resources/app/templates/debug.tmpl index e23e7b5470..15824e6269 100644 --- a/backend/resources/app/templates/debug.tmpl +++ b/backend/resources/app/templates/debug.tmpl @@ -77,7 +77,7 @@ Debug Main Page Import binfile: Import penpot file in binary format. If overwrite is checked, all files will - be overwriten using the same ids found in the file instead of + be overwritten using the same ids found in the file instead of generating a new ones.
@@ -90,7 +90,7 @@ Debug Main Page
- Instead of creating a new file with all relations remaped, + Instead of creating a new file with all relations remapped, reuses all ids and updates/overwrites the objects that are already exists on the database. Warning, this operation should be used with caution. @@ -111,7 +111,7 @@ Debug Main Page
- Do not break on index lookup erros (remap operation). + Do not break on index lookup errors (remap operation). Useful when importing a broken file that has broken relations or missing pieces. diff --git a/backend/src/app/cli/manage.clj b/backend/src/app/cli/manage.clj index b606055c48..fedf040bc2 100644 --- a/backend/src/app/cli/manage.clj +++ b/backend/src/app/cli/manage.clj @@ -111,7 +111,7 @@ :id :verbosity :default 1 :update-fn inc] - ["-q" nil "Dont' print to console" + ["-q" nil "Don't print to console" :id :verbosity :update-fn (constantly 0)] ["-h" "--help"]]) diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 846ccc3d5a..897bd9fcb5 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -76,7 +76,7 @@ (defmethod ig/halt-key! ::server [_ {:keys [server name port] :as cfg}] - (l/info :msg "stoping http server" :name name :port port) + (l/info :msg "stopping http server" :name name :port port) (yt/stop! server)) (defn- not-found-handler diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 5e05ae4897..b78f440d14 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -125,7 +125,7 @@ (l/error :hint "error on persist-events" :cause cause))))] (fn [request respond _] - ;; Fire and forget, log error in case of errro + ;; Fire and forget, log error in case of error (-> (px/submit! executor #(handler request)) (p/catch handle-error)) diff --git a/backend/src/app/loggers/database.clj b/backend/src/app/loggers/database.clj index 92720f26f4..530dc8765f 100644 --- a/backend/src/app/loggers/database.clj +++ b/backend/src/app/loggers/database.clj @@ -82,7 +82,7 @@ (a/go-loop [] (let [msg (a/woff [data] ;; NOTE: foutput is not used directly, it represents the - ;; default output of the exection of the underlying + ;; default output of the execution of the underlying ;; command. (let [finput (tmp/tempfile :prefix "penpot.font." :suffix "") foutput (fs/path (str finput ".woff")) diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj index f4b5f056e7..1429b2f578 100644 --- a/backend/src/app/metrics.clj +++ b/backend/src/app/metrics.clj @@ -51,13 +51,13 @@ :rpc-mutation-timing {::mdef/name "penpot_rpc_mutation_timing" - ::mdef/help "RPC mutation method call timming." + ::mdef/help "RPC mutation method call timing." ::mdef/labels ["name"] ::mdef/type :histogram} :rpc-command-timing {::mdef/name "penpot_rpc_command_timing" - ::mdef/help "RPC command method call timming." + ::mdef/help "RPC command method call timing." ::mdef/labels ["name"] ::mdef/type :histogram} @@ -126,7 +126,7 @@ :executors-completed-tasks {::mdef/name "penpot_executors_completed_tasks_total" - ::mdef/help "Aproximate number of completed tasks by the executor." + ::mdef/help "Approximate number of completed tasks by the executor." ::mdef/labels ["name"] ::mdef/type :counter} diff --git a/backend/src/app/msgbus.clj b/backend/src/app/msgbus.clj index 6a52101f01..7d3959931e 100644 --- a/backend/src/app/msgbus.clj +++ b/backend/src/app/msgbus.clj @@ -138,7 +138,7 @@ (defn- disj-subscription "A low level function responsible on removing subscriptions. The - subscription is trully removed from redis once no single local + subscription is truly removed from redis once no single local subscription is look for it. Intended to be executed in agent." [nsubs cfg topic chan] (let [nsubs (disj nsubs chan)] @@ -159,7 +159,7 @@ topics)))) (defn- unsubscribe-single-channel - "Auxiliar function responsible on removing a single local + "Auxiliary function responsible on removing a single local subscription from the state." [state cfg chan] (let [topics (get-in state [:chans chan]) @@ -211,7 +211,7 @@ (cond (nil? val) (do - (l/trace :hint "stoping io-loop, nil received") + (l/trace :hint "stopping io-loop, nil received") (send-via executor state (fn [state] (->> (vals state) (mapcat identity) diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index 5c0a3e8011..70c9beed64 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -123,7 +123,7 @@ (tokens/verify sprops {:token token :iss :team-invitation})) ;; If invitation member-id does not matches the profile-id, we just proceed to ignore the - ;; invitation because invitations matches exactly; and user can't loging with other email and + ;; invitation because invitations matches exactly; and user can't login with other email and ;; accept invitation with other email response (if (and (some? invitation) (= (:id profile) (:member-id invitation))) {:invitation-token (:invitation-token params)} diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 20c8ec4d21..b52e02c12c 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -268,7 +268,7 @@ (when (not= readed# expected#) (ex/raise :type :validation :code :unexpected-label - :hint (format "unxpected label found: %s, expected: %s" readed# expected#))))) + :hint (format "unexpected label found: %s, expected: %s" readed# expected#))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; API @@ -366,7 +366,7 @@ (def ^:dynamic *state*) (def ^:dynamic *options*) -;; --- EXPORT WRITTER +;; --- EXPORT WRITER (defn- embed-file-assets [data conn file-id] @@ -396,8 +396,8 @@ form)) (process-group-of-assets [data [lib-id items]] - ;; NOTE: there are a posibility that shape refers to a not - ;; existing file because the file was removed. In this + ;; NOTE: there is a possibility that shape refers to an + ;; non-existant file because the file was removed. In this ;; case we just ignore the asset. (if-let [lib (retrieve-file conn lib-id)] (reduce (partial process-asset lib) data items) @@ -433,14 +433,14 @@ :opt [::include-libraries? ::embed-assets?])) (defn write-export! - "Do the exportation of a speficied file in custom penpot binary + "Do the exportation of a specified file in custom penpot binary format. There are some options available for customize the output: - `::include-libraries?`: additionaly to the specified file, all the + `::include-libraries?`: additionally to the specified file, all the linked libraries also will be included (including transitive dependencies). - `::embed-assets?`: instead of including the libraryes, embedd in the + `::embed-assets?`: instead of including the libraries, embed in the same file library all assets used from external libraries." [{:keys [::include-libraries? ::embed-assets?] :as options}] (us/assert! ::write-export-options options) @@ -556,7 +556,7 @@ format. There are some options for customize the importation behavior: - `::overwrite?`: if true, instead of creating new files and remaping id references, + `::overwrite?`: if true, instead of creating new files and remapping id references, it reuses all ids and updates existing objects; defaults to `false`. `::migrate?`: if true, applies the migration before persisting the @@ -621,7 +621,7 @@ (l/debug :hint "update media references" ::l/async false) (vswap! *state* update :media into (map #(update % :id lookup-index)) media') - (l/debug :hint "procesing file" :file-id file-id ::l/async false) + (l/debug :hint "processing file" :file-id file-id ::l/async false) (let [file-id' (lookup-index file-id) data (-> (:data file) diff --git a/backend/src/app/rpc/commands/demo.clj b/backend/src/app/rpc/commands/demo.clj index c4489f2db8..aac3c1ded0 100644 --- a/backend/src/app/rpc/commands/demo.clj +++ b/backend/src/app/rpc/commands/demo.clj @@ -24,11 +24,11 @@ (sv/defmethod ::create-demo-profile "A command that is responsible of creating a demo purpose - profile. It only works if the `demo-users` flag is inabled in the + profile. It only works if the `demo-users` flag is enabled in the configuration." {:auth false ::doc/added "1.15" - ::doc/changes ["1.15" "This methos is migrated from mutations to commands."]} + ::doc/changes ["1.15" "This method is migrated from mutations to commands."]} [{:keys [pool] :as cfg} _] (let [id (uuid/next) sem (System/currentTimeMillis) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 9c45d87980..605df391ec 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -566,7 +566,7 @@ (sv/defmethod ::upsert-file-thumbnail "Creates or updates the file thumbnail. Mainly used for paint the - grid thumbnals." + grid thumbnails." [{:keys [pool] :as cfg} {:keys [profile-id file-id revn data props]}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id file-id) diff --git a/backend/src/app/rpc/mutations/media.clj b/backend/src/app/rpc/mutations/media.clj index e2f49f9a2c..2e04836475 100644 --- a/backend/src/app/rpc/mutations/media.clj +++ b/backend/src/app/rpc/mutations/media.clj @@ -97,7 +97,7 @@ ;; something fails, all leaked (already created storage objects) will ;; be eventually marked as deleted by the touched-gc task. ;; -;; The touched-gc task, performs periodic analisis of all touched +;; The touched-gc task, performs periodic analysis of all touched ;; storage objects and check references of it. This is the reason why ;; `reference` metadata exists: it indicates the name of the table ;; witch holds the reference to storage object (it some kind of diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index e026fafaa6..d796594c0f 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -63,7 +63,7 @@ ;; Update profile props if the indirect prop is coming in ;; the params map and update the profile props data - ;; acordingly. + ;; accordingly. profile (cond-> profile (some? newsletter-subscribed) (update :props assoc :newsletter-subscribed newsletter-subscribed))] diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 154e45d74f..8ed02d1c80 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -318,7 +318,7 @@ (assoc frame :page-id (:id page))))) ;; function responsible to filter objects data structure of - ;; all unneded shapes if a concrete frame is provided. If no + ;; all unneeded shapes if a concrete frame is provided. If no ;; frame, the objects is returned untouched. (filter-objects [objects frame-id] (d/index-by :id (cph/get-children-with-self objects frame-id))) @@ -378,7 +378,7 @@ (update :objects filter-objects frame-id)) ;; Assoc the available thumbnails and prune not visible shapes - ;; for avoid transfer unnecesary data. + ;; for avoid transfer unnecessary data. :always (update :objects assoc-thumbnails page-id thumbs))))) diff --git a/backend/src/app/rpc/rlimit.clj b/backend/src/app/rpc/rlimit.clj index 7d0490af7f..e7d80b7a8c 100644 --- a/backend/src/app/rpc/rlimit.clj +++ b/backend/src/app/rpc/rlimit.clj @@ -84,7 +84,7 @@ ::rscript/path "app/rpc/rlimit/window.lua"}) (def enabled? - "Allows on runtime completly disable rate limiting." + "Allows on runtime completely disable rate limiting." (atom true)) (def ^:private window-opts-re diff --git a/backend/src/app/rpc/semaphore.clj b/backend/src/app/rpc/semaphore.clj index 8af3d7bb76..5e8a5a5ed7 100644 --- a/backend/src/app/rpc/semaphore.clj +++ b/backend/src/app/rpc/semaphore.clj @@ -37,7 +37,7 @@ permits (or permits Long/MAX_VALUE)] (when (>= permits Long/MAX_VALUE) - (l/warn :hint "permits value too hight" :permits permits :semaphore name)) + (l/warn :hint "permits value too high" :permits permits :semaphore name)) ^{::wrk/executor executor ::name name} diff --git a/backend/src/app/setup.clj b/backend/src/app/setup.clj index 3d2d5d96bf..074b973854 100644 --- a/backend/src/app/setup.clj +++ b/backend/src/app/setup.clj @@ -57,7 +57,7 @@ (db/xact-lock! conn 0) (when-not key (l/warn :hint (str "using autogenerated secret-key, it will change on each restart and will invalidate " - "all sessions on each restart, it is hightly recommeded setting up the " + "all sessions on each restart, it is hightly recommended setting up the " "PENPOT_SECRET_KEY environment variable"))) (let [secret (or key (generate-random-key))] diff --git a/backend/src/app/setup/builtin_templates.clj b/backend/src/app/setup/builtin_templates.clj index 9055d4501f..35658e75dd 100644 --- a/backend/src/app/setup/builtin_templates.clj +++ b/backend/src/app/setup/builtin_templates.clj @@ -5,7 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.setup.builtin-templates - "A service/module that is reponsible for download, load & internally + "A service/module that is responsible for download, load & internally expose a set of builtin penpot file templates." (:require [app.common.logging :as l] diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj index 556a365e62..8de524c1ae 100644 --- a/backend/src/app/storage.clj +++ b/backend/src/app/storage.clj @@ -371,7 +371,7 @@ (db/create-array conn "uuid" ids)])) ;; NOTE: A getter that retrieves the key witch will be used - ;; for group ids; previoulsy we have no value, then we + ;; for group ids; previously we have no value, then we ;; introduced the `:reference` prop, and then it is renamed ;; to `:bucket` and now is string instead. This is ;; implemented in this way for backward comaptibilty. diff --git a/backend/src/app/storage/tmp.clj b/backend/src/app/storage/tmp.clj index 743d73a9db..89382a121c 100644 --- a/backend/src/app/storage/tmp.clj +++ b/backend/src/app/storage/tmp.clj @@ -6,7 +6,7 @@ (ns app.storage.tmp "Temporal files service all created files will be tried to clean after - 1 hour afrer creation. This is a best effort, if this process fails, + 1 hour after creation. This is a best effort, if this process fails, the operating system cleaning task should be responsible of permanently delete these files (look at systemd-tempfiles)." (:require @@ -50,7 +50,7 @@ (defmethod ig/halt-key! ::cleaner [_ close-ch] - (l/info :hint "stoping tempfile cleaner service") + (l/info :hint "stopping tempfile cleaner service") (some-> close-ch a/close!)) (defn- remove-temp-file diff --git a/backend/src/app/tasks/objects_gc.clj b/backend/src/app/tasks/objects_gc.clj index 3cf6b9b4a6..c90d1909f4 100644 --- a/backend/src/app/tasks/objects_gc.clj +++ b/backend/src/app/tasks/objects_gc.clj @@ -158,7 +158,7 @@ (recur (rest tables) (+ total (process-table (assoc cfg :table table)))) (do - (l/info :hint "objects gc finished succesfully" + (l/info :hint "objects gc finished successfully" :min-age (dt/format-duration min-age) :total total) diff --git a/backend/src/app/util/time.clj b/backend/src/app/util/time.clj index 5e2647a9bd..69da4c847f 100644 --- a/backend/src/app/util/time.clj +++ b/backend/src/app/util/time.clj @@ -41,7 +41,7 @@ :hours ChronoUnit/HOURS :days ChronoUnit/DAYS :weeks ChronoUnit/WEEKS - :monts ChronoUnit/MONTHS))) + :months ChronoUnit/MONTHS))) ;; --- DURATION diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index 18936972e9..ea9df91afc 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -134,7 +134,7 @@ (aa/thread-sleep interval) (if (.isShutdown executor) - (l/debug :hint "stoping monitor; cause: executor is shutdown") + (l/debug :hint "stopping monitor; cause: executor is shutdown") (assoc state skey steals)))) (monitor-fn [] @@ -146,7 +146,7 @@ (recur (conj items item) state) (recur items state)))) (catch InterruptedException _cause - (l/debug :hint "stoping monitor; interrupted"))))] + (l/debug :hint "stopping monitor; interrupted"))))] (let [thread (Thread. monitor-fn)] (.setDaemon thread true) diff --git a/backend/test/app/services_files_test.clj b/backend/test/app/services_files_test.clj index b400d217f2..004e29b2f4 100644 --- a/backend/test/app/services_files_test.clj +++ b/backend/test/app/services_files_test.clj @@ -183,7 +183,7 @@ :type :image :metadata {:id (:id fmo1)}}}]})] - ;; Check that reference storage objets on filemediaobjects + ;; Check that reference storage objects on filemediaobjects ;; are the same because of deduplication feature. (t/is (= (:media-id fmo1) (:media-id fmo2))) (t/is (= (:thumbnail-id fmo1) (:thumbnail-id fmo2))) @@ -228,8 +228,8 @@ :page-id (first (get-in file [:data :pages])) :id shid}]}) - ;; Now, we have deleted the usag of pointers to the - ;; file-media-objects, if we pase file-gc, they should be marked + ;; Now, we have deleted the usage of pointers to the + ;; file-media-objects, if we paste file-gc, they should be marked ;; as deleted. (let [task (:app.tasks.file-gc/handler th/*system*) res (task {:min-age (dt/duration 0)})] diff --git a/common/src/app/common/attrs.cljc b/common/src/app/common/attrs.cljc index b63f036d8d..83bdd27cc6 100644 --- a/common/src/app/common/attrs.cljc +++ b/common/src/app/common/attrs.cljc @@ -16,7 +16,7 @@ (cond ;; For rotated or stretched shapes, the origin point we show in the menu ;; is not the (:x :y) shape attribute, but the top left coordinate of the - ;; wrapping recangle (see measures.cljs). As the :points attribute cannot + ;; wrapping rectangle (see measures.cljs). As the :points attribute cannot ;; be merged for several objects, we calculate the origin point in two fake ;; attributes to be used in the measures menu. (#{:ox :oy} attr) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index a413bb694b..f4b58f7f29 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -83,7 +83,7 @@ (defn concat-all "A totally lazy implementation of concat with different call - signature. It works like a flatten with a single level of neesting." + signature. It works like a flatten with a single level of nesting." [colls] (lazy-seq (let [c (seq colls) @@ -663,7 +663,7 @@ coll)))) (defn iteration - "Creates a toally lazy seqable via repeated calls to step, a + "Creates a totally lazy seqable via repeated calls to step, a function of some (continuation token) 'k'. The first call to step will be passed initk, returning 'ret'. If (somef ret) is true, (vf ret) will be included in the iteration, else iteration will diff --git a/common/src/app/common/data/macros.cljc b/common/src/app/common/data/macros.cljc index bdf9882b25..a9573782f0 100644 --- a/common/src/app/common/data/macros.cljc +++ b/common/src/app/common/data/macros.cljc @@ -17,7 +17,7 @@ [cljs.analyzer.api :as aapi])) (defmacro select-keys - "A macro version of `select-keys`. Usefull when keys vector is known + "A macro version of `select-keys`. Useful when keys vector is known at compile time (aprox 600% performance boost). It is not 100% equivalent, this macro does not removes not existing @@ -27,7 +27,7 @@ `{ ~@(mapcat (fn [key] [key (list `c/get target key)]) keys) ~@[] }) (defmacro get-in - "A macro version of `get-in`. Usefull when the keys vector is known at + "A macro version of `get-in`. Useful when the keys vector is known at compile time (20-40% performance improvement)." ([target keys] (assert (vector? keys) "keys expected to be a vector") diff --git a/common/src/app/common/logging.cljc b/common/src/app/common/logging.cljc index c9f2e33dfd..9b93c1a108 100644 --- a/common/src/app/common/logging.cljc +++ b/common/src/app/common/logging.cljc @@ -197,7 +197,7 @@ (write-log! ~logger-sym ~level-sym ~cause message#) (catch Throwable cause# (write-log! ~logger-sym (get-level :error) cause# - "unexpected error on writting log"))))))) + "unexpected error on writing log"))))))) nil) `(let [message# (or ~raw (build-message ~(vec props)))] (write-log! ~logger-sym ~level-sym ~cause message#) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 511cd67145..1c7f580c56 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -479,7 +479,7 @@ (into [id] (cph/get-parent-ids (:objects page) id))) need-sync? (fn [operation] ; We need to trigger a sync if the shape has changed any - ; attribute that participates in components syncronization. + ; attribute that participates in components synchronization. (and (= (:type operation) :set) (component-sync-attrs (:attr operation)))) any-sync? (some need-sync? operations)] diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 8ac2307e0c..6714415a35 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -259,7 +259,7 @@ (s/valid? spec value)) (defmacro assert-expr* - "Auxiliar macro for expression assertion." + "Auxiliary macro for expression assertion." [expr hint] `(when-not ~expr (ex/raise :type :assertion @@ -267,7 +267,7 @@ :hint ~hint))) (defmacro assert-spec* - "Auxiliar macro for spec assertion." + "Auxiliary macro for spec assertion." [spec value hint] (let [context (if-let [nsdata (:ns &env)] {:ns (str (:name nsdata)) diff --git a/common/test/app/common/pages_helpers_test.cljc b/common/test/app/common/pages_helpers_test.cljc index 058ff6422e..a6265a1161 100644 --- a/common/test/app/common/pages_helpers_test.cljc +++ b/common/test/app/common/pages_helpers_test.cljc @@ -27,7 +27,7 @@ (t/is (= (cph/insert-at-index [:a :b] 10 [:c]) [:a :b :c])) - ;; insert existing in a contiguos index + ;; insert existing in a contiguous index (t/is (= (cph/insert-at-index [:a :b] 1 [:a]) [:a :b])) diff --git a/exporter/src/app/core.cljs b/exporter/src/app/core.cljs index 80e7d8e628..34b228436b 100644 --- a/exporter/src/app/core.cljs +++ b/exporter/src/app/core.cljs @@ -34,7 +34,7 @@ ;; an empty line for visual feedback of restart (js/console.log "") - (l/info :msg "stoping") + (l/info :msg "stopping") (p/do! (bwr/stop) (redis/stop) diff --git a/exporter/src/app/handlers/resources.cljs b/exporter/src/app/handlers/resources.cljs index 8f1edd0734..85fc174e56 100644 --- a/exporter/src/app/handlers/resources.cljs +++ b/exporter/src/app/handlers/resources.cljs @@ -5,7 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.handlers.resources - "Temporal resouces management." + "Temporal resources management." (:require ["archiver" :as arc] ["fs" :as fs] diff --git a/frontend/cypress/integration/02-onboarding/slides.spec.js b/frontend/cypress/integration/02-onboarding/slides.spec.js index 50be0989c7..c16f5f0039 100644 --- a/frontend/cypress/integration/02-onboarding/slides.spec.js +++ b/frontend/cypress/integration/02-onboarding/slides.spec.js @@ -17,7 +17,7 @@ describe("onboarding slides", () => { cy.demoLogin(); }); - it("go trough all the onboarding slides", () => { + it("go through all the onboarding slides", () => { cy.getBySel("onboarding-welcome").should("exist"); cy.getBySel("onboarding-next-btn").should("exist"); cy.getBySel("onboarding-next-btn").click(); diff --git a/frontend/scripts/jvm-repl b/frontend/scripts/jvm-repl index 6ca8b7d1b3..b59aaaca89 100755 --- a/frontend/scripts/jvm-repl +++ b/frontend/scripts/jvm-repl @@ -1,6 +1,6 @@ #!/usr/bin/env bash -# A repl usefull for debug macros. +# A repl useful for debug macros. export OPTIONS="\ -J-XX:-OmitStackTraceInFastThrow \ diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 318348b020..556d34f856 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -17,7 +17,7 @@ [clojure.spec.alpha :as s] [cuerdas.core :as str])) -;; --- Auxiliar Functions +;; --- Auxiliary Functions (s/def ::platform #{:windows :linux :macos :other}) (s/def ::browser #{:chrome :firefox :safari :edge :other}) diff --git a/frontend/src/app/main/data/exports.cljs b/frontend/src/app/main/data/exports.cljs index 37c6839a7a..09eea1ddac 100644 --- a/frontend/src/app/main/data/exports.cljs +++ b/frontend/src/app/main/data/exports.cljs @@ -229,9 +229,9 @@ (swap! st/ongoing-tasks disj :export)))) ;; We hide need to hide the ui elements of the export after - ;; some interval. We also delay a litle bit more the stopper + ;; some interval. We also delay a little bit more the stopper ;; for ensure that after some security time, the stream is - ;; completelly closed. + ;; completely closed. (->> progress-stream (rx/filter #(= "ended" (:status %))) (rx/take 1) diff --git a/frontend/src/app/main/data/fonts.cljs b/frontend/src/app/main/data/fonts.cljs index 17003e5f87..c46cc65775 100644 --- a/frontend/src/app/main/data/fonts.cljs +++ b/frontend/src/app/main/data/fonts.cljs @@ -124,7 +124,7 @@ (try (assoc params :font (ot/parse data)) (catch :default _e - (log/warn :msg (str/fmt "skiping file %s, unsupported format" (:name params))) + (log/warn :msg (str/fmt "skipping file %s, unsupported format" (:name params))) nil))) (read-blob [blob] @@ -169,7 +169,7 @@ (defn rename-and-regroup "Function responsible to rename a font in a local state and properly - regroup it to the apropriate `font-id` having in account current + regroup it to the appropriate `font-id` having in account current fonts and installed fonts." [current-fonts id name installed-fonts] (let [famdb (-> (merge current-fonts installed-fonts) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 4d44ad0afa..96507a85ee 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -198,7 +198,7 @@ :invitation-token invitation-token}] ;; NOTE: We can't take the profile value from login because - ;; there are cases when login is successfull but the cookie is + ;; there are cases when login is successful but the cookie is ;; not set properly (because of possible misconfiguration). ;; So, we proceed to make an additional call to fetch the ;; profile, and ensure that cookie is set correctly. If diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index e680326c0b..fe64a5d415 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -573,7 +573,7 @@ (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - ;; Ignore any shape whose parent is also intented to be moved + ;; Ignore any shape whose parent is also intended to be moved ids (cph/clean-loops objects ids) ;; If we try to move a parent into a child we remove it @@ -624,7 +624,7 @@ ids) ;; TODO: Probably implementing this using loop/recur will - ;; be more efficient than using reduce and continuos data + ;; be more efficient than using reduce and continuous data ;; desturcturing. ;; Sets the correct components metadata for the moved shapes diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 1f789545e0..bfddb12ba7 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -17,7 +17,7 @@ (log/set-level! :warn) (defn initialized? - "Check if the state is properly intialized in a workspace. This means + "Check if the state is properly initialized in a workspace. This means it has the `:current-page-id` and `:current-file-id` properly set." [state] (and (uuid? (:current-file-id state)) diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index 32295301e6..bc7c5bc270 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -26,7 +26,7 @@ (sort-by ::index))) (defn- get-empty-groups-after-group-creation - "An auxiliar function that finds and returns a set of ids that + "An auxiliary function that finds and returns a set of ids that corresponds to groups that should be deleted after a group creation. The corner case happens when you selects two (or more) shapes that diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 3888a2e805..785a7cb8dd 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -342,7 +342,7 @@ update-fn (fn [component] ;; NOTE: we need to ensure the component exists, - ;; because there are small posibilities of race + ;; because there are small possibilities of race ;; conditions with component deletion. (when component (-> component diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 2702226f3e..549d9b751a 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -164,7 +164,7 @@ that use assets of the given type in the given library. If an asset id is given, only shapes linked to this particular asset will - be syncrhonized." + be synchronized." [it file-id asset-type asset-id library-id state] (s/assert #{:colors :components :typographies} asset-type) (s/assert (s/nilable ::us/uuid) asset-id) @@ -198,7 +198,7 @@ the given library. If an asset id is given, only shapes linked to this particular asset will - be syncrhonized." + be synchronized." [it file-id asset-type asset-id library-id state] (s/assert #{:colors :components :typographies} asset-type) (s/assert (s/nilable ::us/uuid) asset-id) diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 48c1d6063b..e08c75c8ea 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -244,7 +244,7 @@ ;; TODO: it is really need handle SVG here, looks like it already -;; handled separatelly +;; handled separately (defn upload-media-workspace [{:keys [position file-id] :as params}] (let [params (assoc params diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index f37bb89fd8..cc5a0adfb5 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -219,7 +219,7 @@ (fn [[page-id changes]] (dch/update-indices page-id changes)) - ;; We update `position-data` from the incomming message + ;; We update `position-data` from the incoming message changes (->> changes (mapv update-position-data)) changes-by-pages (group-by :page-id changes)] diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 62ea1c2245..421a5dba21 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -304,7 +304,7 @@ (update :workspace-pages dissoc id))) (defn preload-data-uris - "Preloads the image data so it's ready when necesary" + "Preloads the image data so it's ready when necessary" [] (ptk/reify ::preload-data-uris ptk/WatchEvent diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 83122fa223..aaaed84ea4 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -222,7 +222,7 @@ ptk/UpdateEvent (update [_ state] - ;; Only deselect if there is no modal openned + ;; Only deselect if there is no modal opened (cond-> state (or (not check-modal) (not (::md/modal state))) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index a2c7d003ec..5b3d29ea52 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -766,7 +766,7 @@ (pcb/with-objects objects) (pcb/update-shapes moving-frames (fn [shape] - ;; Hide in viwer must be enabled just when a board is moved + ;; Hide in viewer must be enabled just when a board is moved ;; inside another artboard an nested to it, we have to avoid ;; situations like: 1. Moving inside the same frame; 2. Moving ;; outside the frame diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index c0afc15dd3..9a49f806ee 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -214,7 +214,7 @@ ;; https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state (defn use-previous - "Returns the value from previuous render cycle." + "Returns the value from previous render cycle." [value] (let [ref (mf/use-ref value)] (mf/use-effect @@ -234,7 +234,7 @@ (defn use-ref-callback "Returns a stable callback pointer what calls the interned callback. The interned callback will be automatically updated on - each reander if the reference changes and works as noop if the + each render if the reference changes and works as noop if the pointer references to nil value." [f] (let [ptr (mf/use-ref nil)] diff --git a/frontend/src/app/main/ui/onboarding/templates.cljs b/frontend/src/app/main/ui/onboarding/templates.cljs index e5f80821d9..a958af8071 100644 --- a/frontend/src/app/main/ui/onboarding/templates.cljs +++ b/frontend/src/app/main/ui/onboarding/templates.cljs @@ -59,7 +59,7 @@ {::mf/wrap-props false ::mf/register modal/components ::mf/register-as :onboarding-templates} - ;; NOTE: the project usually comes empty, it only comes fullfilled + ;; NOTE: the project usually comes empty, it only comes fulfilled ;; when a user creates a new team just after signup. [props] (let [project-id (unchecked-get props "project-id") diff --git a/frontend/src/app/main/ui/shapes/path.cljs b/frontend/src/app/main/ui/shapes/path.cljs index 72d1777444..031b0458b9 100644 --- a/frontend/src/app/main/ui/shapes/path.cljs +++ b/frontend/src/app/main/ui/shapes/path.cljs @@ -22,7 +22,7 @@ (try (upf/format-path content) (catch :default e - (log/error :hint "unexpected error on formating path" + (log/error :hint "unexpected error on formatting path" :shape-name (:name shape) :shape-id (:id shape) :cause e) diff --git a/frontend/src/app/main/ui/shapes/text/svg_text.cljs b/frontend/src/app/main/ui/shapes/text/svg_text.cljs index 3440a1104b..35124f6c66 100644 --- a/frontend/src/app/main/ui/shapes/text/svg_text.cljs +++ b/frontend/src/app/main/ui/shapes/text/svg_text.cljs @@ -62,7 +62,7 @@ transform (gsh/transform-str shape {:no-flip true}) - ;; These position attributes are not really necesary but they are convenient for for the export + ;; These position attributes are not really necessary but they are convenient for for the export group-props (-> #js {:transform transform :className "text-container" :x x diff --git a/frontend/src/app/main/ui/viewer/handoff/exports.cljs b/frontend/src/app/main/ui/viewer/handoff/exports.cljs index ccd5af7612..3082e08fd6 100644 --- a/frontend/src/app/main/ui/viewer/handoff/exports.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/exports.cljs @@ -44,7 +44,7 @@ ;; In other all cases we only allowed to have a single ;; shape-id because multiple shape-ids are handled - ;; separatelly by the export-modal. + ;; separately by the export-modal. (let [defaults {:page-id page-id :file-id file-id :name filename diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index 02a1e62cc5..fafb5cf53d 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -113,7 +113,7 @@ ::mf/wrap-props false} [props] (let [;; NOTE: with `use-equal-memo` hook we ensure that all values - ;; conserves the reference identity for avoid unnecesary dummy + ;; conserves the reference identity for avoid unnecessary dummy ;; rerenders. mode (h/use-equal-memo (unchecked-get props "interactions-mode")) offset (h/use-equal-memo (unchecked-get props "frame-offset")) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 891e49e262..cbde0cc103 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -116,7 +116,7 @@ (mf/use-effect (fn [] - ;; When a change in the data is received a "force-render" event is emited + ;; When a change in the data is received a "force-render" event is emitted ;; that will force the component to be mounted in memory (let [sub (->> (dwt/force-render-stream (:id shape)) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index a6b527721c..2f4207e140 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -232,7 +232,7 @@ (when (some? node) (cond (= (dom/get-tag-name node) "foreignObject") - ;; The shape width/height will be automaticaly setup when the modifiers are applied + ;; The shape width/height will be automatically setup when the modifiers are applied nil (or (= (dom/get-tag-name node) "mask") diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index 409663bd20..5c06eb06b6 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -103,7 +103,7 @@ (when @show-frame-thumbnail (reset! show-frame-thumbnail false)) - ;; If we don't have the thumbnail data saved (normaly the first load) we update the data + ;; If we don't have the thumbnail data saved (normally the first load) we update the data ;; when available (when (not @thumbnail-data-ref) (st/emit! (dwt/update-thumbnail page-id id) )) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 00cd7303fc..8326bbdb10 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -241,7 +241,7 @@ editing-shape (get text-shapes edition) - ;; This memo is necesary so the viewport-text-wrapper memoize its props correctly + ;; This memo is necessary so the viewport-text-wrapper memoize its props correctly text-shapes-wrapper (mf/use-memo (mf/deps text-shapes edition) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs index e9a32edd6f..5ceec2ac7b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/exports.cljs @@ -53,7 +53,7 @@ ;; In other all cases we only allowed to have a single ;; shape-id because multiple shape-ids are handled - ;; separatelly by the export-modal. + ;; separately by the export-modal. (let [defaults {:page-id page-id :file-id file-id :name sname diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 9a531fe2ee..47b039812a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -79,7 +79,7 @@ ;; To show interactively the measures while the user is manipulating ;; the shape with the mouse, generate a copy of the shapes applying - ;; the transient tranformations. + ;; the transient transformations. shapes (as-> old-shapes $ (map gsh/transform-shape $) (map gsh/translate-to-frame $ frames)) @@ -256,7 +256,7 @@ ;; FRAME PRESETS (when (and (options :presets) - (or (nil? all-types) (= (count all-types) 1))) ;; Dont' show presets if multi selected + (or (nil? all-types) (= (count all-types) 1))) ;; Don't show presets if multi selected [:div.row-flex ;; some frames and some non frames [:div.presets.custom-select.flex-grow {:on-click #(reset! show-presets-dropdown? true)} [:span (tr "workspace.options.size-presets")] diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs index 9d5b1e25ea..a5660ff1d4 100644 --- a/frontend/src/app/render.cljs +++ b/frontend/src/app/render.cljs @@ -76,7 +76,7 @@ It receives a function to execute for retrieve the stream that will be used for creating the subscription. The function should be - stable, so is the responsability of the user of this hook to + stable, so is the responsibility of the user of this hook to properly memoize it. TODO: this should be placed in some generic hooks namespace but his diff --git a/frontend/src/app/util/dom/dnd.cljs b/frontend/src/app/util/dom/dnd.cljs index cfabbba3ed..06d21bc852 100644 --- a/frontend/src/app/util/dom/dnd.cljs +++ b/frontend/src/app/util/dom/dnd.cljs @@ -14,7 +14,7 @@ ;; https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API ;; ;; The API is broken in several ways. Here is some discussion of the problems, -;; and many uncomplete solutions: +;; and many incomplete solutions: ;; https://github.com/lolmaus/jquery.dragbetter/#what-this-is-all-about ;; https://www.w3schools.com/jsref/event_relatedtarget.asp ;; https://stackoverflow.com/questions/14194324/firefox-firing-dragleave-when-dragging-over-text?noredirect=1&lq=1 diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 42984b4241..9fab69a46c 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -69,7 +69,7 @@ (autodetect)))) ;; The translations `data` is a javascript object and should be treated -;; with `goog.object` namespace functions instead of a standart +;; with `goog.object` namespace functions instead of a standard ;; clojure functions. This is for performance reasons because this ;; code is executed in the critical part (application bootstrap) and ;; used in many parts of the application. diff --git a/frontend/src/app/util/path/tools.cljs b/frontend/src/app/util/path/tools.cljs index 2ba9428157..9c420bd41d 100644 --- a/frontend/src/app/util/path/tools.cljs +++ b/frontend/src/app/util/path/tools.cljs @@ -76,7 +76,7 @@ (defn make-curve-point "Changes the content to make the point a 'curve'. The handlers will be positioned - in the same vector that results from te previous->next points but with fixed length." + in the same vector that results from the previous->next points but with fixed length." [content point] (let [indices (upc/point-indices content point) diff --git a/frontend/src/app/util/snap_data.cljs b/frontend/src/app/util/snap_data.cljs index 6ee30a6300..865162aa33 100644 --- a/frontend/src/app/util/snap_data.cljs +++ b/frontend/src/app/util/snap_data.cljs @@ -5,8 +5,8 @@ ;; Copyright (c) KALEIDOS INC (ns app.util.snap-data - "Data structure that holds and retrieves the data to make the snaps. Internaly - is implemented with a balanced binary tree that queries by range. + "Data structure that holds and retrieves the data to make the snaps. + Internally is implemented with a balanced binary tree that queries by range. https://en.wikipedia.org/wiki/Range_tree" (:require [app.common.data :as d] From 0c1d04919f0e6a64141abdbb01af1e2e2ab2c981 Mon Sep 17 00:00:00 2001 From: Schalk Neethling Date: Sun, 2 Oct 2022 16:20:16 +0200 Subject: [PATCH 075/682] :paperclip: Switch to issue forms (on github) GitHub launched issue template forms some time ago. These have helped other open source projects I have been involved in. As you can make certain fields required, it also helps cut down on issue spam. fix #2395 --- .github/ISSUE_TEMPLATE/bug-report.yml | 89 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/bug_report.md | 72 ----------------- .github/ISSUE_TEMPLATE/feature-request.yml | 37 +++++++++ .github/ISSUE_TEMPLATE/feature_request.md | 21 ----- 4 files changed, 126 insertions(+), 93 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/bug-report.yml delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/feature-request.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml new file mode 100644 index 0000000000..67ba1d4fe1 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -0,0 +1,89 @@ +description: Create a report to help us improve +labels: ["bug"] +name: Bug report +title: "bug: " +body: + - type: markdown + attributes: + value: | + ## Before you start + + Please search our [existing issues](https://github.com/penpot/penpot/issues) and open [pull requests](https://github.com/penpot/penpot/pulls) to lessen the change of filing duplicate issues or feature requests. Thank you. + + --- + - type: textarea + attributes: + label: Steps To Reproduce + description: Steps to reproduce the behavior. + placeholder: | + Steps to reproduce the behavior: + 1. Go to '...' + 2. Click on '....' + 3. Scroll down to '....' + validations: + required: true + - type: textarea + id: expected + attributes: + description: A clear and concise description of what you expected to happen. + label: Expected behavior + validations: + required: true + - type: textarea + id: actual + attributes: + description: A clear and concise description of what happens instead; what the bug is. + label: Actual behavior + validations: + required: true + - type: textarea + id: screenshots + attributes: + description: If applicable, add screenshots to help explain your problem. + label: Screenshots or video + - type: textarea + id: desktop + attributes: + label: Desktop (please complete the following information) + placeholder: | + - OS (e.g. iOS): + - Browser & version (e.g. Chrome 89.0): + - type: textarea + id: mobile + attributes: + label: Smartphone (please complete the following information) + placeholder: | + - Device & model (e.g. iPhone 6): + - OS & version (e.g. iOS 8.1): + - Browser & version (e.g. stock browser 22): + - type: textarea + id: environment + attributes: + label: Environment (please complete the following information) + placeholder: | + - Host (e.g. https://design.penpot.app, local instance): + + *If self-hosted:* + - OS Version (e.g. Ubuntu 16.04): + - Docker / Docker-compose version (e.g. Docker version 18.03.0-ce, build 0520e24): + - Image version (e.g. Alpine): + + Docker commands or docker-compose file (if possible and if proceed.x): + ``` + + ``` + - type: textarea + id: frontend-trace + attributes: + label: Frontend Stack Trace + render: console + - type: textarea + id: backend-trace + attributes: + label: Backend Stack Trace + render: console + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Any other context about the problem. diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index bdbe87a01c..0000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,72 +0,0 @@ ---- - -name: Bug report -about: Create a report to help us improve -title: '' -labels: bug -assignees: '' - ---- - -**To Reproduce** - -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' - -**Expected behavior** - -A clear and concise description of what you expected to happen. - -**Actual behavior** - -A clear and concise description of what happens instead; what the bug is. - -**Screenshots** - -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** -- OS (e.g. iOS): -- Browser & version (e.g. Chrome 89.0): - -**Smartphone (please complete the following information):** -- Device & model (e.g. iPhone 6): -- OS & version (e.g. iOS 8.1): -- Browser & version (e.g. stock browser 22): - -**Environment (please complete the following information):** -- Host (e.g. https://design.penpot.app, local instance): - -*If self-hosted:* -- OS Version (e.g. Ubuntu 16.04): -- Docker / Docker-compose version (e.g. Docker version 18.03.0-ce, build 0520e24): -- Image version (e.g. Alpine): - -Docker commands or docker-compose file (if possible and if proceed.x): -``` - -``` - -Frontend Stack Trace: -
- -``` - -``` - -
- -Backend Stack Trace: -
- -``` - -``` - -
- -**Additional context:** - -Any other context about the problem. diff --git a/.github/ISSUE_TEMPLATE/feature-request.yml b/.github/ISSUE_TEMPLATE/feature-request.yml new file mode 100644 index 0000000000..a49d6a57c9 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.yml @@ -0,0 +1,37 @@ +description: Suggest an idea for this project. +labels: ["needs triage", "enhancement"] +name: "Feature request" +title: "feature: " +body: + - type: markdown + attributes: + value: | + ## Before you start + + Please search our [existing issues](https://github.com/penpot/penpot/issues) and open [pull requests](https://github.com/penpot/penpot/pulls) to lessen the change of filing duplicate issues or feature requests. Thank you. + + --- + - type: textarea + id: problem + attributes: + description: A clear and concise description of what the problem is. Ex. I'm always frustrated when (...) + label: Is your feature request related to a problem? Please describe. + validations: + required: true + - type: textarea + id: solution + attributes: + description: A clear and concise description of what you want to happen. + label: Describe the solution you'd like. + validations: + required: true + - type: textarea + id: alternatives + attributes: + label: Describe alternatives you've considered. + description: A clear and concise description of any alternative solutions or features you've considered. + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index f98a4e747e..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,21 +0,0 @@ ---- - -name: Feature request -about: Suggest an idea for this project -title: '' -labels: enhancement -assignees: '' - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when (...) - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. From b41ca755123d82e2a0a60806b1d1eb6468e9b1b6 Mon Sep 17 00:00:00 2001 From: Eva Date: Tue, 4 Oct 2022 13:15:36 +0200 Subject: [PATCH 076/682] :bug: Fix color bullets in colorpicker modal on libraries --- CHANGES.md | 1 + frontend/src/app/main/ui/workspace/colorpicker.cljs | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index fe58596fbc..a40fdae18e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ ### :sparkles: New features ### :bug: Bugs fixed +- Fix color bullets in library color modal [Taiga #4186](https://tree.taiga.io/project/penpot/issue/4186) - Fix shortcut texts alignment [Taiga #4275](https://tree.taiga.io/project/penpot/issue/4275) - Fix some texts and a typo [Taiga #4215](https://tree.taiga.io/project/penpot/issue/4215) - Fix twitter support account link [Taiga #4279](https://tree.taiga.io/project/penpot/issue/4279) diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 11eacb462b..e0ae0d9ad8 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -89,6 +89,7 @@ on-select-library-color (mf/use-fn (fn [color] + (st/emit! (dc/update-colorpicker color)) (on-change color))) on-add-library-color From a08b9adeee2fa3d1365340a03d5cc6ea50a3fea6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 4 Oct 2022 13:31:06 +0200 Subject: [PATCH 077/682] :paperclip: Fix merge issues from staging to develop --- frontend/src/app/main/data/workspace/transforms.cljs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index f135affae4..84ba8d6890 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -761,17 +761,6 @@ changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) - (pcb/update-shapes moving-frames - (fn [shape] - ;; Hide in viewer must be enabled just when a board is moved - ;; inside another artboard an nested to it, we have to avoid - ;; situations like: 1. Moving inside the same frame; 2. Moving - ;; outside the frame - (cond-> shape - (and (not= frame-id (:id shape)) - (not= frame-id (:frame-id shape)) - (not= frame-id uuid/zero)) - (assoc :hide-in-viewer true)))) (pcb/change-parent frame-id moving-shapes))] (when-not (empty? changes) From c5b875c925390c2df9a4f0f7cc6af638b54d9747 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 23 Sep 2022 10:20:20 +0200 Subject: [PATCH 078/682] :sparkles: Add garbage collect of deleted components --- backend/src/app/tasks/file_gc.clj | 73 ++++++++++++++++++++++++++- common/src/app/common/types/file.cljc | 12 +++++ frontend/src/debug.cljs | 4 ++ 3 files changed, 87 insertions(+), 2 deletions(-) diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj index f4800a518b..f40882f96d 100644 --- a/backend/src/app/tasks/file_gc.clj +++ b/backend/src/app/tasks/file_gc.clj @@ -13,6 +13,7 @@ [app.common.data :as d] [app.common.logging :as l] [app.common.pages.migrations :as pmg] + [app.common.types.file :as ctf] [app.common.types.shape-tree :as ctt] [app.config :as cf] [app.db :as db] @@ -45,7 +46,8 @@ (fn [params] (db/with-atomic [conn pool] (let [min-age (or (:min-age params) (:min-age cfg)) - cfg (assoc cfg :min-age min-age :conn conn)] + id (:id params) + cfg (assoc cfg :min-age min-age :conn conn :id id)] (loop [total 0 files (retrieve-candidates cfg)] (if-let [file (first files)] @@ -162,7 +164,73 @@ (let [sql (str "delete from file_thumbnail " " where file_id=? and revn < ?") res (db/exec-one! conn [sql file-id revn])] - (l/debug :hint "delete file thumbnails" :file-id file-id :total (:next.jdbc/update-count res)))) + (when-not (zero? (:next.jdbc/update-count res)) + (l/debug :hint "delete file thumbnails" :file-id file-id :total (:next.jdbc/update-count res))))) + +(def ^:private + sql:retrieve-client-files + "select f.data, f.modified_at + from file as f + left join file_library_rel as fl on (fl.file_id = f.id) + where fl.library_file_id = ? + and f.modified_at < ? + and f.deleted_at is null + order by f.modified_at desc + limit 1") + +(defn- retrieve-client-files + "search al files that use the given library. + Returns a sequence of file-data (only reads database rows one by one)." + [conn library-id] + (let [get-chunk (fn [cursor] + (let [rows (db/exec! conn [sql:retrieve-client-files library-id cursor])] + [(some-> rows peek :modified-at) + (map #(->> % :data blob/decode) rows)]))] + + (d/iteration get-chunk + :vf second + :kf first + :initk (dt/now)))) + +(defn- clean-deleted-components! + "Performs the garbage collection of unreferenced deleted components." + [conn library-id library-data] + (let [find-used-components-file + (fn [components file-data] + ; Find what of the components are used in the file. + (d/filterm #(ctf/used-in? file-data library-id (second %) :component) + components)) + + find-used-components + (fn [components files-data] + ; Find what components are used in any of the files. + (loop [files-data files-data + components components + used-components {}] + (let [file-data (first files-data)] + (if (or (nil? file-data) (empty? components)) + used-components + (let [used-components-file (find-used-components-file components file-data)] + (recur (rest files-data) + (d/filterm #(not (contains? used-components-file (:id %))) components) + (into used-components used-components-file))))))) + + deleted-components (:deleted-components library-data) + saved-components (find-used-components deleted-components + (cons library-data + (retrieve-client-files conn library-id))) + + total (- (count deleted-components) + (count saved-components))] + + (when-not (zero? total) + (l/debug :hint "clean deleted components" :total total) + (let [new-data (-> library-data + (assoc :deleted-components saved-components) + (blob/encode))] + (db/update! conn :file + {:data new-data} + {:id library-id}))))) (defn- process-file [{:keys [conn] :as cfg} {:keys [id data revn modified-at] :as file}] @@ -175,6 +243,7 @@ (clean-file-media! conn id data) (clean-file-frame-thumbnails! conn id data) (clean-file-thumbnails! conn id revn) + (clean-deleted-components! conn id data) ;; Mark file as trimmed (db/update! conn :file diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 2af87b2d5b..74f11a20bb 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -223,6 +223,18 @@ [[asset instances]]))) assets-seq))) +(defn used-in? + "Checks if a specific asset is used in a given file (by any shape in its pages or in + the components of the local library)." + [file-data library-id asset asset-type] + (letfn [(used-in-shape? [shape] + (uses-asset? asset-type shape library-id asset)) + + (used-in-container? [container] + (some used-in-shape? (ctn/shapes-seq container)))] + + (some used-in-container? (containers-seq file-data)))) + (defn get-or-add-library-page "If exists a page named 'Library backup', get the id and calculate the position to start adding new components. If not, create it and start at (0, 0)." diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index fd693882c7..96c9866049 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -158,6 +158,10 @@ (logjs "state" @st/state) nil) +(defn ^:export dump-data [] + (logjs "workspace-data" (get @st/state :workspace-data)) + nil) + (defn ^:export dump-buffer [] (logjs "last-events" @st/last-events) nil) From 687e4dce2a3070a3270cd30cb7e1a5f32f16b19e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 4 Oct 2022 13:49:54 +0200 Subject: [PATCH 079/682] :wrench: Adjust code style --- backend/src/app/tasks/file_gc.clj | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj index f40882f96d..530890317e 100644 --- a/backend/src/app/tasks/file_gc.clj +++ b/backend/src/app/tasks/file_gc.clj @@ -43,10 +43,9 @@ (defmethod ig/init-key ::handler [_ {:keys [pool] :as cfg}] - (fn [params] + (fn [{:keys [id] :as params}] (db/with-atomic [conn pool] (let [min-age (or (:min-age params) (:min-age cfg)) - id (:id params) cfg (assoc cfg :min-age min-age :conn conn :id id)] (loop [total 0 files (retrieve-candidates cfg)] @@ -185,7 +184,7 @@ (let [get-chunk (fn [cursor] (let [rows (db/exec! conn [sql:retrieve-client-files library-id cursor])] [(some-> rows peek :modified-at) - (map #(->> % :data blob/decode) rows)]))] + (map (comp blob/decode :data) rows)]))] (d/iteration get-chunk :vf second @@ -197,28 +196,30 @@ [conn library-id library-data] (let [find-used-components-file (fn [components file-data] - ; Find what of the components are used in the file. - (d/filterm #(ctf/used-in? file-data library-id (second %) :component) - components)) + ; Find which of the components are used in the file. + (into #{} + (filter #(ctf/used-in? file-data library-id % :component)) + components)) find-used-components (fn [components files-data] ; Find what components are used in any of the files. (loop [files-data files-data components components - used-components {}] + used-components #{}] (let [file-data (first files-data)] (if (or (nil? file-data) (empty? components)) used-components (let [used-components-file (find-used-components-file components file-data)] (recur (rest files-data) - (d/filterm #(not (contains? used-components-file (:id %))) components) + (into #{} (remove used-components-file) components) (into used-components used-components-file))))))) - deleted-components (:deleted-components library-data) - saved-components (find-used-components deleted-components - (cons library-data - (retrieve-client-files conn library-id))) + deleted-components (set (vals (:deleted-components library-data))) + saved-components (find-used-components deleted-components + (cons library-data + (retrieve-client-files conn library-id))) + new-deleted-components (d/index-by :id (vec saved-components)) total (- (count deleted-components) (count saved-components))] @@ -226,7 +227,7 @@ (when-not (zero? total) (l/debug :hint "clean deleted components" :total total) (let [new-data (-> library-data - (assoc :deleted-components saved-components) + (assoc :deleted-components new-deleted-components) (blob/encode))] (db/update! conn :file {:data new-data} From eb7f93d2e6e37c0fced579a43409d1ce260d0b87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 4 Oct 2022 15:26:14 +0200 Subject: [PATCH 080/682] :bug: Make restore component work in external libraries --- .../app/main/data/workspace/libraries.cljs | 21 ++++++++++--------- .../app/main/ui/workspace/context_menu.cljs | 8 +++---- .../sidebar/options/menus/component.cljs | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 7a97c584f5..b7e22d304d 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -416,28 +416,29 @@ (rx/of (dch/commit-changes changes)))))))) (defn restore-component - "Restore a deleted component, with the given id, on the current file library." - [id] - (us/assert ::us/uuid id) + "Restore a deleted component, with the given id, in the given file library." + [library-id component-id] + (us/assert ::us/uuid library-id) + (us/assert ::us/uuid component-id) (ptk/reify ::restore-component ptk/WatchEvent (watch [it state _] - (let [data (get state :workspace-data) - component (ctf/get-deleted-component data id) - page (ctpl/get-page data (:main-instance-page component)) + (let [file-data (wsh/get-file state library-id) + component (ctf/get-deleted-component file-data component-id) + page (ctpl/get-page file-data (:main-instance-page component)) ; Make a new main instance, with the same id of the original [_main-instance shapes] (ctn/make-component-instance page component - (:id data) + (:id file-data) (gpt/point (:main-instance-x component) (:main-instance-y component)) {:main-instance? true :force-id (:main-instance-id component)}) changes (-> (pcb/empty-changes it) - (pcb/with-library-data data) + (pcb/with-library-data file-data) (pcb/with-page page)) changes (reduce #(pcb/add-object %1 %2 {:ignore-touched true}) @@ -446,9 +447,9 @@ ; restore-component change needs to be done after add main instance ; because when undo changes, the orden is inverse - changes (pcb/restore-component changes id)] + changes (pcb/restore-component changes component-id)] - (rx/of (dch/commit-changes changes)))))) + (rx/of (dch/commit-changes (assoc changes :file-id library-id))))))) (defn instantiate-component diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index a21ac84513..3e2f487717 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -372,10 +372,10 @@ has-component? (some true? (map #(contains? % :component-id) shapes)) is-component? (and single? (-> shapes first :component-id some?)) - shape-id (->> shapes first :id) - component-id (->> shapes first :component-id) + shape-id (-> shapes first :id) + component-id (-> shapes first :component-id) component-file (-> shapes first :component-file) - main-component? (->> shapes first :main-instance?) + main-component? (-> shapes first :main-instance?) component-shapes (filter #(contains? % :component-id) shapes) components-v2 (features/use-feature :components-v2) @@ -397,7 +397,7 @@ do-navigate-component-file #(st/emit! (dwl/nav-to-component-file component-file)) do-update-component #(st/emit! (dwl/update-component-sync shape-id component-file)) do-update-component-in-bulk #(st/emit! (dwl/update-component-in-bulk component-shapes component-file)) - do-restore-component #(st/emit! (dwl/restore-component component-id)) + do-restore-component #(st/emit! (dwl/restore-component component-file component-id)) _do-update-remote-component #(st/emit! (modal/show diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 49ccd63014..91d2ab9c77 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -64,7 +64,7 @@ #(st/emit! (dwl/update-component-sync id library-id)) do-restore-component - #(st/emit! (dwl/restore-component component-id)) + #(st/emit! (dwl/restore-component library-id component-id)) _do-update-remote-component #(st/emit! (modal/show From f466d7a48487549b61f9ceec856fc653d2e1e80d Mon Sep 17 00:00:00 2001 From: Antonio Date: Mon, 3 Oct 2022 13:58:27 +0000 Subject: [PATCH 081/682] :globe_with_meridians: Add translations for: Catalan. Currently translated at 96.2% (1164 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/ --- frontend/translations/ca.po | 96 ++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 6 deletions(-) diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index f8423e7372..8751728c14 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-05 12:17+0000\n" -"Last-Translator: Rubén \n" -"Language-Team: Catalan " -"\n" +"PO-Revision-Date: 2022-10-04 18:22+0000\n" +"Last-Translator: Antonio \n" +"Language-Team: Catalan \n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -4402,4 +4402,88 @@ msgid "workspace.updates.update" msgstr "Actualitza" msgid "workspace.viewport.click-to-close-path" -msgstr "Feu clic per a tancar el camí" \ No newline at end of file +msgstr "Feu clic per a tancar el camí" + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Aquest fitxer ja s'ha utilitzat amb els Components V2 habilitats." + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "" +"Hi ha hagut un problema en importar la plantilla. La plantilla no s'ha " +"importat." + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Atenció" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Suprimeix el fitxer" +msgstr[1] "Suprimeix els fitxers" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Esteu segur que voleu suprimir aquest fitxer?" +msgstr[1] "Esteu segur que voleu suprimir aquests fitxers?" + +msgid "common.unpublish" +msgstr "Despublica" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "D'acord" + +msgid "common.publish" +msgstr "Publica" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Gestió de l'equip" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"El Penpot està pensat per a equips. Convida a membres i treballeu plegats en " +"projectes i fitxers" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Fes equip!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Aprèn les bases del Penpot mentre et diverteixes amb aquest tutorial pràctic." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Comença el tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial pràctic" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Passeja't pel Penpot i coneix-ne les característiques principals." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Comença la visita" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Passeig per la interfície" + +msgid "dashboard.libraries-and-templates" +msgstr "Biblioteques i plantilles" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Explora'n més i coneix com contribuir-hi" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Despublica la biblioteca" From 5bdc2cc25dbc539e803e8650c66a01a413dc51a4 Mon Sep 17 00:00:00 2001 From: Youkho Date: Sat, 1 Oct 2022 14:59:39 +0000 Subject: [PATCH 082/682] :globe_with_meridians: Add translations for: Arabic. Currently translated at 65.0% (786 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 253 +++++++++++++++++++++++++++++++++++- 1 file changed, 249 insertions(+), 4 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index c4dc77b764..95b2aae8a4 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-01 14:17+0000\n" -"Last-Translator: Shuaib Zahda \n" +"PO-Revision-Date: 2022-10-04 18:22+0000\n" +"Last-Translator: Youkho \n" "Language-Team: Arabic \n" "Language: ar\n" @@ -1111,8 +1111,8 @@ msgstr "عفواً!" msgid "labels.num-of-files" msgid_plural "labels.num-of-files" msgstr[0] "0 ملف" -msgstr[1] "ملفين" -msgstr[2] "3 ملفات" +msgstr[1] "ملف" +msgstr[2] "ملفين" msgstr[3] "قليل" msgstr[4] "كثير" msgstr[5] "غير ذلك" @@ -2774,3 +2774,248 @@ msgstr "مجانا 100٪ !" msgid "onboarding.team-modal.create-team-feature-4" msgstr "عدد غير محدود من الأعضاء" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "محاذاة" + +msgid "shortcuts.align-hcenter" +msgstr "محاذاة المركز أفقيًا" + +msgid "shortcut-section.workspace" +msgstr "مساحة العمل" + +# SECTIONS +msgid "shortcut-section.basics" +msgstr "الأساسيات" + +msgid "shortcut-section.dashboard" +msgstr "لوحة القيادة" + +msgid "shortcuts.align-bottom" +msgstr "محاذاة لأسفل" + +msgid "shortcuts.align-right" +msgstr "محاذاة اليمين" + +msgid "shortcuts.draw-rect" +msgstr "مستطيل" + +msgid "shortcuts.draw-path" +msgstr "المسار" + +msgid "shortcuts.draw-text" +msgstr "نص" + +msgid "shortcuts.export-shapes" +msgstr "تصدير الأشكال" + +msgid "shortcuts.make-corner" +msgstr "إصنع زاوية" + +msgid "shortcuts.go-to-search" +msgstr "بحث" + +msgid "shortcuts.go-to-libs" +msgstr "إذهب إلى المكتبات المشتركة" + +msgid "shortcuts.hide-ui" +msgstr "إظهار / إخفاء واجهة المستخدم" + +msgid "shortcuts.insert-image" +msgstr "إدراج صورة" + +msgid "shortcuts.join-nodes" +msgstr "ربط العقد" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +msgid "onboarding.welcome.title" +msgstr "مرحبًا بك في Penpot" + +msgid "shortcut-subsection.edit" +msgstr "تعديل" + +msgid "shortcut-subsection.main-menu" +msgstr "القائمة الرئيسية" + +msgid "shortcut-subsection.modify-layers" +msgstr "تعديل الطبقات" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "التنقل" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "التنقل" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "التنقل" + +msgid "shortcut-subsection.panels" +msgstr "اللوحات" + +msgid "shortcut-subsection.path-editor" +msgstr "مسارات" + +msgid "shortcut-subsection.shape" +msgstr "الأشكال" + +msgid "shortcut-subsection.tools" +msgstr "أدوات" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "تكبير" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "تكبير" + +msgid "shortcuts.add-comment" +msgstr "تعليقات" + +msgid "shortcuts.add-node" +msgstr "إضافة عقدة" + +msgid "shortcuts.align-vcenter" +msgstr "محاذاة المركز عموديًا" + +msgid "shortcuts.artboard-selection" +msgstr "إنشاء لوحة من الاختيار" + +msgid "shortcuts.bring-back" +msgstr "أرسل إلى الخلف" + +msgid "shortcuts.bring-backward" +msgstr "إرسال إلى الوراء" + +msgid "shortcuts.bring-forward" +msgstr "ثابر للأمام" + +msgid "shortcuts.bring-front" +msgstr "أحضر إلى الأمام" + +msgid "shortcuts.clear-undo" +msgstr "مسح التراجع" + +msgid "shortcuts.copy" +msgstr "إنسخ" + +msgid "shortcuts.create-component" +msgstr "تكوين المكون" + +msgid "shortcuts.cut" +msgstr "إقطع" + +msgid "shortcuts.decrease-zoom" +msgstr "تصغير" + +msgid "shortcuts.increase-zoom" +msgstr "تكبير" + +msgid "shortcuts.make-curve" +msgstr "إصنع منحنى" + +msgid "shortcuts.mask" +msgstr "قناع" + +msgid "shortcuts.align-left" +msgstr "محاذاة اليسار" + +msgid "shortcuts.align-top" +msgstr "‌محاذاة الأعلى" + +msgid "shortcuts.delete" +msgstr "حذف" + +msgid "shortcuts.delete-node" +msgstr "حذف العقدة" + +msgid "shortcuts.detach-component" +msgstr "إفصل المكون" + +msgid "shortcuts.draw-curve" +msgstr "منحنى" + +msgid "shortcuts.draw-ellipse" +msgstr "الشكل البيضاوي" + +msgid "shortcuts.draw-frame" +msgstr "لوحة" + +msgid "shortcuts.draw-nodes" +msgstr "أرسم المسار" + +msgid "shortcuts.duplicate" +msgstr "كرر" + +msgid "shortcuts.escape" +msgstr "إلغي" + +msgid "shortcuts.fit-all" +msgstr "تكبير لتناسب الجميع" + +msgid "shortcuts.flip-horizontal" +msgstr "قلب أفقيًا" + +msgid "shortcuts.flip-vertical" +msgstr "قلب عموديًا" + +msgid "shortcuts.go-to-drafts" +msgstr "إنتقل إلى المسودات" + +msgid "shortcuts.group" +msgstr "مجموعة" + +msgid "shortcuts.h-distribute" +msgstr "وزع أفقيًا" + +msgid "onboarding.templates.title" +msgstr "إبدأ التصميم" + +msgid "onboarding.templates.subtitle" +msgstr "فيما يلي بعض القوالب." + +msgid "shortcuts.merge-nodes" +msgstr "دمج العقد" + +msgid "shortcuts.move-fast-up" +msgstr "تحرك للأعلى بسرعة" + +msgid "shortcuts.move" +msgstr "تحرك" + +msgid "shortcuts.move-fast-down" +msgstr "تحرك بسرعة لأسفل" + +msgid "shortcuts.move-fast-left" +msgstr "تحرك يسارا بسرعة" + +msgid "shortcuts.move-fast-right" +msgstr "تحرك يميناً بسرعة" + +msgid "shortcuts.move-nodes" +msgstr "نقل العقدة" + +msgid "shortcuts.move-unit-down" +msgstr "تحرك لأسفل" + +msgid "shortcuts.move-unit-right" +msgstr "تحرك يميناً" + +msgid "shortcuts.move-unit-left" +msgstr "تحرك يساراً" + +msgid "shortcuts.move-unit-up" +msgstr "تحرك للأعلى" + +msgid "shortcuts.next-frame" +msgstr "اللوحة التالية" + +msgid "shortcut-section.viewer" +msgstr "مشاهد" + +msgid "shortcut-subsection.general-viewer" +msgstr "عام" + +msgid "shortcut-subsection.general-dashboard" +msgstr "عام" From ccb17e68e25531c20db0a5e7ec4196b5d4dac1b5 Mon Sep 17 00:00:00 2001 From: Kevin Nowald Date: Mon, 3 Oct 2022 17:33:28 +0000 Subject: [PATCH 083/682] :globe_with_meridians: Add translations for: Polish. Currently translated at 94.8% (1147 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pl/ --- frontend/translations/pl.po | 250 +++++++++++++++++++++++++++++++++++- 1 file changed, 244 insertions(+), 6 deletions(-) diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po index 54f4a1af98..60c4ecf15a 100644 --- a/frontend/translations/pl.po +++ b/frontend/translations/pl.po @@ -1,16 +1,16 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-05-28 21:16+0000\n" -"Last-Translator: Radek Sawicki \n" -"Language-Team: Polish " -"\n" +"PO-Revision-Date: 2022-10-04 18:22+0000\n" +"Last-Translator: Kevin Nowald \n" +"Language-Team: Polish \n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" -"X-Generator: Weblate 4.13-dev\n" +"X-Generator: Weblate 4.14.1\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -4175,4 +4175,242 @@ msgid "workspace.updates.update" msgstr "Aktualizuj" msgid "workspace.viewport.click-to-close-path" -msgstr "Kliknij, aby zamknąć ścieżkę" \ No newline at end of file +msgstr "Kliknij, aby zamknąć ścieżkę" + +msgid "common.share-link.current-tag" +msgstr "(aktualne)" + +msgid "common.share-link.destroy-link" +msgstr "Usuń link" + +msgid "common.share-link.view-all" +msgstr "Wybierz wszystko" + +msgid "common.share-link.all-users" +msgstr "Wszyscy użytkownicy Penpot" + +msgid "common.share-link.permissions-can-comment" +msgstr "Może komentować" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Może sprawdzać kod" + +msgid "common.share-link.permissions-pages" +msgstr "Udostępnione strony" + +msgid "common.share-link.team-members" +msgstr "Tylko członkowie zespołu" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"Cieszymy się, że tu jesteś. Jeśli potrzebujesz pomocy, poszukaj jej zanim " +"napiszesz." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Społeczność" + +msgid "labels.log-or-sign" +msgstr "Zaloguj się lub zarejestruj" + +msgid "labels.show-comments-list" +msgstr "Pokaż listę komentarzy" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "Prawa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Góra" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "środek" + +msgid "dashboard.export-binary-multi" +msgstr "Pobierz %s plików Penpot (.penpot)" + +msgid "dashboard.libraries-and-templates" +msgstr "Biblioteki i szablony" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Wystąpił problem z importem szablonu. Szablon nie został zaimportowany." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "Służymy pomocą w kwestiach technicznych." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot jest przeznaczony dla zespołów. Zaproś członków do wspólnej pracy nad " +"projektami i plikami" + +msgid "dashboard.export-standard-multi" +msgstr "Pobierz %s plików standardowych (.svg + .json)" + +msgid "dashboard.download-standard-file" +msgstr "Pobierz plik standardowy (.svg + .json)" + +msgid "common.share-link.manage-ops" +msgstr "Zarządzaj uprawnieniami" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Społeczność Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "Przejdź do Twittera" + +msgid "labels.continue-with-penpot" +msgstr "Możesz kontynuować z kontem Penpot" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Czy na pewno chcesz usunąć ten plik?" +msgstr[1] "Czy na pewno chcesz usunąć te pliki?" +msgstr[2] "Czy na pewno chcesz usunąć te pliki?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Te pliki mają biblioteki, które są używane w tym pliku:" +msgstr[1] "Te pliki mają biblioteki, które są używane w tych plikach:" +msgstr[2] "Te pliki mają biblioteki, które są używane w tych plikach:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Ten plik ma biblioteki używane w tym pliku:" +msgstr[1] "Ten plik ma biblioteki używane w tych plikach:" +msgstr[2] "Ten plik ma biblioteki używane w tych plikach:" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Dół" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Kolumna" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding" +msgstr "Padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "Wiersz" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "dół" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "góra" + +msgid "common.unpublish" +msgstr "Cofnij publikację" + +#: src/app/main/ui/dashboard/projects.cljs +#, fuzzy +msgid "dasboard.walkthrough-hero.title" +msgstr "Przewodnik po interfejsie" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Uwaga" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Przejdź do forum Penpot" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Min.Wysokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Min.Szerokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Maksymalna wysokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Maksymalna szerokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Minimalna wysokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Minimalna szerokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Margines" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Wybierz się na spacer po Penpot i poznaj jego główne funkcje." + +msgid "dashboard.download-binary-file" +msgstr "Pobierz plik Penpot (.penpot)" + +msgid "common.publish" +msgstr "Opublikuj" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Zarządzanie zespołem" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Połącz siły!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Naucz się podstaw obsługi Penpot, bawiąc się tym praktycznym tutorialem." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Zacznij tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Praktyczny Tutorial" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Poznaj więcej z nich i dowiedz się, jak pomóc" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Cofnij publikację biblioteki" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Usuń plik" +msgstr[1] "Usuń pliki" +msgstr[2] "Usuń pliki" + +msgid "workspace.shape.menu.restore-main" +msgstr "Przywróć główny komponent" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 udostępniona strona" +msgstr[1] "%s udostępnione strony" +msgstr[2] "%s udostępnionych stron" From fc4461089356ed9bfcd6c2b36b9114599665ad2e Mon Sep 17 00:00:00 2001 From: Zvonimir Juranko Date: Sat, 1 Oct 2022 17:33:19 +0000 Subject: [PATCH 084/682] :globe_with_meridians: Add translations for: Croatian. Currently translated at 93.0% (1125 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/hr/ --- frontend/translations/hr.po | 528 ++++++++++++++++++++++++++++-------- 1 file changed, 416 insertions(+), 112 deletions(-) diff --git a/frontend/translations/hr.po b/frontend/translations/hr.po index e58da43f93..1fc5be9998 100644 --- a/frontend/translations/hr.po +++ b/frontend/translations/hr.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-01 14:17+0000\n" +"PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Zvonimir Juranko \n" "Language-Team: Croatian \n" @@ -91,7 +91,6 @@ msgid "workspace.options.interaction-pos-center" msgstr "Sredina" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -#, fuzzy msgid "workspace.options.interaction-preserve-scroll" msgstr "Sačuvaj položaj scrolanja" @@ -291,7 +290,6 @@ msgid "dasboard.team-hero.title" msgstr "Udruži se!" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -#, fuzzy msgid "dashboard.add-shared" msgstr "Dodaj kao zajedničku biblioteku" @@ -340,7 +338,6 @@ msgstr "" "Biblioteke i predlošci](https://penpot.app/libraries-templates.html)" #: src/app/main/ui/dashboard/file_menu.cljs -#, fuzzy msgid "dashboard.duplicate-multi" msgstr "Kopiraj %s datoteka" @@ -348,9 +345,8 @@ msgstr "Kopiraj %s datoteka" msgid "dashboard.export-shapes" msgstr "Izvezi" -#, fuzzy msgid "dashboard.export-multi" -msgstr "Izvezi %s Penpot datoteka" +msgstr "Izvezi Penpot %s datoteka" #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.title" @@ -365,10 +361,9 @@ msgstr "* Može uključivati komponente, grafike, boje i/ili tipografije." msgid "dashboard.export.options.all.title" msgstr "Izvezi zajedničke biblioteke" -#, fuzzy msgid "dashboard.export.explain" msgstr "" -"Jedna ili više datoteka koju želiš izvesti koriste zajedničke biblioteke. " +"Jedna ili više datoteka koju želiš izvesti koristi zajedničke biblioteke. " "Što želiš učiniti s njihovim stavkama*?" msgid "dashboard.export.options.detach.title" @@ -383,7 +378,7 @@ msgid "dashboard.fonts.fonts-added" msgid_plural "dashboard.fonts.fonts-added" msgstr[0] "1 font dodan" msgstr[1] "%s fontova dodano" -msgstr[2] "" +msgstr[2] "%s fontova dodano" #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -415,7 +410,6 @@ msgstr "Napusti tim" msgid "dashboard.libraries-and-templates" msgstr "Biblioteke i predlošci" -#, fuzzy msgid "dashboard.libraries-and-templates.explore" msgstr "Istraži više njih i saznaj kako doprinijeti" @@ -571,16 +565,14 @@ msgid "errors.generic" msgstr "Dogodilo se nešto loše." #: src/app/main/ui/auth/login.cljs -#, fuzzy msgid "errors.google-auth-not-enabled" -msgstr "Autentifikacija s Googleom onemogućena je na backendu" +msgstr "Autentifikacija s Googleom je onemogućena na backendu" #: src/app/main/ui/components/color_input.cljs msgid "errors.invalid-color" msgstr "Pogrešna boja" #: src/app/main/ui/auth/verify_token.cljs -#, fuzzy msgid "errors.invite-invalid" msgstr "Pogrešna pozivnica" @@ -600,13 +592,11 @@ msgid "errors.media-type-not-allowed" msgstr "Čini se da ovo nije važeća slika." #: src/app/main/ui/dashboard/team.cljs -#, fuzzy msgid "errors.member-is-muted" msgstr "" "Profil koji pozivaš ima isključen e-email (izvješća o neželjenoj pošti ili " "veliki broj odbijanja)." -#, fuzzy msgid "errors.network" msgstr "Nije moguće povezati se s backend poslužiteljem." @@ -654,7 +644,6 @@ msgid "feedback.chat-start" msgstr "Pridruži se chatu" #: src/app/main/ui/settings/feedback.cljs -#, fuzzy msgid "feedback.subject" msgstr "Tema" @@ -827,13 +816,11 @@ msgstr "Krug" msgid "handoff.tabs.code.selected.component" msgstr "Komponenta" -#, fuzzy msgid "handoff.tabs.code.selected.curve" -msgstr "Krivina" +msgstr "Krivulja" -#, fuzzy msgid "handoff.tabs.code.selected.frame" -msgstr "Tabla" +msgstr "Ploča" msgid "handoff.tabs.code.selected.group" msgstr "Grupa" @@ -898,7 +885,6 @@ msgid "labels.continue-with-penpot" msgstr "Možeš nastaviti s Penpot računom" #: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs -#, fuzzy msgid "labels.create-team" msgstr "Kreiraj novi tim" @@ -925,7 +911,6 @@ msgid "labels.delete-comment" msgstr "Izbriši komentar" #: src/app/main/ui/comments.cljs -#, fuzzy msgid "labels.delete-comment-thread" msgstr "Izbriši thread" @@ -1067,20 +1052,20 @@ msgid "labels.num-of-files" msgid_plural "labels.num-of-files" msgstr[0] "1 datoteka" msgstr[1] "%s datoteka" -msgstr[2] "" +msgstr[2] "%s datoteka" msgid "labels.num-of-frames" msgid_plural "labels.num-of-frames" msgstr[0] "1 ploča" msgstr[1] "%s ploča" -msgstr[2] "" +msgstr[2] "%s ploča" #: src/app/main/ui/dashboard/team.cljs msgid "labels.num-of-projects" msgid_plural "labels.num-of-projects" msgstr[0] "1 projekt" msgstr[1] "%s projekata" -msgstr[2] "" +msgstr[2] "%s projekata" #: src/app/main/ui/settings/password.cljs msgid "labels.old-password" @@ -1188,7 +1173,6 @@ msgid "labels.upload-custom-fonts" msgstr "Prenesi custom fontove" #: src/app/main/ui/settings/profile.cljs -#, fuzzy msgid "labels.update" msgstr "Ažuriraj" @@ -1224,7 +1208,6 @@ msgstr "" "identitet." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -#, fuzzy msgid "modals.add-shared-confirm.accept" msgstr "Dodaj kao zajedničku biblioteku" @@ -1274,7 +1257,6 @@ msgid "modals.delete-comment-thread.accept" msgstr "Obriši razgovor" #: src/app/main/ui/comments.cljs -#, fuzzy msgid "modals.delete-comment-thread.message" msgstr "" "Jesi li siguran/na da želiš izbrisati ovaj razgovor? Svi komentari u ovoj " @@ -1328,28 +1310,28 @@ msgid "modals.delete-shared-confirm.accept" msgid_plural "modals.delete-shared-confirm.accept" msgstr[0] "Izbriši datoteku" msgstr[1] "Izbriši datoteke" -msgstr[2] "" +msgstr[2] "Izbriši datoteke" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message" msgid_plural "modals.delete-shared-confirm.scd-message" msgstr[0] "Ova datoteka ima biblioteke koje se koriste u ovoj datoteci:" msgstr[1] "Ova datoteka ima biblioteke koje se koriste u ovim datotekama:" -msgstr[2] "" +msgstr[2] "Ova datoteka ima biblioteke koje se koriste u ovim datotekama:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message-plural" msgid_plural "modals.delete-shared-confirm.scd-message-plural" msgstr[0] "Ove datoteke imaju biblioteke koje se koriste u ovoj datoteci:" msgstr[1] "Ove datoteke imaju biblioteke koje se koriste u ovim datotekama:" -msgstr[2] "" +msgstr[2] "Ove datoteke imaju biblioteke koje se koriste u ovim datotekama:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" msgid_plural "modals.delete-shared-confirm.title" msgstr[0] "Brisanje datoteke" msgstr[1] "Brisanje datoteka" -msgstr[2] "" +msgstr[2] "Brisanje datoteka" #: src/app/main/ui/delete_shared.cljs msgid "modals.delete-shared.title" @@ -1454,26 +1436,25 @@ msgid "modals.nudge-title" msgstr "Nudge amount" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -#, fuzzy msgid "modals.unpublish-shared-confirm.message" msgid_plural "modals.unpublish-shared-confirm.message" msgstr[0] "Jesi li siguran/na da želiš poništiti objavu ove biblioteke?" msgstr[1] "Jesi li siguran/na da želiš poništiti objavu ovih biblioteka?" -msgstr[2] "" +msgstr[2] "Jesi li siguran/na da želiš poništiti objavu ovih biblioteka?" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" msgstr[0] "Koristi se u ovoj datoteci:" msgstr[1] "Koristi se u ovim datotekama:" -msgstr[2] "" +msgstr[2] "Koristi se u ovim datotekama:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.title" msgid_plural "modals.unpublish-shared-confirm.title" msgstr[0] "Poništi objavu biblioteke" msgstr[1] "Poništi objavu biblioteka" -msgstr[2] "" +msgstr[2] "Poništi objavu biblioteka" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" @@ -1512,16 +1493,13 @@ msgstr "Profil je uspješno spremljen!" msgid "notifications.validation-email-sent" msgstr "E-mail za potvrdu poslana je na %s. Provjeri e-email!" -#, fuzzy msgid "onboarding-v2.before-start.desc1" msgstr "" "Postoji mnogo dostupnih resursa koji će ti pomoći da počneš koristiti " "Penpot, poput korisničkog vodiča i našeg Youtube kanala." -#, fuzzy msgid "onboarding-v2.before-start.desc3" -msgstr "" -"Možeš gledati naše tutorijale i tutorijale koje je napravila naša zajednica." +msgstr "Možeš gledati naše upute i upute koje je izradila naša zajednica." msgid "onboarding-v2.before-start.desc3.title" msgstr "Video tutorijali" @@ -1560,14 +1538,12 @@ msgstr "" msgid "onboarding.contrib.desc2.1" msgstr "Možeš pristupiti" -#, fuzzy msgid "onboarding.contrib.desc2.2" msgstr "i slijedi upute za doprinos :)" msgid "onboarding.contrib.link" msgstr "projekt na githubu" -#, fuzzy msgid "onboarding.contrib.title" msgstr "Open Source suradnik?" @@ -1589,11 +1565,9 @@ msgstr "Dizajniraj biblioteke, stilove i komponente" msgid "onboarding.slide.1.alt" msgstr "Interaktivni prototipovi" -#, fuzzy msgid "onboarding.slide.1.desc1" msgstr "Stvaraj bogate interakcije kako bi oponašao/la ponašanje proizvoda." -#, fuzzy msgid "onboarding.slide.1.desc2" msgstr "" "Podijeli sa stakeholderima, predstavi prijedloge svom timu i započni " @@ -1612,7 +1586,6 @@ msgstr "" "i centraliziranim komentarima, idejama i povratnim informacijama izravno " "preko dizajna." -#, fuzzy msgid "onboarding.slide.3.alt" msgstr "Handoff i lowcode" @@ -1682,9 +1655,8 @@ msgstr "Komentari" msgid "shortcuts.add-node" msgstr "Dodaj čvor" -#, fuzzy msgid "shortcuts.align-bottom" -msgstr "Poravnaj dolje" +msgstr "Poravnaj dno" msgid "shortcuts.align-hcenter" msgstr "Poravnaj sredinu vodoravno" @@ -1695,9 +1667,8 @@ msgstr "Poravnaj lijevo" msgid "shortcuts.align-right" msgstr "Poravnaj desno" -#, fuzzy msgid "shortcuts.align-top" -msgstr "Poravnaj gore" +msgstr "Poravnaj vrh" msgid "shortcuts.artboard-selection" msgstr "Kreiraj ploču iz odabira" @@ -1705,13 +1676,11 @@ msgstr "Kreiraj ploču iz odabira" msgid "shortcuts.bool-difference" msgstr "Boolean razlika" -#, fuzzy msgid "shortcuts.bool-exclude" msgstr "Boolean isključenje" -#, fuzzy msgid "shortcuts.bool-intersection" -msgstr "Boolean intersection" +msgstr "Boolean presijek" msgid "shortcuts.bool-union" msgstr "Boolean unija" @@ -1760,9 +1729,8 @@ msgstr "Izbriši čvor" msgid "shortcuts.detach-component" msgstr "Odvoji komponentu" -#, fuzzy msgid "shortcuts.draw-curve" -msgstr "Curve" +msgstr "Krivulja" msgid "shortcuts.draw-ellipse" msgstr "Elipsa" @@ -1801,7 +1769,7 @@ msgid "shortcuts.make-corner" msgstr "Izradi rub" msgid "shortcuts.make-curve" -msgstr "Izradi krivinu" +msgstr "Izradi krivulju" msgid "shortcuts.mask" msgstr "Maskiraj" @@ -1842,16 +1810,14 @@ msgstr "Premijesti gore" msgid "shortcuts.h-distribute" msgstr "Distribuiraj vodoravno" -#, fuzzy msgid "shortcuts.fit-all" msgstr "Zumiraj da stane sve" msgid "shortcuts.open-dashboard" msgstr "Idi na nadzornu ploču" -#, fuzzy msgid "shortcuts.open-handoff" -msgstr "Idi na odjeljak primopredaje gledatelja" +msgstr "Idi na odjeljak primopredaje" msgid "shortcuts.or" msgstr " ili " @@ -1859,7 +1825,6 @@ msgstr " ili " msgid "shortcuts.paste" msgstr "Zaljepi" -#, fuzzy msgid "shortcuts.prev-frame" msgstr "Prethodna ploča" @@ -1869,7 +1834,6 @@ msgstr "Ponovi" msgid "shortcuts.reset-zoom" msgstr "Resetiraj zoom" -#, fuzzy msgid "shortcuts.search-placeholder" msgstr "Pretraži prečace" @@ -1902,7 +1866,6 @@ msgstr "Priključi na \"pixel grid\"" msgid "shortcuts.stop-measure" msgstr "Zaustavi mjerenje" -#, fuzzy msgid "shortcuts.thumbnail-set" msgstr "Postavi sličice" @@ -1949,7 +1912,7 @@ msgstr "Promijeni vidljivost" #, fuzzy msgid "shortcuts.undo" -msgstr "Poništi" +msgstr "Undo" msgid "shortcuts.ungroup" msgstr "Razgrupiraj" @@ -2018,7 +1981,6 @@ msgstr "Ne prikazuj interakcije" msgid "viewer.header.fullscreen" msgstr "Cijeli zaslon" -#, fuzzy msgid "viewer.header.handoff-section" msgstr "Handoff (%s)" @@ -2229,7 +2191,6 @@ msgid "workspace.header.menu.option.help-info" msgstr "Pomoć i informacije" #: src/app/main/ui/workspace/header.cljs -#, fuzzy msgid "workspace.header.menu.option.preferences" msgstr "Preferencije" @@ -2278,7 +2239,6 @@ msgid "workspace.header.viewer" msgstr "Način prikaza (%s)" #: src/app/main/ui/workspace/header.cljs -#, fuzzy msgid "workspace.header.zoom-fill" msgstr "Ispuna - Skaliraj za popunjavanje" @@ -2367,7 +2327,6 @@ msgid "workspace.libraries.no-libraries-need-sync" msgstr "Ne postoje zajedničke biblioteke koje je potrebno ažurirati" #: src/app/main/ui/workspace/libraries.cljs -#, fuzzy msgid "workspace.libraries.search-shared-libraries" msgstr "Pretraži zajedničke biblioteke" @@ -2376,12 +2335,10 @@ msgid "workspace.libraries.no-shared-libraries-available" msgstr "Nema dostupnih zajedničkih biblioteka" #: src/app/main/ui/workspace/libraries.cljs -#, fuzzy msgid "workspace.libraries.shared-libraries" msgstr "ZAJEDNIČKE BIBLIOTEKE" #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -#, fuzzy msgid "workspace.libraries.text.multiple-typography" msgstr "Višestruke tipografije" @@ -2433,11 +2390,9 @@ msgid "workspace.options.blur-options.title.multiple" msgstr "Odabir zamućenja" #: src/app/main/ui/workspace/sidebar/options/page.cljs -#, fuzzy msgid "workspace.options.canvas-background" msgstr "Pozadina canvasa" -#, fuzzy msgid "workspace.options.clip-content" msgstr "Isjeci sadržaj" @@ -2489,7 +2444,6 @@ msgid "workspace.options.constraints.top" msgstr "Vrh" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -#, fuzzy msgid "workspace.options.constraints.topbottom" msgstr "Vrh i dno" @@ -2510,7 +2464,7 @@ msgid "workspace.options.export-object" msgid_plural "workspace.options.export-object" msgstr[0] "Izvezi 1 element" msgstr[1] "Izvezi %s elemenata" -msgstr[2] "" +msgstr[2] "Izvezi %s elemenata" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -2539,7 +2493,7 @@ msgstr "Ispuni" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs #, fuzzy msgid "workspace.options.flows.add-flow-start" -msgstr "Dodaj početak toka" +msgstr "Dodaj početak flowa" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs #, fuzzy @@ -2549,7 +2503,7 @@ msgstr "Početak toka" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs #, fuzzy msgid "workspace.options.flows.flow-starts" -msgstr "Tok započinje" +msgstr "Flow započinje" #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs #, fuzzy @@ -2716,7 +2670,6 @@ msgid "workspace.options.interaction-self" msgstr "sebe" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -#, fuzzy msgid "workspace.options.interaction-toggle-overlay" msgstr "Uključi/isključi preklapanje" @@ -2725,7 +2678,6 @@ msgid "workspace.options.interaction-toggle-overlay-dest" msgstr "Uključi/isključi preklapanje: %s" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -#, fuzzy msgid "workspace.options.interaction-while-hovering" msgstr "Na hover" @@ -2925,7 +2877,6 @@ msgid "workspace.options.layout.space-between" msgstr "prostor između" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -#, fuzzy msgid "workspace.options.layout.title" msgstr "Layout" @@ -3128,7 +3079,6 @@ msgid "workspace.options.text-options.title-selection" msgstr "Selektiraj tekst" #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -#, fuzzy msgid "workspace.options.text-options.titlecase" msgstr "Velika i mala slova" @@ -3144,10 +3094,8 @@ msgid "workspace.options.text-options.vertical-align" msgstr "Okomito poravnanje" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -#, fuzzy msgid "workspace.options.use-play-button" -msgstr "" -"Upotrijebi gumb za reprodukciju u zaglavlju za pokretanje prikaza prototipa." +msgstr "Upotrijebi play gumb u zaglavlju za pokretanje prikaza prototipa." msgid "workspace.options.width" msgstr "Širina" @@ -3350,12 +3298,11 @@ msgstr "Započni obilazak" msgid "common.share-link.permissions-can-comment" msgstr "Dopušten komentar" -#, fuzzy msgid "common.share-link.page-shared" msgid_plural "common.share-link.page-shared" msgstr[0] "1 stranica podijeljena" msgstr[1] "%s stranica podijeljeno" -msgstr[2] "" +msgstr[2] "%s stranica podijeljeno" msgid "common.share-link.permissions-can-inspect" msgstr "Dopušteno provjeriti kod" @@ -3405,7 +3352,6 @@ msgid "dashboard.export-frames.title" msgstr "Izvezi u PDF" #: src/app/main/ui/export.cljs -#, fuzzy msgid "dashboard.export-shapes.how-to" msgstr "" "Postavke izvoza možeš dodati elementima iz svojstava dizajna (na dnu desne " @@ -3455,14 +3401,12 @@ msgstr "" "pružanja usluge](https://penpot.app/terms.html). Također možeš pročitati o [" "licenciranju fontova](https://www.typography.com/faq)." -#, fuzzy msgid "dashboard.import.progress.process-page" msgstr "Obrada stranice: %s" msgid "dashboard.import.import-error" msgstr "Došlo je do problema pri uvozu datoteke. Datoteka nije uvezena." -#, fuzzy msgid "dashboard.import.import-message" msgstr "%s datoteka je uspješno uvezeno." @@ -3480,7 +3424,6 @@ msgstr "%s - %s elementa označeno" msgid "dashboard.import.progress.process-components" msgstr "Obrada komponenti" -#, fuzzy msgid "dashboard.import.progress.process-media" msgstr "Obrada medija" @@ -3520,7 +3463,6 @@ msgid "dashboard.no-matches-for" msgstr "Nisu pronađeni rezultati za “%s”" #: src/app/main/ui/dashboard/sidebar.cljs -#, fuzzy msgid "dashboard.no-projects-placeholder" msgstr "Prikvačeni projekti pojavit će se ovdje" @@ -3660,12 +3602,10 @@ msgstr "" "Sretni smo što si ovdje. Ako trebaš pomoć, pretraži prije objavljivanja." #: src/app/main/ui/settings/feedback.cljs -#, fuzzy msgid "feedback.discourse-title" -msgstr "Penpot community" +msgstr "Penpot zajednica" #: src/app/main/ui/settings/feedback.cljs -#, fuzzy msgid "feedback.twitter-subtitle1" msgstr "Ovdje za pomoć za tvoje tehničke upite." @@ -3699,7 +3639,6 @@ msgstr "Maska" msgid "handoff.tabs.code.selected.multiple" msgstr "%s Označeno" -#, fuzzy msgid "handoff.attributes.typography.text-transform.titlecase" msgstr "Velika i mala slova" @@ -3736,9 +3675,8 @@ msgid "labels.bad-gateway.main-message" msgstr "Loš Gateway" #: src/app/main/ui/dashboard/sidebar.cljs -#, fuzzy msgid "labels.community" -msgstr "Community" +msgstr "Zajenica" msgid "labels.continue" msgstr "Nastavi" @@ -3748,7 +3686,6 @@ msgid "labels.confirm-password" msgstr "Potvrdi lozinku" #: src/app/main/ui/workspace/sidebar/assets.cljs -#, fuzzy msgid "labels.create" msgstr "Kreiraj" @@ -3858,7 +3795,7 @@ msgid "modals.delete-shared-confirm.message" msgid_plural "modals.delete-shared-confirm.message" msgstr[0] "Jesi li siguran/na da želiš izbrisati ovu datoteku?" msgstr[1] "Jesi li siguran/na da želiš izbrisati ove datoteke?" -msgstr[2] "" +msgstr[2] "Jesi li siguran/na da želiš izbrisati ove datoteke?" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.leftright" @@ -3900,7 +3837,7 @@ msgid "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint" msgstr[0] "Ako poništiš objavu, stavke u njoj postaju biblioteka ove datoteke." msgstr[1] "Ako poništiš objavu, stavke u njoj postaju biblioteka ovih datoteka." -msgstr[2] "" +msgstr[2] "Ako poništiš objavu, stavke u njoj postaju biblioteka ovih datoteka." #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.hint" @@ -3931,13 +3868,11 @@ msgstr "Korisnički priručnik" msgid "onboarding-v2.welcome.title" msgstr "Dobrodošli u Penpot!" -#, fuzzy msgid "onboarding-v2.welcome.desc2" msgstr "" "Javni prostor za učenje, dijeljenje i raspravu o Penpotu, njegovoj " -"sadašnjosti i budućnosti s cijelim Community-em i glavnim timom Penpota." +"sadašnjosti i budućnosti s cijelom zajednicom i glavnim timom Penpota." -#, fuzzy msgid "onboarding-v2.welcome.desc3" msgstr "" "Gdje ćete pronaći kako surađivati s prijevodima, zahtjevima za unapređenje, " @@ -3952,7 +3887,6 @@ msgstr "Stvori tim kasnije" msgid "onboarding.choice.team-up.create-team" msgstr "Ime tvojeg tima" -#, fuzzy msgid "onboarding.choice.team-up.invite-members-info" msgstr "" "Ne zaboravi uključiti sve. Programere, dizajnere, menadžere... raznolikost " @@ -3971,7 +3905,6 @@ msgid "onboarding.newsletter.acceptance-message" msgstr "" "Tvoj zahtjev za pretplatu je poslan, poslat ćemo ti e-mail da ga potvrdiš." -#, fuzzy msgid "onboarding.newsletter.desc" msgstr "" "Pretplati se na naš newsletter kako bi bio/la u toku s razvojem proizvoda i " @@ -4057,9 +3990,8 @@ msgstr "Navigacija" msgid "shortcut-subsection.navigation-viewer" msgstr "Navigacija" -#, fuzzy msgid "shortcuts.open-comments" -msgstr "Idi na odjeljak s komentarima gledatelja" +msgstr "Idi na odjeljak s komentarima" msgid "shortcuts.opacity-7" msgstr "Postavi neprozirnost na 70%" @@ -4079,9 +4011,8 @@ msgstr "Okreni vodoravno" msgid "shortcuts.draw-rect" msgstr "Pravokutnik" -#, fuzzy msgid "shortcuts.next-frame" -msgstr "Slijedeći board" +msgstr "Slijedeća ploča" msgid "shortcuts.opacity-3" msgstr "Postavi neprozirnost na 30%" @@ -4122,9 +4053,8 @@ msgstr "Postavi neprozirnost na 20%" msgid "shortcuts.open-color-picker" msgstr "Birač boja" -#, fuzzy msgid "shortcuts.open-interactions" -msgstr "Idi na odjeljak interakcija gledatelja" +msgstr "Idi na odjeljak interakcija" msgid "shortcuts.open-workspace" msgstr "Idi na radni prostor" @@ -4141,7 +4071,7 @@ msgstr "Promijeni stil zooma" #, fuzzy msgid "shortcuts.toggle-snap-guide" -msgstr "Pričvrsti na vodilice" +msgstr "Pričvrsti na guides" msgid "shortcuts.toogle-fullscreen" msgstr "Promijeni cijeli zaslon" @@ -4207,7 +4137,7 @@ msgid "workspace.assets.selected-count" msgid_plural "workspace.assets.selected-count" msgstr[0] "%s odabrana stavka" msgstr[1] "%s odabranih stavki" -msgstr[2] "" +msgstr[2] "%s odabranih stavki" #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.typography" @@ -4252,7 +4182,6 @@ msgid "workspace.assets.typography.text-transform" msgstr "Transformiraj tekst" #: src/app/main/ui/workspace/sidebar/assets.cljs -#, fuzzy msgid "workspace.assets.ungroup" msgstr "Razgrupiraj" @@ -4336,7 +4265,6 @@ msgstr "Spremljeno" msgid "workspace.libraries.colors.recent-colors" msgstr "Nedavno korištene boje" -#, fuzzy msgid "dashboard.export.options.detach.message" msgstr "" "Zajedničke biblioteke neće biti uključene u izvoz i nikakve stavke neće biti " @@ -4352,3 +4280,379 @@ msgid "modals.remove-shared-confirm.hint" msgstr "" "Nakon uklanjanja kao zajedničke biblioteke, biblioteka datoteka ove datoteke " "više neće biti dostupna za korištenje među tvojim ostalim datotekama." + +#, fuzzy +msgid "workspace.shape.menu.path" +msgstr "Path" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Prečaci (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.empty" +msgstr "Do sada nema promjena povijesti" + +msgid "workspace.path.actions.move-nodes" +msgstr "Premijesti čvorove (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Kreiraj komponentu" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Izbriši" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instance" +msgstr "Odvoji instancu" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "Odvoji instance" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Zalijepi" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-main" +msgstr "Prikaži glavnu komponentu" + +msgid "workspace.shape.menu.union" +msgstr "Unija" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Izreži" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Komentari (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Prikaži" + +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "Ukloni sličicu" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.sitemap" +msgstr "Sitemap" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text-palette" +msgstr "Tipografija (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "Izbrisano %s" + +#, fuzzy +msgid "workspace.shape.menu.transform-to-path" +msgstr "Transformiraj u path" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "Ažuriraj glavne komponente" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-main" +msgstr "Ažuriraj glavnu komponentu" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "Povijest (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.color-palette" +msgstr "Paleta boja (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Razgrupiraj" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Premijesti (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +#, fuzzy +msgid "workspace.toolbar.path" +msgstr "Path (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Pravokutnik (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Tekst (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.modify" +msgstr "Izmijenjeno %s" + +#, fuzzy +msgid "workspace.undo.entry.multiple.path" +msgstr "paths" + +msgid "workspace.undo.entry.single.shape" +msgstr "oblik" + +msgid "workspace.undo.entry.single.text" +msgstr "tekst" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "Postoje ažuriranja u zajedničkim bibliotekama" + +msgid "workspace.undo.entry.single.image" +msgstr "slika" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Odbaci" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.update" +msgstr "Ažuriraj" + +msgid "workspace.options.text-options.text-case" +msgstr "Slučaj" + +msgid "workspace.path.actions.add-node" +msgstr "Dodaj čvor (%s)" + +msgid "workspace.path.actions.delete-node" +msgstr "Izbriši čvor (%s)" + +msgid "workspace.path.actions.draw-nodes" +msgstr "Crtaj čvorove (%s)" + +msgid "workspace.path.actions.join-nodes" +msgstr "Spoji čvorove (%s)" + +msgid "workspace.path.actions.make-corner" +msgstr "Do kuta (%s)" + +#, fuzzy +msgid "workspace.path.actions.make-curve" +msgstr "Do krivulje (%s)" + +msgid "workspace.path.actions.merge-nodes" +msgstr "Spoji čvorove (%s)" + +msgid "workspace.path.actions.separate-nodes" +msgstr "Odvoji čvorove(%s)" + +#, fuzzy +msgid "workspace.path.actions.snap-nodes" +msgstr "Priključi čvorove (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "Pošalji natrag" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "Pošalji u pozadinu" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Kopiraj" + +#: src/app/main/ui/workspace/context_menu.cljs +#, fuzzy +msgid "workspace.shape.menu.create-artboard-from-selection" +msgstr "Odabir na ploču" + +#: src/app/main/ui/workspace/context_menu.cljs +#, fuzzy +msgid "workspace.shape.menu.delete-flow-start" +msgstr "Izbriši početak flowa" + +msgid "workspace.shape.menu.difference" +msgstr "Razlika" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Dupliciraj" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Uredi" + +msgid "workspace.shape.menu.exclude" +msgstr "Izuzmi" + +msgid "workspace.shape.menu.flatten" +msgstr "Spljošti" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-vertical" +msgstr "Okreni okomito" + +#: src/app/main/ui/workspace/context_menu.cljs +#, fuzzy +msgid "workspace.shape.menu.flow-start" +msgstr "Početak flowa" + +#: src/app/main/ui/workspace/context_menu.cljs +#, fuzzy +msgid "workspace.shape.menu.front" +msgstr "Postavi naprijed" + +#: src/app/main/ui/workspace/context_menu.cljs +#, fuzzy +msgid "workspace.shape.menu.forward" +msgstr "Postavi ispred" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.go-main" +msgstr "Idi na datoteku glavne komponente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.group" +msgstr "Grupiraj" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Sakrij" + +msgid "workspace.shape.menu.hide-ui" +msgstr "Prikaži/sakrij korisničko sučelje" + +msgid "workspace.shape.menu.intersection" +msgstr "Presjek" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Zaključaj" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Maskiraj" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +#, fuzzy +msgid "workspace.shape.menu.reset-overrides" +msgstr "Poništi overrides" + +msgid "workspace.shape.menu.restore-main" +msgstr "Vrati glavnu komponentu" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "Postavi kao sličicu" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unlock" +msgstr "Otključaj" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unmask" +msgstr "Ukloni masku" + +#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs +msgid "workspace.sidebar.options.svg-attrs.title" +msgstr "Uvezeni SVG atributi" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "workspace.sidebar.sitemap" +msgstr "Stranice" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.curve" +msgstr "Krivulja (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "Elipsa (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Ploča (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Slika (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.move" +msgstr "Premiješteni objekti" + +msgid "workspace.undo.entry.multiple.circle" +msgstr "krugovi" + +msgid "workspace.undo.entry.multiple.component" +msgstr "komponente" + +msgid "workspace.undo.entry.multiple.curve" +msgstr "krivulje" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "ploča" + +msgid "workspace.undo.entry.multiple.group" +msgstr "grupe" + +msgid "workspace.undo.entry.multiple.multiple" +msgstr "objekti" + +msgid "workspace.undo.entry.multiple.page" +msgstr "stranice" + +msgid "workspace.undo.entry.multiple.rect" +msgstr "pravokutnici" + +msgid "workspace.undo.entry.multiple.shape" +msgstr "oblici" + +msgid "workspace.undo.entry.multiple.text" +msgstr "tekstovi" + +msgid "workspace.undo.entry.single.curve" +msgstr "krivulja" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.new" +msgstr "Novo %s" + +msgid "workspace.undo.entry.single.circle" +msgstr "krug" + +msgid "workspace.undo.entry.single.component" +msgstr "komponenta" + +msgid "workspace.undo.entry.single.frame" +msgstr "ploča" + +msgid "workspace.undo.entry.single.group" +msgstr "grupa" + +msgid "workspace.undo.entry.single.multiple" +msgstr "objekt" + +msgid "workspace.undo.entry.single.page" +msgstr "stranica" + +#, fuzzy +msgid "workspace.undo.entry.single.path" +msgstr "path" + +msgid "workspace.undo.entry.single.rect" +msgstr "pravokutnik" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.unknown" +msgstr "Operacija izvršena %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.title" +msgstr "Povijest" + +#, fuzzy +msgid "workspace.viewport.click-to-close-path" +msgstr "Pritisni da zatvoriš path" From c9255282129f18bd3745f43c0e6be7abad0b5cd6 Mon Sep 17 00:00:00 2001 From: Denys M Date: Sun, 2 Oct 2022 15:50:32 +0000 Subject: [PATCH 085/682] :globe_with_meridians: Add translations for: Ukrainian (ukr_UA). Currently translated at 19.6% (238 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ukr_UA/ --- frontend/translations/ukr_UA.po | 520 +++++++++++++++++++++++++++++++- 1 file changed, 519 insertions(+), 1 deletion(-) diff --git a/frontend/translations/ukr_UA.po b/frontend/translations/ukr_UA.po index 2689327c32..f37ada8f3d 100644 --- a/frontend/translations/ukr_UA.po +++ b/frontend/translations/ukr_UA.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-01 14:17+0000\n" +"PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Denys M. \n" "Language-Team: Ukrainian \n" @@ -359,3 +359,521 @@ msgstr "або" #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.owner" msgstr "Власник" + +msgid "handoff.tabs.code.selected.image" +msgstr "Зображення" + +msgid "labels.save" +msgstr "Зберегти" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Кадр" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Крива" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Код" + +msgid "handoff.tabs.code.selected.component" +msgstr "Компонент" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Коло" + +msgid "handoff.tabs.code.selected.group" +msgstr "Група" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.send" +msgstr "Надіслати" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Статус" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +msgstr "Оновити" + +#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.remove" +msgstr "Видалити" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.projects" +msgstr "Проекти" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Вирівнювання" + +msgid "shortcut-subsection.edit" +msgstr "Редагувати" + +msgid "labels.recent" +msgstr "Нещодавні" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.rename" +msgstr "Перейменувати" + +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +msgid "labels.retry" +msgstr "Повторити" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.settings" +msgstr "Налаштування" + +msgid "shortcut-subsection.general-viewer" +msgstr "Загальний" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.role" +msgstr "Роль" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.sending" +msgstr "Надсилаю…" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "Бібліотеки" + +msgid "labels.skip" +msgstr "Пропустити" + +msgid "labels.start" +msgstr "Почати" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Посібники" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.accept" +msgstr "Оновити" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.cancel" +msgstr "Відмінити" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Основи" + +#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "settings.multiple" +msgstr "Змішаний" + +msgid "shortcut-section.dashboard" +msgstr "Панель управління" + +msgid "shortcut-section.viewer" +msgstr "Переглядач" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Навігація" + +msgid "shortcut-subsection.path-editor" +msgstr "Контури" + +msgid "shortcut-section.workspace" +msgstr "Робоче поле" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Загальний" + +msgid "shortcut-subsection.panels" +msgstr "Панелі" + +msgid "shortcuts.delete" +msgstr "Видалити" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Навігація" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Навігація" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.delete" +msgstr "Видалити" + +msgid "shortcuts.go-to-search" +msgstr "Пошук" + +msgid "shortcut-subsection.shape" +msgstr "Форми" + +msgid "shortcuts.cut" +msgstr "Вирізати" + +msgid "shortcut-subsection.tools" +msgstr "Інструменти" + +msgid "shortcuts.copy" +msgstr "Скопіювати" + +msgid "shortcuts.draw-curve" +msgstr "Крива" + +msgid "shortcuts.draw-frame" +msgstr "Рамка" + +msgid "shortcuts.draw-ellipse" +msgstr "Еліпс" + +msgid "shortcuts.draw-text" +msgstr "Текст" + +msgid "shortcuts.draw-path" +msgstr "Контур" + +msgid "shortcuts.draw-rect" +msgstr "Прямокутник" + +msgid "shortcuts.group" +msgstr "Група" + +msgid "shortcuts.or" +msgstr " або " + +msgid "shortcuts.duplicate" +msgstr "Дублікат" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Створити дуплікат" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Група" + +msgid "shortcuts.escape" +msgstr "Відмінити" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.preferences" +msgstr "Налаштування" + +msgid "shortcuts.paste" +msgstr "Вставити" + +msgid "shortcuts.ungroup" +msgstr "Розбити групу" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Ресурси" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Кольори" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Бібліотеки" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Графіка" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Компоненти" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.edit" +msgstr "Редагувати" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Графіка" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.reset-zoom" +msgstr "Скинути" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename" +msgstr "Перейменувати" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Типографіка" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Файл" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "СПІЛЬНІ" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Редагувати" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.view" +msgstr "Вид" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Додати" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.hsv" +msgstr "HSV" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Збережено" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Збереження" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "БІБЛІОТЕКИ" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "БІБЛІОТЕКА" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Компонент" + +msgid "common.publish" +msgstr "Опублікувати" + +#, fuzzy +msgid "common.share-link.current-tag" +msgstr "(поточне)" + +msgid "common.unpublish" +msgstr "Зняти з публікації" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(копія)" + +msgid "dashboard.draft-title" +msgstr "Чорновик" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Створити дублікат" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Експорт" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Бібліотеки" + +msgid "dashboard.options" +msgstr "Опції" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Закріпити/Відчепити" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Проекти" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Пошук…" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "Електронна пошта" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Центр" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Всередину" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Назовні" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Точковий" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Змішаний" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Немає" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Суцільний" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Товщина" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Текст" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Немає" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Перечеркнутий" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Підчеркнутий" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Який є" + +msgid "handoff.tabs.code.selected.mask" +msgstr "Маска" + +msgid "handoff.tabs.code.selected.path" +msgstr "Контур" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Прямокутник" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.text" +msgstr "Текст" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Інформація" + +msgid "labels.accept" +msgstr "Прийняти" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.dashboard" +msgstr "Панель управління" + +msgid "labels.default" +msgstr "за умовчуванням" + +msgid "labels.font-variants" +msgstr "Стилі" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "Очікування" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.permissions" +msgstr "Дозволи" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.profile" +msgstr "Профіль" + +msgid "labels.upload" +msgstr "Завантаження" + +msgid "labels.uploading" +msgstr "Завантажую…" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Спостерігач" + +msgid "labels.workspace" +msgstr "Робоче поле" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(ви)" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Зняти з публікації" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Масштабування" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Масштабування" + +msgid "shortcuts.add-comment" +msgstr "Коментарі" + +msgid "shortcuts.mask" +msgstr "Маска" + +msgid "shortcuts.move" +msgstr "Перемістити" + +msgid "viewer.breaking-change.message" +msgstr "Упс!" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Інтеракції" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Мапа сайту" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Шрифт" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Розмір" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Варіант" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.ungroup" +msgstr "Розгрупувати" + +msgid "workspace.focus.selection" +msgstr "Вибір" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Оновити" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "ОНОВЛЕННЯ" + +msgid "workspace.library.libraries" +msgstr "Бібліотеки" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Фон" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Шар" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Розмиття" From 66cd60e02ca8049abdd71a8566c8c5972472b5e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 5 Oct 2022 10:05:22 +0200 Subject: [PATCH 086/682] :bug: Fix component sync in undo --- frontend/src/app/main/data/workspace/changes.cljs | 3 ++- frontend/src/app/main/data/workspace/libraries.cljs | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index c0acf92390..0b68bd1b78 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -150,7 +150,8 @@ :hint-origin (ptk/type origin) :changes redo-changes :page-id page-id - :frames frames}) + :frames frames + :save-undo? save-undo?}) ptk/UpdateEvent (update [_ state] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 7a97c584f5..79b0ad4cf1 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -842,11 +842,11 @@ check-changes (fn [[event data]] - (let [changes (-> event deref :changes) + (let [{:keys [changes save-undo?]} (deref event) components-changed (reduce #(into %1 (ch/components-changed data %2)) #{} changes)] - (when (d/not-empty? components-changed) + (when (and (d/not-empty? components-changed) save-undo?) (log/info :msg "DETECTED COMPONENTS CHANGED" :ids (map str components-changed)) (run! st/emit! From 919fb96b34f0c03763bc47b409f399dc00c19220 Mon Sep 17 00:00:00 2001 From: "K.B.Dharun Krishna" Date: Wed, 5 Oct 2022 05:59:51 +0000 Subject: [PATCH 087/682] :globe_with_meridians: Add translations for: Tamil. Currently translated at 2.0% (25 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ta/ --- frontend/translations/ta.po | 81 ++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/frontend/translations/ta.po b/frontend/translations/ta.po index f9289093cd..187429d501 100644 --- a/frontend/translations/ta.po +++ b/frontend/translations/ta.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-01 14:17+0000\n" +"PO-Revision-Date: 2022-10-07 02:17+0000\n" "Last-Translator: K.B.Dharun Krishna \n" "Language-Team: Tamil \n" @@ -42,3 +42,82 @@ msgstr "" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" msgstr "ஏற்கனவே ஒரு கணக்கு உள்ளதா?" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "கடவுச்சொல்லை மறந்துவிட்டீர்களா?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "முழு பெயர்" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "இங்கே உள்நுழைக" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "உள்நுழை" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "உங்களை மீண்டும் சந்திப்பதில் மகிழ்ச்சி!" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "கிட்ஹப்" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "கிட்லேப்" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "கூகுள்" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "ஓப்பன் ஐடி" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "புதிய கடவுச்சொல்லை உள்ளிடவும்" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "" +"சுயவிவரம் சரிபார்க்கப்படவில்லை, தொடர்வதற்கு முன் சுயவிவரத்தைச் " +"சரிபார்க்கவும்." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "கடவுச்சொல் மீட்பு இணைப்பு உங்கள் இன்பாக்ஸிற்கு அனுப்பப்பட்டது." + +#: src/app/main/ui/auth/verify_token.cljs +#, fuzzy +msgid "auth.notifications.team-invitation-accepted" +msgstr "அணியில் வெற்றிகரமாக இணைந்தார்" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "பென்பாட் அஞ்சல் பட்டியலில் குழுசேர ஒப்புக்கொள்கிறேன்." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "மீட்பு டோக்கன் செல்லுபடியாகாது." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-successfully" +msgstr "கடவுச்சொல் வெற்றிகரமாக மாற்றப்பட்டது" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "கடவுச்சொல்" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "குறைந்தது 8 எழுத்துகள்" From e3f0c2eaeb6514f47203bdad0eec783c7e1da31e Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 10 Oct 2022 08:47:38 +0200 Subject: [PATCH 088/682] :globe_with_meridians: Added translation for: Faroese. --- frontend/translations/fo.po | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 frontend/translations/fo.po diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po new file mode 100644 index 0000000000..4f8f6e6dec --- /dev/null +++ b/frontend/translations/fo.po @@ -0,0 +1,2 @@ +msgid "" +msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file From 374909e05e4d8dd4a9fe5cdc0f100096a9ad415c Mon Sep 17 00:00:00 2001 From: luz paz Date: Tue, 4 Oct 2022 09:03:08 -0400 Subject: [PATCH 089/682] =?UTF-8?q?=F0=9F=94=A7=20Fix=20typos=20in=20sourc?= =?UTF-8?q?e=20code=20(follow-up)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is a follow-up to e30bea0b6f but fixes source typos. --- backend/dev/user.clj | 2 +- frontend/src/app/main/ui/components/numeric_input.cljs | 6 +++--- frontend/src/app/main/ui/dashboard/change_owner.cljs | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/dev/user.clj b/backend/dev/user.clj index 591ddac704..0ef74e9db4 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -86,7 +86,7 @@ (alter-var-root #'system (fn [sys] (when sys (ig/halt! sys)) nil)) - :stoped) + :stopped) (defn restart [] diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index 45bb33e768..83a5bf0293 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -225,7 +225,7 @@ (mf/use-layout-effect (mf/deps handle-mouse-wheel) (fn [] - (let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:pasive false})]] + (let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:passive false})]] #(doseq [key keys] (events/unlistenByKey key))))) @@ -240,7 +240,7 @@ (mf/use-layout-effect (mf/deps handle-mouse-wheel) (fn [] - (let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:pasive false})]] + (let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:passive false})]] #(doseq [key keys] (events/unlistenByKey key))))) @@ -248,7 +248,7 @@ (mf/use-layout-effect (mf/deps handle-mouse-wheel) (fn [] - (let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:pasive false})]] + (let [keys [(events/listen (mf/ref-val ref) EventType.WHEEL handle-mouse-wheel #js {:passive false})]] #(doseq [key keys] (events/unlistenByKey key))))) diff --git a/frontend/src/app/main/ui/dashboard/change_owner.cljs b/frontend/src/app/main/ui/dashboard/change_owner.cljs index f836f41820..607f115e16 100644 --- a/frontend/src/app/main/ui/dashboard/change_owner.cljs +++ b/frontend/src/app/main/ui/dashboard/change_owner.cljs @@ -51,7 +51,7 @@ [:p (tr "modals.leave-and-reassign.hint1" (:name team))] (if (empty? members) - [:p (tr "modals.leave-and-reassign.forbiden")] + [:p (tr "modals.leave-and-reassign.forbidden")] [:* [:& fm/form {:form form} [:& fm/select {:name :member-id From 85bd44e37b6f3fa3eafa5fe38e0d3b45a9c07711 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 10 Oct 2022 09:51:37 +0200 Subject: [PATCH 090/682] :paperclip: Update translation files --- frontend/translations/ar.po | 2 +- frontend/translations/ca.po | 2 +- frontend/translations/de.po | 2 +- frontend/translations/en.po | 2 +- frontend/translations/es.po | 2 +- frontend/translations/eu.po | 2 +- frontend/translations/fr.po | 2 +- frontend/translations/he.po | 2 +- frontend/translations/pl.po | 2 +- frontend/translations/pt_BR.po | 2 +- frontend/translations/ro.po | 2 +- frontend/translations/tr.po | 2 +- frontend/translations/zh_CN.po | 2 +- 13 files changed, 13 insertions(+), 13 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 05f2d297d6..f7a15163d9 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1413,7 +1413,7 @@ msgstr "حذف العضو" msgid "modals.invite-member-confirm.accept" msgstr "إرسال دعوة" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "لا يمكنك مغادرة الفريق إذا لم يكن هناك عضو آخر للترقية إلى المالك. قد ترغب " "في حذف الفريق." diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 18650ca5dc..5a362db281 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -1706,7 +1706,7 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "Segur que vols deixar l'equip %s?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "No es pot abandonar l'equip si no hi ha cap altre membre capaç d'ascendir a " "propietari. És possible que vulgueu eliminar l'equip." diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 6f6fc3417b..6d1fecb629 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1756,7 +1756,7 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "Sind Sie sicher, dass Sie das %s-Team verlassen wollen?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "Sie können das Team nicht verlassen, wenn es kein anderes Mitglied gibt, " "das Sie zum Besitzer ernennen können. Sie können das Team jedoch löschen." diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 6cc39984f0..1518ab50a1 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1791,7 +1791,7 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "Are you sure you want to leave the %s team?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "You can not leave the team if there is no other member to promote to owner. " "You might want to delete the team." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 2015c61b65..d1bbf27906 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1873,7 +1873,7 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "¿Seguro que quieres abandonar el equipo %s?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "No puede abandonar el equipo si no hay otro miembro al que promocionar a " "dueño. Quizás quiere borrar el equipo." diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index 762b5b73ce..252e097ef8 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -1695,7 +1695,7 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "Benetan %s taldea utzi egin nahi duzu?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "Ezin duzu taldea utzi ez badago jabe berria izateko hautagairik. Agian " "taldea ezabatu egin nahi duzu." diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index c5cc3ab4d5..31c1943993 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -1759,7 +1759,7 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "Êtes-vous sûr de vouloir quitter l'équipe %s ?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "Vous ne pouvez pas quitter l'équipe si il n'y a aucun membre à promouvoir " "comme propriétaire. Vous pourriez plutôt supprimer l'équipe." diff --git a/frontend/translations/he.po b/frontend/translations/he.po index abc3b7479b..a587473d93 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -1703,7 +1703,7 @@ msgstr "כיוון שאין עוד חברים בצוות הזה מלבדך, הצ msgid "modals.leave-and-close-confirm.message" msgstr "ברצונך לעזוב את הצוות %s?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "אי אפשר לעזוב צוות אם אין חברים שאפשר לקדם לבעלות עליה. אולי עדיף למחוק את " "הצוות." diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po index f10241a786..20a6ab0f11 100644 --- a/frontend/translations/pl.po +++ b/frontend/translations/pl.po @@ -1642,7 +1642,7 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "Czy na pewno chcesz opuścić zespół %s?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "Nie możesz opuścić zespołu, jeśli nie ma innego członka, którego można by " "awansować na właściciela. Możesz chcieć usunąć zespół." diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index d238d3d3f2..268b656d40 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1312,7 +1312,7 @@ msgstr "Excluir membro da equipe" msgid "modals.invite-member-confirm.accept" msgstr "Enviar convite" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "Você não pode deixar a equipe se não houver outro membro para promover a " "proprietário. Você pode excluir a equipe." diff --git a/frontend/translations/ro.po b/frontend/translations/ro.po index c8b6884ef8..c36ce7e082 100644 --- a/frontend/translations/ro.po +++ b/frontend/translations/ro.po @@ -1226,7 +1226,7 @@ msgstr "Elimină un membru al echipei" msgid "modals.invite-member-confirm.accept" msgstr "Trimite invitație" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "Nu puteţi părăsi echipa dacă nu există un alt membru care să devină " "administrator. Aţi putea şterge echipa." diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 118bfafb2b..50cefeeed8 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -1736,7 +1736,7 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "%s takımından ayrılmak istediğinizden emin misiniz?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "" "Birisini takımın sahibi yapmadan takımı bırakamazsın. Takımı silmek " "isteyebilirsin." diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index b24deeee11..89ecfd0707 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -1558,7 +1558,7 @@ msgstr "由于你是这个团队的唯一成员,这个团队将连同其项目 msgid "modals.leave-and-close-confirm.message" msgstr "您确定要离开 %s 团队吗?" -msgid "modals.leave-and-reassign.forbiden" +msgid "modals.leave-and-reassign.forbidden" msgstr "如果不能推选另一个成员作为团队所有者,你就无法离开团队。你或许想要删除该团队。" #: src/app/main/ui/dashboard/sidebar.cljs From b9958306938116ab1bbde8101da2e728397aaae0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 10 Oct 2022 10:04:11 +0200 Subject: [PATCH 091/682] :paperclip: Sort translation files --- frontend/translations/ar.po | 1662 +++++----- frontend/translations/ca.po | 175 +- frontend/translations/cs.po | 6 +- frontend/translations/de.po | 985 +++--- frontend/translations/en.po | 58 +- frontend/translations/es.po | 76 +- frontend/translations/eu.po | 540 ++-- frontend/translations/fa.po | 314 +- frontend/translations/fi.po | 6 +- frontend/translations/fo.po | 6 +- frontend/translations/fr.po | 941 +++--- frontend/translations/he.po | 543 ++-- frontend/translations/hr.po | 3342 ++++++++++---------- frontend/translations/id.po | 132 +- frontend/translations/it.po | 747 +++-- frontend/translations/pl.po | 481 ++- frontend/translations/pt_BR.po | 5058 +++++++++++++++--------------- frontend/translations/pt_PT.po | 2557 ++++++++------- frontend/translations/ru.po | 1700 +++++----- frontend/translations/ta.po | 58 +- frontend/translations/tr.po | 531 ++-- frontend/translations/ukr_UA.po | 1052 +++---- frontend/translations/zh_CN.po | 1912 ++++++----- frontend/translations/zh_Hant.po | 2 +- 24 files changed, 11427 insertions(+), 11457 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 4ed48f0f3a..80287261db 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Youkho \n" -"Language-Team: Arabic \n" +"Language-Team: Arabic " +"\n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -166,6 +166,9 @@ msgstr "عند إنشاء حساب جديد ، فإنك توافق على شرو msgid "auth.verification-email-sent" msgstr "لقد أرسلنا رسالة تحقق إلى بريدك الالكتروني" +msgid "common.publish" +msgstr "أنشر" + msgid "common.share-link.all-users" msgstr "جميع مستخدمي Penpot" @@ -192,6 +195,15 @@ msgstr "تم حذف الرابط بنجاح" msgid "common.share-link.manage-ops" msgstr "إدارة التصاريح" +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "لم يتم مشاركة الصفحة" +msgstr[1] "مشاركة صفحة واحدة" +msgstr[2] "مشاركة صفحتين" +msgstr[3] "مشاركة صفحات" +msgstr[4] "مشاركة صفحة" +msgstr[5] "مشاركة صفحة" + msgid "common.share-link.permissions-can-comment" msgstr "يمكن التعليق" @@ -216,6 +228,45 @@ msgstr "مشاركة النماذج" msgid "common.share-link.view-all" msgstr "اختر الكل" +msgid "common.unpublish" +msgstr "إلغاء النشر" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "إدارة الفريق" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "Penpot تم تصميمه للفرق. أدعُ زملاءك للعمل سوياَ على المشاريع والملفات" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "إعمل فريق!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "تعلم أساسيات برنامج Penpot بينما تستمع بتجربته في هذه الدورة التعليمية." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "إبدا الدورة التعليمية" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "دورة تعليمية عملية" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "إذهب في جولة في البرنامج وتعرف على ميزاته." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "إبدا الجولة" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "جولة في الواجهة" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "أضف كمكتبة مشتركة" @@ -271,6 +322,9 @@ msgstr "" msgid "dashboard.export-binary-multi" msgstr "تنزيل ملفات ٪s Penpot (.penpot)" +msgid "dashboard.export-frames" +msgstr "صدر اللوحة الفنية الى ملف PDF…" + #: src/app/main/ui/export.cljs msgid "dashboard.export-frames.title" msgstr "استخراج PDF" @@ -278,19 +332,89 @@ msgstr "استخراج PDF" msgid "dashboard.export-multi" msgstr "تصدير %s الملفات" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "%s من %s عناصر مختارة" + #: src/app/main/ui/workspace/header.cljs msgid "dashboard.export-shapes" msgstr "استخراج" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"يمكنك إضافة إعدادات التصدير إلى العناصر من خصائص التصميم (أسفل الشريط " +"الجانبي الأيمن)." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "معلومات عن كيفية إعداد التصدير." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "لا يوجد عناصر بإعدادت التصدير." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "إختيار التصدير" + msgid "dashboard.export-single" msgstr "تصدير الملف" +msgid "dashboard.export-standard-multi" +msgstr "تحميل %s ملفات أساسية (.svg + .json)" + +msgid "dashboard.export.detail" +msgstr "* ربما يحتوي على عناصر، رسومات، الوان، و/أو خطوط." + +msgid "dashboard.export.explain" +msgstr "" +"ملف أو أكثر تريد تصديرهم يستخدمون مكتبات مشتركة. ماذا تريد أن تفعل في " +"أصولهم*؟" + +msgid "dashboard.export.options.all.message" +msgstr "سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم." + +msgid "dashboard.export.options.all.title" +msgstr "صدر المكتبات المشتركة" + +msgid "dashboard.export.options.detach.message" +msgstr "" +"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى " +"المكتبة. " + +msgid "dashboard.export.options.detach.title" +msgstr "عامل أصول المكتبة المشتركة كعناصر بسيطة" + +msgid "dashboard.export.options.merge.message" +msgstr "سيتم تصدير ملفك مع دمج جميع الأصول الخارجية في مكتبة الملفات." + +msgid "dashboard.export.options.merge.title" +msgstr "تضمين أصول المكتبة المشتركة في مكتبات الملفات" + +msgid "dashboard.export.title" +msgstr "صدر الملفات" + msgid "dashboard.fonts.deleted-placeholder" msgstr "الخط محذوف" +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "الغاء الكل" + msgid "dashboard.fonts.empty-placeholder" msgstr "لا يزال ليس لديك خطوط مخصصة مثبتة." +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "لم يتم إضافة أي خط" +msgstr[1] "تمت إضافة خط واحد" +msgstr[2] "تمت إضافة خطين" +msgstr[3] "عدد قليل من الخطوط المضافة" +msgstr[4] "تمت إضافة العديد من الخطوط" +msgstr[5] "" + #, markdown msgid "dashboard.fonts.hero-text1" msgstr "" @@ -307,9 +431,46 @@ msgstr "" "(https://penpot.app/terms.html). قد ترغب أيضًا في القراءة عن [ترخيص الخطوط] " "(2)." +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "حمل الكل" + msgid "dashboard.import" msgstr "استيراد ملفات" +msgid "dashboard.import.analyze-error" +msgstr "لم نستطع استيراد أو إدراج هذا الملف" + +msgid "dashboard.import.import-error" +msgstr "حصل خلل خلال إدراج الملف. لم يتم إدراج الملف على البرنامج." + +msgid "dashboard.import.import-message" +msgstr "%s ملف تم ادراجهم بنجاح." + +msgid "dashboard.import.import-warning" +msgstr "تحتوي بعض الملفات على كائنات غير صالحة تمت إزالتها." + +msgid "dashboard.import.progress.process-colors" +msgstr "يتم معالجة الألوان" + +msgid "dashboard.import.progress.process-components" +msgstr "يتم معالجة العناصر" + +msgid "dashboard.import.progress.process-media" +msgstr "يتم معالجة الوسائط" + +msgid "dashboard.import.progress.process-page" +msgstr "يتم معالجة الصفحة: %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "يتم معالجة الخطوط" + +msgid "dashboard.import.progress.upload-data" +msgstr "تحميل البيانات للخادم (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "تحميل الملف: %s" + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "قم بدعوة للفريق" @@ -318,6 +479,15 @@ msgstr "قم بدعوة للفريق" msgid "dashboard.leave-team" msgstr "ترك الفريق" +msgid "dashboard.libraries-and-templates" +msgstr "المكتبات & القوالب" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "استعرض المزيد منهم وتعلم كيف تساهم" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "حدثت مشكلة في استيراد النموذج. لم يتم استيراد النموذج." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "المكتبات المشتركة" @@ -357,6 +527,14 @@ msgstr "+ مشروع جديد" msgid "dashboard.new-project-prefix" msgstr "مشروع جديد" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "أرسل لي الأخبار وتحديثات المنتجات والتوصيات حول Penpot." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "الإشتراك في" + #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" msgstr "لم يتم العثور على مطابقات ل \"%s\"" @@ -412,6 +590,10 @@ msgstr "هل تريد إزالة حسابك؟" msgid "dashboard.remove-shared" msgstr "إزالة كمكتبة مشتركة" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "حفظ الإعدادات" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.search-placeholder" msgstr "بحث…" @@ -484,6 +666,10 @@ msgstr "نتائج البحث" msgid "dashboard.type-something" msgstr "اكتب لإظهار نتائج البحث" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "الغاء نشر المكتبة" + #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "تحديث الإعدادات" @@ -504,6 +690,18 @@ msgstr "اسمك" msgid "dashboard.your-penpot" msgstr "Penpot الخاص بك" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "حسناَ" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "إنتباه" + +#: src/app/main/ui/confirm.cljs +msgid "ds.component-subtitle" +msgstr "عناصر سيتم تحديثها:" + #: src/app/main/ui/confirm.cljs msgid "ds.confirm-cancel" msgstr "إلغاء الأمر" @@ -520,10 +718,21 @@ msgstr "هل أنت متأكد؟" msgid "ds.updated-at" msgstr "محدث: %s" +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "موفر المصادقة غير معد ومسجل." + +msgid "errors.auth.unable-to-login" +msgstr "يبدوا أنك غير مصرح لك أو أن الجلسة إنتهت." + #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" msgstr "لا يمكن للمتصفح إجراء هذه العملية" +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "تم استخدام هذا الملف بالفعل مع تمكين المكونات V2." + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" msgstr "البريد الإلكتروني مستخدم بالفعل" @@ -532,6 +741,9 @@ msgstr "البريد الإلكتروني مستخدم بالفعل" msgid "errors.email-already-validated" msgstr "تم التحقق من صحة البريد الإلكتروني." +msgid "errors.email-as-password" +msgstr "لا يمكنك استخدام بريدك الإلكتروني ككلمة مرور" + #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.email-has-permanent-bounces" msgstr "يحتوي البريد الإلكتروني «%s» على العديد من تقارير الارتداد الدائم." @@ -540,6 +752,9 @@ msgstr "يحتوي البريد الإلكتروني «%s» على العديد msgid "errors.email-invalid-confirmation" msgstr "يجب أن يتطابق البريد الإلكتروني للتأكيد" +msgid "errors.email-spam-or-permanent-bounces" +msgstr "تم الإبلاغ عن البريد الإلكتروني «٪ s» كبريد عشوائي أو مرتد بشكل دائم." + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.generic" msgstr "حدث خطأ ما." @@ -552,6 +767,13 @@ msgstr "المصادقة مع جوجل تعطلت في الخلفية" msgid "errors.invalid-color" msgstr "لون غير صالح" +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "دعوة غير صالحة" + +msgid "errors.invite-invalid.info" +msgstr "هذه الدعوة قد تلغى أو قد تنتهي." + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "تم تعطيل مصادقة LDAP." @@ -598,6 +820,15 @@ msgstr "" msgid "errors.registration-disabled" msgstr "التسجيل معطل حاليا." +msgid "errors.team-leave.insufficient-members" +msgstr "أعضاء غير كافيين لمغادرة الفريق ، ربما تريد حذفه." + +msgid "errors.team-leave.member-does-not-exists" +msgstr "العضو الذي تحاول تعيينه غير موجود." + +msgid "errors.team-leave.owner-cant-leave" +msgstr "لا يمكن للمالك مغادرة الفريق ، يجب إعادة تعيين دور المالك." + msgid "errors.terms-privacy-agreement-invalid" msgstr "يجب أن تقبل شروط الخدمة وسياسة الخصوصية الخاصة بنا." @@ -629,6 +860,18 @@ msgstr "ترغب في الكلام؟ تحدث معنا في Gitter" msgid "feedback.description" msgstr "وصف" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "انتقل إلى منتدى Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "يسعدنا وجودك هنا. إذا كنت بحاجة إلى مساعدة، يرجى البحث أولا قبل النشر." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "مجتمع Penpot" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" msgstr "موضوع" @@ -643,6 +886,14 @@ msgstr "" msgid "feedback.title" msgstr "البريد الإلكتروني" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "اذهب إلى Twitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "هنا للمساعدة في استفساراتك التقنية." + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.twitter-title" msgstr "حساب دعم تويتر" @@ -959,6 +1210,9 @@ msgstr "خطوط مخصصة" msgid "labels.dashboard" msgstr "لوحة التحكم" +msgid "labels.default" +msgstr "إفتراضي" + #: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete" msgstr "حذف" @@ -1028,6 +1282,10 @@ msgstr "الأنماط" msgid "labels.fonts" msgstr "الخطوط" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "مستودع Github" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.give-feedback" msgstr "إعطاء ملاحظات" @@ -1068,6 +1326,16 @@ msgstr "الدعوات" msgid "labels.language" msgstr "اللغة" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.libraries-and-templates" +msgstr "المكتبات والقوالب" + +msgid "labels.link" +msgstr "رابط" + +msgid "labels.log-or-sign" +msgstr "تسجيل الدخول أو الاشتراك" + #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" msgstr "تسجيل خروج" @@ -1075,6 +1343,10 @@ msgstr "تسجيل خروج" msgid "labels.manage-fonts" msgstr "إدارة الخطوط" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "عضو" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.members" msgstr "الأعضاء" @@ -1087,10 +1359,21 @@ msgstr "الاسم" msgid "labels.new-password" msgstr "كلمة مرور جديدة" +msgid "labels.next" +msgstr "التالي" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs msgid "labels.no-comments-available" msgstr "ليس لديك أي إشعارات تعليق معلقة" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "لا توجد دعوات." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." + #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" msgstr "لقد سجلت الدخول باعتبارك" @@ -1151,6 +1434,10 @@ msgstr "مالك" msgid "labels.password" msgstr "كلمة المرور" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "قيد الانتظار" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.permissions" msgstr "اذونات" @@ -1174,6 +1461,10 @@ msgstr "ملاحظات الإصدار" msgid "labels.remove" msgstr "إزالة" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "إزالة العضو" + #: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "labels.rename" msgstr "اعاده تسميه" @@ -1182,6 +1473,10 @@ msgstr "اعاده تسميه" msgid "labels.rename-team" msgstr "إعادة تسمية الفريق" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "إعادة إرسال الدعوة" + #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "أعد المحاولة" @@ -1216,6 +1511,9 @@ msgstr "الخدمة غير متوفرة" msgid "labels.settings" msgstr "إعدادات" +msgid "labels.share-prototype" +msgstr "مشاركة النموذج الأولي" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.shared-libraries" msgstr "المكتبات المشتركة" @@ -1224,6 +1522,9 @@ msgstr "المكتبات المشتركة" msgid "labels.show-all-comments" msgstr "إظهار كافة التعليقات" +msgid "labels.show-comments-list" +msgstr "قائمة التعليقات" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-your-comments" msgstr "إظهار تعليقاتك فقط" @@ -1232,6 +1533,20 @@ msgstr "إظهار تعليقاتك فقط" msgid "labels.sign-out" msgstr "خروج" +msgid "labels.skip" +msgstr "تخطي" + +msgid "labels.start" +msgstr "ابدأ" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "الحالة" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "الدروس" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "تحديث" @@ -1253,10 +1568,21 @@ msgstr "جارٍ الرفع …" msgid "labels.viewer" msgstr "مشاهد" +msgid "labels.workspace" +msgstr "مساحة العمل" + #: src/app/main/ui/comments.cljs msgid "labels.write-new-comment" msgstr "كتابة تعليق جديد" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(أنت)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.your-account" +msgstr "حسابك" + #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" msgstr "جاري تحميل الصورة…" @@ -1275,6 +1601,10 @@ msgstr "" msgid "modals.add-shared-confirm.message" msgstr "إضافة “%s” كمكتبة مشتركة" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.big-nudge" +msgstr "دفعة كبيرة" + #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.confirm-email" msgstr "تحقق من البريد الإلكتروني الجديد" @@ -1295,6 +1625,10 @@ msgstr "تغيير البريد الإلكتروني" msgid "modals.change-email.title" msgstr "تغيير بريدك الإلكتروني" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." + #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" msgstr "إلغاء والاحتفاظ بحسابي" @@ -1383,6 +1717,10 @@ msgstr "هل أنت متأكد أنك تريد حذف هذا المشروع؟" msgid "modals.delete-project-confirm.title" msgstr "حذف المشروع" +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "حذف الملف" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "حذف الفريق" @@ -1413,6 +1751,21 @@ msgstr "حذف العضو" msgid "modals.invite-member-confirm.accept" msgstr "إرسال دعوة" +msgid "modals.invite-member.emails" +msgstr "رسائل البريد الإلكتروني، مفصولة بفواصل" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "ادعُ الأعضاء إلى الفريق" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "نظرًا لأنك العضو الوحيد في هذا الفريق ، فسيتم حذف الفريق مع مشاريعه وملفاته." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "هل أنت متأكد أنك تريد مغادرة فريق %s ؟" + msgid "modals.leave-and-reassign.forbidden" msgstr "" "لا يمكنك مغادرة الفريق إذا لم يكن هناك عضو آخر للترقية إلى المالك. قد ترغب " @@ -1450,10 +1803,20 @@ msgstr "هل أنت متأكد أنك تريد مغادرة هذا الفريق msgid "modals.leave-confirm.title" msgstr "مغادرة الفريق" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.nudge-title" +msgstr "كمية الدفع" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.accept" msgstr "رقى" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.hint" +msgstr "" +"إذا قمت بنقل الملكية ، فسوف تقوم بتغيير دورك إلى المسؤول ، وستفقد بعض " +"الأذونات على هذا الفريق. " + #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.message" msgstr "هل أنت متأكد أنك تريد ترقية هذا المستخدم إلى مالك؟" @@ -1476,6 +1839,24 @@ msgstr "" msgid "modals.remove-shared-confirm.message" msgstr "إزالة “%s” كمكتبة مشتركة" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "دفعة صغيرة" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "إلغاء النشر" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"أنت على وشك تحديث المكونات في مكتبة مشتركة. قد يؤثر هذا على الملفات الأخرى " +"التي تستخدمها." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.message" +msgstr "تحديث المكونات في مكتبة مشتركة" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.accept" msgstr "تحديث المكون" @@ -1510,6 +1891,210 @@ msgstr "تم حفظ الملف الشخصي بنجاح!" msgid "notifications.validation-email-sent" msgstr "تم إرسال رسالة التحقق إلى %s. راجع بريدك الالكتروني!" +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"يجب أن تعلم أن هناك الكثير من الموارد المتاحة لمساعدتك في بدء استخدام " +"Penpot ، مثل دليل المستخدم وقناة Youtube الخاصة بنا." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"معلومات مفصلة حول كيفية استخدام Penpot. من النماذج الأولية إلى تنظيم أو " +"مشاركة التصاميم." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "دليل المستخدم" + +msgid "onboarding-v2.before-start.desc3" +msgstr "يمكنك مشاهدة برامجنا التعليمية والبرامج التعليمية التي قدمها مجتمعنا." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "دروس الفيديو" + +msgid "onboarding-v2.before-start.title" +msgstr "قبل ان تبدا" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot هو برنامج مفتوح المصدر وهو من صنع Kaleidos وكذلك المجتمع ، حيث يساعد " +"الكثير من الناس بعضهم البعض بالفعل. يمكن للجميع التعاون من خلال:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"مساحة عامة للتعلم والمشاركة والمناقشة حول Penpot وحاضرها ومستقبلها مع " +"المجتمع بأكمله وفريق Penpot الأساسي." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "المشاركة في المجتمع" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"حيث ستجد كيفية التعاون في الترجمات وطلبات الميزات والمساهمات الأساسية " +"والبحث عن الأخطاء …" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "دليل المساهمة" + +msgid "onboarding-v2.welcome.title" +msgstr "مرحبًا بك في Penpot!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "أنشئ فريقًا لاحقًا" + +msgid "onboarding.choice.team-up.create-team" +msgstr "اسم فريقك" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "بعد تسمية فريقك ، ستتمكن من دعوة الأشخاص للانضمام." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "أدخل اسم الفريق" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "دعوة أعضاء" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "تذكر أن تشمل الجميع. المطورين والمصممين والمديرين ... التنوع يضيف :)" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "أنشئ فريقًا وادعُه لاحقًا" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "أنشئ فريقًا وأرسل الدعوات" + +msgid "onboarding.choice.team-up.roles" +msgstr "دعوة مع الدور:" + +msgid "onboarding.choice.title" +msgstr "مرحبًا بك في Penpot" + +msgid "onboarding.contrib.alt" +msgstr "مصدر مفتوح" + +msgid "onboarding.contrib.desc1" +msgstr "" +"Penpot هو برنامج مفتوح المصدر ، تم إنشاؤه بواسطة المجتمع ومن أجله. إذا كنت " +"ترغب في التعاون ، فأنت أكثر من موضع ترحيب!" + +msgid "onboarding.contrib.desc2.1" +msgstr "يمكنك الوصول إلى" + +msgid "onboarding.contrib.desc2.2" +msgstr "واتبع تعليمات المساهمة :)" + +msgid "onboarding.contrib.link" +msgstr "مشروع على github" + +msgid "onboarding.contrib.title" +msgstr "مساهم في المصدر المفتوح؟" + +msgid "onboarding.newsletter.accept" +msgstr "نعم ، اشترك" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "تم إرسال طلب الاشتراك الخاص بك ، وسوف نرسل لك بريدًا إلكترونيًا لتأكيده." + +msgid "onboarding.newsletter.decline" +msgstr "لا شكرا" + +msgid "onboarding.newsletter.desc" +msgstr "" +"اشترك في النشرة الإخبارية لدينا للبقاء على اطلاع دائم بتقدم تطوير المنتج " +"والأخبار." + +msgid "onboarding.newsletter.policy" +msgstr "سياسة الخصوصية." + +msgid "onboarding.newsletter.privacy1" +msgstr "نظرًا لأننا نهتم بالخصوصية ، فإليك موقعنا " + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"سوف نرسل لك رسائل البريد الإلكتروني ذات الصلة فقط. يمكنك إلغاء الاشتراك في " +"أي وقت في ملف تعريف المستخدم الخاص بك أو عبر رابط إلغاء الاشتراك في أي من " +"رسائلنا الإخبارية." + +msgid "onboarding.newsletter.title" +msgstr "هل تريد تلقي أخبار Penpot؟" + +msgid "onboarding.slide.0.alt" +msgstr "خلق التصاميم" + +msgid "onboarding.slide.0.desc1" +msgstr "قم بإنشاء واجهات مستخدم جميلة بالتعاون مع جميع أعضاء الفريق." + +msgid "onboarding.slide.0.desc2" +msgstr "الحفاظ على الاتساق على نطاق واسع مع المكونات والمكتبات وأنظمة التصميم." + +msgid "onboarding.slide.0.title" +msgstr "مكتبات التصميم والأنماط والمكونات" + +msgid "onboarding.slide.1.alt" +msgstr "النماذج التفاعلية" + +msgid "onboarding.slide.1.desc1" +msgstr "أنشئ تفاعلات غنية لتقليد سلوك المنتج." + +msgid "onboarding.slide.1.desc2" +msgstr "" +"شارك مع أصحاب المصلحة ، وقدم مقترحات إلى فريقك وابدأ في اختبار المستخدم " +"بتصميماتك ، كل ذلك في مكان واحد." + +msgid "onboarding.slide.1.title" +msgstr "اجعل تصميماتك تنبض بالحياة من خلال التفاعلات" + +msgid "onboarding.slide.2.alt" +msgstr "احصل على تعليقات" + +msgid "onboarding.slide.2.desc1" +msgstr "" +"يعمل جميع أعضاء الفريق في وقت واحد مع تصميمات متعددة اللاعبين في الوقت " +"الفعلي وتعليقات وأفكار وتعليقات مركزية مباشرة على التصميمات." + +msgid "onboarding.slide.2.title" +msgstr "الحصول على ردود الفعل ، وتقديم ومشاركة عملك" + +msgid "onboarding.slide.3.desc1" +msgstr "" +"قم بمزامنة التصميم والرمز لجميع المكونات والأنماط الخاصة بك واحصل على " +"مقتطفات التعليمات البرمجية." + +msgid "onboarding.slide.3.desc2" +msgstr "" +"احصل على مواصفات التعليمات البرمجية وقدمها مثل الترميز (SVG ، HTML) أو " +"الأنماط (CSS ، Less ، Stylus…)." + +msgid "onboarding.team-modal.create-team" +msgstr "أنشئ فريقًا" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"يسمح لك الفريق بالتعاون مع مستخدمي Penpot الآخرين الذين يعملون في نفس " +"الملفات والمشاريع." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "ملفات ومشاريع غير محدودة" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "إدارة الأدوار" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "عدد غير محدود من الأعضاء" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "مجانا 100٪ !" + +msgid "onboarding.templates.subtitle" +msgstr "فيما يلي بعض القوالب." + +msgid "onboarding.templates.title" +msgstr "إبدأ التصميم" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +msgid "onboarding.welcome.title" +msgstr "مرحبًا بك في Penpot" + #: src/app/main/ui/auth/recovery.cljs msgid "profile.recovery.go-to-login" msgstr "اذهب إلى تسجيل الدخول" @@ -1518,6 +2103,239 @@ msgstr "اذهب إلى تسجيل الدخول" msgid "settings.multiple" msgstr "مختلط" +# SECTIONS +msgid "shortcut-section.basics" +msgstr "الأساسيات" + +msgid "shortcut-section.dashboard" +msgstr "لوحة القيادة" + +msgid "shortcut-section.viewer" +msgstr "مشاهد" + +msgid "shortcut-section.workspace" +msgstr "مساحة العمل" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "محاذاة" + +msgid "shortcut-subsection.edit" +msgstr "تعديل" + +msgid "shortcut-subsection.general-dashboard" +msgstr "عام" + +msgid "shortcut-subsection.general-viewer" +msgstr "عام" + +msgid "shortcut-subsection.main-menu" +msgstr "القائمة الرئيسية" + +msgid "shortcut-subsection.modify-layers" +msgstr "تعديل الطبقات" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "التنقل" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "التنقل" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "التنقل" + +msgid "shortcut-subsection.panels" +msgstr "اللوحات" + +msgid "shortcut-subsection.path-editor" +msgstr "مسارات" + +msgid "shortcut-subsection.shape" +msgstr "الأشكال" + +msgid "shortcut-subsection.tools" +msgstr "أدوات" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "تكبير" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "تكبير" + +msgid "shortcuts.add-comment" +msgstr "تعليقات" + +msgid "shortcuts.add-node" +msgstr "إضافة عقدة" + +msgid "shortcuts.align-bottom" +msgstr "محاذاة لأسفل" + +msgid "shortcuts.align-hcenter" +msgstr "محاذاة المركز أفقيًا" + +msgid "shortcuts.align-left" +msgstr "محاذاة اليسار" + +msgid "shortcuts.align-right" +msgstr "محاذاة اليمين" + +msgid "shortcuts.align-top" +msgstr "‌محاذاة الأعلى" + +msgid "shortcuts.align-vcenter" +msgstr "محاذاة المركز عموديًا" + +msgid "shortcuts.artboard-selection" +msgstr "إنشاء لوحة من الاختيار" + +msgid "shortcuts.bring-back" +msgstr "أرسل إلى الخلف" + +msgid "shortcuts.bring-backward" +msgstr "إرسال إلى الوراء" + +msgid "shortcuts.bring-forward" +msgstr "ثابر للأمام" + +msgid "shortcuts.bring-front" +msgstr "أحضر إلى الأمام" + +msgid "shortcuts.clear-undo" +msgstr "مسح التراجع" + +msgid "shortcuts.copy" +msgstr "إنسخ" + +msgid "shortcuts.create-component" +msgstr "تكوين المكون" + +msgid "shortcuts.cut" +msgstr "إقطع" + +msgid "shortcuts.decrease-zoom" +msgstr "تصغير" + +msgid "shortcuts.delete" +msgstr "حذف" + +msgid "shortcuts.delete-node" +msgstr "حذف العقدة" + +msgid "shortcuts.detach-component" +msgstr "إفصل المكون" + +msgid "shortcuts.draw-curve" +msgstr "منحنى" + +msgid "shortcuts.draw-ellipse" +msgstr "الشكل البيضاوي" + +msgid "shortcuts.draw-frame" +msgstr "لوحة" + +msgid "shortcuts.draw-nodes" +msgstr "أرسم المسار" + +msgid "shortcuts.draw-path" +msgstr "المسار" + +msgid "shortcuts.draw-rect" +msgstr "مستطيل" + +msgid "shortcuts.draw-text" +msgstr "نص" + +msgid "shortcuts.duplicate" +msgstr "كرر" + +msgid "shortcuts.escape" +msgstr "إلغي" + +msgid "shortcuts.export-shapes" +msgstr "تصدير الأشكال" + +msgid "shortcuts.fit-all" +msgstr "تكبير لتناسب الجميع" + +msgid "shortcuts.flip-horizontal" +msgstr "قلب أفقيًا" + +msgid "shortcuts.flip-vertical" +msgstr "قلب عموديًا" + +msgid "shortcuts.go-to-drafts" +msgstr "إنتقل إلى المسودات" + +msgid "shortcuts.go-to-libs" +msgstr "إذهب إلى المكتبات المشتركة" + +msgid "shortcuts.go-to-search" +msgstr "بحث" + +msgid "shortcuts.group" +msgstr "مجموعة" + +msgid "shortcuts.h-distribute" +msgstr "وزع أفقيًا" + +msgid "shortcuts.hide-ui" +msgstr "إظهار / إخفاء واجهة المستخدم" + +msgid "shortcuts.increase-zoom" +msgstr "تكبير" + +msgid "shortcuts.insert-image" +msgstr "إدراج صورة" + +msgid "shortcuts.join-nodes" +msgstr "ربط العقد" + +msgid "shortcuts.make-corner" +msgstr "إصنع زاوية" + +msgid "shortcuts.make-curve" +msgstr "إصنع منحنى" + +msgid "shortcuts.mask" +msgstr "قناع" + +msgid "shortcuts.merge-nodes" +msgstr "دمج العقد" + +msgid "shortcuts.move" +msgstr "تحرك" + +msgid "shortcuts.move-fast-down" +msgstr "تحرك بسرعة لأسفل" + +msgid "shortcuts.move-fast-left" +msgstr "تحرك يسارا بسرعة" + +msgid "shortcuts.move-fast-right" +msgstr "تحرك يميناً بسرعة" + +msgid "shortcuts.move-fast-up" +msgstr "تحرك للأعلى بسرعة" + +msgid "shortcuts.move-nodes" +msgstr "نقل العقدة" + +msgid "shortcuts.move-unit-down" +msgstr "تحرك لأسفل" + +msgid "shortcuts.move-unit-left" +msgstr "تحرك يساراً" + +msgid "shortcuts.move-unit-right" +msgstr "تحرك يميناً" + +msgid "shortcuts.move-unit-up" +msgstr "تحرك للأعلى" + +msgid "shortcuts.next-frame" +msgstr "اللوحة التالية" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -2059,13 +2877,7 @@ msgstr "تصدير" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "تصدير 0 عنصر" -msgstr[1] "تصدير عنصر واحد" -msgstr[2] "تصدير عنصرين" -msgstr[3] "تصدير عناصر" -msgstr[4] "تصدير عنصر" -msgstr[5] "تصدير عنصر" +msgstr "تصدير 0 عنصر" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -2183,835 +2995,13 @@ msgstr "قاتم" msgid "workspace.options.layer-options.blend-mode.difference" msgstr "اختلاف" +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "يوجد تحديثات في المكتبات المشتركة" + #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.update" msgstr "تحديث" msgid "workspace.viewport.click-to-close-path" -msgstr "انقر لإغلاق المسار" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.upload-all" -msgstr "حمل الكل" - -msgid "dashboard.export.title" -msgstr "صدر الملفات" - -msgid "dashboard.import.progress.process-components" -msgstr "يتم معالجة العناصر" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "Penpot تم تصميمه للفرق. أدعُ زملاءك للعمل سوياَ على المشاريع والملفات" - -msgid "dashboard.import.progress.process-media" -msgstr "يتم معالجة الوسائط" - -msgid "common.publish" -msgstr "أنشر" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "دورة تعليمية عملية" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "إبدا الدورة التعليمية" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "إبدا الجولة" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "جولة في الواجهة" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-multiple.selected" -msgstr "%s من %s عناصر مختارة" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.no-elements" -msgstr "لا يوجد عناصر بإعدادت التصدير." - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.title" -msgstr "إختيار التصدير" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.how-to-link" -msgstr "معلومات عن كيفية إعداد التصدير." - -msgid "dashboard.export.options.all.title" -msgstr "صدر المكتبات المشتركة" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.dismiss-all" -msgstr "الغاء الكل" - -msgid "dashboard.import.progress.process-colors" -msgstr "يتم معالجة الألوان" - -msgid "dashboard.import.import-message" -msgstr "%s ملف تم ادراجهم بنجاح." - -msgid "dashboard.import.import-error" -msgstr "حصل خلل خلال إدراج الملف. لم يتم إدراج الملف على البرنامج." - -msgid "dashboard.import.progress.process-page" -msgstr "يتم معالجة الصفحة: %s" - -msgid "dashboard.import.progress.process-typographies" -msgstr "يتم معالجة الخطوط" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.save-settings" -msgstr "حفظ الإعدادات" - -msgid "common.unpublish" -msgstr "إلغاء النشر" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "حسناَ" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "إنتباه" - -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.there-are-updates" -msgstr "يوجد تحديثات في المكتبات المشتركة" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "إدارة الفريق" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "إعمل فريق!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "إذهب في جولة في البرنامج وتعرف على ميزاته." - -msgid "dashboard.export-standard-multi" -msgstr "تحميل %s ملفات أساسية (.svg + .json)" - -msgid "dashboard.export.detail" -msgstr "* ربما يحتوي على عناصر، رسومات، الوان، و/أو خطوط." - -msgid "dashboard.import.analyze-error" -msgstr "لم نستطع استيراد أو إدراج هذا الملف" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "تعلم أساسيات برنامج Penpot بينما تستمع بتجربته في هذه الدورة التعليمية." - -msgid "dashboard.libraries-and-templates" -msgstr "المكتبات & القوالب" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "استعرض المزيد منهم وتعلم كيف تساهم" - -msgid "common.share-link.page-shared" -msgid_plural "common.share-link.page-shared" -msgstr[0] "لم يتم مشاركة الصفحة" -msgstr[1] "مشاركة صفحة واحدة" -msgstr[2] "مشاركة صفحتين" -msgstr[3] "مشاركة صفحات" -msgstr[4] "مشاركة صفحة" -msgstr[5] "مشاركة صفحة" - -msgid "dashboard.import.progress.upload-data" -msgstr "تحميل البيانات للخادم (%s/%s)" - -msgid "dashboard.import.progress.upload-media" -msgstr "تحميل الملف: %s" - -msgid "dashboard.export-frames" -msgstr "صدر اللوحة الفنية الى ملف PDF…" - -msgid "errors.team-leave.insufficient-members" -msgstr "أعضاء غير كافيين لمغادرة الفريق ، ربما تريد حذفه." - -msgid "errors.team-leave.member-does-not-exists" -msgstr "العضو الذي تحاول تعيينه غير موجود." - -msgid "labels.next" -msgstr "التالي" - -msgid "labels.share-prototype" -msgstr "مشاركة النموذج الأولي" - -msgid "dashboard.export.options.detach.title" -msgstr "عامل أصول المكتبة المشتركة كعناصر بسيطة" - -msgid "dashboard.export.options.merge.title" -msgstr "تضمين أصول المكتبة المشتركة في مكتبات الملفات" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-title" -msgstr "الإشتراك في" - -#: src/app/main/ui/auth/login.cljs -msgid "errors.auth-provider-not-configured" -msgstr "موفر المصادقة غير معد ومسجل." - -msgid "errors.email-spam-or-permanent-bounces" -msgstr "تم الإبلاغ عن البريد الإلكتروني «٪ s» كبريد عشوائي أو مرتد بشكل دائم." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-subtitle1" -msgstr "يسعدنا وجودك هنا. إذا كنت بحاجة إلى مساعدة، يرجى البحث أولا قبل النشر." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "انتقل إلى منتدى Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-title" -msgstr "مجتمع Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-go-to" -msgstr "اذهب إلى Twitter" - -msgid "labels.default" -msgstr "إفتراضي" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.github-repo" -msgstr "مستودع Github" - -msgid "labels.log-or-sign" -msgstr "تسجيل الدخول أو الاشتراك" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.member" -msgstr "عضو" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-msg" -msgstr "أرسل لي الأخبار وتحديثات المنتجات والتوصيات حول Penpot." - -#: src/app/main/ui/confirm.cljs -msgid "ds.component-subtitle" -msgstr "عناصر سيتم تحديثها:" - -msgid "errors.auth.unable-to-login" -msgstr "يبدوا أنك غير مصرح لك أو أن الجلسة إنتهت." - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.remove-member" -msgstr "إزالة العضو" - -msgid "labels.skip" -msgstr "تخطي" - -msgid "labels.start" -msgstr "ابدأ" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.your-account" -msgstr "حسابك" - -msgid "modals.invite-member.emails" -msgstr "رسائل البريد الإلكتروني، مفصولة بفواصل" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.libraries-and-templates" -msgstr "المكتبات والقوالب" - -msgid "labels.link" -msgstr "رابط" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.pending-invitation" -msgstr "قيد الانتظار" - -msgid "labels.show-comments-list" -msgstr "قائمة التعليقات" - -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.big-nudge" -msgstr "دفعة كبيرة" - -msgid "dashboard.export.explain" -msgstr "" -"ملف أو أكثر تريد تصديرهم يستخدمون مكتبات مشتركة. ماذا تريد أن تفعل في " -"أصولهم*؟" - -msgid "dashboard.export.options.all.message" -msgstr "" -"سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم." - -msgid "dashboard.import.import-warning" -msgstr "تحتوي بعض الملفات على كائنات غير صالحة تمت إزالتها." - -msgid "errors.email-as-password" -msgstr "لا يمكنك استخدام بريدك الإلكتروني ككلمة مرور" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "حذف الملف" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "ادعُ الأعضاء إلى الفريق" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "الغاء نشر المكتبة" - -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.invite-invalid" -msgstr "دعوة غير صالحة" - -msgid "errors.invite-invalid.info" -msgstr "هذه الدعوة قد تلغى أو قد تنتهي." - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.status" -msgstr "الحالة" - -msgid "errors.team-leave.owner-cant-leave" -msgstr "لا يمكن للمالك مغادرة الفريق ، يجب إعادة تعيين دور المالك." - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.tutorials" -msgstr "الدروس" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-subtitle1" -msgstr "هنا للمساعدة في استفساراتك التقنية." - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.resend-invitation" -msgstr "إعادة إرسال الدعوة" - -msgid "labels.workspace" -msgstr "مساحة العمل" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.no-invitations" -msgstr "لا توجد دعوات." - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.no-invitations-hint" -msgstr "" -"اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.you" -msgstr "(أنت)" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.change-owner-and-leave-confirm.message" -msgstr "" -"أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.how-to" -msgstr "" -"يمكنك إضافة إعدادات التصدير إلى العناصر من خصائص التصميم (أسفل الشريط " -"الجانبي الأيمن)." - -msgid "dashboard.export.options.detach.message" -msgstr "" -"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى المكتبة. " - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-close-confirm.hint" -msgstr "" -"نظرًا لأنك العضو الوحيد في هذا الفريق ، فسيتم حذف الفريق مع مشاريعه وملفاته." - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "حدثت مشكلة في استيراد النموذج. لم يتم استيراد النموذج." - -msgid "dashboard.export.options.merge.message" -msgstr "سيتم تصدير ملفك مع دمج جميع الأصول الخارجية في مكتبة الملفات." - -msgid "onboarding.choice.title" -msgstr "مرحبًا بك في Penpot" - -msgid "onboarding.contrib.alt" -msgstr "مصدر مفتوح" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-close-confirm.message" -msgstr "هل أنت متأكد أنك تريد مغادرة فريق %s ؟" - -msgid "onboarding.newsletter.privacy2" -msgstr "" -"سوف نرسل لك رسائل البريد الإلكتروني ذات الصلة فقط. يمكنك إلغاء الاشتراك في " -"أي وقت في ملف تعريف المستخدم الخاص بك أو عبر رابط إلغاء الاشتراك في أي من " -"رسائلنا الإخبارية." - -msgid "onboarding.slide.0.alt" -msgstr "خلق التصاميم" - -msgid "onboarding.slide.0.desc1" -msgstr "قم بإنشاء واجهات مستخدم جميلة بالتعاون مع جميع أعضاء الفريق." - -msgid "onboarding.slide.0.desc2" -msgstr "الحفاظ على الاتساق على نطاق واسع مع المكونات والمكتبات وأنظمة التصميم." - -msgid "onboarding.slide.0.title" -msgstr "مكتبات التصميم والأنماط والمكونات" - -msgid "onboarding.slide.1.alt" -msgstr "النماذج التفاعلية" - -msgid "onboarding.slide.2.alt" -msgstr "احصل على تعليقات" - -msgid "onboarding.slide.2.desc1" -msgstr "" -"يعمل جميع أعضاء الفريق في وقت واحد مع تصميمات متعددة اللاعبين في الوقت " -"الفعلي وتعليقات وأفكار وتعليقات مركزية مباشرة على التصميمات." - -msgid "onboarding.slide.2.title" -msgstr "الحصول على ردود الفعل ، وتقديم ومشاركة عملك" - -msgid "onboarding.slide.3.desc1" -msgstr "" -"قم بمزامنة التصميم والرمز لجميع المكونات والأنماط الخاصة بك واحصل على " -"مقتطفات التعليمات البرمجية." - -msgid "onboarding.slide.3.desc2" -msgstr "" -"احصل على مواصفات التعليمات البرمجية وقدمها مثل الترميز (SVG ، HTML) أو " -"الأنماط (CSS ، Less ، Stylus…)." - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.fonts-added" -msgid_plural "dashboard.fonts.fonts-added" -msgstr[0] "لم يتم إضافة أي خط" -msgstr[1] "تمت إضافة خط واحد" -msgstr[2] "تمت إضافة خطين" -msgstr[3] "عدد قليل من الخطوط المضافة" -msgstr[4] "تمت إضافة العديد من الخطوط" -msgstr[5] "" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.hint" -msgstr "" -"إذا قمت بنقل الملكية ، فسوف تقوم بتغيير دورك إلى المسؤول ، وستفقد بعض " -"الأذونات على هذا الفريق. " - -msgid "onboarding.contrib.desc1" -msgstr "" -"Penpot هو برنامج مفتوح المصدر ، تم إنشاؤه بواسطة المجتمع ومن أجله. إذا كنت " -"ترغب في التعاون ، فأنت أكثر من موضع ترحيب!" - -msgid "onboarding.contrib.desc2.1" -msgstr "يمكنك الوصول إلى" - -msgid "onboarding.contrib.desc2.2" -msgstr "واتبع تعليمات المساهمة :)" - -msgid "onboarding.newsletter.acceptance-message" -msgstr "" -"تم إرسال طلب الاشتراك الخاص بك ، وسوف نرسل لك بريدًا إلكترونيًا لتأكيده." - -msgid "onboarding.newsletter.decline" -msgstr "لا شكرا" - -msgid "onboarding.newsletter.desc" -msgstr "" -"اشترك في النشرة الإخبارية لدينا للبقاء على اطلاع دائم بتقدم تطوير المنتج " -"والأخبار." - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "تم استخدام هذا الملف بالفعل مع تمكين المكونات V2." - -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.nudge-title" -msgstr "كمية الدفع" - -msgid "onboarding-v2.before-start.desc3" -msgstr "يمكنك مشاهدة برامجنا التعليمية والبرامج التعليمية التي قدمها مجتمعنا." - -msgid "onboarding-v2.before-start.desc3.title" -msgstr "دروس الفيديو" - -msgid "onboarding-v2.before-start.title" -msgstr "قبل ان تبدا" - -msgid "onboarding-v2.welcome.desc1" -msgstr "" -"Penpot هو برنامج مفتوح المصدر وهو من صنع Kaleidos وكذلك المجتمع ، حيث يساعد " -"الكثير من الناس بعضهم البعض بالفعل. يمكن للجميع التعاون من خلال:" - -msgid "onboarding-v2.welcome.desc2.title" -msgstr "المشاركة في المجتمع" - -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.small-nudge" -msgstr "دفعة صغيرة" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "إلغاء النشر" - -msgid "onboarding.newsletter.policy" -msgstr "سياسة الخصوصية." - -msgid "onboarding.newsletter.privacy1" -msgstr "نظرًا لأننا نهتم بالخصوصية ، فإليك موقعنا " - -msgid "onboarding.slide.1.desc1" -msgstr "أنشئ تفاعلات غنية لتقليد سلوك المنتج." - -msgid "onboarding.slide.1.desc2" -msgstr "" -"شارك مع أصحاب المصلحة ، وقدم مقترحات إلى فريقك وابدأ في اختبار المستخدم " -"بتصميماتك ، كل ذلك في مكان واحد." - -msgid "onboarding.slide.1.title" -msgstr "اجعل تصميماتك تنبض بالحياة من خلال التفاعلات" - -msgid "onboarding.team-modal.create-team" -msgstr "أنشئ فريقًا" - -msgid "onboarding.choice.team-up.create-team" -msgstr "اسم فريقك" - -msgid "onboarding.choice.team-up.create-team-desc" -msgstr "بعد تسمية فريقك ، ستتمكن من دعوة الأشخاص للانضمام." - -msgid "onboarding.choice.team-up.create-team-placeholder" -msgstr "أدخل اسم الفريق" - -msgid "onboarding.choice.team-up.invite-members" -msgstr "دعوة أعضاء" - -msgid "onboarding.choice.team-up.invite-members-skip" -msgstr "أنشئ فريقًا وادعُه لاحقًا" - -msgid "onboarding.choice.team-up.invite-members-submit" -msgstr "أنشئ فريقًا وأرسل الدعوات" - -msgid "onboarding.newsletter.title" -msgstr "هل تريد تلقي أخبار Penpot؟" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component-in-bulk.hint" -msgstr "" -"أنت على وشك تحديث المكونات في مكتبة مشتركة. قد يؤثر هذا على الملفات الأخرى " -"التي تستخدمها." - -msgid "onboarding-v2.before-start.desc1" -msgstr "" -"يجب أن تعلم أن هناك الكثير من الموارد المتاحة لمساعدتك في بدء استخدام Penpot " -"، مثل دليل المستخدم وقناة Youtube الخاصة بنا." - -msgid "onboarding-v2.welcome.desc3" -msgstr "" -"حيث ستجد كيفية التعاون في الترجمات وطلبات الميزات والمساهمات الأساسية والبحث " -"عن الأخطاء …" - -msgid "onboarding.contrib.title" -msgstr "مساهم في المصدر المفتوح؟" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component-in-bulk.message" -msgstr "تحديث المكونات في مكتبة مشتركة" - -msgid "onboarding-v2.before-start.desc2.title" -msgstr "دليل المستخدم" - -msgid "onboarding-v2.welcome.title" -msgstr "مرحبًا بك في Penpot!" - -msgid "onboarding-v2.before-start.desc2" -msgstr "" -"معلومات مفصلة حول كيفية استخدام Penpot. من النماذج الأولية إلى تنظيم أو " -"مشاركة التصاميم." - -msgid "onboarding-v2.welcome.desc3.title" -msgstr "دليل المساهمة" - -msgid "onboarding.contrib.link" -msgstr "مشروع على github" - -msgid "onboarding.newsletter.accept" -msgstr "نعم ، اشترك" - -msgid "onboarding-v2.welcome.desc2" -msgstr "" -"مساحة عامة للتعلم والمشاركة والمناقشة حول Penpot وحاضرها ومستقبلها مع " -"المجتمع بأكمله وفريق Penpot الأساسي." - -msgid "onboarding.choice.team-up.create-later" -msgstr "أنشئ فريقًا لاحقًا" - -msgid "onboarding.choice.team-up.invite-members-info" -msgstr "تذكر أن تشمل الجميع. المطورين والمصممين والمديرين ... التنوع يضيف :)" - -msgid "onboarding.choice.team-up.roles" -msgstr "دعوة مع الدور:" - -msgid "onboarding.team-modal.create-team-desc" -msgstr "" -"يسمح لك الفريق بالتعاون مع مستخدمي Penpot الآخرين الذين يعملون في نفس " -"الملفات والمشاريع." - -msgid "onboarding.team-modal.create-team-feature-1" -msgstr "ملفات ومشاريع غير محدودة" - -msgid "onboarding.team-modal.create-team-feature-3" -msgstr "إدارة الأدوار" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "مجانا 100٪ !" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "عدد غير محدود من الأعضاء" - -# SUBSECTIONS -msgid "shortcut-subsection.alignment" -msgstr "محاذاة" - -msgid "shortcuts.align-hcenter" -msgstr "محاذاة المركز أفقيًا" - -msgid "shortcut-section.workspace" -msgstr "مساحة العمل" - -# SECTIONS -msgid "shortcut-section.basics" -msgstr "الأساسيات" - -msgid "shortcut-section.dashboard" -msgstr "لوحة القيادة" - -msgid "shortcuts.align-bottom" -msgstr "محاذاة لأسفل" - -msgid "shortcuts.align-right" -msgstr "محاذاة اليمين" - -msgid "shortcuts.draw-rect" -msgstr "مستطيل" - -msgid "shortcuts.draw-path" -msgstr "المسار" - -msgid "shortcuts.draw-text" -msgstr "نص" - -msgid "shortcuts.export-shapes" -msgstr "تصدير الأشكال" - -msgid "shortcuts.make-corner" -msgstr "إصنع زاوية" - -msgid "shortcuts.go-to-search" -msgstr "بحث" - -msgid "shortcuts.go-to-libs" -msgstr "إذهب إلى المكتبات المشتركة" - -msgid "shortcuts.hide-ui" -msgstr "إظهار / إخفاء واجهة المستخدم" - -msgid "shortcuts.insert-image" -msgstr "إدراج صورة" - -msgid "shortcuts.join-nodes" -msgstr "ربط العقد" - -msgid "onboarding.welcome.alt" -msgstr "Penpot" - -msgid "onboarding.welcome.title" -msgstr "مرحبًا بك في Penpot" - -msgid "shortcut-subsection.edit" -msgstr "تعديل" - -msgid "shortcut-subsection.main-menu" -msgstr "القائمة الرئيسية" - -msgid "shortcut-subsection.modify-layers" -msgstr "تعديل الطبقات" - -msgid "shortcut-subsection.navigation-dashboard" -msgstr "التنقل" - -msgid "shortcut-subsection.navigation-viewer" -msgstr "التنقل" - -msgid "shortcut-subsection.navigation-workspace" -msgstr "التنقل" - -msgid "shortcut-subsection.panels" -msgstr "اللوحات" - -msgid "shortcut-subsection.path-editor" -msgstr "مسارات" - -msgid "shortcut-subsection.shape" -msgstr "الأشكال" - -msgid "shortcut-subsection.tools" -msgstr "أدوات" - -msgid "shortcut-subsection.zoom-viewer" -msgstr "تكبير" - -msgid "shortcut-subsection.zoom-workspace" -msgstr "تكبير" - -msgid "shortcuts.add-comment" -msgstr "تعليقات" - -msgid "shortcuts.add-node" -msgstr "إضافة عقدة" - -msgid "shortcuts.align-vcenter" -msgstr "محاذاة المركز عموديًا" - -msgid "shortcuts.artboard-selection" -msgstr "إنشاء لوحة من الاختيار" - -msgid "shortcuts.bring-back" -msgstr "أرسل إلى الخلف" - -msgid "shortcuts.bring-backward" -msgstr "إرسال إلى الوراء" - -msgid "shortcuts.bring-forward" -msgstr "ثابر للأمام" - -msgid "shortcuts.bring-front" -msgstr "أحضر إلى الأمام" - -msgid "shortcuts.clear-undo" -msgstr "مسح التراجع" - -msgid "shortcuts.copy" -msgstr "إنسخ" - -msgid "shortcuts.create-component" -msgstr "تكوين المكون" - -msgid "shortcuts.cut" -msgstr "إقطع" - -msgid "shortcuts.decrease-zoom" -msgstr "تصغير" - -msgid "shortcuts.increase-zoom" -msgstr "تكبير" - -msgid "shortcuts.make-curve" -msgstr "إصنع منحنى" - -msgid "shortcuts.mask" -msgstr "قناع" - -msgid "shortcuts.align-left" -msgstr "محاذاة اليسار" - -msgid "shortcuts.align-top" -msgstr "‌محاذاة الأعلى" - -msgid "shortcuts.delete" -msgstr "حذف" - -msgid "shortcuts.delete-node" -msgstr "حذف العقدة" - -msgid "shortcuts.detach-component" -msgstr "إفصل المكون" - -msgid "shortcuts.draw-curve" -msgstr "منحنى" - -msgid "shortcuts.draw-ellipse" -msgstr "الشكل البيضاوي" - -msgid "shortcuts.draw-frame" -msgstr "لوحة" - -msgid "shortcuts.draw-nodes" -msgstr "أرسم المسار" - -msgid "shortcuts.duplicate" -msgstr "كرر" - -msgid "shortcuts.escape" -msgstr "إلغي" - -msgid "shortcuts.fit-all" -msgstr "تكبير لتناسب الجميع" - -msgid "shortcuts.flip-horizontal" -msgstr "قلب أفقيًا" - -msgid "shortcuts.flip-vertical" -msgstr "قلب عموديًا" - -msgid "shortcuts.go-to-drafts" -msgstr "إنتقل إلى المسودات" - -msgid "shortcuts.group" -msgstr "مجموعة" - -msgid "shortcuts.h-distribute" -msgstr "وزع أفقيًا" - -msgid "onboarding.templates.title" -msgstr "إبدأ التصميم" - -msgid "onboarding.templates.subtitle" -msgstr "فيما يلي بعض القوالب." - -msgid "shortcuts.merge-nodes" -msgstr "دمج العقد" - -msgid "shortcuts.move-fast-up" -msgstr "تحرك للأعلى بسرعة" - -msgid "shortcuts.move" -msgstr "تحرك" - -msgid "shortcuts.move-fast-down" -msgstr "تحرك بسرعة لأسفل" - -msgid "shortcuts.move-fast-left" -msgstr "تحرك يسارا بسرعة" - -msgid "shortcuts.move-fast-right" -msgstr "تحرك يميناً بسرعة" - -msgid "shortcuts.move-nodes" -msgstr "نقل العقدة" - -msgid "shortcuts.move-unit-down" -msgstr "تحرك لأسفل" - -msgid "shortcuts.move-unit-right" -msgstr "تحرك يميناً" - -msgid "shortcuts.move-unit-left" -msgstr "تحرك يساراً" - -msgid "shortcuts.move-unit-up" -msgstr "تحرك للأعلى" - -msgid "shortcuts.next-frame" -msgstr "اللوحة التالية" - -msgid "shortcut-section.viewer" -msgstr "مشاهد" - -msgid "shortcut-subsection.general-viewer" -msgstr "عام" - -msgid "shortcut-subsection.general-dashboard" -msgstr "عام" +msgstr "انقر لإغلاق المسار" \ No newline at end of file diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index ebacd0ca4a..373c8b8338 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Antonio \n" -"Language-Team: Catalan \n" +"Language-Team: Catalan " +"\n" "Language: ca\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -171,6 +171,9 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "S'ha enviat un correu de verificació a" +msgid "common.publish" +msgstr "Publica" + msgid "common.share-link.all-users" msgstr "Tots els usuaris de Penpot" @@ -226,6 +229,49 @@ msgstr "Compartiu prototips" msgid "common.share-link.view-all" msgstr "Selecciona-ho tot" +msgid "common.unpublish" +msgstr "Despublica" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Gestió de l'equip" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"El Penpot està pensat per a equips. Convida a membres i treballeu plegats " +"en projectes i fitxers" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Fes equip!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Aprèn les bases del Penpot mentre et diverteixes amb aquest tutorial " +"pràctic." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Comença el tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial pràctic" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Passeja't pel Penpot i coneix-ne les característiques principals." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Comença la visita" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Passeig per la interfície" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Afegeix a la biblioteca compartida" @@ -443,6 +489,17 @@ msgstr "Convida a l'equip" msgid "dashboard.leave-team" msgstr "Abandona l'equip" +msgid "dashboard.libraries-and-templates" +msgstr "Biblioteques i plantilles" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Explora'n més i coneix com contribuir-hi" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "" +"Hi ha hagut un problema en importar la plantilla. La plantilla no s'ha " +"importat." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Biblioteques compartides" @@ -621,6 +678,10 @@ msgstr "Resultats de la cerca" msgid "dashboard.type-something" msgstr "Escriviu per cercar resultats" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Despublica la biblioteca" + #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "Actualitza la configuració" @@ -641,6 +702,14 @@ msgstr "Nom" msgid "dashboard.your-penpot" msgstr "El meu Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "D'acord" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Atenció" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Components a actualitzar:" @@ -672,6 +741,10 @@ msgstr "Sembla que no esteu autenticat o que la sessió ha caducat." msgid "errors.clipboard-not-implemented" msgstr "El vostre navegador no pot fer aquesta operació" +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Aquest fitxer ja s'ha utilitzat amb els Components V2 habilitats." + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" msgstr "Aquest correu ja està en ús" @@ -1663,6 +1736,18 @@ msgstr "Segur que voleu eliminar el projecte?" msgid "modals.delete-project-confirm.title" msgstr "Elimina el projecte" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Suprimeix el fitxer" +msgstr[1] "Suprimeix els fitxers" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Esteu segur que voleu suprimir aquest fitxer?" +msgstr[1] "Esteu segur que voleu suprimir aquests fitxers?" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "Elimina l'equip" @@ -4398,88 +4483,4 @@ msgid "workspace.updates.update" msgstr "Actualitza" msgid "workspace.viewport.click-to-close-path" -msgstr "Feu clic per a tancar el camí" - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "Aquest fitxer ja s'ha utilitzat amb els Components V2 habilitats." - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "" -"Hi ha hagut un problema en importar la plantilla. La plantilla no s'ha " -"importat." - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Atenció" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Suprimeix el fitxer" -msgstr[1] "Suprimeix els fitxers" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "Esteu segur que voleu suprimir aquest fitxer?" -msgstr[1] "Esteu segur que voleu suprimir aquests fitxers?" - -msgid "common.unpublish" -msgstr "Despublica" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "D'acord" - -msgid "common.publish" -msgstr "Publica" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Gestió de l'equip" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"El Penpot està pensat per a equips. Convida a membres i treballeu plegats en " -"projectes i fitxers" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Fes equip!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "" -"Aprèn les bases del Penpot mentre et diverteixes amb aquest tutorial pràctic." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Comença el tutorial" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Tutorial pràctic" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "Passeja't pel Penpot i coneix-ne les característiques principals." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Comença la visita" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Passeig per la interfície" - -msgid "dashboard.libraries-and-templates" -msgstr "Biblioteques i plantilles" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Explora'n més i coneix com contribuir-hi" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Despublica la biblioteca" +msgstr "Feu clic per a tancar el camí" \ No newline at end of file diff --git a/frontend/translations/cs.po b/frontend/translations/cs.po index 4f8f6e6dec..b1fc23594e 100644 --- a/frontend/translations/cs.po +++ b/frontend/translations/cs.po @@ -1,2 +1,6 @@ msgid "" -msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file +msgstr "" +"X-Generator: Weblate\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" \ No newline at end of file diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 7f800f22f5..e9ca1adeeb 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-23 20:54+0000\n" "Last-Translator: Stas Haas \n" -"Language-Team: German \n" +"Language-Team: German " +"\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -174,6 +174,9 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Wir haben eine Bestätigungs-E-Mail gesendet an" +msgid "common.publish" +msgstr "Veröffentlichen" + msgid "common.share-link.all-users" msgstr "Alle Penpot-Benutzer" @@ -229,6 +232,49 @@ msgstr "Prototypen teilen" msgid "common.share-link.view-all" msgstr "Alle auswählen" +msgid "common.unpublish" +msgstr "Veröffentlichung aufheben" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Teammanagement" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot ist für Teams gedacht. Um gemeinsam an Projekten und Dateien zu " +"arbeiten, laden Sie Mitglieder ein" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Teamwork!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Lernen Sie die Grundlagen von Penpot und haben Sie Spaß mit diesem " +"praktischen Tutorial." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Tutorial starten" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Praktisches Tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Erkunden Sie Penpot um mehr über die wichtigsten Funktionen zu erfahren." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Tour starten" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Benutzeroberfläche erkunden" + #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -448,6 +494,17 @@ msgstr "Zum Team einladen" msgid "dashboard.leave-team" msgstr "Team verlassen" +msgid "dashboard.libraries-and-templates" +msgstr "Bibliotheken & Vorlagen" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Entdecken Sie weitere und erfahren Sie, wie Sie beitragen können" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "" +"Beim Importieren der Vorlage ist ein Problem aufgetreten. Die Vorlage wurde " +"nicht importiert." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Gemeinsam genutzte Bibliotheken" @@ -632,6 +689,10 @@ msgstr "Suchergebnisse" msgid "dashboard.type-something" msgstr "Zum Suchen etwas eingeben" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Veröffentlichung der Bibliothek aufheben" + #: src/app/main/ui/settings/profile.cljs, #: src/app/main/ui/settings/password.cljs, #: src/app/main/ui/settings/options.cljs @@ -658,6 +719,14 @@ msgstr "Ihr Name" msgid "dashboard.your-penpot" msgstr "Ihr Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "OK" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Achtung" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Zu aktualisierende Komponenten:" @@ -689,6 +758,10 @@ msgstr "Anscheinend sind Sie nicht authentifiziert oder die Sitzung ist abgelauf msgid "errors.clipboard-not-implemented" msgstr "Ihr Browser kann diese Funktion nicht ausführen" +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Diese Datei wurde bereits mit aktivierten V2-Komponenten verwendet." + #: src/app/main/ui/auth/verify_token.cljs, #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" @@ -1713,6 +1786,42 @@ msgstr "Sind Sie sicher, dass Sie dieses Projekt löschen möchten?" msgid "modals.delete-project-confirm.title" msgstr "Projekt löschen" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Datei löschen" +msgstr[1] "Dateien löschen" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Möchten Sie diese Datei wirklich löschen?" +msgstr[1] "Möchten Sie diese Dateien wirklich löschen?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Diese Datei enthält Bibliotheken, die in dieser Datei verwendet werden:" +msgstr[1] "Diese Datei enthält Bibliotheken, die in diesen Dateien verwendet werden:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Diese Dateien enthalten Bibliotheken, die in dieser Datei verwendet werden:" +msgstr[1] "" +"Diese Dateien enthalten Bibliotheken, die in diesen Dateien verwendet " +"werden:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Datei löschen" +msgstr[1] "Dateien löschen" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Datei löschen" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "Team löschen" @@ -1746,6 +1855,10 @@ msgstr "Einladung senden" msgid "modals.invite-member.emails" msgstr "E-Mails, durch Komma getrennt" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Mitglieder in das Team einladen" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" msgstr "" @@ -1838,6 +1951,38 @@ msgstr "Entfernen Sie “%s” als gemeinsam genutzte Bibliothek" msgid "modals.small-nudge" msgstr "Minimal" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Veröffentlichung aufheben" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Wenn Sie die Veröffentlichung aufheben, werden die darin enthaltenen " +"Elemente zu einer Bibliothek dieser Datei." +msgstr[1] "" +"Wenn Sie die Veröffentlichung aufheben, werden die darin enthaltenen " +"Elemente zu einer Bibliothek dieser Dateien." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Möchten Sie die Veröffentlichung dieser Bibliothek wirklich aufheben?" +msgstr[1] "Möchten Sie die Veröffentlichung dieser Bibliotheken wirklich aufheben?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Es wird in dieser Datei verwendet:" +msgstr[1] "Es wird in diesen Dateien verwendet:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Veröffentlichung der Bibliothek aufheben" +msgstr[1] "Veröffentlichung der Bibliotheken aufheben" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" @@ -1892,6 +2037,59 @@ msgstr "Profil erfolgreich gespeichert!" msgid "notifications.validation-email-sent" msgstr "Verifizierungs-E-Mail an %s gesendet. Prüfen Sie Ihren Posteingang!" +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Sie sollten wissen, dass es viele Ressourcen gibt, die Ihnen den Einstieg " +"in Penpot erleichtern, wie z. B. das Benutzerhandbuch und unseren " +"Youtube-Kanal." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Detaillierte Informationen über die Verwendung von Penpot. Vom Prototyping " +"bis zum Organisieren und Teilen von Designs." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Benutzerhandbuch" + +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"Sie können sich unsere Tutorials und die von unserer Community erstellten " +"Tutorials ansehen." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Video-Tutorials" + +msgid "onboarding-v2.before-start.title" +msgstr "Bevor Sie beginnen" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot ist Open Source und wird sowohl von Kaleidos als auch von der " +"Community entwickelt, wo sich viele Leute bereits gegenseitig helfen. Jeder " +"kann mitmachen:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Ein öffentlicher Raum zum Lernen, Teilen und Diskutieren über Penpot, seine " +"Gegenwart und Zukunft mit der gesamten Community und dem Penpot-Kernteam." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "In der Community mitmachen" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Hier erfahren Sie, wie Sie bei Übersetzungen, Feature Requests, " +"Core-Entwicklung und der Fehlersuche helfen können…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Leitfaden für Mitwirkende" + +msgid "onboarding-v2.welcome.title" +msgstr "Willkommen bei Penpot!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Team später erstellen" + msgid "onboarding.choice.team-up.create-team" msgstr "Ihr Teamname" @@ -1909,12 +2107,20 @@ msgstr "" "Im Teambereich können Sie auch später Mitglieder einladen und die Rollen " "ändern." +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Denken Sie daran, alle einzubeziehen. Entwickler, Designer, Manager... die " +"Vielfalt macht's :)" + msgid "onboarding.choice.team-up.invite-members-skip" msgstr "Team erstellen und später einladen" msgid "onboarding.choice.team-up.invite-members-submit" msgstr "Team erstellen und Einladungen versenden" +msgid "onboarding.choice.team-up.roles" +msgstr "Einladen mit der Rolle:" + msgid "onboarding.choice.title" msgstr "Willkommen bei Penpot" @@ -2032,6 +2238,29 @@ msgstr "Eine zentrale Anlaufstelle für alle" msgid "onboarding.team-input-placeholder" msgstr "Neuen Teamnamen eingeben" +msgid "onboarding.team-modal.create-team" +msgstr "Ein Team erstellen" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"In einem Team können Sie mit anderen Penpot-Nutzern zusammenarbeiten, die " +"an denselben Dateien und Projekten arbeiten." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Unbegrenzte Anzahl von Dateien und Projekten" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Multiplayer-Edition" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Rollenverwaltung" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Unbegrenzte Anzahl von Mitgliedern" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% kostenlos!" + msgid "onboarding.team.create.button" msgstr "Team erstellen" @@ -2188,6 +2417,24 @@ msgstr "Zeichenfläche aus Auswahl erstellen" msgid "shortcuts.bool-difference" msgstr "Subtrahieren (Boolesche Operation)" +msgid "shortcuts.bool-exclude" +msgstr "Ausschluss" + +msgid "shortcuts.bool-intersection" +msgstr "Schnittmenge" + +msgid "shortcuts.bool-union" +msgstr "Vereinigung" + +msgid "shortcuts.bring-back" +msgstr "In den Hintergrund" + +msgid "shortcuts.bring-backward" +msgstr "Eins nach hinten" + +msgid "shortcuts.bring-forward" +msgstr "Eins nach vorne" + msgid "shortcuts.bring-front" msgstr "In den Vordergrund" @@ -2284,6 +2531,9 @@ msgstr "Bild einfügen" msgid "shortcuts.join-nodes" msgstr "Punkte verbinden" +msgid "shortcuts.make-corner" +msgstr "Zur Ecke umwandeln" + msgid "shortcuts.make-curve" msgstr "Kurve erstellen" @@ -2323,6 +2573,181 @@ msgstr "Nach rechts verschieben" msgid "shortcuts.move-unit-up" msgstr "Nach oben verschieben" +msgid "shortcuts.next-frame" +msgstr "Nächstes Board" + +msgid "shortcuts.not-found" +msgstr "Kein Tastenkürzel gefunden" + +msgid "shortcuts.opacity-0" +msgstr "Deckkraft auf 100% setzen" + +msgid "shortcuts.opacity-1" +msgstr "Deckkraft auf 10% setzen" + +msgid "shortcuts.opacity-2" +msgstr "Deckkraft auf 20% setzen" + +msgid "shortcuts.opacity-3" +msgstr "Deckkraft auf 30% setzen" + +msgid "shortcuts.opacity-4" +msgstr "Deckkraft auf 40% setzen" + +msgid "shortcuts.opacity-5" +msgstr "Deckkraft auf 50% setzen" + +msgid "shortcuts.opacity-6" +msgstr "Deckkraft auf 60% setzen" + +msgid "shortcuts.opacity-7" +msgstr "Deckkraft auf 70% setzen" + +msgid "shortcuts.opacity-8" +msgstr "Deckkraft auf 80% setzen" + +msgid "shortcuts.opacity-9" +msgstr "Deckkraft auf 90% setzen" + +msgid "shortcuts.open-color-picker" +msgstr "Farbwähler" + +msgid "shortcuts.open-comments" +msgstr "Zum Kommentarbereich im Ansichtsmodus" + +msgid "shortcuts.open-dashboard" +msgstr "Zum Dashboard" + +msgid "shortcuts.open-handoff" +msgstr "Zur Übergabe im Ansichtsmodus" + +msgid "shortcuts.open-interactions" +msgstr "Zur Zuschauerinteraktionen" + +msgid "shortcuts.open-viewer" +msgstr "Zum Ansichtsmodus" + +msgid "shortcuts.open-workspace" +msgstr "Zum Arbeitsbereich" + +msgid "shortcuts.or" +msgstr " oder " + +msgid "shortcuts.paste" +msgstr "Einfügen" + +msgid "shortcuts.prev-frame" +msgstr "Vorheriges Board" + +msgid "shortcuts.redo" +msgstr "Wiederholen" + +msgid "shortcuts.reset-zoom" +msgstr "Zoom zurücksetzen" + +msgid "shortcuts.search-placeholder" +msgstr "Tastenkürzel suchen" + +msgid "shortcuts.select-all" +msgstr "Alles auswählen" + +msgid "shortcuts.separate-nodes" +msgstr "Punkte trennen" + +msgid "shortcuts.show-pixel-grid" +msgstr "Pixelraster ein-/ausblenden" + +msgid "shortcuts.show-shortcuts" +msgstr "Tastenkürzel ein-/ausblenden" + +msgid "shortcuts.snap-nodes" +msgstr "An den Punkten ausrichten" + +msgid "shortcuts.snap-pixel-grid" +msgstr "Am Pixelraster ausrichten" + +msgid "shortcuts.start-editing" +msgstr "Mit der Bearbeitung beginnen" + +msgid "shortcuts.start-measure" +msgstr "Mit der Vermessung beginnen" + +msgid "shortcuts.stop-measure" +msgstr "Mit der Vermessung abbrechen" + +msgid "shortcuts.thumbnail-set" +msgstr "Miniaturansichten festlegen" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Tastatürkürzel" + +msgid "shortcuts.toggle-alignment" +msgstr "Dynamische Ausrichtung umschalten" + +msgid "shortcuts.toggle-assets" +msgstr "Assets einblenden" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Farbpalette ein-/ausblenden" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Fokusmodus umschalten" + +msgid "shortcuts.toggle-grid" +msgstr "Raster ein-/ausblenden" + +msgid "shortcuts.toggle-history" +msgstr "Verlauf ein-/ausblenden" + +msgid "shortcuts.toggle-layers" +msgstr "Ebenen ein-/ausblenden" + +msgid "shortcuts.toggle-lock" +msgstr "Auswahl sperren" + +msgid "shortcuts.toggle-lock-size" +msgstr "Seitenverhältnis sperren/entsperren" + +msgid "shortcuts.toggle-rules" +msgstr "Lineale ein-/ausblenden" + +msgid "shortcuts.toggle-scale-text" +msgstr "Textskalierung aktivieren/deaktivieren" + +msgid "shortcuts.toggle-snap-grid" +msgstr "Am Raster ausrichten" + +msgid "shortcuts.toggle-snap-guide" +msgstr "An Hilfslinien ausrichten" + +msgid "shortcuts.toggle-textpalette" +msgstr "Textpalette ein-/ausblenden" + +msgid "shortcuts.toggle-visibility" +msgstr "Elemente ein-/ausblenden" + +msgid "shortcuts.toggle-zoom-style" +msgstr "Zoom-Optionen umschalten" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Vollbild aktivieren/deaktivieren" + +msgid "shortcuts.undo" +msgstr "Rückgängig" + +msgid "shortcuts.ungroup" +msgstr "Gruppierung aufheben" + +msgid "shortcuts.unmask" +msgstr "Maske entfernen" + +msgid "shortcuts.v-distribute" +msgstr "Vertikal verteilen" + +msgid "shortcuts.zoom-selected" +msgstr "Zur Auswahl zoomen" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -2560,6 +2985,9 @@ msgstr "Name der Gruppe" msgid "workspace.assets.libraries" msgstr "Bibliotheken" +msgid "workspace.assets.local-library" +msgstr "lokale Bibliothek" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.not-found" msgstr "Keine Assets gefunden" @@ -3031,9 +3459,7 @@ msgstr "Auswahl exportieren" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "1 Element exportieren" -msgstr[1] "%s Elemente exportieren" +msgstr "1 Element exportieren" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -3193,6 +3619,10 @@ msgstr "Auflösen" msgid "workspace.options.interaction-animation-none" msgstr "Keine" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-push" +msgstr "Push" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-animation-slide" msgstr "Reinfahren" @@ -3457,6 +3887,22 @@ msgstr "Ausgewählte Ebenen" msgid "workspace.options.layout-item.advanced-ops" msgstr "Erweiterte Optionen" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Max.Höhe" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Max.Breite" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Min.Höhe" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Min.Breite" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.max-h" msgstr "Max. Höhe" @@ -3473,6 +3919,26 @@ msgstr "Min. Höhe" msgid "workspace.options.layout-item.min-w" msgstr "Min. Breite" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Elementgröße ändern" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Maximale Höhe" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Maximale Breite" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Mindesthöhe" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Mindestbreite" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-h" msgstr "Maximale Höhe" @@ -3513,6 +3979,10 @@ msgstr "Spalte-umgekehrt" msgid "workspace.options.layout.gap" msgstr "Abstand" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "Mitte" + #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.h.left" msgstr "links" @@ -3541,6 +4011,10 @@ msgstr "Einfacher Rand" msgid "workspace.options.layout.no-wrap" msgstr "no wrap" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.packed" +msgstr "kompakt" + #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.padding" msgstr "Padding" @@ -3557,6 +4031,14 @@ msgstr "Einfaches Padding" msgid "workspace.options.layout.right" msgstr "Rechts" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "im Leerraum verteilt" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "Abstand zwischen" + #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.title" msgstr "Layout" @@ -4052,6 +4534,9 @@ msgstr "Pfad" msgid "workspace.shape.menu.reset-overrides" msgstr "Änderungen zurücksetzen" +msgid "workspace.shape.menu.restore-main" +msgstr "Hauptkomponente wiederherstellen" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.select-layer" msgstr "Ebene auswählen" @@ -4326,492 +4811,4 @@ msgid "workspace.updates.update" msgstr "Aktualisieren" msgid "workspace.viewport.click-to-close-path" -msgstr "Klicken Sie, um den Pfad zu schließen" - -msgid "shortcuts.opacity-8" -msgstr "Deckkraft auf 80% setzen" - -msgid "shortcuts.opacity-9" -msgstr "Deckkraft auf 90% setzen" - -msgid "shortcuts.open-color-picker" -msgstr "Farbwähler" - -msgid "shortcuts.not-found" -msgstr "Kein Tastenkürzel gefunden" - -msgid "shortcuts.open-handoff" -msgstr "Zur Übergabe im Ansichtsmodus" - -msgid "shortcuts.open-dashboard" -msgstr "Zum Dashboard" - -msgid "shortcuts.open-workspace" -msgstr "Zum Arbeitsbereich" - -msgid "shortcuts.or" -msgstr " oder " - -msgid "shortcuts.paste" -msgstr "Einfügen" - -msgid "shortcuts.prev-frame" -msgstr "Vorheriges Board" - -msgid "shortcuts.toggle-grid" -msgstr "Raster ein-/ausblenden" - -msgid "shortcuts.toggle-layers" -msgstr "Ebenen ein-/ausblenden" - -msgid "shortcuts.toggle-alignment" -msgstr "Dynamische Ausrichtung umschalten" - -msgid "shortcuts.toggle-assets" -msgstr "Assets einblenden" - -msgid "shortcuts.toggle-colorpalette" -msgstr "Farbpalette ein-/ausblenden" - -msgid "shortcuts.toggle-focus-mode" -msgstr "Fokusmodus umschalten" - -msgid "shortcuts.toggle-lock" -msgstr "Auswahl sperren" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "Mitte" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.packed" -msgstr "kompakt" - -msgid "common.unpublish" -msgstr "Veröffentlichung aufheben" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "OK" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Achtung" - -msgid "shortcuts.opacity-7" -msgstr "Deckkraft auf 70% setzen" - -msgid "shortcuts.redo" -msgstr "Wiederholen" - -msgid "shortcuts.snap-pixel-grid" -msgstr "Am Pixelraster ausrichten" - -msgid "shortcuts.search-placeholder" -msgstr "Tastenkürzel suchen" - -msgid "shortcuts.start-editing" -msgstr "Mit der Bearbeitung beginnen" - -msgid "shortcuts.open-comments" -msgstr "Zum Kommentarbereich im Ansichtsmodus" - -msgid "shortcuts.thumbnail-set" -msgstr "Miniaturansichten festlegen" - -#: src/app/main/ui/workspace/sidebar/shortcuts.cljs -msgid "shortcuts.title" -msgstr "Tastatürkürzel" - -msgid "shortcuts.toggle-history" -msgstr "Verlauf ein-/ausblenden" - -msgid "shortcuts.start-measure" -msgstr "Mit der Vermessung beginnen" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" -msgstr "Max.Höhe" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.space-around" -msgstr "im Leerraum verteilt" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot ist für Teams gedacht. Um gemeinsam an Projekten und Dateien zu " -"arbeiten, laden Sie Mitglieder ein" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Teamwork!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Tutorial starten" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Praktisches Tutorial" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Tour starten" - -msgid "dashboard.libraries-and-templates" -msgstr "Bibliotheken & Vorlagen" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "" -"Beim Importieren der Vorlage ist ein Problem aufgetreten. Die Vorlage wurde " -"nicht importiert." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Veröffentlichung der Bibliothek aufheben" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "" -"Erkunden Sie Penpot um mehr über die wichtigsten Funktionen zu erfahren." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Datei löschen" -msgstr[1] "Dateien löschen" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "Möchten Sie diese Datei wirklich löschen?" -msgstr[1] "Möchten Sie diese Dateien wirklich löschen?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "Datei löschen" -msgstr[1] "Dateien löschen" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "Veröffentlichung aufheben" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.scd-message" -msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "Es wird in dieser Datei verwendet:" -msgstr[1] "Es wird in diesen Dateien verwendet:" - -msgid "onboarding-v2.before-start.desc2.title" -msgstr "Benutzerhandbuch" - -msgid "onboarding-v2.before-start.desc3" -msgstr "" -"Sie können sich unsere Tutorials und die von unserer Community erstellten " -"Tutorials ansehen." - -msgid "onboarding-v2.before-start.desc3.title" -msgstr "Video-Tutorials" - -msgid "onboarding-v2.before-start.title" -msgstr "Bevor Sie beginnen" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Diese Datei enthält Bibliotheken, die in dieser Datei verwendet werden:" -msgstr[1] "" -"Diese Datei enthält Bibliotheken, die in diesen Dateien verwendet werden:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "" -"Diese Dateien enthalten Bibliotheken, die in dieser Datei verwendet werden:" -msgstr[1] "" -"Diese Dateien enthalten Bibliotheken, die in diesen Dateien verwendet werden:" - -msgid "onboarding-v2.welcome.desc1" -msgstr "" -"Penpot ist Open Source und wird sowohl von Kaleidos als auch von der " -"Community entwickelt, wo sich viele Leute bereits gegenseitig helfen. Jeder " -"kann mitmachen:" - -msgid "onboarding-v2.before-start.desc1" -msgstr "" -"Sie sollten wissen, dass es viele Ressourcen gibt, die Ihnen den Einstieg in " -"Penpot erleichtern, wie z. B. das Benutzerhandbuch und unseren Youtube-Kanal." - -msgid "onboarding-v2.welcome.desc2.title" -msgstr "In der Community mitmachen" - -msgid "onboarding-v2.welcome.desc3.title" -msgstr "Leitfaden für Mitwirkende" - -msgid "onboarding-v2.welcome.desc3" -msgstr "" -"Hier erfahren Sie, wie Sie bei Übersetzungen, Feature Requests, Core-" -"Entwicklung und der Fehlersuche helfen können…" - -msgid "onboarding.choice.team-up.create-later" -msgstr "Team später erstellen" - -msgid "onboarding.team-modal.create-team" -msgstr "Ein Team erstellen" - -msgid "onboarding.team-modal.create-team-desc" -msgstr "" -"In einem Team können Sie mit anderen Penpot-Nutzern zusammenarbeiten, die an " -"denselben Dateien und Projekten arbeiten." - -msgid "onboarding.choice.team-up.roles" -msgstr "Einladen mit der Rolle:" - -msgid "workspace.assets.local-library" -msgstr "lokale Bibliothek" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" -msgstr "Maximale Höhe" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" -msgstr "Maximale Breite" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" -msgstr "Mindesthöhe" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" -msgstr "Mindestbreite" - -msgid "workspace.shape.menu.restore-main" -msgstr "Hauptkomponente wiederherstellen" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "" -"Lernen Sie die Grundlagen von Penpot und haben Sie Spaß mit diesem " -"praktischen Tutorial." - -msgid "common.publish" -msgstr "Veröffentlichen" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Teammanagement" - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "Diese Datei wurde bereits mit aktivierten V2-Komponenten verwendet." - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "Mitglieder in das Team einladen" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "Datei löschen" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "Veröffentlichung der Bibliothek aufheben" -msgstr[1] "Veröffentlichung der Bibliotheken aufheben" - -msgid "onboarding-v2.welcome.title" -msgstr "Willkommen bei Penpot!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Benutzeroberfläche erkunden" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "Möchten Sie die Veröffentlichung dieser Bibliothek wirklich aufheben?" -msgstr[1] "Möchten Sie die Veröffentlichung dieser Bibliotheken wirklich aufheben?" - -msgid "onboarding.choice.team-up.invite-members-info" -msgstr "" -"Denken Sie daran, alle einzubeziehen. Entwickler, Designer, Manager... die " -"Vielfalt macht's :)" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "Unbegrenzte Anzahl von Mitgliedern" - -msgid "onboarding.team-modal.create-team-feature-1" -msgstr "Unbegrenzte Anzahl von Dateien und Projekten" - -msgid "onboarding.team-modal.create-team-feature-2" -msgstr "Multiplayer-Edition" - -msgid "onboarding.team-modal.create-team-feature-3" -msgstr "Rollenverwaltung" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "100% kostenlos!" - -msgid "shortcuts.stop-measure" -msgstr "Mit der Vermessung abbrechen" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-animation-push" -msgstr "Push" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" -msgstr "Max.Breite" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" -msgstr "Min.Höhe" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" -msgstr "Min.Breite" - -msgid "shortcuts.bring-backward" -msgstr "Eins nach hinten" - -msgid "shortcuts.bring-forward" -msgstr "Eins nach vorne" - -msgid "shortcuts.bring-back" -msgstr "In den Hintergrund" - -msgid "shortcuts.make-corner" -msgstr "Zur Ecke umwandeln" - -msgid "shortcuts.next-frame" -msgstr "Nächstes Board" - -msgid "shortcuts.opacity-2" -msgstr "Deckkraft auf 20% setzen" - -msgid "shortcuts.opacity-3" -msgstr "Deckkraft auf 30% setzen" - -msgid "shortcuts.opacity-4" -msgstr "Deckkraft auf 40% setzen" - -msgid "shortcuts.opacity-5" -msgstr "Deckkraft auf 50% setzen" - -msgid "shortcuts.opacity-6" -msgstr "Deckkraft auf 60% setzen" - -msgid "shortcuts.opacity-0" -msgstr "Deckkraft auf 100% setzen" - -msgid "shortcuts.opacity-1" -msgstr "Deckkraft auf 10% setzen" - -msgid "shortcuts.select-all" -msgstr "Alles auswählen" - -msgid "shortcuts.separate-nodes" -msgstr "Punkte trennen" - -msgid "shortcuts.show-pixel-grid" -msgstr "Pixelraster ein-/ausblenden" - -msgid "shortcuts.show-shortcuts" -msgstr "Tastenkürzel ein-/ausblenden" - -msgid "shortcuts.reset-zoom" -msgstr "Zoom zurücksetzen" - -msgid "shortcuts.snap-nodes" -msgstr "An den Punkten ausrichten" - -msgid "shortcuts.toggle-rules" -msgstr "Lineale ein-/ausblenden" - -msgid "shortcuts.toggle-snap-grid" -msgstr "Am Raster ausrichten" - -msgid "shortcuts.toggle-snap-guide" -msgstr "An Hilfslinien ausrichten" - -msgid "shortcuts.toggle-lock-size" -msgstr "Seitenverhältnis sperren/entsperren" - -msgid "shortcuts.toggle-scale-text" -msgstr "Textskalierung aktivieren/deaktivieren" - -msgid "shortcuts.toggle-textpalette" -msgstr "Textpalette ein-/ausblenden" - -msgid "shortcuts.toggle-visibility" -msgstr "Elemente ein-/ausblenden" - -msgid "shortcuts.undo" -msgstr "Rückgängig" - -msgid "shortcuts.ungroup" -msgstr "Gruppierung aufheben" - -msgid "shortcuts.toogle-fullscreen" -msgstr "Vollbild aktivieren/deaktivieren" - -msgid "shortcuts.unmask" -msgstr "Maske entfernen" - -msgid "shortcuts.zoom-selected" -msgstr "Zur Auswahl zoomen" - -msgid "shortcuts.toggle-zoom-style" -msgstr "Zoom-Optionen umschalten" - -msgid "shortcuts.open-interactions" -msgstr "Zur Zuschauerinteraktionen" - -msgid "shortcuts.open-viewer" -msgstr "Zum Ansichtsmodus" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title" -msgstr "Elementgröße ändern" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Entdecken Sie weitere und erfahren Sie, wie Sie beitragen können" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "" -"Wenn Sie die Veröffentlichung aufheben, werden die darin enthaltenen " -"Elemente zu einer Bibliothek dieser Datei." -msgstr[1] "" -"Wenn Sie die Veröffentlichung aufheben, werden die darin enthaltenen " -"Elemente zu einer Bibliothek dieser Dateien." - -msgid "onboarding-v2.before-start.desc2" -msgstr "" -"Detaillierte Informationen über die Verwendung von Penpot. Vom Prototyping " -"bis zum Organisieren und Teilen von Designs." - -msgid "onboarding-v2.welcome.desc2" -msgstr "" -"Ein öffentlicher Raum zum Lernen, Teilen und Diskutieren über Penpot, seine " -"Gegenwart und Zukunft mit der gesamten Community und dem Penpot-Kernteam." - -msgid "shortcuts.v-distribute" -msgstr "Vertikal verteilen" - -msgid "shortcuts.bool-union" -msgstr "Vereinigung" - -msgid "shortcuts.bool-exclude" -msgstr "Ausschluss" - -msgid "shortcuts.bool-intersection" -msgstr "Schnittmenge" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.space-between" -msgstr "Abstand zwischen" +msgstr "Klicken Sie, um den Pfad zu schließen" \ No newline at end of file diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 1518ab50a1..d5b72d2cbb 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -812,6 +812,9 @@ msgstr "Confirmation password must match" msgid "errors.password-too-short" msgstr "Password should at least be 8 characters" +msgid "errors.profile-blocked" +msgstr "The profile is blocked" + #: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.profile-is-muted" msgstr "Your profile has emails muted (spam reports or high bounces)." @@ -1962,6 +1965,25 @@ msgstr "Video tutorials" msgid "onboarding-v2.before-start.title" msgstr "Before you start" +msgid "onboarding-v2.newsletter.desc" +msgstr "" +"Subscribe to the Penpot newsletter to stay up to date with the product " +"development progress and news." + +msgid "onboarding-v2.newsletter.news" +msgstr "Send me news about Penpot (blog posts, video tutorials, streamings...)." + +msgid "onboarding-v2.newsletter.privacy1" +msgstr "We care about privacy, here you can read our " + +msgid "onboarding-v2.newsletter.privacy2" +msgstr "" +"We will only send relevant emails to you. You can unsubscribe at any time " +"via the unsubscribe link in any of our newsletters." + +msgid "onboarding-v2.newsletter.updates" +msgstr "Send me product updates (new features, releases, fixes...)." + msgid "onboarding-v2.welcome.desc1" msgstr "" "Penpot is Open Source and it is made by Kaleidos as well as the community, " @@ -2050,25 +2072,6 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "No, thanks" -msgid "onboarding-v2.newsletter.desc" -msgstr "" -"Subscribe to the Penpot newsletter to stay up to date with the product " -"development progress and news." - -msgid "onboarding-v2.newsletter.updates" -msgstr "Send me product updates (new features, releases, fixes...)." - -msgid "onboarding-v2.newsletter.news" -msgstr "Send me news about Penpot (blog posts, video tutorials, streamings...)." - -msgid "onboarding-v2.newsletter.privacy1" -msgstr "We care about privacy, here you can read our " - -msgid "onboarding-v2.newsletter.privacy2" -msgstr "" -"We will only send relevant emails to you. You can unsubscribe at any time " -"via the unsubscribe link in any of our newsletters." - msgid "onboarding.newsletter.policy" msgstr "Privacy Policy." @@ -4330,6 +4333,12 @@ msgstr "Update main components" msgid "workspace.shape.menu.update-main" msgstr "Update main component" +msgid "workspace.sidebar.collapse" +msgstr "Collapse sidebar" + +msgid "workspace.sidebar.expand" +msgstr "Expand sidebar" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.sidebar.history" msgstr "History (%s)" @@ -4370,12 +4379,6 @@ msgstr "Imported SVG Attributes" msgid "workspace.sidebar.sitemap" msgstr "Pages" -msgid "workspace.sidebar.collapse" -msgstr "Collapse sidebar" - -msgid "workspace.sidebar.expand" -msgstr "Expand sidebar" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.sitemap" msgstr "Sitemap" @@ -4560,7 +4563,4 @@ msgid "workspace.updates.update" msgstr "Update" msgid "workspace.viewport.click-to-close-path" -msgstr "Click to close the path" - -msgid "errors.profile-blocked" -msgstr "The profile is blocked" +msgstr "Click to close the path" \ No newline at end of file diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 8fe711ab0b..2fe5b5bbee 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-23 20:54+0000\n" "Last-Translator: Dário \n" -"Language-Team: Spanish \n" +"Language-Team: Spanish " +"\n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -742,6 +742,10 @@ msgstr "¿Seguro?" msgid "ds.updated-at" msgstr "Actualizado: %s" +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Proveedor de autenticación no configurado." + msgid "errors.auth.unable-to-login" msgstr "Parece que no has iniciado sesión, o la sesión ha expirado." @@ -837,6 +841,9 @@ msgstr "La contraseña de confirmación debe coincidir" msgid "errors.password-too-short" msgstr "La contraseña debe tener 8 caracteres como mínimo" +msgid "errors.profile-blocked" +msgstr "El perfil esta blockeado" + #: src/app/main/ui/auth/recovery_request.cljs, #: src/app/main/ui/settings/change_email.cljs, #: src/app/main/ui/dashboard/team.cljs @@ -2057,6 +2064,30 @@ msgstr "Tutoriales de video" msgid "onboarding-v2.before-start.title" msgstr "Antes de comenzar" +msgid "onboarding-v2.newsletter.desc" +msgstr "" +"Suscríbete a la newsletter de Penpot para estar al día de los progresos del " +"producto y noticias." + +msgid "onboarding-v2.newsletter.news" +msgstr "" +"Quiero recibir noticias sobre Penpot (artículos del blog, vídeo tutoriales, " +"directos...)." + +msgid "onboarding-v2.newsletter.privacy1" +msgstr "Nos importa la privacidad, aquí puedes leer nuestra " + +msgid "onboarding-v2.newsletter.privacy2" +msgstr "" +"Sólo te enviaremos emails relevantes para ti. Puedes desuscribirte en " +"cualquier momento usando el vínculo de desuscripción en cualquiera de " +"nuestras newsletters." + +msgid "onboarding-v2.newsletter.updates" +msgstr "" +"Quiero recibir información sobre actualizaciones del producto (nuevas " +"funcionalidades, releases, mejoras...)." + msgid "onboarding-v2.welcome.desc1" msgstr "" "Penpot es Código Abierto y está hecho por Kaleidos y la comunidad, donde ya " @@ -2144,26 +2175,6 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "No, gracias" -msgid "onboarding-v2.newsletter.desc" -msgstr "" -"Suscríbete a la newsletter de Penpot para estar al día de los progresos del " -"producto y noticias." - -msgid "onboarding-v2.newsletter.updates" -msgstr "Quiero recibir información sobre actualizaciones del producto (nuevas funcionalidades, releases, mejoras...)." - -msgid "onboarding-v2.newsletter.news" -msgstr "Quiero recibir noticias sobre Penpot (artículos del blog, vídeo tutoriales, directos...)." - -msgid "onboarding-v2.newsletter.privacy1" -msgstr "Nos importa la privacidad, aquí puedes leer nuestra " - -msgid "onboarding-v2.newsletter.privacy2" -msgstr "" -"Sólo te enviaremos emails relevantes para ti. Puedes desuscribirte en " -"cualquier momento usando el vínculo de desuscripción en " -"cualquiera de nuestras newsletters." - msgid "onboarding.newsletter.policy" msgstr "Política de Privacidad." @@ -4536,6 +4547,12 @@ msgstr "Actualizar componentes" msgid "workspace.shape.menu.update-main" msgstr "Actualizar componente principal" +msgid "workspace.sidebar.collapse" +msgstr "Cerrar barra lateral" + +msgid "workspace.sidebar.expand" +msgstr "Abrir barra lateral" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.sidebar.history" msgstr "Historial (%s)" @@ -4577,12 +4594,6 @@ msgstr "Atributos del SVG Importado" msgid "workspace.sidebar.sitemap" msgstr "Páginas" -msgid "workspace.sidebar.collapse" -msgstr "Cerrar barra lateral" - -msgid "workspace.sidebar.expand" -msgstr "Abrir barra lateral" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.sitemap" msgstr "Mapa del sitio" @@ -4767,11 +4778,4 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" - -msgid "errors.profile-blocked" -msgstr "El perfil esta blockeado" - -#: src/app/main/ui/auth/login.cljs -msgid "errors.auth-provider-not-configured" -msgstr "Proveedor de autenticación no configurado." +msgstr "Pulsar para cerrar la ruta" \ No newline at end of file diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index ed8cf0654f..ea37c44014 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-16 21:18+0000\n" "Last-Translator: Mikel Larreategi \n" -"Language-Team: Basque \n" +"Language-Team: Basque " +"\n" "Language: eu\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -171,6 +171,9 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Egiaztapen mezu bat bidali dugu helbide honetara" +msgid "common.publish" +msgstr "Argitaratu" + msgid "common.share-link.all-users" msgstr "Penpoten erabiltzaile guztiak" @@ -224,6 +227,49 @@ msgstr "Partekatu prototipoak" msgid "common.share-link.view-all" msgstr "Aukeratu guztiak" +msgid "common.unpublish" +msgstr "Argitaraketa atzera bota" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Taldearen kudeaketa" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot taldeentzat sortuta dago. Gonbidatu beste pertsona batzuk proiektu " +"eta fitxategietan batera lan egiteko." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Egin taldea!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Ikasi Penpoten oinarriak tutorial atsegin honekin." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Hasi tutoriala" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial atsegina" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "" +"Eman begirada bat Penpoti bere oinarrizko funtzionalitateak zein diren " +"ezagutzeko." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Hasi" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Interfazea ezagutu" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Gehitu partekatutako liburutegi bezala" @@ -438,6 +484,15 @@ msgstr "Gonbidatu taldera" msgid "dashboard.leave-team" msgstr "Utzi taldea" +msgid "dashboard.libraries-and-templates" +msgstr "Liburutegi eta txantiloiak" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Gehiago ikusi eta ikasi nola lagundu" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Arazo bat egon da txantiloia inportatzean. Ezin izan da inportatu." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Partekatutako liburutegiak" @@ -616,6 +671,10 @@ msgstr "Bilaketaren emaitzak" msgid "dashboard.type-something" msgstr "Idatzi bilaktzeko zerbaitu" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Atzera bota liburutegia argitaratzea" + #: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "Eguneratu aukerak" @@ -636,6 +695,14 @@ msgstr "Izena" msgid "dashboard.your-penpot" msgstr "Zure Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Oharra" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Eguneratzeko dauden osagaiak:" @@ -667,6 +734,10 @@ msgstr "Badirudi ez zarela sartu edo zure saioa iraungi egin dela." msgid "errors.clipboard-not-implemented" msgstr "Zure nabigatzaileak ezin du hori egin" +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Fitxategi hau V2 Osagaiak aktibatuta erabili da." + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" msgstr "Eposta helbide hori erabilita dago" @@ -1648,6 +1719,48 @@ msgstr "Benetan proiektu hau ezabatu egin nahi duzu?" msgid "modals.delete-project-confirm.title" msgstr "Ezabatu proiektua" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Ezabatu fitxategia" +msgstr[1] "Ezabatu fitxategiak" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Benetan fitxategi hau ezabatu nahi duzu?" +msgstr[1] "Benetan fitxategi hauek ezabatu nahi dituzu?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "" +"Ezabatu nahi duzun fitxategiak, fitxategi honetan erabiltzen den liburutegi " +"bat du:" +msgstr[1] "" +"Ezabatu nahi duzuen fitxategiak, fitxategi hauetan erabiltzen den " +"liburutegi bat du:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "" +"Ezabatu nahi dituzun fitxategiek, fitxategi honetan erabiltzen den " +"liburutegi bat dute:" +msgstr[1] "" +"Ezabatu nahi dituzun fitxategiek, fitxategi hauetan erabiltzen den " +"liburutegi bat dute:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Ezabatu fitxategia" +msgstr[1] "Ezabatu fitxategiak" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Ezabatu fitxategia" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "Ezabatu taldea" @@ -1685,6 +1798,10 @@ msgstr "Posta elektronikoak, komarekin banatuta" msgid "modals.invite-member.title" msgstr "Gonbidatu taldera sartzeko" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Gonbidatu kideak taldera" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" msgstr "" @@ -1768,6 +1885,38 @@ msgstr "Gehitu \"%s\" partekatutako liburutegi bezala" msgid "modals.small-nudge" msgstr "Gutxienekoa" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Argitaratzea atzera bota" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Argitaratzea atzera botatzen baduzu, elementuak fitxategiaren liburutegira " +"pasatuko dira." +msgstr[1] "" +"Argitaratzea atzera botatzen baduzu, elementuak fitxategien liburutegietara " +"pasatuko dira." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Benetan liburutegi honen argitaratzea atzera bota nahi duzu?" +msgstr[1] "Benetan liburutegi hauen argitaratzea atzera bota nahi duzu?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Fitxategi honetan erabiltzen ari da:" +msgstr[1] "Fitxategi hauetan erabiltzen ari da:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Liburutegiaren argitaratzea atzera bota" +msgstr[1] "Liburutegian argitaratzea atzera bota" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" msgstr "" @@ -1814,6 +1963,52 @@ msgstr "" "Posta elektronikoa egiaztatzeko mezua ondo bidali da %s helbidera. " "Egiaztatu zure eposta." +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Penpotekin lanean hasteko hainbat eta hainbat baliabide daude, adibidez " +"erabiltzailearen gida eta gure Youtube kanala." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Penpot erabiltzeari buruko informazioa. Prototipoak egitetik, diseinuak " +"antolatu eta partekatzera." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Erabiltzailearen gida" + +msgid "onboarding-v2.before-start.desc3" +msgstr "Gure eta komunitateak egindako tutorialak ikusi ditzakezu." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Bideo tutorialak" + +msgid "onboarding-v2.before-start.title" +msgstr "Hasi aurretik" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot Kode Irekikoa da eta Kaleidos eta komunitateak egindakoa da. " +"Laguntzeko erak:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Penpoten oraina eta etorkizunari buruz ikasi, partekatu eta eztabaidatzeko " +"tokia." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "Komunitatean parte hartzen" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Hemen izango duzu itzulpenekin laguntzeko informazioa, funtzionalitateak " +"eskatzeko modua, erroreak bilatzekoa…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Laguntzeko gida" + +msgid "onboarding-v2.welcome.title" +msgstr "Ongi etorri Penpotera!" + msgid "onboarding.choice.desc" msgstr "Nola hasi nahi duzu?" @@ -1831,6 +2026,9 @@ msgstr "" "Norbaitekin lan egiten duzu? Sortu talde bat eta gonbidatu elkarrekin lan " "egin eta baliabideak partekatzeko." +msgid "onboarding.choice.team-up.create-later" +msgstr "Sortu taldea beranduago" + msgid "onboarding.choice.team-up.create-team" msgstr "Zure taldearen izena" @@ -1848,12 +2046,20 @@ msgstr "" "Geroago ere gonbidatu ditzakezu eta bakoitzaren baimenak ezarri taldearen " "ezarpenetatik." +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Ez ahaztu garapeneko, diseinuko, kudeaketako... pertsonak sartzea, " +"dibertsitatea ona da :)" + msgid "onboarding.choice.team-up.invite-members-skip" msgstr "Sortu taldea eta ondoren gonbidatu" msgid "onboarding.choice.team-up.invite-members-submit" msgstr "Sortu taldea eta bidali gonbidapenak" +msgid "onboarding.choice.team-up.roles" +msgstr "Gonbidatu rol honekin:" + msgid "onboarding.choice.title" msgstr "Ongi etorri Penpotera" @@ -1961,6 +2167,29 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "\"Egiaren iturri\" partekatua" +msgid "onboarding.team-modal.create-team" +msgstr "Sortu talde bat" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Talde batek Penpoten fitxategi eta proiektuetan elkarrekin lan egiteko " +"aukera ematen du." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Mugarik gabeko fitxategi eta proiektu kopurua" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Jokalari anitzeko edizioa" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Rolen kudeaketa" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Partehartzaile muga gabe" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "%100 doan!" + msgid "onboarding.templates.subtitle" msgstr "Hemen dituzu txantiloi batzuk." @@ -2640,6 +2869,9 @@ msgstr "Taldearen izena" msgid "workspace.assets.libraries" msgstr "Liburutegiak" +msgid "workspace.assets.local-library" +msgstr "liburutegi lokala" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.not-found" msgstr "Ez da baliabiderik aurkitu" @@ -3507,6 +3739,22 @@ msgstr "Aukeratutako geruzak" msgid "workspace.options.layout-item.advanced-ops" msgstr "Aukera aurreratuak" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Gehieneko altuera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Gehieneko zabalera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Gutxieneko altuera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Gutxieneko zabalera" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.max-h" msgstr "Gehienezko altuera" @@ -3527,6 +3775,22 @@ msgstr "Gutxieneko zabalera" msgid "workspace.options.layout-item.title" msgstr "Elementuaren tamaina aldatzea" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Gehieneko altuera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Gehieneko zabalaera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Gutxieneko altuera" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Gutxieneko zabalera" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-h" msgstr "Gehienezko altuera" @@ -4107,6 +4371,9 @@ msgstr "Bidea" msgid "workspace.shape.menu.reset-overrides" msgstr "Berrezarri gainidazketak" +msgid "workspace.shape.menu.restore-main" +msgstr "Berrezarri osagai nagusia" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.select-layer" msgstr "Aukeratu geruza" @@ -4375,271 +4642,4 @@ msgid "workspace.updates.update" msgstr "Eguneratu" msgid "workspace.viewport.click-to-close-path" -msgstr "Egin klik bidea ixteko" - -msgid "common.publish" -msgstr "Argitaratu" - -msgid "common.unpublish" -msgstr "Argitaraketa atzera bota" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "Ok" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Oharra" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Egin taldea!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Hasi tutoriala" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "" -"Eman begirada bat Penpoti bere oinarrizko funtzionalitateak zein diren " -"ezagutzeko." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Hasi" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "Ezabatu fitxategia" -msgstr[1] "Ezabatu fitxategiak" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "Ezabatu fitxategia" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "Gonbidatu kideak taldera" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "Arazo bat egon da txantiloia inportatzean. Ezin izan da inportatu." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "Ikasi Penpoten oinarriak tutorial atsegin honekin." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Interfazea ezagutu" - -msgid "dashboard.libraries-and-templates" -msgstr "Liburutegi eta txantiloiak" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Taldearen kudeaketa" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Tutorial atsegina" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot taldeentzat sortuta dago. Gonbidatu beste pertsona batzuk proiektu " -"eta fitxategietan batera lan egiteko." - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Gehiago ikusi eta ikasi nola lagundu" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Atzera bota liburutegia argitaratzea" - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "Fitxategi hau V2 Osagaiak aktibatuta erabili da." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Ezabatu fitxategia" -msgstr[1] "Ezabatu fitxategiak" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "Benetan fitxategi hau ezabatu nahi duzu?" -msgstr[1] "Benetan fitxategi hauek ezabatu nahi dituzu?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "Argitaratzea atzera bota" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "" -"Ezabatu nahi duzun fitxategiak, fitxategi honetan erabiltzen den liburutegi " -"bat du:" -msgstr[1] "" -"Ezabatu nahi duzuen fitxategiak, fitxategi hauetan erabiltzen den liburutegi " -"bat du:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "" -"Ezabatu nahi dituzun fitxategiek, fitxategi honetan erabiltzen den " -"liburutegi bat dute:" -msgstr[1] "" -"Ezabatu nahi dituzun fitxategiek, fitxategi hauetan erabiltzen den " -"liburutegi bat dute:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "" -"Argitaratzea atzera botatzen baduzu, elementuak fitxategiaren liburutegira " -"pasatuko dira." -msgstr[1] "" -"Argitaratzea atzera botatzen baduzu, elementuak fitxategien liburutegietara " -"pasatuko dira." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "Benetan liburutegi honen argitaratzea atzera bota nahi duzu?" -msgstr[1] "Benetan liburutegi hauen argitaratzea atzera bota nahi duzu?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.scd-message" -msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "Fitxategi honetan erabiltzen ari da:" -msgstr[1] "Fitxategi hauetan erabiltzen ari da:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "Liburutegiaren argitaratzea atzera bota" -msgstr[1] "Liburutegian argitaratzea atzera bota" - -msgid "onboarding-v2.before-start.desc2.title" -msgstr "Erabiltzailearen gida" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" -msgstr "Gehieneko zabalera" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" -msgstr "Gutxieneko altuera" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" -msgstr "Gehieneko altuera" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" -msgstr "Gutxieneko altuera" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" -msgstr "Gutxieneko zabalera" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" -msgstr "Gehieneko altuera" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" -msgstr "Gehieneko zabalaera" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" -msgstr "Gutxieneko zabalera" - -msgid "onboarding-v2.before-start.desc1" -msgstr "" -"Penpotekin lanean hasteko hainbat eta hainbat baliabide daude, adibidez " -"erabiltzailearen gida eta gure Youtube kanala." - -msgid "onboarding-v2.before-start.desc2" -msgstr "" -"Penpot erabiltzeari buruko informazioa. Prototipoak egitetik, diseinuak " -"antolatu eta partekatzera." - -msgid "onboarding-v2.before-start.desc3" -msgstr "Gure eta komunitateak egindako tutorialak ikusi ditzakezu." - -msgid "onboarding-v2.before-start.desc3.title" -msgstr "Bideo tutorialak" - -msgid "onboarding-v2.before-start.title" -msgstr "Hasi aurretik" - -msgid "onboarding-v2.welcome.desc1" -msgstr "" -"Penpot Kode Irekikoa da eta Kaleidos eta komunitateak egindakoa da. " -"Laguntzeko erak:" - -msgid "onboarding-v2.welcome.desc2" -msgstr "" -"Penpoten oraina eta etorkizunari buruz ikasi, partekatu eta eztabaidatzeko " -"tokia." - -msgid "onboarding-v2.welcome.desc2.title" -msgstr "Komunitatean parte hartzen" - -msgid "onboarding-v2.welcome.desc3" -msgstr "" -"Hemen izango duzu itzulpenekin laguntzeko informazioa, funtzionalitateak " -"eskatzeko modua, erroreak bilatzekoa…" - -msgid "onboarding-v2.welcome.desc3.title" -msgstr "Laguntzeko gida" - -msgid "onboarding-v2.welcome.title" -msgstr "Ongi etorri Penpotera!" - -msgid "onboarding.choice.team-up.create-later" -msgstr "Sortu taldea beranduago" - -msgid "onboarding.choice.team-up.invite-members-info" -msgstr "" -"Ez ahaztu garapeneko, diseinuko, kudeaketako... pertsonak sartzea, " -"dibertsitatea ona da :)" - -msgid "onboarding.choice.team-up.roles" -msgstr "Gonbidatu rol honekin:" - -msgid "onboarding.team-modal.create-team" -msgstr "Sortu talde bat" - -msgid "onboarding.team-modal.create-team-desc" -msgstr "" -"Talde batek Penpoten fitxategi eta proiektuetan elkarrekin lan egiteko " -"aukera ematen du." - -msgid "onboarding.team-modal.create-team-feature-1" -msgstr "Mugarik gabeko fitxategi eta proiektu kopurua" - -msgid "onboarding.team-modal.create-team-feature-2" -msgstr "Jokalari anitzeko edizioa" - -msgid "onboarding.team-modal.create-team-feature-3" -msgstr "Rolen kudeaketa" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "Partehartzaile muga gabe" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "%100 doan!" - -msgid "workspace.assets.local-library" -msgstr "liburutegi lokala" - -msgid "workspace.shape.menu.restore-main" -msgstr "Berrezarri osagai nagusia" +msgstr "Egin klik bidea ixteko" \ No newline at end of file diff --git a/frontend/translations/fa.po b/frontend/translations/fa.po index a4b1131709..2cd42678da 100644 --- a/frontend/translations/fa.po +++ b/frontend/translations/fa.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-01 14:17+0000\n" "Last-Translator: Ahmad HosseinBor <123hozeifeh@gmail.com>\n" -"Language-Team: Persian \n" +"Language-Team: Persian " +"\n" "Language: fa\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -175,6 +175,9 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "ما یک ایمیل تأیید ارسال کردیم به" +msgid "common.publish" +msgstr "انتشار" + msgid "common.share-link.all-users" msgstr "همه کاربران Penpot" @@ -233,6 +236,36 @@ msgstr "اشتراک‌گذاری پروتوتایپ‌ها" msgid "common.share-link.view-all" msgstr "انتخاب همه" +msgid "common.unpublish" +msgstr "لغو انتشار" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "مدیریت تیم" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot برای تیم‌ها در نظر گرفته شده است. از اعضا دعوت کنید تا روی پروژه‌ها " +"و فایل‌ها با هم کار کنند" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "در حالی که با این آموزش سرگرم می‌شوید، اصول اولیه را در Penpot بیاموزید." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "شروع آموزش" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "در پنپات قدم بزنید و با ویژگی‌های اصلی آن آشنا شوید." + +#: src/app/main/ui/dashboard/projects.cljs +#, fuzzy +msgid "dasboard.walkthrough-hero.start" +msgstr "شروع تور" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "افزودن به‌عنوان کتابخانه مشترک" @@ -452,6 +485,13 @@ msgstr "دعوت به تیم" msgid "dashboard.leave-team" msgstr "خروج از تیم" +msgid "dashboard.libraries-and-templates" +msgstr "کتابخانه‌ها و قالب‌ها" + +#, fuzzy +msgid "dashboard.libraries-and-templates.explore" +msgstr "بیشتر آنها را کاوش کنید و بدانید که چگونه مشارکت کنید" + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "کتابخانه‌های مشترک" @@ -634,6 +674,15 @@ msgstr "تم رابط کاربری" msgid "dashboard.title-search" msgstr "نتایج جستجو" +#: src/app/main/ui/dashboard/search.cljs +#, fuzzy +msgid "dashboard.type-something" +msgstr "برای نمایش نتایج جستجو تایپ کنید" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "لغو انتشار کتابخانه" + #: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "به‌روزرسانی تنظیمات" @@ -654,6 +703,15 @@ msgstr "نام شما" msgid "dashboard.your-penpot" msgstr "پن‌پات شما" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "خیلی خوب" + +#: src/app/main/ui/alert.cljs +#, fuzzy +msgid "ds.alert-title" +msgstr "توجه" + #: src/app/main/ui/confirm.cljs #, fuzzy msgid "ds.component-subtitle" @@ -676,10 +734,24 @@ msgstr "مطمئنی؟" msgid "ds.updated-at" msgstr "به‌روزشده: %s" +#: src/app/main/ui/auth/login.cljs +#, fuzzy +msgid "errors.auth-provider-not-configured" +msgstr "ارائه دهنده احراز هویت پیکربندی نشده است." + +#, fuzzy +msgid "errors.auth.unable-to-login" +msgstr "به نظر می‌رسد شما احراز هویت نشده‌اید یا جلسه منقضی شده است." + #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" msgstr "مرورگر شما نمی‌تواند این عملیات را انجام دهد" +#: src/app/main/data/workspace/persistence.cljs +#, fuzzy +msgid "errors.components-v2" +msgstr "این فایل قبلاً با فعال بودن Components V2 استفاده شده است." + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs #, fuzzy msgid "errors.email-already-exists" @@ -754,6 +826,10 @@ msgstr "ثبت‌نام در حال حاضر غیرفعال است." msgid "errors.team-leave.insufficient-members" msgstr "اعضای کافی برای ترک تیم وجود ندارد، احتمالاً می‌خواهید آن را حذف کنید." +#, fuzzy +msgid "errors.team-leave.member-does-not-exists" +msgstr "عضوی که می‌خواهید اختصاص دهید وجود ندارد." + msgid "errors.team-leave.owner-cant-leave" msgstr "مالک نمی‌تواند تیم را ترک کند، شما باید نقش مالک را مجدداً اختصاص دهید." @@ -784,6 +860,21 @@ msgstr "پیوستن به چت" msgid "feedback.description" msgstr "شرح" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "به انجمن Penpot بروید" + +#: src/app/main/ui/settings/feedback.cljs +#, fuzzy +msgid "feedback.discourse-subtitle1" +msgstr "" +"ما خوشحالیم که شما اینجا هستید. اگر به کمک نیاز دارید، لطفا قبل از ارسال " +"پست جستجو کنید." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "انجمن Penpot" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-go-to" msgstr "برو سراغ بحث‌ها" @@ -806,6 +897,20 @@ msgstr "" msgid "feedback.title" msgstr "ایمیل" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "به توییتر بروید" + +#: src/app/main/ui/settings/feedback.cljs +#, fuzzy +msgid "feedback.twitter-subtitle1" +msgstr "اینجا برای کمک به سوالات فنی شما." + +#: src/app/main/ui/settings/feedback.cljs +#, fuzzy +msgid "feedback.twitter-title" +msgstr "حساب پشتیبانی در توییتر" + #: src/app/main/ui/settings/password.cljs msgid "generic.error" msgstr "خطایی رخ داده است" @@ -1532,6 +1637,10 @@ msgstr "" "آیا مطمئنید که می‌خواهید این تیم را حذف کنید؟ تمام پروژه‌ها و فایل‌های " "مرتبط با تیم به طور دائم حذف خواهند شد." +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "آیا مطمئن هستید که می‌خواهید این عضو را از تیم حذف کنید؟" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.accept" msgstr "به‌روزرسانی" @@ -1544,6 +1653,21 @@ msgstr "لغو" msgid "notifications.profile-saved" msgstr "پروفایل با موفقیت ذخیره شد!" +#, fuzzy +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"اطلاعات دقیق در مورد نحوه استفاده از Penpot. از نمونه‌سازی تا سازماندهی یا " +"به اشتراک‌گذاری طرح‌ها." + +#, fuzzy +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"شما می‌توانید آموزش‌های ما و آموزش‌های ساخته شده توسط انجمن ما را تماشا " +"کنید." + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "پس از نامگذاری تیم خود، می‌توانید افراد را برای پیوستن دعوت کنید." + msgid "onboarding.newsletter.privacy2" msgstr "" "ما فقط ایمیل‌های مربوطه را برای شما ارسال می‌کنیم. شما می‌توانید در هر زمان " @@ -2156,6 +2280,11 @@ msgstr "عمل" msgid "workspace.options.interaction-animation" msgstr "انیمیشن" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-animation-dissolve" +msgstr "حل کردن" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-animation-none" msgstr "هیچ‌یک" @@ -2241,6 +2370,26 @@ msgstr "تاریک" msgid "workspace.options.layer-options.blend-mode.difference" msgstr "تفاوت" +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.blend-mode.hue" +msgstr "رنگ" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.blend-mode.lighten" +msgstr "روشن کردن" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.blend-mode.luminosity" +msgstr "درخشندگی" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +#, fuzzy +msgid "workspace.options.layer-options.blend-mode.multiply" +msgstr "تکثیر" + #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.blend-mode.normal" msgstr "معمولی" @@ -2280,6 +2429,11 @@ msgstr "تلاش دوباره" msgid "workspace.options.rotation" msgstr "چرخش" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.select-a-shape" +msgstr "یک شکل، برد یا گروه را برای کشیدن اتصال به تابلوی دیگر انتخاب کنید." + #: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs msgid "workspace.options.shadow-options.blur" msgstr "محو" @@ -2774,158 +2928,4 @@ msgid "workspace.updates.update" msgstr "به‌روزرسانی" msgid "workspace.viewport.click-to-close-path" -msgstr "برای بستن مسیر کلیک کنید" - -msgid "common.unpublish" -msgstr "لغو انتشار" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "خیلی خوب" - -msgid "common.publish" -msgstr "انتشار" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "شروع آموزش" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "در پنپات قدم بزنید و با ویژگی‌های اصلی آن آشنا شوید." - -#: src/app/main/ui/dashboard/projects.cljs -#, fuzzy -msgid "dasboard.walkthrough-hero.start" -msgstr "شروع تور" - -msgid "dashboard.libraries-and-templates" -msgstr "کتابخانه‌ها و قالب‌ها" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "مدیریت تیم" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "" -"در حالی که با این آموزش سرگرم می‌شوید، اصول اولیه را در Penpot بیاموزید." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "لغو انتشار کتابخانه" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -#, fuzzy -msgid "workspace.options.layer-options.blend-mode.hue" -msgstr "رنگ" - -#, fuzzy -msgid "onboarding-v2.before-start.desc2" -msgstr "" -"اطلاعات دقیق در مورد نحوه استفاده از Penpot. از نمونه‌سازی تا سازماندهی یا " -"به اشتراک‌گذاری طرح‌ها." - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -#, fuzzy -msgid "workspace.options.select-a-shape" -msgstr "یک شکل، برد یا گروه را برای کشیدن اتصال به تابلوی دیگر انتخاب کنید." - -#, fuzzy -msgid "onboarding-v2.before-start.desc3" -msgstr "" -"شما می‌توانید آموزش‌های ما و آموزش‌های ساخته شده توسط انجمن ما را تماشا کنید." - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -#, fuzzy -msgid "workspace.options.layer-options.blend-mode.lighten" -msgstr "روشن کردن" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -#, fuzzy -msgid "workspace.options.layer-options.blend-mode.luminosity" -msgstr "درخشندگی" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -#, fuzzy -msgid "workspace.options.layer-options.blend-mode.multiply" -msgstr "تکثیر" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -#, fuzzy -msgid "workspace.options.interaction-animation-dissolve" -msgstr "حل کردن" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot برای تیم‌ها در نظر گرفته شده است. از اعضا دعوت کنید تا روی پروژه‌ها و " -"فایل‌ها با هم کار کنند" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.message" -msgstr "آیا مطمئن هستید که می‌خواهید این عضو را از تیم حذف کنید؟" - -msgid "onboarding.choice.team-up.create-team-desc" -msgstr "پس از نامگذاری تیم خود، می‌توانید افراد را برای پیوستن دعوت کنید." - -#: src/app/main/ui/auth/login.cljs -#, fuzzy -msgid "errors.auth-provider-not-configured" -msgstr "ارائه دهنده احراز هویت پیکربندی نشده است." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "به انجمن Penpot بروید" - -#: src/app/main/ui/settings/feedback.cljs -#, fuzzy -msgid "feedback.discourse-subtitle1" -msgstr "" -"ما خوشحالیم که شما اینجا هستید. اگر به کمک نیاز دارید، لطفا قبل از ارسال پست " -"جستجو کنید." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-title" -msgstr "انجمن Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-go-to" -msgstr "به توییتر بروید" - -#: src/app/main/ui/alert.cljs -#, fuzzy -msgid "ds.alert-title" -msgstr "توجه" - -#, fuzzy -msgid "errors.auth.unable-to-login" -msgstr "به نظر می‌رسد شما احراز هویت نشده‌اید یا جلسه منقضی شده است." - -#: src/app/main/ui/settings/feedback.cljs -#, fuzzy -msgid "feedback.twitter-title" -msgstr "حساب پشتیبانی در توییتر" - -#: src/app/main/data/workspace/persistence.cljs -#, fuzzy -msgid "errors.components-v2" -msgstr "این فایل قبلاً با فعال بودن Components V2 استفاده شده است." - -#, fuzzy -msgid "errors.team-leave.member-does-not-exists" -msgstr "عضوی که می‌خواهید اختصاص دهید وجود ندارد." - -#: src/app/main/ui/dashboard/search.cljs -#, fuzzy -msgid "dashboard.type-something" -msgstr "برای نمایش نتایج جستجو تایپ کنید" - -#, fuzzy -msgid "dashboard.libraries-and-templates.explore" -msgstr "بیشتر آنها را کاوش کنید و بدانید که چگونه مشارکت کنید" - -#: src/app/main/ui/settings/feedback.cljs -#, fuzzy -msgid "feedback.twitter-subtitle1" -msgstr "اینجا برای کمک به سوالات فنی شما." +msgstr "برای بستن مسیر کلیک کنید" \ No newline at end of file diff --git a/frontend/translations/fi.po b/frontend/translations/fi.po index 4f8f6e6dec..b1fc23594e 100644 --- a/frontend/translations/fi.po +++ b/frontend/translations/fi.po @@ -1,2 +1,6 @@ msgid "" -msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file +msgstr "" +"X-Generator: Weblate\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" \ No newline at end of file diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index 4f8f6e6dec..b1fc23594e 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -1,2 +1,6 @@ msgid "" -msgstr "X-Generator: Weblate\nMIME-Version: 1.0\nContent-Type: text/plain; charset=UTF-8\nContent-Transfer-Encoding: 8bit" \ No newline at end of file +msgstr "" +"X-Generator: Weblate\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" \ No newline at end of file diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index e8657c89c9..675dbb0c24 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-20 13:46+0000\n" "Last-Translator: Swapnil C \n" -"Language-Team: French \n" +"Language-Team: French " +"\n" "Language: fr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -172,6 +172,9 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Nous avons envoyé un e-mail de vérification à" +msgid "common.publish" +msgstr "Publier" + msgid "common.share-link.all-users" msgstr "Tous les utilisateurs de Penpot" @@ -227,6 +230,47 @@ msgstr "Partager les prototypes" msgid "common.share-link.view-all" msgstr "Tout sélectionner" +msgid "common.unpublish" +msgstr "Dépublier" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Gestion de l'équipe" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot est conçu pour les équipes. Invitez les membres pour collaborer sur " +"des projets et des fichiers" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Faites une équipe  !" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Apprenez les bases de Penpot en s'amusant avec ce tutoriel pratique." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Démarrer le tutoriel" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutoriel pratique" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Explorez Penpot et découvrir ses fonctionnalités." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Commencer le guide" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Démonstration de l'interface" + #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -451,6 +495,17 @@ msgstr "Inviter dans l’équipe" msgid "dashboard.leave-team" msgstr "Quitter l’équipe" +msgid "dashboard.libraries-and-templates" +msgstr "Bibliothèques et Modèles" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "En explorez plus et découvrir comment contribuer" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "" +"Il y avait un problème pendant l'importation de la modèle. La modèle n'est " +"pas importé." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Bibliothèques Partagées" @@ -635,6 +690,10 @@ msgstr "Résultats de recherche" msgid "dashboard.type-something" msgstr "Écrivez pour rechercher" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Retirer la Bibliothèque" + #: src/app/main/ui/settings/profile.cljs, #: src/app/main/ui/settings/password.cljs, #: src/app/main/ui/settings/options.cljs @@ -661,6 +720,14 @@ msgstr "Votre nom complet" msgid "dashboard.your-penpot" msgstr "Votre Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Attention" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Composants à mettre à jour :" @@ -692,6 +759,10 @@ msgstr "Il semblerait que vous n'êtes pas authentifié ou que votre session a e msgid "errors.clipboard-not-implemented" msgstr "Votre navigateur ne peut pas effectuer cette opération" +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Ce fichier a été déjà utilisé avec Components V2 activé." + #: src/app/main/ui/auth/verify_token.cljs, #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" @@ -1716,6 +1787,40 @@ msgstr "Êtes‑vous sûr de vouloir supprimer ce projet ?" msgid "modals.delete-project-confirm.title" msgstr "Supprimer un projet" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Supprimer le fichier" +msgstr[1] "Supprimer les fichiers" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Êtes-vous sûr de vouloir supprimer ce fichier ?" +msgstr[1] "Êtes-vous sûr de vouloir supprimer ces fichiers ?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Ce fichier contient les bibliothèques utilisées dans ce fichier :" +msgstr[1] "Ce fichier contient les bibliothèques utilisées dans ces fichiers :" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Ces fichiers contiennent les bibliothèques utilisées dans ce fichier :" +msgstr[1] "Ces fichiers contiennent les bibliothèques utilisées dans ces fichiers :" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Suppression du fichier" +msgstr[1] "Suppression des fichiers" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Suppression du fichier" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "Supprimer l’équipe" @@ -1749,6 +1854,10 @@ msgstr "Envoyer l'invitation" msgid "modals.invite-member.emails" msgstr "Adresse e-mail, séparées par des virgules" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Inviter des membres dans l'équipe" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" msgstr "" @@ -1839,6 +1948,22 @@ msgstr "Retirer « %s » en tant que Bibliothèque Partagée" msgid "modals.small-nudge" msgstr "Petit nudge" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Si vous le retirez, les ressources là-dedans deviennent une bibliothèque de " +"ce fichier." +msgstr[1] "" +"Si vous le retirez, les ressources là-dedans deviennent une bibliothèque de " +"ces fichiers." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Vous êtes sûr de vouloir retirer cette bibliothèque ?" +msgstr[1] "Vous êtes sûr de vouloir retirer ces bibliothèques ?" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" msgstr "" @@ -1889,6 +2014,23 @@ msgstr "Profil enregistré avec succès !" msgid "notifications.validation-email-sent" msgstr "E‑mail de vérification envoyé à %s. Vérifiez votre e‑mail !" +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"De l'information détaillée sur Penpot. De prototypage à l’organisation et " +"le partage des designs." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Guide utilisateur" + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Tutoriels videos" + +msgid "onboarding-v2.before-start.title" +msgstr "Avant de démarrer" + +msgid "onboarding-v2.welcome.title" +msgstr "Bienvenu sur Penpot !" + msgid "onboarding.choice.desc" msgstr "Comment voulez-vous commencer ?" @@ -2043,6 +2185,18 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "Un référentiel unique de contenus" +msgid "onboarding.team-modal.create-team" +msgstr "Créer une équipe" + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Nombre de projets et de fichiers illimité" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Nombre de membres illimité" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% gratuit !" + msgid "onboarding.templates.subtitle" msgstr "Voici quelques modèles." @@ -2454,6 +2608,12 @@ msgstr "Verrouiller les proportions" msgid "shortcuts.toggle-rules" msgstr "Afficher/masquer les règles" +msgid "shortcuts.ungroup" +msgstr "Dégrouper" + +msgid "shortcuts.unmask" +msgstr "Démasquer" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -2514,6 +2674,9 @@ msgstr "%s - Mode spectateur - Penpot" msgid "title.workspace" msgstr "%s - Penpot" +msgid "viewer.breaking-change.message" +msgstr "Désolé !" + #: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs msgid "viewer.empty-state" msgstr "Aucun plan de travail trouvé sur la page." @@ -2799,6 +2962,14 @@ msgstr "Masquer la palette de couleurs" msgid "workspace.header.menu.hide-rules" msgstr "Masquer les règles" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Éditer" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Fichier" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.select-all" msgstr "Tout sélectionner" @@ -2843,6 +3014,10 @@ msgstr "Modifications non sauvegardées" msgid "workspace.header.viewer" msgstr "Mode spectateur (%s)" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-full-screen" +msgstr "Plein écran" + #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.add" msgstr "Ajouter" @@ -2865,6 +3040,10 @@ msgstr "Bibliothèque du fichier" msgid "workspace.libraries.colors.recent-colors" msgstr "Couleurs récentes" +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RVBA" + #: src/app/main/ui/workspace/colorpicker.cljs msgid "workspace.libraries.colors.save-color" msgstr "Enregistrer le style de couleur" @@ -2949,6 +3128,10 @@ msgstr "Mes bibliothèques" msgid "workspace.library.store" msgstr "Prédéfinies" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.add-interaction" +msgstr "Cliquer sur le bouton + pour ajouter des interactions." + msgid "workspace.options.blur-options.background-blur" msgstr "Fond" @@ -3024,26 +3207,48 @@ msgstr "Conception" msgid "workspace.options.export" msgstr "Export" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-multiple" +msgstr "Exporter la sélection" + #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "Exporter l'élément" -msgstr[1] "Exporter les %s éléments" +msgstr "Exporter l'élément" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" msgstr "Suffixe" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "L'export est terminé" + #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.exporting-object" msgstr "Exportation…" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "L'export a échoué" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-slow" +msgstr "L'export est étonnamment lent" + #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.fill" msgstr "Remplissage" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.add-flow-start" +msgstr "Indiquer le début du flux" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-start" +msgstr "Début du flux" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.auto" msgstr "Automatique" @@ -3052,6 +3257,13 @@ msgstr "Automatique" msgid "workspace.options.grid.column" msgstr "Colonnes" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "Quadrillage" + +msgid "workspace.options.grid.params.color" +msgstr "Couleur" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.columns" msgstr "Colonnes" @@ -3132,6 +3344,73 @@ msgstr "Remplissage de groupe" msgid "workspace.options.group-stroke" msgstr "Contour de groupe" +msgid "workspace.options.height" +msgstr "Hauteur" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-action" +msgstr "Action" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-none" +msgstr "Aucune" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-duration" +msgstr "Durée" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-none" +msgstr "(non définie)" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-url" +msgstr "Ouvrir l'url" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-out" +msgstr "Sortie" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-left" +msgstr "En bas à gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-right" +msgstr "En bas à droite" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-center" +msgstr "Au centre" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-center" +msgstr "Centrer en haut" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-left" +msgstr "En haut à gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-right" +msgstr "En haut à droite" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-preserve-scroll" +msgstr "Conserver la position du défilement" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-prev-screen" +msgstr "Écran précédent" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-trigger" +msgstr "Déclencheur" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-url" +msgstr "URL" + #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.blend-mode.color" msgstr "Couleur" @@ -3208,6 +3487,118 @@ msgstr "Grouper les calques" msgid "workspace.options.layer-options.title.multiple" msgstr "Calques sélectionnés" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "Options avancées" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Hauteur max" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Largeur max" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Hauteur min" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Largeur min" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Redimensionnement de l'élément" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Hauteur maximale" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Largeur maximale" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Hauteur minimale" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Largeur minimale" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "En bas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Colonne" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "Colonne inversée" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "au centre" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "a gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "a droite" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "A gauche" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "La marge" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "Tous les côtés" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Une marge simple" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "Tous les côtés" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "À droite" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.title" +msgstr "Mise en page" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "En haut" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "en bas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "au centre" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "en haut" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "Plus de couleurs" + #: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.position" @@ -3228,6 +3619,10 @@ msgstr "Tous les coins" msgid "workspace.options.radius.single-corners" msgstr "Coins individuels" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.retry" +msgstr "Réessayer" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.rotation" msgstr "Rotation" @@ -3242,6 +3637,10 @@ msgstr "" msgid "workspace.options.select-artboard" msgstr "Sélectionner un plan de travail" +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "Couleurs sélectionnées" + #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.selection-fill" msgstr "Remplissage de sélection" @@ -3254,6 +3653,9 @@ msgstr "Contour de sélection" msgid "workspace.options.shadow-options.blur" msgstr "Flou" +msgid "workspace.options.shadow-options.color" +msgstr "Couleur de l'ombre" + #: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs msgid "workspace.options.shadow-options.drop-shadow" msgstr "Ombre portée" @@ -3299,6 +3701,10 @@ msgstr "Tailles prédéfinies" msgid "workspace.options.stroke" msgstr "Bordure" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "Aucune" + #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke.center" msgstr "Centre" @@ -3443,6 +3849,15 @@ msgstr "" "Utilisez le bouton de lecture dans l’en‑tête pour exécuter la vue du " "prototype." +msgid "workspace.options.width" +msgstr "Largeur" + +msgid "workspace.options.x" +msgstr "X" + +msgid "workspace.options.y" +msgstr "Y" + msgid "workspace.path.actions.add-node" msgstr "Ajouter un nœud (%s)" @@ -3485,6 +3900,10 @@ msgstr "Éloigner" msgid "workspace.shape.menu.copy" msgstr "Copier" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-artboard-from-selection" +msgstr "Créer un plan de travail depuis la sélection" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.create-component" msgstr "Créer un composant" @@ -3503,6 +3922,13 @@ msgstr "Supprimer" msgid "workspace.shape.menu.detach-instance" msgstr "Détacher l’instance" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "Détacher les instances" + +msgid "workspace.shape.menu.difference" +msgstr "Difference" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.duplicate" msgstr "Dupliquer" @@ -3511,6 +3937,12 @@ msgstr "Dupliquer" msgid "workspace.shape.menu.edit" msgstr "Modifier" +msgid "workspace.shape.menu.exclude" +msgstr "Exclure" + +msgid "workspace.shape.menu.flatten" +msgstr "Aplatir" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.flip-horizontal" msgstr "Retourner horizontalement" @@ -3540,6 +3972,9 @@ msgstr "Groupe" msgid "workspace.shape.menu.hide" msgstr "Masquer" +msgid "workspace.shape.menu.hide-ui" +msgstr "Montrer/Masquer l'interface" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.lock" msgstr "Bloquer" @@ -3554,6 +3989,9 @@ msgstr "Masque" msgid "workspace.shape.menu.paste" msgstr "Coller" +msgid "workspace.shape.menu.path" +msgstr "Chemin d'accès" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs, #: src/app/main/ui/workspace/context_menu.cljs @@ -3569,10 +4007,16 @@ msgstr "Montrer" msgid "workspace.shape.menu.show-main" msgstr "Afficher le composant principal" +msgid "workspace.shape.menu.transform-to-path" +msgstr "Transformer en chemin d'accès" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.ungroup" msgstr "Dégrouper" +msgid "workspace.shape.menu.union" +msgstr "Union" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.unlock" msgstr "Débloquer" @@ -3595,6 +4039,21 @@ msgstr "Historique (%s)" msgid "workspace.sidebar.layers" msgstr "Calques" +msgid "workspace.sidebar.layers.groups" +msgstr "Groupes" + +msgid "workspace.sidebar.layers.images" +msgstr "Images" + +msgid "workspace.sidebar.layers.masks" +msgstr "Les masques" + +msgid "workspace.sidebar.layers.shapes" +msgstr "Formes" + +msgid "workspace.sidebar.layers.texts" +msgstr "Textes" + #: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, #: src/app/main/ui/handoff/attributes/svg.cljs msgid "workspace.sidebar.options.svg-attrs.title" @@ -3648,6 +4107,10 @@ msgstr "Chemin (%s)" msgid "workspace.toolbar.rect" msgstr "Rectangle (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Raccourcis (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.text" msgstr "Texte (%s)" @@ -3780,470 +4243,4 @@ msgid "workspace.updates.update" msgstr "Actualiser" msgid "workspace.viewport.click-to-close-path" -msgstr "Cliquez pour fermer le chemin" - -msgid "workspace.shape.menu.transform-to-path" -msgstr "Transformer en chemin d'accès" - -msgid "workspace.options.grid.params.color" -msgstr "Couleur" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-center" -msgstr "Au centre" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-top-right" -msgstr "En haut à droite" - -msgid "workspace.options.shadow-options.color" -msgstr "Couleur de l'ombre" - -msgid "workspace.options.width" -msgstr "Largeur" - -msgid "workspace.options.x" -msgstr "X" - -msgid "workspace.shape.menu.exclude" -msgstr "Exclure" - -msgid "workspace.shape.menu.path" -msgstr "Chemin d'accès" - -msgid "shortcuts.ungroup" -msgstr "Dégrouper" - -msgid "shortcuts.unmask" -msgstr "Démasquer" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.grid-title" -msgstr "Quadrillage" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.advanced-ops" -msgstr "Options avancées" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title" -msgstr "Redimensionnement de l'élément" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "au centre" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.bottom" -msgstr "En bas" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" -msgstr "Colonne" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" -msgstr "Colonne inversée" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "a droite" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.left" -msgstr "A gauche" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin" -msgstr "La marge" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin-all" -msgstr "Tous les côtés" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin-simple" -msgstr "Une marge simple" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.right" -msgstr "À droite" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.title" -msgstr "Mise en page" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.top" -msgstr "En haut" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.edit" -msgstr "Éditer" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.file" -msgstr "Fichier" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.export-multiple" -msgstr "Exporter la sélection" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-complete" -msgstr "L'export est terminé" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-action" -msgstr "Action" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-animation-none" -msgstr "Aucune" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.retry" -msgstr "Réessayer" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.selection-color" -msgstr "Couleurs sélectionnées" - -msgid "workspace.sidebar.layers.groups" -msgstr "Groupes" - -msgid "workspace.sidebar.layers.texts" -msgstr "Textes" - -msgid "workspace.sidebar.layers.masks" -msgstr "Les masques" - -msgid "common.publish" -msgstr "Publier" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "Ok" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Attention" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "Inviter des membres dans l'équipe" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "Suppression du fichier" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-object-error" -msgstr "L'export a échoué" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-object-slow" -msgstr "L'export est étonnamment lent" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-duration" -msgstr "Durée" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-out" -msgstr "Sortie" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-left" -msgstr "En bas à gauche" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-top-center" -msgstr "Centrer en haut" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-top-left" -msgstr "En haut à gauche" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-preserve-scroll" -msgstr "Conserver la position du défilement" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-url" -msgstr "URL" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" -msgstr "Hauteur maximale" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" -msgstr "Largeur maximale" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" -msgstr "Hauteur minimale" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" -msgstr "Largeur minimale" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" -msgstr "Largeur max" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" -msgstr "Hauteur min" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" -msgstr "Largeur min" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "a gauche" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.padding-all" -msgstr "Tous les côtés" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "en bas" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "en haut" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.none" -msgstr "Aucune" - -msgid "workspace.sidebar.layers.images" -msgstr "Images" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Supprimer le fichier" -msgstr[1] "Supprimer les fichiers" - -msgid "onboarding.team-modal.create-team" -msgstr "Créer une équipe" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "Nombre de membres illimité" - -msgid "onboarding.team-modal.create-team-feature-1" -msgstr "Nombre de projets et de fichiers illimité" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "100% gratuit !" - -msgid "workspace.options.height" -msgstr "Hauteur" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-trigger" -msgstr "Déclencheur" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-prev-screen" -msgstr "Écran précédent" - -msgid "workspace.options.y" -msgstr "Y" - -msgid "viewer.breaking-change.message" -msgstr "Désolé !" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.zoom-full-screen" -msgstr "Plein écran" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-none" -msgstr "(non définie)" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.add-interaction" -msgstr "Cliquer sur le bouton + pour ajouter des interactions." - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-open-url" -msgstr "Ouvrir l'url" - -msgid "onboarding-v2.before-start.desc2.title" -msgstr "Guide utilisateur" - -msgid "onboarding-v2.welcome.title" -msgstr "Bienvenu sur Penpot !" - -msgid "onboarding-v2.before-start.title" -msgstr "Avant de démarrer" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "Êtes-vous sûr de vouloir supprimer ce fichier ?" -msgstr[1] "Êtes-vous sûr de vouloir supprimer ces fichiers ?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "Suppression du fichier" -msgstr[1] "Suppression des fichiers" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Démarrer le tutoriel" - -msgid "onboarding-v2.before-start.desc3.title" -msgstr "Tutoriels videos" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-right" -msgstr "En bas à droite" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" -msgstr "Hauteur max" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "au centre" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.more-colors" -msgstr "Plus de couleurs" - -msgid "workspace.shape.menu.union" -msgstr "Union" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot est conçu pour les équipes. Invitez les membres pour collaborer sur " -"des projets et des fichiers" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "Explorez Penpot et découvrir ses fonctionnalités." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Tutoriel pratique" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Faites une équipe  !" - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "Ce fichier a été déjà utilisé avec Components V2 activé." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Ce fichier contient les bibliothèques utilisées dans ce fichier :" -msgstr[1] "Ce fichier contient les bibliothèques utilisées dans ces fichiers :" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "Ces fichiers contiennent les bibliothèques utilisées dans ce fichier :" -msgstr[1] "" -"Ces fichiers contiennent les bibliothèques utilisées dans ces fichiers :" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "" -"Si vous le retirez, les ressources là-dedans deviennent une bibliothèque de " -"ce fichier." -msgstr[1] "" -"Si vous le retirez, les ressources là-dedans deviennent une bibliothèque de " -"ces fichiers." - -msgid "workspace.shape.menu.flatten" -msgstr "Aplatir" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.flows.flow-start" -msgstr "Début du flux" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.create-artboard-from-selection" -msgstr "Créer un plan de travail depuis la sélection" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.detach-instances-in-bulk" -msgstr "Détacher les instances" - -msgid "workspace.sidebar.layers.shapes" -msgstr "Formes" - -msgid "common.unpublish" -msgstr "Dépublier" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.flows.add-flow-start" -msgstr "Indiquer le début du flux" - -msgid "workspace.shape.menu.hide-ui" -msgstr "Montrer/Masquer l'interface" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Gestion de l'équipe" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Commencer le guide" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "Apprenez les bases de Penpot en s'amusant avec ce tutoriel pratique." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Démonstration de l'interface" - -msgid "dashboard.libraries-and-templates" -msgstr "Bibliothèques et Modèles" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "En explorez plus et découvrir comment contribuer" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "" -"Il y avait un problème pendant l'importation de la modèle. La modèle n'est " -"pas importé." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Retirer la Bibliothèque" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "Vous êtes sûr de vouloir retirer cette bibliothèque ?" -msgstr[1] "Vous êtes sûr de vouloir retirer ces bibliothèques ?" - -msgid "onboarding-v2.before-start.desc2" -msgstr "" -"De l'information détaillée sur Penpot. De prototypage à l’organisation et le " -"partage des designs." - -msgid "workspace.shape.menu.difference" -msgstr "Difference" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.shortcuts" -msgstr "Raccourcis (%s)" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.rgba" -msgstr "RVBA" +msgstr "Cliquez pour fermer le chemin" \ No newline at end of file diff --git a/frontend/translations/he.po b/frontend/translations/he.po index b9b389b68c..df71db2e68 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-10 17:20+0000\n" "Last-Translator: Yaron Shahrabani \n" -"Language-Team: Hebrew \n" +"Language-Team: Hebrew " +"\n" "Language: he\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -167,6 +167,9 @@ msgstr "יצירת חשבון חדש מהווה את הסכמתך לתנאי ה msgid "auth.verification-email-sent" msgstr "שלחנו הודעת דוא״ל לאימות אל" +msgid "common.publish" +msgstr "פרסום" + msgid "common.share-link.all-users" msgstr "כל משתמשי Penpot" @@ -222,6 +225,45 @@ msgstr "שיתוף אבות טיפוס" msgid "common.share-link.view-all" msgstr "בחירה בהכול" +msgid "common.unpublish" +msgstr "ביטול פרסום" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "ניהול צוות" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "Penpot מיועד לצוותים. אפשר להזמין חברים כדי לעבוד ביחד על מיזמים וקבצים" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "להתגבש כקבוצה!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "כאן נלמד את היסודות של Penpot תוך השתעשעות עם המדריך המעשי הזה." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "התחלת המדריך" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "מדריך מעשי" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "סיור במרחבי Penpot ועריכת היכרות עם יכולות המפתח שלו." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "התחלת הסיור" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "סיור בנבכי מנשק המשתמש" + #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -429,6 +471,15 @@ msgstr "הזמנה לצוות" msgid "dashboard.leave-team" msgstr "עזיבת הצוות" +msgid "dashboard.libraries-and-templates" +msgstr "ספריות ותבניות" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "עיון ביותר כאלה והסברים כיצד לתרום להן" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "אירעה שגיאה בייבוא התבנית והיא לא ייובאה." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "ספריות משותפות" @@ -611,6 +662,10 @@ msgstr "תוצאות חיפוש" msgid "dashboard.type-something" msgstr "נא להקליד כדי לחפש" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "ביטול פרסום ספרייה" + #: src/app/main/ui/settings/profile.cljs, #: src/app/main/ui/settings/password.cljs, #: src/app/main/ui/settings/options.cljs @@ -637,6 +692,14 @@ msgstr "שמך" msgid "dashboard.your-penpot" msgstr "ה־Penpot שלך" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "אישור" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "תשומת לב" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "רכיבים לעדכון:" @@ -668,6 +731,10 @@ msgstr "נראה שלא עברת אימות או שתוקף ההפעלה פג." msgid "errors.clipboard-not-implemented" msgstr "הדפדפן שלך לא יכול לבצע את הפעולה הזאת" +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "בקובץ זה כבר נעשה שימוש עם גרסה 2 של Components." + #: src/app/main/ui/auth/verify_token.cljs, #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" @@ -1664,6 +1731,50 @@ msgstr "למחוק את המיזם הזה?" msgid "modals.delete-project-confirm.title" msgstr "מחיקת מיזם" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "מחיקת קובץ" +msgstr[1] "מחיקת קבצים" +msgstr[2] "מחיקת קבצים" +msgstr[3] "מחיקת קבצים" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "למחוק את הקובץ?" +msgstr[1] "למחוק את הקבצים?" +msgstr[2] "למחוק את הקבצים?" +msgstr[3] "למחוק את הקבצים?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "לקובץ זה יש ספריות שנעשה בהן שימוש בקובץ הזה:" +msgstr[1] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" +msgstr[2] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" +msgstr[3] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקובץ הזה:" +msgstr[1] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" +msgstr[2] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" +msgstr[3] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "מחיקת קובץ" +msgstr[1] "מחיקת קבצים" +msgstr[2] "מחיקת קבצים" +msgstr[3] "מחיקת קבצים" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "מחיקת קובץ" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "מחיקת צוות" @@ -1695,6 +1806,10 @@ msgstr "שליחת הזמנה" msgid "modals.invite-member.emails" msgstr "כתובות דוא״ל, מופרדות בפסיקים" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "הזמנת חברים לצוות" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" msgstr "כיוון שאין עוד חברים בצוות הזה מלבדך, הצוות יימחק על כל המיזמים והקבצים שלו." @@ -1783,6 +1898,42 @@ msgstr "הסרת „%s” כספריה משותפת" msgid "modals.small-nudge" msgstr "הינד קטן" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "ביטול פרסום" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "ביטול הפרסום הופך את המשאבים לספרייה של הקובץ הזה." +msgstr[1] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." +msgstr[2] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." +msgstr[3] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "לבטל את פרסום הספרייה הזאת?" +msgstr[1] "לבטל את פרסום הספריות האלו?" +msgstr[2] "לבטל את פרסום הספריות האלו?" +msgstr[3] "לבטל את פרסום הספריות האלו?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "בשימוש בקובץ הזה:" +msgstr[1] "בשימוש בקבצים האלה:" +msgstr[2] "בשימוש בקבצים האלה:" +msgstr[3] "בשימוש בקבצים האלה:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "ביטול פרסום ספרייה" +msgstr[1] "ביטול פרסום ספריות" +msgstr[2] "ביטול פרסום ספריות" +msgstr[3] "ביטול פרסום ספריות" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" @@ -1835,6 +1986,53 @@ msgstr "הפרופיל נשמר בהצלחה!" msgid "notifications.validation-email-sent" msgstr "הודעת האימות נשלחה בדוא״ל אל %s. נא לבדוק את הדוא״ל שלך!" +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"רצוי לדעת שיש מגוון משאבים זמינים לך כדי לסייע לך להתחיל להשתמש ב־Penpot " +"כמו המדריך למשתמשים וערוץ ה־YouTube שלנו." + +msgid "onboarding-v2.before-start.desc2" +msgstr "מידע מפורט על אופן השימוש ב־Penpot. החל מתכנון אבטיפוס ועד שיתוף עיצובים." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "מדריך למשתמשים" + +msgid "onboarding-v2.before-start.desc3" +msgstr "אפשר לצפות במדריכים שלנו ובמדריכים שנוצרו על ידי חברי הקהילה שלנו." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "מדריכים מצולמים" + +msgid "onboarding-v2.before-start.title" +msgstr "לפני שמתחילים" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot הוא בקוד פתוח והוא נוצר על ידי Kaleidos וגם על ידי הקהילה בה מגוון " +"אנשים כבר מסייעים זה לזה. כל אחד יכול לתרום דרך:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"מקום ציבורי ללמידה, שיתוף ודיון על Penpot, ההווה והעתיד שלו עם כל הקהילה " +"וצוות הליבה של Penpot." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "השתתפות בפעילות הקהילתית" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"כאן אפשר למצוא מידע על כיצד לשתף פעולה בנושאי תרגום, בקשות יכולות, תרומות " +"ליבה, מצוד אחר תקלות…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "מדריך למתנדבים" + +msgid "onboarding-v2.welcome.title" +msgstr "ברוך בואך ל־Penpot!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "ליצירת צוות מאוחר יותר" + msgid "onboarding.choice.team-up.create-team" msgstr "שם הצוות שלך" @@ -1847,12 +2045,18 @@ msgstr "נא למלא את שם הצוות" msgid "onboarding.choice.team-up.invite-members" msgstr "הזמנת חברים" +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "רצוי לזכור לכלול את כולם. מפתחים, מעצבים, מנהלים… גיוון מעשיר :)" + msgid "onboarding.choice.team-up.invite-members-skip" msgstr "יצירת צוות והזמנה בהמשך" msgid "onboarding.choice.team-up.invite-members-submit" msgstr "יצירת צוות ושליחת הזמנות" +msgid "onboarding.choice.team-up.roles" +msgstr "הזמנה עם התפקיד:" + msgid "onboarding.choice.title" msgstr "ברוך בואך אל Penpot" @@ -1958,6 +2162,29 @@ msgstr "מקור משותף יחיד לאמת" msgid "onboarding.team-input-placeholder" msgstr "נא למלא שם חדש לצוות" +msgid "onboarding.team-modal.create-team" +msgstr "יצירת צוות" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"צוות מאפשר לך לשתף פעולה עם משתמשים אחרים ב־Penpot שעובדים על אותם קבצים " +"ומיזמים." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "אין הגבלה על קבצים או מיזמים" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "מהדורת ריבוי משתתפים" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "ניהול תפקידים" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "ללא הגבלת משתמשים" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% בחינם!" + msgid "onboarding.team.create.button" msgstr "יצירת צוות" @@ -2675,6 +2902,9 @@ msgstr "שם קבוצה" msgid "workspace.assets.libraries" msgstr "ספריות" +msgid "workspace.assets.local-library" +msgstr "ספרייה מקומית" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.not-found" msgstr "לא נמצאו משאבים" @@ -3144,11 +3374,7 @@ msgstr "ייצוא הבחירה" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "ייצוא רכיב" -msgstr[1] "ייצוא %s רכיבים" -msgstr[2] "ייצוא %s רכיבים" -msgstr[3] "ייצוא %s רכיבים" +msgstr "ייצוא רכיב" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -3573,6 +3799,22 @@ msgstr "שכבות נבחרות" msgid "workspace.options.layout-item.advanced-ops" msgstr "אפשרויות מתקדמות" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "גובה מר.‏" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "רוחב מר.‏" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "גובה מז.‏" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "רוחב מז.‏" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.max-h" msgstr "גובה מרבי" @@ -3593,6 +3835,22 @@ msgstr "רוחב מזערי" msgid "workspace.options.layout-item.title" msgstr "שינוי גודל רכיבים" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "גובה מרבי" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "רוחב מרבי" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "גובה מזערי" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "רוחב מזערי" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-h" msgstr "גובה מרבי" @@ -4191,6 +4449,9 @@ msgstr "נתיב" msgid "workspace.shape.menu.reset-overrides" msgstr "איפוס מעקפים" +msgid "workspace.shape.menu.restore-main" +msgstr "שחזור הרכיב הראשי" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.select-layer" msgstr "בחירת שכבה" @@ -4464,270 +4725,4 @@ msgid "workspace.updates.update" msgstr "עדכון" msgid "workspace.viewport.click-to-close-path" -msgstr "לחיצה תסגור את הנתיב" - -msgid "common.unpublish" -msgstr "ביטול פרסום" - -msgid "dashboard.libraries-and-templates" -msgstr "ספריות ותבניות" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "אישור" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "תשומת לב" - -msgid "onboarding.choice.team-up.create-later" -msgstr "ליצירת צוות מאוחר יותר" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" -msgstr "גובה מזערי" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" -msgstr "גובה מר.‏" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" -msgstr "רוחב מר.‏" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" -msgstr "גובה מז.‏" - -msgid "workspace.shape.menu.restore-main" -msgstr "שחזור הרכיב הראשי" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "התחלת הסיור" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "עיון ביותר כאלה והסברים כיצד לתרום להן" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "אירעה שגיאה בייבוא התבנית והיא לא ייובאה." - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "בקובץ זה כבר נעשה שימוש עם גרסה 2 של Components." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "לקובץ זה יש ספריות שנעשה בהן שימוש בקובץ הזה:" -msgstr[1] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" -msgstr[2] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" -msgstr[3] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "ביטול הפרסום הופך את המשאבים לספרייה של הקובץ הזה." -msgstr[1] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." -msgstr[2] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." -msgstr[3] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "לבטל את פרסום הספרייה הזאת?" -msgstr[1] "לבטל את פרסום הספריות האלו?" -msgstr[2] "לבטל את פרסום הספריות האלו?" -msgstr[3] "לבטל את פרסום הספריות האלו?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.scd-message" -msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "בשימוש בקובץ הזה:" -msgstr[1] "בשימוש בקבצים האלה:" -msgstr[2] "בשימוש בקבצים האלה:" -msgstr[3] "בשימוש בקבצים האלה:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "ביטול פרסום ספרייה" -msgstr[1] "ביטול פרסום ספריות" -msgstr[2] "ביטול פרסום ספריות" -msgstr[3] "ביטול פרסום ספריות" - -msgid "onboarding-v2.welcome.desc2.title" -msgstr "השתתפות בפעילות הקהילתית" - -msgid "onboarding-v2.welcome.desc3" -msgstr "" -"כאן אפשר למצוא מידע על כיצד לשתף פעולה בנושאי תרגום, בקשות יכולות, תרומות " -"ליבה, מצוד אחר תקלות…" - -msgid "onboarding-v2.welcome.desc2" -msgstr "" -"מקום ציבורי ללמידה, שיתוף ודיון על Penpot, ההווה והעתיד שלו עם כל הקהילה " -"וצוות הליבה של Penpot." - -msgid "onboarding-v2.welcome.desc1" -msgstr "" -"Penpot הוא בקוד פתוח והוא נוצר על ידי Kaleidos וגם על ידי הקהילה בה מגוון " -"אנשים כבר מסייעים זה לזה. כל אחד יכול לתרום דרך:" - -msgid "onboarding-v2.before-start.title" -msgstr "לפני שמתחילים" - -msgid "onboarding-v2.before-start.desc3.title" -msgstr "מדריכים מצולמים" - -msgid "onboarding-v2.before-start.desc3" -msgstr "אפשר לצפות במדריכים שלנו ובמדריכים שנוצרו על ידי חברי הקהילה שלנו." - -msgid "onboarding-v2.before-start.desc2.title" -msgstr "מדריך למשתמשים" - -msgid "onboarding-v2.before-start.desc2" -msgstr "" -"מידע מפורט על אופן השימוש ב־Penpot. החל מתכנון אבטיפוס ועד שיתוף עיצובים." - -msgid "onboarding-v2.before-start.desc1" -msgstr "" -"רצוי לדעת שיש מגוון משאבים זמינים לך כדי לסייע לך להתחיל להשתמש ב־Penpot כמו " -"המדריך למשתמשים וערוץ ה־YouTube שלנו." - -msgid "workspace.assets.local-library" -msgstr "ספרייה מקומית" - -msgid "onboarding.team-modal.create-team-feature-3" -msgstr "ניהול תפקידים" - -msgid "onboarding.team-modal.create-team" -msgstr "יצירת צוות" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "ניהול צוות" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "מחיקת קובץ" -msgstr[1] "מחיקת קבצים" -msgstr[2] "מחיקת קבצים" -msgstr[3] "מחיקת קבצים" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "Penpot מיועד לצוותים. אפשר להזמין חברים כדי לעבוד ביחד על מיזמים וקבצים" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "להתגבש כקבוצה!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "סיור בנבכי מנשק המשתמש" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקובץ הזה:" -msgstr[1] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" -msgstr[2] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" -msgstr[3] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "מחיקת קובץ" - -msgid "common.publish" -msgstr "פרסום" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "כאן נלמד את היסודות של Penpot תוך השתעשעות עם המדריך המעשי הזה." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "התחלת המדריך" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "מדריך מעשי" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "סיור במרחבי Penpot ועריכת היכרות עם יכולות המפתח שלו." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "ביטול פרסום ספרייה" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "למחוק את הקובץ?" -msgstr[1] "למחוק את הקבצים?" -msgstr[2] "למחוק את הקבצים?" -msgstr[3] "למחוק את הקבצים?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "מחיקת קובץ" -msgstr[1] "מחיקת קבצים" -msgstr[2] "מחיקת קבצים" -msgstr[3] "מחיקת קבצים" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "הזמנת חברים לצוות" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" -msgstr "רוחב מזערי" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" -msgstr "גובה מרבי" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "ביטול פרסום" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "ללא הגבלת משתמשים" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" -msgstr "רוחב מרבי" - -msgid "onboarding.team-modal.create-team-feature-2" -msgstr "מהדורת ריבוי משתתפים" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "100% בחינם!" - -msgid "onboarding.team-modal.create-team-feature-1" -msgstr "אין הגבלה על קבצים או מיזמים" - -msgid "onboarding.team-modal.create-team-desc" -msgstr "" -"צוות מאפשר לך לשתף פעולה עם משתמשים אחרים ב־Penpot שעובדים על אותם קבצים " -"ומיזמים." - -msgid "onboarding.choice.team-up.roles" -msgstr "הזמנה עם התפקיד:" - -msgid "onboarding-v2.welcome.title" -msgstr "ברוך בואך ל־Penpot!" - -msgid "onboarding.choice.team-up.invite-members-info" -msgstr "רצוי לזכור לכלול את כולם. מפתחים, מעצבים, מנהלים… גיוון מעשיר :)" - -msgid "onboarding-v2.welcome.desc3.title" -msgstr "מדריך למתנדבים" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" -msgstr "רוחב מז.‏" +msgstr "לחיצה תסגור את הנתיב" \ No newline at end of file diff --git a/frontend/translations/hr.po b/frontend/translations/hr.po index 1fc5be9998..4efa1c061e 100644 --- a/frontend/translations/hr.po +++ b/frontend/translations/hr.po @@ -2,208 +2,93 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Zvonimir Juranko \n" -"Language-Team: Croatian \n" +"Language-Team: Croatian " +"\n" "Language: hr\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.14.1\n" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-animation-push" -msgstr "Gurni" +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Već imaš račun?" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-ms" -msgstr "ms" +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "Provjeri svoj e-mail i klikni na vezu da potvrdiš i počneš koristiti Penpot." -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing-ease" -msgstr "Ease" +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Potvrdi lozinku" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-navigate-to" -msgstr "Idi do" +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Kreiraj demo račun" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -#, fuzzy -msgid "workspace.options.interaction-mouse-enter" -msgstr "Ulaz mišem" +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Samo želiš isprobati?" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -#, fuzzy -msgid "workspace.options.interaction-out" -msgstr "Vani" +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Ovo je DEMO usluga. NEMOJ KORISTITI za pravi rad. Projekti će se povremeno " +"brisati." -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-close-overlay-dest" -msgstr "Zatvori preklapanje: %s" +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.email" +msgstr "E-mail" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-on-click" -msgstr "Na klik" +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Zaboravljena lozinka?" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-open-overlay" -msgstr "Otvoreno preklapanje" +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Puno ime" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-left" -msgstr "Dolje lijevo" +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Prijavi se ovdje" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-right" -msgstr "Dolje desno" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Prijava" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-center" -msgstr "Dolje sredina" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Drago nam je vidjeti te opet!" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-top-center" -msgstr "Gore sredina" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "GitHub" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-top-right" -msgstr "Gore desno" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "GitLab" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-position" -msgstr "Pozicija" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Google" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-close-overlay" -msgstr "Zatvori preklapanje" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "LDAP" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-trigger" -msgstr "Okidač" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "OpenID" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-url" -msgstr "URL" +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Unesi novu lozinku" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-center" -msgstr "Sredina" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-preserve-scroll" -msgstr "Sačuvaj položaj scrolanja" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-manual" -msgstr "Priručnik" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.select-layer" -msgstr "Označi layer" - -#: src/app/main/ui/workspace/left_toolbar.cljs -#, fuzzy -msgid "workspace.sidebar.layers" -msgstr "Layeri" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "sredina" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.radius.all-corners" -msgstr "Svi kutevi" - -msgid "workspace.sidebar.layers.components" -msgstr "Komponente" - -msgid "workspace.sidebar.layers.images" -msgstr "Slike" - -msgid "workspace.sidebar.layers.masks" -msgstr "Maske" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin-simple" -msgstr "Jednostavna margina" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -#, fuzzy -msgid "workspace.options.layout.padding" -msgstr "Padding" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -#, fuzzy -msgid "workspace.options.layout.no-wrap" -msgstr "bez omota" - -msgid "workspace.options.opacity" -msgstr "Neprozirnost" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -#, fuzzy -msgid "workspace.options.layout.packed" -msgstr "upakiran" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.top" -msgstr "Vrh" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.more-lib-colors" -msgstr "Više boja iz biblioteke" - -#: src/app/main/ui/workspace/sidebar/options.cljs -msgid "workspace.options.prototype" -msgstr "Prototip" - -msgid "workspace.options.radius" -msgstr "Radius" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "dno" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "vrh" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -#, fuzzy -msgid "workspace.options.layout.wrap" -msgstr "omotaj" - -#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.position" -msgstr "Pozicija" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.more-colors" -msgstr "Više boja" - -msgid "workspace.options.stroke-width" -msgstr "Širina poteza" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title.group" -msgstr "Grupiraj sjenu" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.inner-shadow" -msgstr "Unutarnja sjena" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.outer" -msgstr "Vani" - -#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.size" -msgstr "Veličina" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title" -msgstr "Sjena" +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "Želim se pretplatiti se na Penpot mailing listu." #: src/app/main/ui/auth/recovery.cljs msgid "auth.notifications.invalid-token-error" @@ -217,13 +102,29 @@ msgstr "Lozinka uspješno promijenjena" msgid "auth.notifications.profile-not-verified" msgstr "Profil nije potvrđen, potvrdi profil prije nastavka." +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Veza za oporavak lozinke poslana je u tvoj inbox." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Uspješno pridružen/a timu" + #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.password" msgstr "Lozinka" +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Najmanje 8 znamenki" + msgid "auth.privacy-policy" msgstr "Pravila privatnosti" +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Obnovi lozinku" + #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.recovery-request-subtitle" msgstr "Poslat ćemo ti e-mail sa uputama" @@ -232,6 +133,22 @@ msgstr "Poslat ćemo ti e-mail sa uputama" msgid "auth.recovery-request-title" msgstr "Zaboravljena lozinka?" +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Promjeni lozinku" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Još nemaš račun?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Stvori račun" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "Besplatno je, Open Source" + #: src/app/main/ui/auth/register.cljs msgid "auth.register-title" msgstr "Stvori račun" @@ -276,43 +193,107 @@ msgstr "Izradi poveznicu" msgid "common.share-link.link-copied-success" msgstr "Poveznica uspješno kopirana" +msgid "common.share-link.link-deleted-success" +msgstr "Poveznica uspješno izbrisana" + +msgid "common.share-link.manage-ops" +msgstr "Upravljanje dopuštenjima" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 stranica podijeljena" +msgstr[1] "%s stranica podijeljeno" +msgstr[2] "%s stranica podijeljeno" + +msgid "common.share-link.permissions-can-comment" +msgstr "Dopušten komentar" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Dopušteno provjeriti kod" + +msgid "common.share-link.permissions-hint" +msgstr "Svatko sa poveznicom imat će pristup" + msgid "common.share-link.permissions-pages" msgstr "Stranice podijeljene" +msgid "common.share-link.placeholder" +msgstr "Ovdje će se pojaviti poveznica za dijeljenje" + +msgid "common.share-link.team-members" +msgstr "Samo članovi tima" + +msgid "common.share-link.title" +msgstr "Podijeli prototip" + msgid "common.share-link.view-all" msgstr "Označi sve" msgid "common.unpublish" msgstr "Poništi objavu" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Upravljanje timom" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot je namijenjen timovima. Pozovi članove da zajedno rade na projektima " +"i datotekama" + #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.title" msgstr "Udruži se!" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.add-shared" -msgstr "Dodaj kao zajedničku biblioteku" - #: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Praktični vodič" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.change-email" -msgstr "Promijeni e-mail" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Pregledaj sučelje" +msgid "dasboard.tutorial-hero.info" +msgstr "Nauči osnove na Penpotu dok se zabavljaš uz ovaj praktični vodič." #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.start" msgstr "Pokreni praktični vodič" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Praktični vodič" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Prošeći Penpotom i upoznaj glavne karakteristike." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Započni obilazak" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Pregledaj sučelje" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Dodaj kao zajedničku biblioteku" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Promijeni e-mail" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(kopiraj)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Kreiraj novi tim" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.default-team-name" msgstr "Tvoj Penpot" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Obriši tim" + msgid "dashboard.download-binary-file" msgstr "Preuzmi Penpot datoteku (.penpot)" @@ -326,6 +307,10 @@ msgstr "Skica" msgid "dashboard.duplicate" msgstr "Kopija" +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Kopiraj %s datoteka" + #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.empty-files" msgstr "Ovdje još uvijek nemaš datoteka" @@ -334,19 +319,44 @@ msgstr "Ovdje još uvijek nemaš datoteka" #, markdown msgid "dashboard.empty-placeholder-drafts" msgstr "" -"O ne! Još nemaš datoteka! Ako želiš isprobati neke predloške, idi na [" -"Biblioteke i predlošci](https://penpot.app/libraries-templates.html)" +"O ne! Još nemaš datoteka! Ako želiš isprobati neke predloške, idi na " +"[Biblioteke i predlošci](https://penpot.app/libraries-templates.html)" -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate-multi" -msgstr "Kopiraj %s datoteka" +msgid "dashboard.export-binary-multi" +msgstr "Preuzmi %s Penpot datoteke (.penpot)" + +msgid "dashboard.export-frames" +msgstr "Izvezi artboard u PDF…" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Izvezi u PDF" + +msgid "dashboard.export-multi" +msgstr "Izvezi Penpot %s datoteka" + +#: src/app/main/ui/export.cljs +#, fuzzy +msgid "dashboard.export-multiple.selected" +msgstr "%s - %s elementa označeno" #: src/app/main/ui/workspace/header.cljs msgid "dashboard.export-shapes" msgstr "Izvezi" -msgid "dashboard.export-multi" -msgstr "Izvezi Penpot %s datoteka" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Postavke izvoza možeš dodati elementima iz svojstava dizajna (na dnu desne " +"bočne trake)." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Informacije kako postaviti izvoz na Penpot." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Nema elemenata s postavkama izvoza." #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.title" @@ -355,20 +365,51 @@ msgstr "Izvezi odabir" msgid "dashboard.export-single" msgstr "Izvezi Penpot datoteku" +msgid "dashboard.export-standard-multi" +msgstr "Preuzmi %s standardne datoteke (.svg + .json)" + msgid "dashboard.export.detail" msgstr "* Može uključivati komponente, grafike, boje i/ili tipografije." -msgid "dashboard.export.options.all.title" -msgstr "Izvezi zajedničke biblioteke" - msgid "dashboard.export.explain" msgstr "" "Jedna ili više datoteka koju želiš izvesti koristi zajedničke biblioteke. " "Što želiš učiniti s njihovim stavkama*?" +msgid "dashboard.export.options.all.message" +msgstr "" +"datoteke sa zajedničkim bibliotekama bit će uključene u izvoz, održavajući " +"njihovu poveznicu." + +msgid "dashboard.export.options.all.title" +msgstr "Izvezi zajedničke biblioteke" + +msgid "dashboard.export.options.detach.message" +msgstr "" +"Zajedničke biblioteke neće biti uključene u izvoz i nikakve stavke neće " +"biti dodani u biblioteku. " + msgid "dashboard.export.options.detach.title" msgstr "Tretiraj stavke zajedničke biblioteke kao osnovne objekte" +msgid "dashboard.export.options.merge.message" +msgstr "" +"Tvoja će datoteka biti izvezena sa svim vanjskim stavkama spojenim u " +"biblioteku datoteka." + +msgid "dashboard.export.options.merge.title" +msgstr "Uključi stavke zajedničke biblioteke u biblioteke datoteka" + +msgid "dashboard.export.title" +msgstr "Izvezi datoteke" + +msgid "dashboard.fonts.deleted-placeholder" +msgstr "Font izbrisan" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "Odbaci sve" + #, fuzzy msgid "dashboard.fonts.empty-placeholder" msgstr "Još uvijek nemaš instalirane custom fontove." @@ -380,6 +421,22 @@ msgstr[0] "1 font dodan" msgstr[1] "%s fontova dodano" msgstr[2] "%s fontova dodano" +#, fuzzy, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"Svaki web-font koji ovdje preneseš biti će dodan na popis fontova koji je " +"dostupan u tekstualnim svojstvima datoteka ovog tima. Fontovi s istim " +"nazivom fontova biti će grupirani kao **jedan font**. Možeš učitati fontove " +"sa sljedećim formatima: **TTF, OTF i WOFF** (biti će potreban samo jedan)." + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"Možeš učitavati samo fontove koje posjeduješ ili imaš licencu za korištenje " +"u Penpotu. Saznaj više u odjeljku Prava na sadržaj [Penpotovih uvjeta " +"pružanja usluge](https://penpot.app/terms.html). Također možeš pročitati o " +"[licenciranju fontova](https://www.typography.com/faq)." + #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" msgstr "Učitaj sve" @@ -390,6 +447,27 @@ msgstr "Uvezi Penpot datoteke" msgid "dashboard.import.analyze-error" msgstr "Ups! Nismo mogli uvesti ovu datoteku" +msgid "dashboard.import.import-error" +msgstr "Došlo je do problema pri uvozu datoteke. Datoteka nije uvezena." + +msgid "dashboard.import.import-message" +msgstr "%s datoteka je uspješno uvezeno." + +msgid "dashboard.import.import-warning" +msgstr "Neke su datoteke sadržavale nevažeće objekte koji su uklonjeni." + +msgid "dashboard.import.progress.process-colors" +msgstr "Obrada boja" + +msgid "dashboard.import.progress.process-components" +msgstr "Obrada komponenti" + +msgid "dashboard.import.progress.process-media" +msgstr "Obrada medija" + +msgid "dashboard.import.progress.process-page" +msgstr "Obrada stranice: %s" + msgid "dashboard.import.progress.process-typographies" msgstr "Obrada tipografija" @@ -427,6 +505,10 @@ msgstr "učitavanje tvojih datoteka…" msgid "dashboard.loading-fonts" msgstr "učitavanje tvojih fontova…" +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Premijesti u" + #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.move-to-multi" msgstr "Premijesti %s datoteke u" @@ -443,10 +525,38 @@ msgstr "+ Nova datoteka" msgid "dashboard.new-file-prefix" msgstr "Nova datoteka" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Novi projekt" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Novi projekt" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "Šaljite mi vijesti, ažuriranja proizvoda i preporuke o Penpotu." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Pretplata na newsletter" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "Nisu pronađeni rezultati za “%s”" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "Prikvačeni projekti pojavit će se ovdje" + #: src/app/main/ui/auth/verify_token.cljs msgid "dashboard.notifications.email-changed-successfully" msgstr "Tvoja e-mail adresa je uspješno ažurirana" +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "Tvoja e-mail adresa je uspješno potvrđena" + #: src/app/main/ui/settings/password.cljs msgid "dashboard.notifications.password-saved" msgstr "Lozinka je uspješno spremljena!" @@ -482,6 +592,22 @@ msgstr "Promakni u vlasnika" msgid "dashboard.remove-account" msgstr "Želiš li ukloniti svoj račun?" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.remove-shared" +msgstr "Ukloni kao zajedničku biblioteku" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "Spremi postavke" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Pretraži…" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "Pretraga \"%s\"…" + #: src/app/main/ui/settings/options.cljs msgid "dashboard.select-ui-language" msgstr "Odaberite jezik korisničkog sučelja" @@ -498,6 +624,14 @@ msgstr "Prikaži sve datoteke" msgid "dashboard.success-delete-file" msgstr "Tvoja datoteka je uspješno izbrisana" +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "Tvoj projekt je uspješno izbrisan" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "Tvoja datoteka je uspješno duplicirana" + #: src/app/main/ui/dashboard/project_menu.cljs msgid "dashboard.success-duplicate-project" msgstr "Tvoj projekt je uspješno dupliciran" @@ -510,6 +644,10 @@ msgstr "Tvoja datoteka je uspješno premještena" msgid "dashboard.success-move-files" msgstr "Tvoje datoteke su uspješno premještene" +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "Tvoj projekt je uspješno premješten" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.switch-team" msgstr "Promijeni tim" @@ -538,6 +676,74 @@ msgstr "Pretraži rezultate" msgid "dashboard.type-something" msgstr "Upiši za rezultate pretraživanja" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Poništi objavu biblioteke" + +#: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs +msgid "dashboard.update-settings" +msgstr "Ažuriraj postavke" + +#: src/app/main/ui/settings.cljs +msgid "dashboard.your-account-title" +msgstr "Tvoj korisnički račun" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "E-mail" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "Ime" + +#: src/app/main/ui/dashboard/search.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/libraries.cljs, src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.your-penpot" +msgstr "Tvoj Penpot" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "U redu" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Pažnja" + +#: src/app/main/ui/confirm.cljs +msgid "ds.component-subtitle" +msgstr "Komponente za ažuriranje:" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "Poništi" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "U redu" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "Jesi li siguran/na?" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "ds.updated-at" +msgstr "Ažurirano: %s" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Pružatelj autentifikacije nije konfiguriran." + +#, fuzzy +msgid "errors.auth.unable-to-login" +msgstr "Čini se da nisi autentificiran/a ili je sesija istekla." + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "Tvoj preglednik ne može izvršiti ovu operaciju" + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Ova datoteka je već korištena s omogućenim komponentama V2." + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" msgstr "E-mail se već koristi" @@ -576,6 +782,9 @@ msgstr "Pogrešna boja" msgid "errors.invite-invalid" msgstr "Pogrešna pozivnica" +msgid "errors.invite-invalid.info" +msgstr "Ova pozivnica je možda otkazana ili je istekla." + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "Onemogućena je LDAP provjera autentičnosti." @@ -583,6 +792,10 @@ msgstr "Onemogućena je LDAP provjera autentičnosti." msgid "errors.media-format-unsupported" msgstr "Format slike nije podržan (mora biti svg, jpg ili png)." +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "Slika je prevelika za umetanje." + #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" msgstr "Čini se da sadržaj slike ne odgovara ekstenziji datoteke." @@ -604,6 +817,10 @@ msgstr "Nije moguće povezati se s backend poslužiteljem." msgid "errors.password-invalid-confirmation" msgstr "Lozinka za potvrdu mora odgovarati" +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "Lozinka mora sadržavati najmanje 8 znakova" + #: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.profile-is-muted" msgstr "" @@ -620,6 +837,9 @@ msgstr "Nedovoljno članova za napuštanje tima, vjerojatno ga želiš izbrisati msgid "errors.team-leave.member-does-not-exists" msgstr "Član kojeg pokušavaš dodijeliti ne postoji." +msgid "errors.team-leave.owner-cant-leave" +msgstr "Vlasnik ne može napustiti tim, moraš ponovno dodijeliti ulogu vlasnika." + msgid "errors.terms-privacy-agreement-invalid" msgstr "Moraš prihvatiti naše uvjete pružanja usluge i politiku privatnosti." @@ -643,6 +863,26 @@ msgstr "Stara lozinka je netočna" msgid "feedback.chat-start" msgstr "Pridruži se chatu" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "Imaš želju za razgovorom? Razgovaraj s nama na Gitteru" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "Opis" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Idi na Penpot forum" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "Sretni smo što si ovdje. Ako trebaš pomoć, pretraži prije objavljivanja." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Penpot zajednica" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" msgstr "Tema" @@ -661,10 +901,18 @@ msgstr "E-mail" msgid "feedback.twitter-go-to" msgstr "Idi na Twitter" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "Ovdje za pomoć za tvoje tehničke upite." + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.twitter-title" msgstr "Twitter korisnički račun za podršku" +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +msgstr "Došlo je do pogreške" + #: src/app/main/ui/handoff/attributes/blur.cljs msgid "handoff.attributes.blur" msgstr "Zamućenje" @@ -721,6 +969,10 @@ msgstr "Radius" msgid "handoff.attributes.layout.rotation" msgstr "Rotacija" +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Vrh" + #: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.width" msgstr "Širina" @@ -750,16 +1002,22 @@ msgid "handoff.attributes.stroke" msgstr "Potez" #, permanent -msgid "handoff.attributes.stroke.alignment.outer" -msgstr "Vani" +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Sredina" #, permanent msgid "handoff.attributes.stroke.alignment.inner" msgstr "Unutra" #, permanent -msgid "handoff.attributes.stroke.alignment.center" -msgstr "Sredina" +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Vani" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Točkasto" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Miksano" msgid "handoff.attributes.stroke.style.none" msgstr "Nikakav" @@ -771,6 +1029,10 @@ msgstr "Solidan" msgid "handoff.attributes.stroke.width" msgstr "Širina" +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografija" + #: src/app/main/ui/handoff/attributes/text.cljs #, fuzzy msgid "handoff.attributes.typography.font-family" @@ -810,6 +1072,23 @@ msgstr "Podcrtano" msgid "handoff.attributes.typography.text-transform" msgstr "Transformiraj tekst" +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "Mala slova" + +#, fuzzy +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Nikakav" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Velika i mala slova" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "Velika slova" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Kod" + msgid "handoff.tabs.code.selected.circle" msgstr "Krug" @@ -825,6 +1104,20 @@ msgstr "Ploča" msgid "handoff.tabs.code.selected.group" msgstr "Grupa" +msgid "handoff.tabs.code.selected.image" +msgstr "Slika" + +msgid "handoff.tabs.code.selected.mask" +msgstr "Maska" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s Označeno" + +#, fuzzy +msgid "handoff.tabs.code.selected.path" +msgstr "Path" + msgid "handoff.tabs.code.selected.rect" msgstr "Pravokutnik" @@ -834,9 +1127,21 @@ msgstr "SVG" msgid "handoff.tabs.code.selected.text" msgstr "Tekst" +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Informacija" + msgid "history.alert-message" msgstr "Vidiš verziju %s" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Prečaci" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.about-penpot" +msgstr "O Penpotu" + msgid "labels.accept" msgstr "Prihvati" @@ -864,10 +1169,17 @@ msgstr "" "Čini se da moraš malo pričekati i pokušati ponovno; vršimo mala održavanja " "naših servera." +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.main-message" +msgstr "Loš Gateway" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.cancel" msgstr "Odbaci" +msgid "labels.centered" +msgstr "Sredina" + msgid "labels.close" msgstr "Zatvori" @@ -875,15 +1187,30 @@ msgstr "Zatvori" msgid "labels.comments" msgstr "Komentari" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Zajenica" + +#: src/app/main/ui/settings/password.cljs +msgid "labels.confirm-password" +msgstr "Potvrdi lozinku" + msgid "labels.content" msgstr "Sadržaj" +msgid "labels.continue" +msgstr "Nastavi" + msgid "labels.continue-with" msgstr "Nastavi sa" msgid "labels.continue-with-penpot" msgstr "Možeš nastaviti s Penpot računom" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "labels.create" +msgstr "Kreiraj" + #: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs msgid "labels.create-team" msgstr "Kreiraj novi tim" @@ -937,6 +1264,10 @@ msgstr "Uredi datoteku" msgid "labels.editor" msgstr "Urednik" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "E-mail" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.expired-invitation" msgstr "Isteklo" @@ -980,6 +1311,13 @@ msgstr "Povratak" msgid "labels.help-center" msgstr "Centar za pomoć" +#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs +msgid "labels.hide-resolved-comments" +msgstr "Sakrij riješene komentare" + +msgid "labels.icons" +msgstr "Ikone" + msgid "labels.images" msgstr "Slike" @@ -1011,18 +1349,36 @@ msgstr "Biblioteke i predlošci" msgid "labels.link" msgstr "Poveznica" +msgid "labels.log-or-sign" +msgstr "Prijava ili registracija" + #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" msgstr "Odjava" +msgid "labels.manage-fonts" +msgstr "Uredi fontove" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.member" msgstr "Član" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.members" +msgstr "Članovi" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.name" msgstr "Ime" +#: src/app/main/ui/settings/password.cljs +msgid "labels.new-password" +msgstr "Nova lozinka" + +#, fuzzy +msgid "labels.next" +msgstr "Slijedeće" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs msgid "labels.no-comments-available" msgstr "Nemaš obavijesti o komentarima na čekanju" @@ -1082,6 +1438,18 @@ msgstr "ili" msgid "labels.owner" msgstr "Vlasnik" +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Lozinka" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "U tijeku" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.permissions" +msgstr "Dozvole" + #: src/app/main/ui/settings/sidebar.cljs msgid "labels.profile" msgstr "Profil" @@ -1106,17 +1474,32 @@ msgstr "Ukloni" msgid "labels.remove-member" msgstr "Ukloni člana" +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.rename" +msgstr "Preimenuj" + #: src/app/main/ui/dashboard/team_form.cljs msgid "labels.rename-team" msgstr "Preimenuj tim" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "Ponovno pošalji pozivnicu" + #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Pokušaj ponovo" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.role" +msgstr "Uloga" + msgid "labels.save" msgstr "Spremi" +msgid "labels.search-font" +msgstr "Pretraži font" + #: src/app/main/ui/settings/feedback.cljs msgid "labels.send" msgstr "Pošalji" @@ -1125,10 +1508,18 @@ msgstr "Pošalji" msgid "labels.sending" msgstr "Slanje…" +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.desc-message" +msgstr "U programiranom smo održavanju naših sustava." + #: src/app/main/ui/static.cljs msgid "labels.service-unavailable.main-message" msgstr "Usluga je nedostupna" +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.settings" +msgstr "Postavke" + msgid "labels.share-prototype" msgstr "Podijeli prototip" @@ -1165,20 +1556,20 @@ msgstr "Status" msgid "labels.tutorials" msgstr "Upute" -#: src/app/main/ui/dashboard/team_form.cljs -msgid "labels.update-team" -msgstr "Ažuriraj tim" - -msgid "labels.upload-custom-fonts" -msgstr "Prenesi custom fontove" - #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "Ažuriraj" +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Ažuriraj tim" + msgid "labels.upload" msgstr "Prenesi" +msgid "labels.upload-custom-fonts" +msgstr "Prenesi custom fontove" + msgid "labels.uploading" msgstr "Prijenos…" @@ -1193,19 +1584,17 @@ msgstr "Radni prostor" msgid "labels.write-new-comment" msgstr "Napiši novi komentar" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(ti)" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.your-account" msgstr "Tvoj korisnički račun" -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.confirm-email" -msgstr "Potvrdite novi e-mail" - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.info" -msgstr "" -"Poslat ćemo e-mail na tvoj trenutni e-mail \"%s\" kako bismo potvrdili tvoj " -"identitet." +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "Učitavanje slike…" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.accept" @@ -1226,6 +1615,24 @@ msgstr "Dodajte “%s” kao zajedničku biblioteku" msgid "modals.big-nudge" msgstr "Big nudge" +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.confirm-email" +msgstr "Potvrdite novi e-mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.info" +msgstr "" +"Poslat ćemo e-mail na tvoj trenutni e-mail \"%s\" kako bismo potvrdili tvoj " +"identitet." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Novi e-mail" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "Promijeni e-mail" + #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.title" msgstr "Promijeni svoj e-mail" @@ -1282,6 +1689,19 @@ msgstr "Brisanje datoteke" msgid "modals.delete-file-multi-confirm.accept" msgstr "Izbriši datoteke" +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "Jesi li siguran/na da želiš izbrisati %s datoteke?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "Brisanje %s datoteka" + +msgid "modals.delete-font-variant.message" +msgstr "" +"Jesi li siguran/na da želiš izbrisati ovaj stil fonta? Neće se učitati ako " +"se koristi u datoteci." + msgid "modals.delete-font-variant.title" msgstr "Brisanje fonta" @@ -1297,6 +1717,14 @@ msgstr "Brisanje fonta" msgid "modals.delete-page.body" msgstr "Jesi li siguran/na da želiš izbrisati ovu stranicu?" +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Brisanje stranice" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Brisanje projekta" + #: src/app/main/ui/dashboard/project_menu.cljs msgid "modals.delete-project-confirm.message" msgstr "Jesi li siguran/na da želiš izbrisati ovaj projekt?" @@ -1312,6 +1740,13 @@ msgstr[0] "Izbriši datoteku" msgstr[1] "Izbriši datoteke" msgstr[2] "Izbriši datoteke" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Jesi li siguran/na da želiš izbrisati ovu datoteku?" +msgstr[1] "Jesi li siguran/na da želiš izbrisati ove datoteke?" +msgstr[2] "Jesi li siguran/na da želiš izbrisati ove datoteke?" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message" msgid_plural "modals.delete-shared-confirm.scd-message" @@ -1363,6 +1798,10 @@ msgstr "Jesi li siguran/na da želiš izbrisati ovog člana iz tima?" msgid "modals.delete-team-member-confirm.title" msgstr "Brisanje člana tima" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Pošalji pozivnicu" + msgid "modals.invite-member.emails" msgstr "E-mail, odvojeno zarezom" @@ -1370,10 +1809,21 @@ msgstr "E-mail, odvojeno zarezom" msgid "modals.invite-team-member.title" msgstr "Pozovi članove u tim" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Budući da si jedini član ovog tima, tim će biti izbrisan zajedno sa svojim " +"projektima i datotekama." + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.message" msgstr "Jesi li siguran/na da želiš napustiti tim %s?" +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"Ne možeš napustiti tim ako nema drugog člana kojeg možeš promovirati u " +"vlasnika. Možda želiš izbrisati tim." + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint1" msgstr "" @@ -1404,10 +1854,21 @@ msgstr "Jesi li siguran/na da želiš napustiti ovaj tim?" msgid "modals.leave-confirm.title" msgstr "Napuštanje tima" +#: src/app/main/ui/workspace/nudge.cljs +#, fuzzy +msgid "modals.nudge-title" +msgstr "Nudge amount" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.accept" msgstr "Prenesi vlasništvo" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.hint" +msgstr "" +"Ako preneseš vlasništvo, promijenit ćeš svoju ulogu u Administrator, čime " +"ćeš izgubiti neka dopuštenja za ovaj tim. " + #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.message" msgstr "" @@ -1418,22 +1879,36 @@ msgstr "" msgid "modals.promote-owner-confirm.title" msgstr "Novi vlasnik tima" -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.small-nudge" -msgstr "Mali pomak" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.accept" msgstr "Ukloni kao zajedničku biblioteku" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#, fuzzy +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Nakon uklanjanja kao zajedničke biblioteke, biblioteka datoteka ove " +"datoteke više neće biti dostupna za korištenje među tvojim ostalim " +"datotekama." + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.message" msgstr "Ukloni “%s” kao zajedničku biblioteku" #: src/app/main/ui/workspace/nudge.cljs -#, fuzzy -msgid "modals.nudge-title" -msgstr "Nudge amount" +msgid "modals.small-nudge" +msgstr "Mali pomak" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Poništi objavu" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "Ako poništiš objavu, stavke u njoj postaju biblioteka ove datoteke." +msgstr[1] "Ako poništiš objavu, stavke u njoj postaju biblioteka ovih datoteka." +msgstr[2] "Ako poništiš objavu, stavke u njoj postaju biblioteka ovih datoteka." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" @@ -1470,6 +1945,10 @@ msgstr "Ažuriraj komponente u zajedničkoj biblioteci" msgid "modals.update-remote-component.accept" msgstr "Ažuriraj" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.cancel" +msgstr "Poništi" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" msgstr "" @@ -1480,10 +1959,15 @@ msgstr "" msgid "modals.update-remote-component.message" msgstr "Ažuriraj komponentu u zajedničkoj biblioteci" +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Pozivnica je uspješno poslana" + #: src/app/main/ui/settings/delete_account.cljs msgid "notifications.profile-deletion-not-allowed" msgstr "" -"Ne možeš izbrisati svoj profil. Ponovno dodijeli svoje timove prije nastavka." +"Ne možeš izbrisati svoj profil. Ponovno dodijeli svoje timove prije " +"nastavka." #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs msgid "notifications.profile-saved" @@ -1498,6 +1982,14 @@ msgstr "" "Postoji mnogo dostupnih resursa koji će ti pomoći da počneš koristiti " "Penpot, poput korisničkog vodiča i našeg Youtube kanala." +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Detaljne informacije o tome kako koristiti Penpot. Od izrade prototipova do " +"organiziranja ili dijeljenja dizajna." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Korisnički priručnik" + msgid "onboarding-v2.before-start.desc3" msgstr "Možeš gledati naše upute i upute koje je izradila naša zajednica." @@ -1512,9 +2004,45 @@ msgstr "" "Penpot je Open Source i izradio ga je Kaleidos kao i zajednica, gdje mnogi " "ljudi već pomažu jedni drugima. Svi mogu surađivati na:" +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Javni prostor za učenje, dijeljenje i raspravu o Penpotu, njegovoj " +"sadašnjosti i budućnosti s cijelom zajednicom i glavnim timom Penpota." + msgid "onboarding-v2.welcome.desc2.title" msgstr "Sudjelovanje u Community-u" +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Gdje ćete pronaći kako surađivati s prijevodima, zahtjevima za unapređenje, " +"temeljnim doprinosima, potragom za bugovima…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Vodič za doprinos" + +msgid "onboarding-v2.welcome.title" +msgstr "Dobrodošli u Penpot!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Stvori tim kasnije" + +msgid "onboarding.choice.team-up.create-team" +msgstr "Ime tvojeg tima" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "Nakon što imenuješ svoj tim, moći ćeš pozvati ljude da se pridruže." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Unesi naziv tima" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Pozovi članove" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Ne zaboravi uključiti sve. Programere, dizajnere, menadžere... raznolikost " +"se isplati :)" + msgid "onboarding.choice.team-up.invite-members-skip" msgstr "Stvori tim i pozovi kasnije" @@ -1547,6 +2075,35 @@ msgstr "projekt na githubu" msgid "onboarding.contrib.title" msgstr "Open Source suradnik?" +msgid "onboarding.newsletter.accept" +msgstr "Da, pretplati se" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "Tvoj zahtjev za pretplatu je poslan, poslat ćemo ti e-mail da ga potvrdiš." + +msgid "onboarding.newsletter.decline" +msgstr "Ne, hvala" + +msgid "onboarding.newsletter.desc" +msgstr "" +"Pretplati se na naš newsletter kako bi bio/la u toku s razvojem proizvoda i " +"novostima." + +msgid "onboarding.newsletter.policy" +msgstr "Politika privatnosti." + +msgid "onboarding.newsletter.privacy1" +msgstr "Budući da brinemo o privatnosti, evo našeg " + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Poslat ćemo ti samo relevantne e-mailove. Pretplatu možeš odjaviti u bilo " +"kojem trenutku u svom korisničkom profilu ili putem linka za odjavu u bilo " +"kojem od naših newslettera." + +msgid "onboarding.newsletter.title" +msgstr "Želiš primati Penpot novostii?" + #, fuzzy msgid "onboarding.slide.0.alt" msgstr "Stvori dizajn" @@ -1586,6 +2143,9 @@ msgstr "" "i centraliziranim komentarima, idejama i povratnim informacijama izravno " "preko dizajna." +msgid "onboarding.slide.2.title" +msgstr "Sakupi povratne informacije, predstavi i podijeli svoj rad" + msgid "onboarding.slide.3.alt" msgstr "Handoff i lowcode" @@ -1597,8 +2157,8 @@ msgstr "" msgid "onboarding.slide.3.desc2" msgstr "" -"Dobij i pruži specifikacije koda kao što su oznake (SVG, HTML) ili stilovi (" -"CSS, Less, Stylus…)." +"Dobij i pruži specifikacije koda kao što su oznake (SVG, HTML) ili stilovi " +"(CSS, Less, Stylus…)." msgid "onboarding.slide.3.title" msgstr "Jedan zajednički izvor istine" @@ -1606,6 +2166,11 @@ msgstr "Jedan zajednički izvor istine" msgid "onboarding.team-modal.create-team" msgstr "Kreiraj tim" +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Tim ti omogućuje suradnju s drugim Penpot korisnicima koji rade na istim " +"datotekama i projektima." + msgid "onboarding.team-modal.create-team-feature-1" msgstr "Neograničen broj datoteka i projekata" @@ -1615,6 +2180,12 @@ msgstr "Izdanje za više igrača" msgid "onboarding.team-modal.create-team-feature-3" msgstr "Upravljanje ulogama" +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Neograničen broj članova" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% besplatno!" + msgid "onboarding.templates.subtitle" msgstr "Evo nekoliko predložaka." @@ -1627,6 +2198,53 @@ msgstr "Penpot" msgid "onboarding.welcome.title" msgstr "Dobrodošli u Penpot" +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Idi na prijavu" + +#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "settings.multiple" +msgstr "Izmješano" + +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Osnove" + +msgid "shortcut-section.dashboard" +msgstr "Nadzorna ploča" + +msgid "shortcut-section.viewer" +msgstr "Gledatelj" + +msgid "shortcut-section.workspace" +msgstr "Radni prostor" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Poravnanje" + +msgid "shortcut-subsection.edit" +msgstr "Uredi" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Generičko" + +msgid "shortcut-subsection.general-viewer" +msgstr "Generičko" + +msgid "shortcut-subsection.main-menu" +msgstr "Glavni meni" + +#, fuzzy +msgid "shortcut-subsection.modify-layers" +msgstr "Izmijeni layere" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Navigacija" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Navigacija" + msgid "shortcut-subsection.navigation-workspace" msgstr "Navigacija" @@ -1670,6 +2288,9 @@ msgstr "Poravnaj desno" msgid "shortcuts.align-top" msgstr "Poravnaj vrh" +msgid "shortcuts.align-vcenter" +msgstr "Poravnaj sredinu okomito" + msgid "shortcuts.artboard-selection" msgstr "Kreiraj ploču iz odabira" @@ -1685,14 +2306,14 @@ msgstr "Boolean presijek" msgid "shortcuts.bool-union" msgstr "Boolean unija" -msgid "shortcuts.bring-forward" -msgstr "Premijesti naprijed" +msgid "shortcuts.bring-back" +msgstr "Premijesti u pozadinu" msgid "shortcuts.bring-backward" msgstr "Premiijesti nazad" -msgid "shortcuts.bring-back" -msgstr "Premijesti u pozadinu" +msgid "shortcuts.bring-forward" +msgstr "Premijesti naprijed" #, fuzzy msgid "shortcuts.bring-front" @@ -1714,9 +2335,6 @@ msgstr "Kreiraj novo" msgid "shortcuts.cut" msgstr "Izreži" -msgid "shortcuts.align-vcenter" -msgstr "Poravnaj sredinu okomito" - msgid "shortcuts.decrease-zoom" msgstr "Zoom out" @@ -1738,11 +2356,14 @@ msgstr "Elipsa" msgid "shortcuts.draw-frame" msgstr "Panel" +msgid "shortcuts.draw-nodes" +msgstr "Ucrtaj put" + msgid "shortcuts.draw-path" msgstr "Put" -msgid "shortcuts.draw-nodes" -msgstr "Ucrtaj put" +msgid "shortcuts.draw-rect" +msgstr "Pravokutnik" msgid "shortcuts.draw-text" msgstr "Tekst" @@ -1753,6 +2374,33 @@ msgstr "Dupliciraj" msgid "shortcuts.escape" msgstr "Poništi" +msgid "shortcuts.export-shapes" +msgstr "Izvezi oblike" + +msgid "shortcuts.fit-all" +msgstr "Zumiraj da stane sve" + +msgid "shortcuts.flip-horizontal" +msgstr "Okreni vodoravno" + +msgid "shortcuts.flip-vertical" +msgstr "Okreni okomito" + +msgid "shortcuts.go-to-drafts" +msgstr "Idi na nacrte" + +msgid "shortcuts.go-to-libs" +msgstr "Idi na zajedničke biblioteke" + +msgid "shortcuts.go-to-search" +msgstr "Traži" + +msgid "shortcuts.group" +msgstr "Grupiraj" + +msgid "shortcuts.h-distribute" +msgstr "Distribuiraj vodoravno" + msgid "shortcuts.hide-ui" msgstr "Prikaži/sakrij UI" @@ -1807,11 +2455,47 @@ msgstr "Premijesti desno" msgid "shortcuts.move-unit-up" msgstr "Premijesti gore" -msgid "shortcuts.h-distribute" -msgstr "Distribuiraj vodoravno" +msgid "shortcuts.next-frame" +msgstr "Slijedeća ploča" -msgid "shortcuts.fit-all" -msgstr "Zumiraj da stane sve" +msgid "shortcuts.not-found" +msgstr "Nema pronađenih prečaca" + +msgid "shortcuts.opacity-0" +msgstr "Postavi neprozirnost na 100%" + +msgid "shortcuts.opacity-1" +msgstr "Postavi neprozirnost na 10%" + +msgid "shortcuts.opacity-2" +msgstr "Postavi neprozirnost na 20%" + +msgid "shortcuts.opacity-3" +msgstr "Postavi neprozirnost na 30%" + +msgid "shortcuts.opacity-4" +msgstr "Postavi neprozirnost na 40%" + +msgid "shortcuts.opacity-5" +msgstr "Postavi neprozirnost na 50%" + +msgid "shortcuts.opacity-6" +msgstr "Postavi neprozirnost na 60%" + +msgid "shortcuts.opacity-7" +msgstr "Postavi neprozirnost na 70%" + +msgid "shortcuts.opacity-8" +msgstr "Postavi neprozirnost na 80%" + +msgid "shortcuts.opacity-9" +msgstr "Postavi neprozirnost na 90%" + +msgid "shortcuts.open-color-picker" +msgstr "Birač boja" + +msgid "shortcuts.open-comments" +msgstr "Idi na odjeljak s komentarima" msgid "shortcuts.open-dashboard" msgstr "Idi na nadzornu ploču" @@ -1819,6 +2503,15 @@ msgstr "Idi na nadzornu ploču" msgid "shortcuts.open-handoff" msgstr "Idi na odjeljak primopredaje" +msgid "shortcuts.open-interactions" +msgstr "Idi na odjeljak interakcija" + +msgid "shortcuts.open-viewer" +msgstr "Idi na odjeljak interakcija gledatelja" + +msgid "shortcuts.open-workspace" +msgstr "Idi na radni prostor" + msgid "shortcuts.or" msgstr " ili " @@ -1843,26 +2536,26 @@ msgstr "Označi sve" msgid "shortcuts.separate-nodes" msgstr "Posebni čvorovi" +#, fuzzy +msgid "shortcuts.show-pixel-grid" +msgstr "Prikaži/sakrij \"pixel grid\"" + msgid "shortcuts.show-shortcuts" msgstr "Prikaži/sakrij prečace" msgid "shortcuts.snap-nodes" msgstr "Priključi na čvorove" +#, fuzzy +msgid "shortcuts.snap-pixel-grid" +msgstr "Priključi na \"pixel grid\"" + msgid "shortcuts.start-editing" msgstr "Počni uređivanje" msgid "shortcuts.start-measure" msgstr "Počni mjerenje" -#, fuzzy -msgid "shortcuts.show-pixel-grid" -msgstr "Prikaži/sakrij \"pixel grid\"" - -#, fuzzy -msgid "shortcuts.snap-pixel-grid" -msgstr "Priključi na \"pixel grid\"" - msgid "shortcuts.stop-measure" msgstr "Zaustavi mjerenje" @@ -1879,37 +2572,55 @@ msgstr "Promijena dinamičkog poravnanja" msgid "shortcuts.toggle-assets" msgstr "Promijena stavaka" +msgid "shortcuts.toggle-colorpalette" +msgstr "Promijena palete boja" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Promijena fokus moda" + +#, fuzzy +msgid "shortcuts.toggle-grid" +msgstr "Promijena \"grida\"" + +msgid "shortcuts.toggle-history" +msgstr "Promijena povijesti" + +msgid "shortcuts.toggle-layers" +msgstr "Promijena layera" + msgid "shortcuts.toggle-lock" msgstr "Zaključaj odabrano" msgid "shortcuts.toggle-lock-size" msgstr "Zaključaj proporcije" -msgid "shortcuts.toggle-layers" -msgstr "Promijena layera" - -msgid "shortcuts.toggle-history" -msgstr "Promijena povijesti" - -msgid "shortcuts.toggle-focus-mode" -msgstr "Promijena fokus moda" - -msgid "shortcuts.toggle-colorpalette" -msgstr "Promijena palete boja" - #, fuzzy -msgid "shortcuts.toggle-grid" -msgstr "Promijena \"grida\"" +msgid "shortcuts.toggle-rules" +msgstr "Prikaži/sakrij \"rules\"" msgid "shortcuts.toggle-scale-text" msgstr "Promjena mjerila teksta" +#, fuzzy +msgid "shortcuts.toggle-snap-grid" +msgstr "Poravnanje s \"gridom\"" + +#, fuzzy +msgid "shortcuts.toggle-snap-guide" +msgstr "Pričvrsti na guides" + msgid "shortcuts.toggle-textpalette" msgstr "Promijeni paletu teksta" msgid "shortcuts.toggle-visibility" msgstr "Promijeni vidljivost" +msgid "shortcuts.toggle-zoom-style" +msgstr "Promijeni stil zooma" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Promijeni cijeli zaslon" + #, fuzzy msgid "shortcuts.undo" msgstr "Undo" @@ -1920,9 +2631,20 @@ msgstr "Razgrupiraj" msgid "shortcuts.unmask" msgstr "Makni masku" +msgid "shortcuts.v-distribute" +msgstr "Distribuiraj okomito" + msgid "shortcuts.zoom-selected" msgstr "Zoomiraj na selektirano" +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - Penpot" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.font-providers" +msgstr "Dobavljači fontova - %s - Penpot" + #: src/app/main/ui/dashboard/fonts.cljs msgid "title.dashboard.fonts" msgstr "Fontovi - %s - Penpot" @@ -1935,29 +2657,54 @@ msgstr "Projekti - %s - Penpot" msgid "title.dashboard.search" msgstr "Traži - %s - Penpot" -msgid "shortcuts.v-distribute" -msgstr "Distribuiraj okomito" - -#, fuzzy -msgid "shortcuts.toggle-snap-grid" -msgstr "Poravnanje s \"gridom\"" - #: src/app/main/ui/dashboard/libraries.cljs msgid "title.dashboard.shared-libraries" msgstr "Zajedničke biblioteke - %s - Penpot" +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot - Sloboda dizajna za timove" + #: src/app/main/ui/settings/feedback.cljs msgid "title.settings.feedback" msgstr "Pošalji povratnu informaciju - Penpot" +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Postavke - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Lozinka - Penpot" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Profil - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-invitations" +msgstr "Pozivnice - %s - Penpot" + #: src/app/main/ui/dashboard/team.cljs msgid "title.team-members" msgstr "Članovi - %s - Penpot" +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Postavke - %s - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Način prikaza - Penpot" + +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + msgid "viewer.breaking-change.description" msgstr "" -"Ova poveznica za dijeljenje više nije važeća. Napravi novu ili traži novu od " -"vlasnika." +"Ova poveznica za dijeljenje više nije važeća. Napravi novu ili traži novu " +"od vlasnika." msgid "viewer.breaking-change.message" msgstr "Oprosti!" @@ -1995,6 +2742,10 @@ msgstr "Interakcije (%s)" msgid "viewer.header.share.copy-link" msgstr "Kopiraj poveznicu" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Kreiraj poveznicu" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.placeholder" msgstr "Ovdje će se pojaviti poveznica za dijeljenje" @@ -2023,6 +2774,10 @@ msgstr "Sitemap" msgid "workspace.align.hcenter" msgstr "Poravnaj vodoravno u sredinu (%s)" +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "Distribuiraj vodoravni razmak (%s)" + #: src/app/main/ui/workspace/sidebar/align.cljs msgid "workspace.align.hleft" msgstr "Poravnaj lijevo (%s)" @@ -2035,22 +2790,26 @@ msgstr "Poravnaj desno (%s)" msgid "workspace.align.vbottom" msgstr "Poravnaj dolje (%s)" -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.vdistribute" -msgstr "Distribuiraj okomiti razmak (%s)" - #: src/app/main/ui/workspace/sidebar/align.cljs msgid "workspace.align.vcenter" msgstr "Poravnaj okomito u sredinu (%s)" #: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.hdistribute" -msgstr "Distribuiraj vodoravni razmak (%s)" +msgid "workspace.align.vdistribute" +msgstr "Distribuiraj okomiti razmak (%s)" #: src/app/main/ui/workspace/sidebar/align.cljs msgid "workspace.align.vtop" msgstr "Poravnaj gore (%s)" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Stavke" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Sve stavke" + msgid "workspace.assets.box-filter-graphics" msgstr "Grafika" @@ -2062,22 +2821,13 @@ msgstr "Boje" msgid "workspace.assets.components" msgstr "Komponente" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.assets" -msgstr "Stavke" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.box-filter-all" -msgstr "Sve stavke" - #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.create-group" msgstr "Kreiraj grupu" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.create-group-hint" -msgstr "" -"Tvoje stavke će se automatski imenovati kao \"naziv grupe / naziv stavke\"" +msgstr "Tvoje stavke će se automatski imenovati kao \"naziv grupe / naziv stavke\"" #: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.delete" @@ -2103,6 +2853,17 @@ msgstr "Grupa" msgid "workspace.assets.group-name" msgstr "Ime grupe" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Biblioteke" + +msgid "workspace.assets.local-library" +msgstr "lokalna biblioteka" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "Nisu pronađene stavke" + #: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.rename" msgstr "Preimenuj" @@ -2115,16 +2876,66 @@ msgstr "Preimenuj grupu" msgid "workspace.assets.search" msgstr "Pretraži stavke" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.selected-count" +msgid_plural "workspace.assets.selected-count" +msgstr[0] "%s odabrana stavka" +msgstr[1] "%s odabranih stavki" +msgstr[2] "%s odabranih stavki" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "PODIJELJENO" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografija" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Font" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Veličina" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Varijanta" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Idi na datoteku biblioteke stilova za uređivanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Razmak između slova" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.assets.typography.line-height" msgstr "Visina linije" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs +msgid "workspace.assets.typography.sample" +msgstr "Ag" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +msgstr "Transformiraj tekst" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.ungroup" +msgstr "Razgrupiraj" + msgid "workspace.focus.focus-mode" msgstr "Fokus mode" msgid "workspace.focus.focus-off" msgstr "Fokus isključen" +msgid "workspace.focus.focus-on" +msgstr "Fokus uključen" + msgid "workspace.focus.selection" msgstr "Odabir" @@ -2144,6 +2955,32 @@ msgstr "Onemogući dinamičko poravnanje" msgid "workspace.header.menu.disable-scale-text" msgstr "Onemogući skaliranje teksta" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Onemogući \"snap to grid\"" + +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.disable-snap-guides" +msgstr "Onemogući \"snap to guides\"" + +#, fuzzy +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Onemogući \"snap to pixel\"" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Omogući dinamičko poravnanje" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-scale-text" +msgstr "Omogući skaliranje teksta" + +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Poravnanje s \"gridom\"" + #: src/app/main/ui/workspace/header.cljs #, fuzzy msgid "workspace.header.menu.enable-snap-guides" @@ -2157,6 +2994,11 @@ msgstr "Omogući \"snap to pixel\"" msgid "workspace.header.menu.hide-artboard-names" msgstr "Sakrij nazive ploča" +#: src/app/main/ui/workspace/header.cljs +#, fuzzy +msgid "workspace.header.menu.hide-grid" +msgstr "Sakrij \"grid\"" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-palette" msgstr "Sakrij paletu boja" @@ -2181,11 +3023,6 @@ msgstr "Uredi" msgid "workspace.header.menu.option.file" msgstr "Datoteka" -#: src/app/main/ui/workspace/header.cljs -#, fuzzy -msgid "workspace.header.menu.hide-grid" -msgstr "Sakrij \"grid\"" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.option.help-info" msgstr "Pomoć i informacije" @@ -2210,13 +3047,22 @@ msgstr "Prikaži nazive ploča" msgid "workspace.header.menu.show-grid" msgstr "Prikaži \"grid\"" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Prikaži paletu boja" + #, fuzzy msgid "workspace.header.menu.show-pixel-grid" msgstr "Prikaži \"pixel grid\"" #: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-palette" -msgstr "Prikaži paletu boja" +#, fuzzy +msgid "workspace.header.menu.show-rules" +msgstr "Prikaži \"rules\"" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-textpalette" +msgstr "Prikaži paletu fontova" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" @@ -2226,6 +3072,10 @@ msgstr "Resetiraj" msgid "workspace.header.save-error" msgstr "Greška kod spremanja" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Spremljeno" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.saving" msgstr "Spremanje" @@ -2278,6 +3128,10 @@ msgstr "Biblioteka datoteka" msgid "workspace.libraries.colors.hsv" msgstr "HSV" +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Nedavno korištene boje" + #: src/app/main/ui/workspace/colorpicker.cljs msgid "workspace.libraries.colors.rgb-complementary" msgstr "RGB komplementarno" @@ -2318,22 +3172,22 @@ msgstr "BIBLIOTEKE" msgid "workspace.libraries.library" msgstr "BIBLIOTEKA" -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-matches-for" -msgstr "Nisu pronađeni rezultati za “%s”" - #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-libraries-need-sync" msgstr "Ne postoje zajedničke biblioteke koje je potrebno ažurirati" #: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.search-shared-libraries" -msgstr "Pretraži zajedničke biblioteke" +msgid "workspace.libraries.no-matches-for" +msgstr "Nisu pronađeni rezultati za “%s”" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-shared-libraries-available" msgstr "Nema dostupnih zajedničkih biblioteka" +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Pretraži zajedničke biblioteke" + #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.shared-libraries" msgstr "ZAJEDNIČKE BIBLIOTEKE" @@ -2377,13 +3231,17 @@ msgstr "Klikni gumb + za dodavanje interakcija." msgid "workspace.options.blur-options.background-blur" msgstr "Pozadina" +#, fuzzy +msgid "workspace.options.blur-options.layer-blur" +msgstr "Layer" + #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs msgid "workspace.options.blur-options.title" msgstr "Zamuti" -#, fuzzy -msgid "workspace.options.blur-options.layer-blur" -msgstr "Layer" +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.group" +msgstr "Grupiraj zamućenje" #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs msgid "workspace.options.blur-options.title.multiple" @@ -2396,29 +3254,6 @@ msgstr "Pozadina canvasa" msgid "workspace.options.clip-content" msgstr "Isjeci sadržaj" -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.column" -msgstr "Kolumne" - -msgid "workspace.options.grid.params.color" -msgstr "Boja" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.columns" -msgstr "Kolumne" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.height" -msgstr "Visina" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.margin" -msgstr "Margina" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.rows" -msgstr "Redovi" - #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs msgid "workspace.options.component" msgstr "Komponenta" @@ -2427,6 +3262,10 @@ msgstr "Komponenta" msgid "workspace.options.constraints" msgstr "Ograničenja" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.bottom" +msgstr "Dno" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.center" msgstr "Sredina" @@ -2439,6 +3278,18 @@ msgstr "Popravi prilikom scrolanja" msgid "workspace.options.constraints.left" msgstr "Lijevo" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.leftright" +msgstr "Lijevo i desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.right" +msgstr "Desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.scale" +msgstr "Skala" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.top" msgstr "Vrh" @@ -2505,11 +3356,43 @@ msgstr "Početak toka" msgid "workspace.options.flows.flow-starts" msgstr "Flow započinje" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Auto" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Kolumne" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +#, fuzzy +msgid "workspace.options.grid.grid-title" +msgstr "Grid" + +msgid "workspace.options.grid.params.color" +msgstr "Boja" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Kolumne" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs #, fuzzy msgid "workspace.options.grid.params.gutter" msgstr "Gutter" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.height" +msgstr "Visina" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Margina" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.rows" +msgstr "Redovi" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.set-default" msgstr "Postavi kao zadano" @@ -2518,10 +3401,22 @@ msgstr "Postavi kao zadano" msgid "workspace.options.grid.params.size" msgstr "Veličina" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type" +msgstr "Tip" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.type.bottom" msgstr "Dno" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Lijevo" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.type.right" msgstr "Desno" @@ -2579,18 +3474,34 @@ msgstr "Animacija" msgid "workspace.options.interaction-animation-dissolve" msgstr "Razriješi" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-none" +msgstr "Nijedan" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-push" +msgstr "Gurni" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs #, fuzzy msgid "workspace.options.interaction-animation-slide" msgstr "Slide" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-background" +msgstr "Dodajte pozadinsko preklapanje" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-close-outside" msgstr "Zatvori kada klikneš izvana" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-background" -msgstr "Dodajte pozadinsko preklapanje" +msgid "workspace.options.interaction-close-overlay" +msgstr "Zatvori preklapanje" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay-dest" +msgstr "Zatvori preklapanje: %s" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-delay" @@ -2608,6 +3519,10 @@ msgstr "Trajanje" msgid "workspace.options.interaction-easing" msgstr "Easing" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease" +msgstr "Ease" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs #, fuzzy msgid "workspace.options.interaction-easing-ease-in" @@ -2631,10 +3546,23 @@ msgstr "Linearno" msgid "workspace.options.interaction-in" msgstr "U" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-mouse-enter" +msgstr "Ulaz mišem" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-mouse-leave" msgstr "Izlaz mišem" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-ms" +msgstr "ms" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to" +msgstr "Idi do" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-navigate-to-dest" msgstr "Idi do: %s" @@ -2649,17 +3577,66 @@ msgid "workspace.options.interaction-offset-effect" msgstr "Offset učinak" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-open-url" -msgstr "Otvoreni url" +msgid "workspace.options.interaction-on-click" +msgstr "Na klik" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-overlay" +msgstr "Otvoreno preklapanje" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-open-overlay-dest" msgstr "Otvoreno preklapanje: %s" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-url" +msgstr "Otvoreni url" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +#, fuzzy +msgid "workspace.options.interaction-out" +msgstr "Vani" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-center" +msgstr "Dolje sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-left" +msgstr "Dolje lijevo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-right" +msgstr "Dolje desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-center" +msgstr "Sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-manual" +msgstr "Priručnik" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-center" +msgstr "Gore sredina" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-pos-top-left" msgstr "Gore lijevo" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-right" +msgstr "Gore desno" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-position" +msgstr "Pozicija" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-preserve-scroll" +msgstr "Sačuvaj položaj scrolanja" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-prev-screen" msgstr "Prethodni ekran" @@ -2677,6 +3654,14 @@ msgstr "Uključi/isključi preklapanje" msgid "workspace.options.interaction-toggle-overlay-dest" msgstr "Uključi/isključi preklapanje: %s" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-trigger" +msgstr "Okidač" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-url" +msgstr "URL" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-while-hovering" msgstr "Na hover" @@ -2733,14 +3718,14 @@ msgstr "Svjetlost" msgid "workspace.options.layer-options.blend-mode.multiply" msgstr "Umnoži" -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.overlay" -msgstr "Preklapanje" - #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.blend-mode.normal" msgstr "Normalno" +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.overlay" +msgstr "Preklapanje" + #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.blend-mode.saturation" msgstr "Saturacija" @@ -2773,25 +3758,25 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Napredne opcije" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" -msgstr "Min.visina" +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Max.visina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.layout-max-w" msgstr "Max.širina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" -msgstr "Max.visina" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title" -msgstr "Promjena veličine elementa" +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Min.visina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.layout-min-w" msgstr "Min.širina" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Promjena veličine elementa" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.layout-max-h" msgstr "Maksimalna visina" @@ -2856,6 +3841,25 @@ msgstr "Margina" msgid "workspace.options.layout.margin-all" msgstr "Sve strane" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Jednostavna margina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.no-wrap" +msgstr "bez omota" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.packed" +msgstr "upakiran" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.padding" +msgstr "Padding" + #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.padding-all" msgstr "Sve strane" @@ -2880,6 +3884,53 @@ msgstr "prostor između" msgid "workspace.options.layout.title" msgstr "Layout" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Vrh" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "dno" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "vrh" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +#, fuzzy +msgid "workspace.options.layout.wrap" +msgstr "omotaj" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "Više boja" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-lib-colors" +msgstr "Više boja iz biblioteke" + +msgid "workspace.options.opacity" +msgstr "Neprozirnost" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.position" +msgstr "Pozicija" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Prototip" + +msgid "workspace.options.radius" +msgstr "Radius" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Svi kutevi" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs #, fuzzy msgid "workspace.options.radius.single-corners" @@ -2931,6 +3982,10 @@ msgstr "Boja sjene" msgid "workspace.options.shadow-options.drop-shadow" msgstr "Spusti sjenu" +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Unutarnja sjena" + #: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs msgid "workspace.options.shadow-options.offsetx" msgstr "X" @@ -2943,6 +3998,29 @@ msgstr "Y" msgid "workspace.options.shadow-options.spread" msgstr "Proširi" +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Sjena" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.group" +msgstr "Grupiraj sjenu" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Odabir sjena" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.show-fill-on-export" +msgstr "Prikaži u izvozu" + +msgid "workspace.options.show-in-viewer" +msgstr "Prikaži u načinu pregleda" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.size" +msgstr "Veličina" + #: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs #, fuzzy msgid "workspace.options.size-presets" @@ -2988,10 +4066,41 @@ msgstr "Trokutna strelica" msgid "workspace.options.stroke-color" msgstr "Boja poteza" +msgid "workspace.options.stroke-width" +msgstr "Širina poteza" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Sredina" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Crtkano" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Točkasto" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Unutra" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Miješano" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.outer" +msgstr "Vani" + #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke.solid" msgstr "Čvrsto" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Poravnaj dno" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.align-center" msgstr "Poravnaj sredinu" @@ -3005,6 +4114,10 @@ msgstr "Složi u blok" msgid "workspace.options.text-options.align-left" msgstr "Poravnaj lijevo" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-middle" +msgstr "Poravnaj sredinu" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.align-right" msgstr "Poravnaj desno" @@ -3066,6 +4179,9 @@ msgstr "Unaprijed postavljeno" msgid "workspace.options.text-options.strikethrough" msgstr "Precrtanko" +msgid "workspace.options.text-options.text-case" +msgstr "Slučaj" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.title" msgstr "Tekst" @@ -3103,1326 +4219,9 @@ msgstr "Širina" msgid "workspace.options.x" msgstr "X" -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.flip-horizontal" -msgstr "Okreni vodoravno" - -msgid "workspace.sidebar.layers.frames" -msgstr "Ploče" - -msgid "workspace.sidebar.layers.groups" -msgstr "Grupe" - -#, fuzzy -msgid "workspace.sidebar.layers.search" -msgstr "Traži layere" - -msgid "workspace.sidebar.layers.shapes" -msgstr "Oblici" - -msgid "workspace.sidebar.layers.texts" -msgstr "Tekstovi" - -msgid "workspace.undo.entry.multiple.color" -msgstr "stavke boja" - -msgid "workspace.undo.entry.multiple.media" -msgstr "grafičke stavke" - -msgid "workspace.undo.entry.multiple.typography" -msgstr "tipografske stavke" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title.multiple" -msgstr "Odabir sjena" - -#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs -msgid "workspace.options.show-fill-on-export" -msgstr "Prikaži u izvozu" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dashed" -msgstr "Crtkano" - -msgid "workspace.options.show-in-viewer" -msgstr "Prikaži u načinu pregleda" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.center" -msgstr "Sredina" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dotted" -msgstr "Točkasto" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.inner" -msgstr "Unutra" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.mixed" -msgstr "Miješano" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-bottom" -msgstr "Poravnaj dno" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-middle" -msgstr "Poravnaj sredinu" - msgid "workspace.options.y" msgstr "Y" -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.register-submit" -msgstr "Stvori račun" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.register" -msgstr "Još nemaš račun?" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.login-here" -msgstr "Prijavi se ovdje" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.confirm-password" -msgstr "Potvrdi lozinku" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.already-have-account" -msgstr "Već imaš račun?" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.create-demo-account" -msgstr "Kreiraj demo račun" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.check-your-email" -msgstr "" -"Provjeri svoj e-mail i klikni na vezu da potvrdiš i počneš koristiti Penpot." - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.notifications.recovery-token-sent" -msgstr "Veza za oporavak lozinke poslana je u tvoj inbox." - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.create-demo-profile" -msgstr "Samo želiš isprobati?" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-title" -msgstr "Drago nam je vidjeti te opet!" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-github-submit" -msgstr "GitHub" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-google-submit" -msgstr "Google" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.new-password" -msgstr "Unesi novu lozinku" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.demo-warning" -msgstr "" -"Ovo je DEMO usluga. NEMOJ KORISTITI za pravi rad. Projekti će se povremeno " -"brisati." - -#: src/app/main/ui/auth/login.cljs -msgid "auth.forgot-password" -msgstr "Zaboravljena lozinka?" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.fullname" -msgstr "Puno ime" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.email" -msgstr "E-mail" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-submit" -msgstr "Prijava" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-gitlab-submit" -msgstr "GitLab" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-ldap-submit" -msgstr "LDAP" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-oidc-submit" -msgstr "OpenID" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.newsletter-subscription" -msgstr "Želim se pretplatiti se na Penpot mailing listu." - -#: src/app/main/ui/auth/verify_token.cljs -msgid "auth.notifications.team-invitation-accepted" -msgstr "Uspješno pridružen/a timu" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.recovery-request-submit" -msgstr "Obnovi lozinku" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.password-length-hint" -msgstr "Najmanje 8 znamenki" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.recovery-submit" -msgstr "Promjeni lozinku" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.register-subtitle" -msgstr "Besplatno je, Open Source" - -msgid "common.share-link.link-deleted-success" -msgstr "Poveznica uspješno izbrisana" - -msgid "common.share-link.manage-ops" -msgstr "Upravljanje dopuštenjima" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Započni obilazak" - -msgid "common.share-link.permissions-can-comment" -msgstr "Dopušten komentar" - -msgid "common.share-link.page-shared" -msgid_plural "common.share-link.page-shared" -msgstr[0] "1 stranica podijeljena" -msgstr[1] "%s stranica podijeljeno" -msgstr[2] "%s stranica podijeljeno" - -msgid "common.share-link.permissions-can-inspect" -msgstr "Dopušteno provjeriti kod" - -msgid "common.share-link.permissions-hint" -msgstr "Svatko sa poveznicom imat će pristup" - -msgid "common.share-link.placeholder" -msgstr "Ovdje će se pojaviti poveznica za dijeljenje" - -msgid "common.share-link.title" -msgstr "Podijeli prototip" - -msgid "common.share-link.team-members" -msgstr "Samo članovi tima" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Upravljanje timom" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot je namijenjen timovima. Pozovi članove da zajedno rade na projektima " -"i datotekama" - -#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs -msgid "dashboard.copy-suffix" -msgstr "(kopiraj)" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.create-new-team" -msgstr "+ Kreiraj novi tim" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.delete-team" -msgstr "Obriši tim" - -msgid "dashboard.export-binary-multi" -msgstr "Preuzmi %s Penpot datoteke (.penpot)" - -msgid "dashboard.export-frames" -msgstr "Izvezi artboard u PDF…" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-frames.title" -msgstr "Izvezi u PDF" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.how-to" -msgstr "" -"Postavke izvoza možeš dodati elementima iz svojstava dizajna (na dnu desne " -"bočne trake)." - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.how-to-link" -msgstr "Informacije kako postaviti izvoz na Penpot." - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.no-elements" -msgstr "Nema elemenata s postavkama izvoza." - -msgid "dashboard.export.options.merge.message" -msgstr "" -"Tvoja će datoteka biti izvezena sa svim vanjskim stavkama spojenim u " -"biblioteku datoteka." - -msgid "dashboard.export.options.all.message" -msgstr "" -"datoteke sa zajedničkim bibliotekama bit će uključene u izvoz, održavajući " -"njihovu poveznicu." - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.dismiss-all" -msgstr "Odbaci sve" - -msgid "dashboard.export.title" -msgstr "Izvezi datoteke" - -msgid "dashboard.fonts.deleted-placeholder" -msgstr "Font izbrisan" - -#, fuzzy, markdown -msgid "dashboard.fonts.hero-text1" -msgstr "" -"Svaki web-font koji ovdje preneseš biti će dodan na popis fontova koji je " -"dostupan u tekstualnim svojstvima datoteka ovog tima. Fontovi s istim " -"nazivom fontova biti će grupirani kao **jedan font**. Možeš učitati fontove " -"sa sljedećim formatima: **TTF, OTF i WOFF** (biti će potreban samo jedan)." - -#, markdown -msgid "dashboard.fonts.hero-text2" -msgstr "" -"Možeš učitavati samo fontove koje posjeduješ ili imaš licencu za korištenje " -"u Penpotu. Saznaj više u odjeljku Prava na sadržaj [Penpotovih uvjeta " -"pružanja usluge](https://penpot.app/terms.html). Također možeš pročitati o [" -"licenciranju fontova](https://www.typography.com/faq)." - -msgid "dashboard.import.progress.process-page" -msgstr "Obrada stranice: %s" - -msgid "dashboard.import.import-error" -msgstr "Došlo je do problema pri uvozu datoteke. Datoteka nije uvezena." - -msgid "dashboard.import.import-message" -msgstr "%s datoteka je uspješno uvezeno." - -msgid "dashboard.import.progress.process-colors" -msgstr "Obrada boja" - -msgid "dashboard.import.import-warning" -msgstr "Neke su datoteke sadržavale nevažeće objekte koji su uklonjeni." - -#: src/app/main/ui/export.cljs -#, fuzzy -msgid "dashboard.export-multiple.selected" -msgstr "%s - %s elementa označeno" - -msgid "dashboard.import.progress.process-components" -msgstr "Obrada komponenti" - -msgid "dashboard.import.progress.process-media" -msgstr "Obrada medija" - -msgid "dashboard.export-standard-multi" -msgstr "Preuzmi %s standardne datoteke (.svg + .json)" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "Nauči osnove na Penpotu dok se zabavljaš uz ovaj praktični vodič." - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-project-prefix" -msgstr "Novi projekt" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-msg" -msgstr "Šaljite mi vijesti, ažuriranja proizvoda i preporuke o Penpotu." - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-title" -msgstr "Pretplata na newsletter" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "Prošeći Penpotom i upoznaj glavne karakteristike." - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to" -msgstr "Premijesti u" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dashboard.new-project" -msgstr "+ Novi projekt" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.no-matches-for" -msgstr "Nisu pronađeni rezultati za “%s”" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.no-projects-placeholder" -msgstr "Prikvačeni projekti pojavit će se ovdje" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.search-placeholder" -msgstr "Pretraži…" - -#: src/app/main/ui/confirm.cljs -msgid "ds.confirm-ok" -msgstr "U redu" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.searching-for" -msgstr "Pretraga \"%s\"…" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-delete-project" -msgstr "Tvoj projekt je uspješno izbrisan" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Pažnja" - -#: src/app/main/ui/dashboard/grid.cljs -msgid "ds.updated-at" -msgstr "Ažurirano: %s" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.your-email" -msgstr "E-mail" - -#: src/app/main/ui/auth/verify_token.cljs -msgid "dashboard.notifications.email-verified-successfully" -msgstr "Tvoja e-mail adresa je uspješno potvrđena" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.save-settings" -msgstr "Spremi postavke" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-duplicate-file" -msgstr "Tvoja datoteka je uspješno duplicirana" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-move-project" -msgstr "Tvoj projekt je uspješno premješten" - -#: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs -msgid "dashboard.update-settings" -msgstr "Ažuriraj postavke" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Poništi objavu biblioteke" - -#: src/app/main/ui/settings.cljs -msgid "dashboard.your-account-title" -msgstr "Tvoj korisnički račun" - -#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs -msgid "ds.confirm-title" -msgstr "Jesi li siguran/na?" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.your-name" -msgstr "Ime" - -#: src/app/main/ui/dashboard/search.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/libraries.cljs, src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.your-penpot" -msgstr "Tvoj Penpot" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "U redu" - -#: src/app/main/ui/confirm.cljs -msgid "ds.confirm-cancel" -msgstr "Poništi" - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.media-too-large" -msgstr "Slika je prevelika za umetanje." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.description" -msgstr "Opis" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography" -msgstr "Tipografija" - -#: src/app/main/ui/confirm.cljs -msgid "ds.component-subtitle" -msgstr "Komponente za ažuriranje:" - -#: src/app/main/ui/auth/login.cljs -msgid "errors.auth-provider-not-configured" -msgstr "Pružatelj autentifikacije nije konfiguriran." - -msgid "errors.invite-invalid.info" -msgstr "Ova pozivnica je možda otkazana ili je istekla." - -#, fuzzy -msgid "errors.auth.unable-to-login" -msgstr "Čini se da nisi autentificiran/a ili je sesija istekla." - -#: src/app/main/data/workspace.cljs -msgid "errors.clipboard-not-implemented" -msgstr "Tvoj preglednik ne može izvršiti ovu operaciju" - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "Ova datoteka je već korištena s omogućenim komponentama V2." - -#: src/app/main/ui/settings/password.cljs -msgid "generic.error" -msgstr "Došlo je do pogreške" - -#: src/app/main/ui/settings/password.cljs -msgid "errors.password-too-short" -msgstr "Lozinka mora sadržavati najmanje 8 znakova" - -msgid "errors.team-leave.owner-cant-leave" -msgstr "Vlasnik ne može napustiti tim, moraš ponovno dodijeliti ulogu vlasnika." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.chat-subtitle" -msgstr "Imaš želju za razgovorom? Razgovaraj s nama na Gitteru" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "Idi na Penpot forum" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-subtitle1" -msgstr "" -"Sretni smo što si ovdje. Ako trebaš pomoć, pretraži prije objavljivanja." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-title" -msgstr "Penpot zajednica" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-subtitle1" -msgstr "Ovdje za pomoć za tvoje tehničke upite." - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.top" -msgstr "Vrh" - -msgid "handoff.attributes.stroke.style.dotted" -msgstr "Točkasto" - -#, fuzzy -msgid "handoff.attributes.typography.text-transform.none" -msgstr "Nikakav" - -msgid "handoff.attributes.stroke.style.mixed" -msgstr "Miksano" - -msgid "handoff.attributes.typography.text-transform.lowercase" -msgstr "Mala slova" - -msgid "handoff.attributes.typography.text-transform.uppercase" -msgstr "Velika slova" - -msgid "handoff.tabs.code.selected.image" -msgstr "Slika" - -msgid "handoff.tabs.code.selected.mask" -msgstr "Maska" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code.selected.multiple" -msgstr "%s Označeno" - -msgid "handoff.attributes.typography.text-transform.titlecase" -msgstr "Velika i mala slova" - -msgid "labels.centered" -msgstr "Sredina" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code" -msgstr "Kod" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.info" -msgstr "Informacija" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.about-penpot" -msgstr "O Penpotu" - -#, fuzzy -msgid "handoff.tabs.code.selected.path" -msgstr "Path" - -#: src/app/main/ui/workspace/header.cljs -msgid "label.shortcuts" -msgstr "Prečaci" - -msgid "onboarding.team-modal.create-team-desc" -msgstr "" -"Tim ti omogućuje suradnju s drugim Penpot korisnicima koji rade na istim " -"datotekama i projektima." - -#: src/app/main/ui/static.cljs -msgid "labels.bad-gateway.main-message" -msgstr "Loš Gateway" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.community" -msgstr "Zajenica" - -msgid "labels.continue" -msgstr "Nastavi" - -#: src/app/main/ui/settings/password.cljs -msgid "labels.confirm-password" -msgstr "Potvrdi lozinku" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "labels.create" -msgstr "Kreiraj" - -#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs -msgid "labels.hide-resolved-comments" -msgstr "Sakrij riješene komentare" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.members" -msgstr "Članovi" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs -msgid "labels.email" -msgstr "E-mail" - -msgid "labels.icons" -msgstr "Ikone" - -#: src/app/main/ui/settings/password.cljs -msgid "labels.new-password" -msgstr "Nova lozinka" - -msgid "labels.log-or-sign" -msgstr "Prijava ili registracija" - -msgid "labels.manage-fonts" -msgstr "Uredi fontove" - -#, fuzzy -msgid "labels.next" -msgstr "Slijedeće" - -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "labels.rename" -msgstr "Preimenuj" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.permissions" -msgstr "Dozvole" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.resend-invitation" -msgstr "Ponovno pošalji pozivnicu" - -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.password" -msgstr "Lozinka" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.pending-invitation" -msgstr "U tijeku" - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.new-email" -msgstr "Novi e-mail" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.role" -msgstr "Uloga" - -#: src/app/main/ui/static.cljs -msgid "labels.service-unavailable.desc-message" -msgstr "U programiranom smo održavanju naših sustava." - -msgid "labels.search-font" -msgstr "Pretraži font" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.message" -msgstr "Jesi li siguran/na da želiš izbrisati %s datoteke?" - -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.settings" -msgstr "Postavke" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.you" -msgstr "(ti)" - -#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs -msgid "media.loading" -msgstr "Učitavanje slike…" - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.submit" -msgstr "Promijeni e-mail" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.title" -msgstr "Brisanje %s datoteka" - -msgid "modals.delete-font-variant.message" -msgstr "" -"Jesi li siguran/na da želiš izbrisati ovaj stil fonta? Neće se učitati ako " -"se koristi u datoteci." - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "modals.delete-page.title" -msgstr "Brisanje stranice" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.accept" -msgstr "Brisanje projekta" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "Jesi li siguran/na da želiš izbrisati ovu datoteku?" -msgstr[1] "Jesi li siguran/na da želiš izbrisati ove datoteke?" -msgstr[2] "Jesi li siguran/na da želiš izbrisati ove datoteke?" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.leftright" -msgstr "Lijevo i desno" - -#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs -msgid "workspace.options.blur-options.title.group" -msgstr "Grupiraj zamućenje" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.bottom" -msgstr "Dno" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.right" -msgstr "Desno" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.scale" -msgstr "Skala" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-member-confirm.accept" -msgstr "Pošalji pozivnicu" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-close-confirm.hint" -msgstr "" -"Budući da si jedini član ovog tima, tim će biti izbrisan zajedno sa svojim " -"projektima i datotekama." - -msgid "modals.leave-and-reassign.forbiden" -msgstr "" -"Ne možeš napustiti tim ako nema drugog člana kojeg možeš promovirati u " -"vlasnika. Možda želiš izbrisati tim." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "Ako poništiš objavu, stavke u njoj postaju biblioteka ove datoteke." -msgstr[1] "Ako poništiš objavu, stavke u njoj postaju biblioteka ovih datoteka." -msgstr[2] "Ako poništiš objavu, stavke u njoj postaju biblioteka ovih datoteka." - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.hint" -msgstr "" -"Ako preneseš vlasništvo, promijenit ćeš svoju ulogu u Administrator, čime " -"ćeš izgubiti neka dopuštenja za ovaj tim. " - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "Poništi objavu" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.cancel" -msgstr "Poništi" - -#: src/app/main/ui/dashboard/team.cljs -msgid "notifications.invitation-email-sent" -msgstr "Pozivnica je uspješno poslana" - -msgid "onboarding-v2.before-start.desc2" -msgstr "" -"Detaljne informacije o tome kako koristiti Penpot. Od izrade prototipova do " -"organiziranja ili dijeljenja dizajna." - -msgid "onboarding-v2.before-start.desc2.title" -msgstr "Korisnički priručnik" - -msgid "onboarding-v2.welcome.title" -msgstr "Dobrodošli u Penpot!" - -msgid "onboarding-v2.welcome.desc2" -msgstr "" -"Javni prostor za učenje, dijeljenje i raspravu o Penpotu, njegovoj " -"sadašnjosti i budućnosti s cijelom zajednicom i glavnim timom Penpota." - -msgid "onboarding-v2.welcome.desc3" -msgstr "" -"Gdje ćete pronaći kako surađivati s prijevodima, zahtjevima za unapređenje, " -"temeljnim doprinosima, potragom za bugovima…" - -msgid "onboarding-v2.welcome.desc3.title" -msgstr "Vodič za doprinos" - -msgid "onboarding.choice.team-up.create-later" -msgstr "Stvori tim kasnije" - -msgid "onboarding.choice.team-up.create-team" -msgstr "Ime tvojeg tima" - -msgid "onboarding.choice.team-up.invite-members-info" -msgstr "" -"Ne zaboravi uključiti sve. Programere, dizajnere, menadžere... raznolikost " -"se isplati :)" - -msgid "onboarding.choice.team-up.create-team-desc" -msgstr "Nakon što imenuješ svoj tim, moći ćeš pozvati ljude da se pridruže." - -msgid "onboarding.choice.team-up.create-team-placeholder" -msgstr "Unesi naziv tima" - -msgid "onboarding.choice.team-up.invite-members" -msgstr "Pozovi članove" - -msgid "onboarding.newsletter.acceptance-message" -msgstr "" -"Tvoj zahtjev za pretplatu je poslan, poslat ćemo ti e-mail da ga potvrdiš." - -msgid "onboarding.newsletter.desc" -msgstr "" -"Pretplati se na naš newsletter kako bi bio/la u toku s razvojem proizvoda i " -"novostima." - -msgid "onboarding.newsletter.privacy1" -msgstr "Budući da brinemo o privatnosti, evo našeg " - -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Poslat ćemo ti samo relevantne e-mailove. Pretplatu možeš odjaviti u bilo " -"kojem trenutku u svom korisničkom profilu ili putem linka za odjavu u bilo " -"kojem od naših newslettera." - -msgid "onboarding.newsletter.title" -msgstr "Želiš primati Penpot novostii?" - -msgid "onboarding.newsletter.policy" -msgstr "Politika privatnosti." - -msgid "onboarding.newsletter.accept" -msgstr "Da, pretplati se" - -msgid "onboarding.newsletter.decline" -msgstr "Ne, hvala" - -msgid "onboarding.slide.2.title" -msgstr "Sakupi povratne informacije, predstavi i podijeli svoj rad" - -msgid "shortcut-section.dashboard" -msgstr "Nadzorna ploča" - -msgid "shortcut-section.viewer" -msgstr "Gledatelj" - -# SECTIONS -msgid "shortcut-section.basics" -msgstr "Osnove" - -#: src/app/main/ui/auth/recovery.cljs -msgid "profile.recovery.go-to-login" -msgstr "Idi na prijavu" - -#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs -msgid "settings.multiple" -msgstr "Izmješano" - -msgid "shortcut-subsection.main-menu" -msgstr "Glavni meni" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "Neograničen broj članova" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "100% besplatno!" - -# SUBSECTIONS -msgid "shortcut-subsection.alignment" -msgstr "Poravnanje" - -msgid "shortcut-section.workspace" -msgstr "Radni prostor" - -msgid "shortcut-subsection.edit" -msgstr "Uredi" - -msgid "shortcuts.opacity-6" -msgstr "Postavi neprozirnost na 60%" - -msgid "shortcut-subsection.general-dashboard" -msgstr "Generičko" - -msgid "shortcut-subsection.general-viewer" -msgstr "Generičko" - -#, fuzzy -msgid "shortcut-subsection.modify-layers" -msgstr "Izmijeni layere" - -msgid "shortcut-subsection.navigation-dashboard" -msgstr "Navigacija" - -msgid "shortcut-subsection.navigation-viewer" -msgstr "Navigacija" - -msgid "shortcuts.open-comments" -msgstr "Idi na odjeljak s komentarima" - -msgid "shortcuts.opacity-7" -msgstr "Postavi neprozirnost na 70%" - -msgid "shortcuts.export-shapes" -msgstr "Izvezi oblike" - -msgid "shortcuts.go-to-drafts" -msgstr "Idi na nacrte" - -msgid "shortcuts.flip-vertical" -msgstr "Okreni okomito" - -msgid "shortcuts.flip-horizontal" -msgstr "Okreni vodoravno" - -msgid "shortcuts.draw-rect" -msgstr "Pravokutnik" - -msgid "shortcuts.next-frame" -msgstr "Slijedeća ploča" - -msgid "shortcuts.opacity-3" -msgstr "Postavi neprozirnost na 30%" - -msgid "shortcuts.opacity-4" -msgstr "Postavi neprozirnost na 40%" - -msgid "shortcuts.opacity-5" -msgstr "Postavi neprozirnost na 50%" - -msgid "shortcuts.opacity-8" -msgstr "Postavi neprozirnost na 80%" - -msgid "shortcuts.opacity-9" -msgstr "Postavi neprozirnost na 90%" - -msgid "shortcuts.group" -msgstr "Grupiraj" - -msgid "shortcuts.go-to-libs" -msgstr "Idi na zajedničke biblioteke" - -msgid "shortcuts.go-to-search" -msgstr "Traži" - -msgid "shortcuts.not-found" -msgstr "Nema pronađenih prečaca" - -msgid "shortcuts.opacity-0" -msgstr "Postavi neprozirnost na 100%" - -msgid "shortcuts.opacity-1" -msgstr "Postavi neprozirnost na 10%" - -msgid "shortcuts.opacity-2" -msgstr "Postavi neprozirnost na 20%" - -msgid "shortcuts.open-color-picker" -msgstr "Birač boja" - -msgid "shortcuts.open-interactions" -msgstr "Idi na odjeljak interakcija" - -msgid "shortcuts.open-workspace" -msgstr "Idi na radni prostor" - -msgid "shortcuts.open-viewer" -msgstr "Idi na odjeljak interakcija gledatelja" - -#, fuzzy -msgid "shortcuts.toggle-rules" -msgstr "Prikaži/sakrij \"rules\"" - -msgid "shortcuts.toggle-zoom-style" -msgstr "Promijeni stil zooma" - -#, fuzzy -msgid "shortcuts.toggle-snap-guide" -msgstr "Pričvrsti na guides" - -msgid "shortcuts.toogle-fullscreen" -msgstr "Promijeni cijeli zaslon" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-animation-none" -msgstr "Nijedan" - -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs -msgid "title.default" -msgstr "Penpot - Sloboda dizajna za timove" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-variant-id" -msgstr "Varijanta" - -#: src/app/main/ui/settings/options.cljs -msgid "title.settings.options" -msgstr "Postavke - Penpot" - -#: src/app/main/ui/settings/password.cljs -msgid "title.settings.password" -msgstr "Lozinka - Penpot" - -#: src/app/main/ui/settings/profile.cljs -msgid "title.settings.profile" -msgstr "Profil - Penpot" - -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-settings" -msgstr "Postavke - %s - Penpot" - -#: src/app/main/ui/dashboard/files.cljs -msgid "title.dashboard.files" -msgstr "%s - Penpot" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "title.dashboard.font-providers" -msgstr "Dobavljači fontova - %s - Penpot" - -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-invitations" -msgstr "Pozivnice - %s - Penpot" - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "title.viewer" -msgstr "%s - Način prikaza - Penpot" - -#: src/app/main/ui/workspace.cljs -msgid "title.workspace" -msgstr "%s - Penpot" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.create-link" -msgstr "Kreiraj poveznicu" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs -msgid "workspace.assets.typography.sample" -msgstr "Ag" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.selected-count" -msgid_plural "workspace.assets.selected-count" -msgstr[0] "%s odabrana stavka" -msgstr[1] "%s odabranih stavki" -msgstr[2] "%s odabranih stavki" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.typography" -msgstr "Tipografija" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.libraries" -msgstr "Biblioteke" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.letter-spacing" -msgstr "Razmak između slova" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.not-found" -msgstr "Nisu pronađene stavke" - -msgid "workspace.assets.local-library" -msgstr "lokalna biblioteka" - -msgid "dashboard.export.options.merge.title" -msgstr "Uključi stavke zajedničke biblioteke u biblioteke datoteka" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.shared" -msgstr "PODIJELJENO" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-id" -msgstr "Font" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-size" -msgstr "Veličina" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.go-to-edit" -msgstr "Idi na datoteku biblioteke stilova za uređivanje" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.text-transform" -msgstr "Transformiraj tekst" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.ungroup" -msgstr "Razgrupiraj" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.assets" -msgstr "Stavke" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-scale-text" -msgstr "Omogući skaliranje teksta" - -msgid "workspace.focus.focus-on" -msgstr "Fokus uključen" - -msgid "workspace.undo.entry.single.color" -msgstr "stavka boja" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-dynamic-alignment" -msgstr "Omogući dinamičko poravnanje" - -msgid "workspace.undo.entry.single.media" -msgstr "grafička stavka" - -msgid "workspace.undo.entry.single.typography" -msgstr "tipografska stavka" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-snap-grid" -msgstr "Onemogući \"snap to grid\"" - -#, fuzzy -msgid "workspace.header.menu.disable-snap-pixel-grid" -msgstr "Onemogući \"snap to pixel\"" - -#: src/app/main/ui/workspace/header.cljs -#, fuzzy -msgid "workspace.header.menu.disable-snap-guides" -msgstr "Onemogući \"snap to guides\"" - -#: src/app/main/ui/workspace/header.cljs -#, fuzzy -msgid "workspace.header.menu.enable-snap-grid" -msgstr "Poravnanje s \"gridom\"" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.auto" -msgstr "Auto" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type" -msgstr "Tip" - -#: src/app/main/ui/workspace/header.cljs -#, fuzzy -msgid "workspace.header.menu.show-rules" -msgstr "Prikaži \"rules\"" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -#, fuzzy -msgid "workspace.options.grid.grid-title" -msgstr "Grid" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-textpalette" -msgstr "Prikaži paletu fontova" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.center" -msgstr "Sredina" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.left" -msgstr "Lijevo" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.saved" -msgstr "Spremljeno" - -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.recent-colors" -msgstr "Nedavno korištene boje" - -msgid "dashboard.export.options.detach.message" -msgstr "" -"Zajedničke biblioteke neće biti uključene u izvoz i nikakve stavke neće biti " -"dodani u biblioteku. " - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.remove-shared" -msgstr "Ukloni kao zajedničku biblioteku" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -#, fuzzy -msgid "modals.remove-shared-confirm.hint" -msgstr "" -"Nakon uklanjanja kao zajedničke biblioteke, biblioteka datoteka ove datoteke " -"više neće biti dostupna za korištenje među tvojim ostalim datotekama." - -#, fuzzy -msgid "workspace.shape.menu.path" -msgstr "Path" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.shortcuts" -msgstr "Prečaci (%s)" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.empty" -msgstr "Do sada nema promjena povijesti" - -msgid "workspace.path.actions.move-nodes" -msgstr "Premijesti čvorove (%s)" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.create-component" -msgstr "Kreiraj komponentu" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.delete" -msgstr "Izbriši" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.detach-instance" -msgstr "Odvoji instancu" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.detach-instances-in-bulk" -msgstr "Odvoji instance" - -#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.paste" -msgstr "Zalijepi" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show-main" -msgstr "Prikaži glavnu komponentu" - -msgid "workspace.shape.menu.union" -msgstr "Unija" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.cut" -msgstr "Izreži" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.comments" -msgstr "Komentari (%s)" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show" -msgstr "Prikaži" - -msgid "workspace.shape.menu.thumbnail-remove" -msgstr "Ukloni sličicu" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.sitemap" -msgstr "Sitemap" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.text-palette" -msgstr "Tipografija (%s)" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.delete" -msgstr "Izbrisano %s" - -#, fuzzy -msgid "workspace.shape.menu.transform-to-path" -msgstr "Transformiraj u path" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.update-components-in-bulk" -msgstr "Ažuriraj glavne komponente" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.update-main" -msgstr "Ažuriraj glavnu komponentu" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.sidebar.history" -msgstr "Povijest (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.color-palette" -msgstr "Paleta boja (%s)" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.ungroup" -msgstr "Razgrupiraj" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.move" -msgstr "Premijesti (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -#, fuzzy -msgid "workspace.toolbar.path" -msgstr "Path (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.rect" -msgstr "Pravokutnik (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.text" -msgstr "Tekst (%s)" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.modify" -msgstr "Izmijenjeno %s" - -#, fuzzy -msgid "workspace.undo.entry.multiple.path" -msgstr "paths" - -msgid "workspace.undo.entry.single.shape" -msgstr "oblik" - -msgid "workspace.undo.entry.single.text" -msgstr "tekst" - -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.there-are-updates" -msgstr "Postoje ažuriranja u zajedničkim bibliotekama" - -msgid "workspace.undo.entry.single.image" -msgstr "slika" - -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.dismiss" -msgstr "Odbaci" - -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.update" -msgstr "Ažuriraj" - -msgid "workspace.options.text-options.text-case" -msgstr "Slučaj" - msgid "workspace.path.actions.add-node" msgstr "Dodaj čvor (%s)" @@ -4445,6 +4244,9 @@ msgstr "Do krivulje (%s)" msgid "workspace.path.actions.merge-nodes" msgstr "Spoji čvorove (%s)" +msgid "workspace.path.actions.move-nodes" +msgstr "Premijesti čvorove (%s)" + msgid "workspace.path.actions.separate-nodes" msgstr "Odvoji čvorove(%s)" @@ -4469,11 +4271,31 @@ msgstr "Kopiraj" msgid "workspace.shape.menu.create-artboard-from-selection" msgstr "Odabir na ploču" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Kreiraj komponentu" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Izreži" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Izbriši" + #: src/app/main/ui/workspace/context_menu.cljs #, fuzzy msgid "workspace.shape.menu.delete-flow-start" msgstr "Izbriši početak flowa" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instance" +msgstr "Odvoji instancu" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "Odvoji instance" + msgid "workspace.shape.menu.difference" msgstr "Razlika" @@ -4491,6 +4313,10 @@ msgstr "Izuzmi" msgid "workspace.shape.menu.flatten" msgstr "Spljošti" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Okreni vodoravno" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.flip-vertical" msgstr "Okreni okomito" @@ -4502,13 +4328,13 @@ msgstr "Početak flowa" #: src/app/main/ui/workspace/context_menu.cljs #, fuzzy -msgid "workspace.shape.menu.front" -msgstr "Postavi naprijed" +msgid "workspace.shape.menu.forward" +msgstr "Postavi ispred" #: src/app/main/ui/workspace/context_menu.cljs #, fuzzy -msgid "workspace.shape.menu.forward" -msgstr "Postavi ispred" +msgid "workspace.shape.menu.front" +msgstr "Postavi naprijed" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.go-main" @@ -4536,6 +4362,14 @@ msgstr "Zaključaj" msgid "workspace.shape.menu.mask" msgstr "Maskiraj" +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Zalijepi" + +#, fuzzy +msgid "workspace.shape.menu.path" +msgstr "Path" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs #, fuzzy msgid "workspace.shape.menu.reset-overrides" @@ -4544,9 +4378,35 @@ msgstr "Poništi overrides" msgid "workspace.shape.menu.restore-main" msgstr "Vrati glavnu komponentu" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Označi layer" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Prikaži" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-main" +msgstr "Prikaži glavnu komponentu" + +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "Ukloni sličicu" + msgid "workspace.shape.menu.thumbnail-set" msgstr "Postavi kao sličicu" +#, fuzzy +msgid "workspace.shape.menu.transform-to-path" +msgstr "Transformiraj u path" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Razgrupiraj" + +msgid "workspace.shape.menu.union" +msgstr "Unija" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.unlock" msgstr "Otključaj" @@ -4555,6 +4415,48 @@ msgstr "Otključaj" msgid "workspace.shape.menu.unmask" msgstr "Ukloni masku" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "Ažuriraj glavne komponente" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-main" +msgstr "Ažuriraj glavnu komponentu" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "Povijest (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +#, fuzzy +msgid "workspace.sidebar.layers" +msgstr "Layeri" + +msgid "workspace.sidebar.layers.components" +msgstr "Komponente" + +msgid "workspace.sidebar.layers.frames" +msgstr "Ploče" + +msgid "workspace.sidebar.layers.groups" +msgstr "Grupe" + +msgid "workspace.sidebar.layers.images" +msgstr "Slike" + +msgid "workspace.sidebar.layers.masks" +msgstr "Maske" + +#, fuzzy +msgid "workspace.sidebar.layers.search" +msgstr "Traži layere" + +msgid "workspace.sidebar.layers.shapes" +msgstr "Oblici" + +msgid "workspace.sidebar.layers.texts" +msgstr "Tekstovi" + #: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs msgid "workspace.sidebar.options.svg-attrs.title" msgstr "Uvezeni SVG atributi" @@ -4563,6 +4465,22 @@ msgstr "Uvezeni SVG atributi" msgid "workspace.sidebar.sitemap" msgstr "Stranice" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.sitemap" +msgstr "Sitemap" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Stavke" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.color-palette" +msgstr "Paleta boja (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Komentari (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.curve" msgstr "Krivulja (%s)" @@ -4579,6 +4497,43 @@ msgstr "Ploča (%s)" msgid "workspace.toolbar.image" msgstr "Slika (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Premijesti (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +#, fuzzy +msgid "workspace.toolbar.path" +msgstr "Path (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Pravokutnik (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Prečaci (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Tekst (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text-palette" +msgstr "Tipografija (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.empty" +msgstr "Do sada nema promjena povijesti" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "Izbrisano %s" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.modify" +msgstr "Izmijenjeno %s" + #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.move" msgstr "Premiješteni objekti" @@ -4586,6 +4541,9 @@ msgstr "Premiješteni objekti" msgid "workspace.undo.entry.multiple.circle" msgstr "krugovi" +msgid "workspace.undo.entry.multiple.color" +msgstr "stavke boja" + msgid "workspace.undo.entry.multiple.component" msgstr "komponente" @@ -4598,12 +4556,19 @@ msgstr "ploča" msgid "workspace.undo.entry.multiple.group" msgstr "grupe" +msgid "workspace.undo.entry.multiple.media" +msgstr "grafičke stavke" + msgid "workspace.undo.entry.multiple.multiple" msgstr "objekti" msgid "workspace.undo.entry.multiple.page" msgstr "stranice" +#, fuzzy +msgid "workspace.undo.entry.multiple.path" +msgstr "paths" + msgid "workspace.undo.entry.multiple.rect" msgstr "pravokutnici" @@ -4613,8 +4578,8 @@ msgstr "oblici" msgid "workspace.undo.entry.multiple.text" msgstr "tekstovi" -msgid "workspace.undo.entry.single.curve" -msgstr "krivulja" +msgid "workspace.undo.entry.multiple.typography" +msgstr "tipografske stavke" #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.new" @@ -4623,15 +4588,27 @@ msgstr "Novo %s" msgid "workspace.undo.entry.single.circle" msgstr "krug" +msgid "workspace.undo.entry.single.color" +msgstr "stavka boja" + msgid "workspace.undo.entry.single.component" msgstr "komponenta" +msgid "workspace.undo.entry.single.curve" +msgstr "krivulja" + msgid "workspace.undo.entry.single.frame" msgstr "ploča" msgid "workspace.undo.entry.single.group" msgstr "grupa" +msgid "workspace.undo.entry.single.image" +msgstr "slika" + +msgid "workspace.undo.entry.single.media" +msgstr "grafička stavka" + msgid "workspace.undo.entry.single.multiple" msgstr "objekt" @@ -4645,6 +4622,15 @@ msgstr "path" msgid "workspace.undo.entry.single.rect" msgstr "pravokutnik" +msgid "workspace.undo.entry.single.shape" +msgstr "oblik" + +msgid "workspace.undo.entry.single.text" +msgstr "tekst" + +msgid "workspace.undo.entry.single.typography" +msgstr "tipografska stavka" + #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.unknown" msgstr "Operacija izvršena %s" @@ -4653,6 +4639,18 @@ msgstr "Operacija izvršena %s" msgid "workspace.undo.title" msgstr "Povijest" +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Odbaci" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "Postoje ažuriranja u zajedničkim bibliotekama" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.update" +msgstr "Ažuriraj" + #, fuzzy msgid "workspace.viewport.click-to-close-path" -msgstr "Pritisni da zatvoriš path" +msgstr "Pritisni da zatvoriš path" \ No newline at end of file diff --git a/frontend/translations/id.po b/frontend/translations/id.po index 789560f099..c18ae56de2 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-23 20:54+0000\n" "Last-Translator: Dário \n" -"Language-Team: Indonesian \n" +"Language-Team: Indonesian " +"\n" "Language: id\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -223,6 +223,18 @@ msgstr "Bagikan prototipe" msgid "common.share-link.view-all" msgstr "Pilih Semua" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Mulai tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Mulai tur" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Panduan Antarmuka" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Tambahkan sebagai Pustaka Bersama" @@ -321,24 +333,30 @@ msgstr "" "Pustaka bersama tidak akan dimasukkan dalam hasil ekspor dan tidak ada aset " "yang akan ditambahkan ke pustaka. " -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.upload-all" -msgstr "Unggah semua" +msgid "dashboard.fonts.deleted-placeholder" +msgstr "Fon dihapus" + +msgid "dashboard.fonts.empty-placeholder" +msgstr "Tak ada fon khusus yang terpasang." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.fonts-added" msgid_plural "dashboard.fonts.fonts-added" msgstr[0] "%s fon ditambahkan" +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Unggah semua" + msgid "dashboard.import" msgstr "Impor berkas Penpot" -msgid "dashboard.fonts.empty-placeholder" -msgstr "Tak ada fon khusus yang terpasang." - msgid "dashboard.import.analyze-error" msgstr "Aduh! Kami tidak dapat mengimpor berkas ini" +msgid "dashboard.import.import-error" +msgstr "Terdapat masalah saat mengimpor berkas. Berkasnya tidak terimpor." + msgid "dashboard.import.import-message" msgstr "%s berkas telah berhasil diimpor." @@ -354,39 +372,59 @@ msgstr "Memproses media" msgid "dashboard.import.progress.process-page" msgstr "Memproses halaman: %s" -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Mulai tutorial" +msgid "dashboard.import.progress.upload-data" +msgstr "Mengunggah data ke server (%s/%s)" -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Mulai tur" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Panduan Antarmuka" - -msgid "dashboard.import.import-error" -msgstr "Terdapat masalah saat mengimpor berkas. Berkasnya tidak terimpor." - -msgid "dashboard.fonts.deleted-placeholder" -msgstr "Fon dihapus" +msgid "dashboard.import.progress.upload-media" +msgstr "Mengunggah berkas: %s" #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Undang ke tim" +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Tinggalkan tim" + +msgid "dashboard.libraries-and-templates" +msgstr "Pustaka & Template" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Jelajahi lebih banyak dan pelajari cara berkontribusi" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Terjadi masalah saat mengimpor template. Template tidak diimpor." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Pustaka" +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "memuat berkas-berkasmu …" + +msgid "dashboard.loading-fonts" +msgstr "memuat fon-fon kamu …" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Pindahkan ke" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Pindahkan %s berkas ke" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Pindahkan ke tim lain" + #: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs msgid "dashboard.new-file" msgstr "+ Buat Berkas" -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to-other-team" -msgstr "Pindahkan ke tim lain" +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "Berkas Baru" #: src/app/main/ui/dashboard/projects.cljs msgid "dashboard.new-project" @@ -398,42 +436,4 @@ msgstr "Proyek Baru" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-msg" -msgstr "Kirimkan saya berita, pembaruan produk dan rekomendasi tentang Penpot." - -msgid "dashboard.libraries-and-templates" -msgstr "Pustaka & Template" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Jelajahi lebih banyak dan pelajari cara berkontribusi" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "Terjadi masalah saat mengimpor template. Template tidak diimpor." - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-file-prefix" -msgstr "Berkas Baru" - -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.leave-team" -msgstr "Tinggalkan tim" - -#: src/app/main/ui/dashboard/grid.cljs -msgid "dashboard.loading-files" -msgstr "memuat berkas-berkasmu …" - -msgid "dashboard.loading-fonts" -msgstr "memuat fon-fon kamu …" - -msgid "dashboard.import.progress.upload-data" -msgstr "Mengunggah data ke server (%s/%s)" - -msgid "dashboard.import.progress.upload-media" -msgstr "Mengunggah berkas: %s" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to" -msgstr "Pindahkan ke" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to-multi" -msgstr "Pindahkan %s berkas ke" +msgstr "Kirimkan saya berita, pembaruan produk dan rekomendasi tentang Penpot." \ No newline at end of file diff --git a/frontend/translations/it.po b/frontend/translations/it.po index 2c2384b201..a19a4dedbb 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-01 14:17+0000\n" "Last-Translator: Jacopo Lodovico Trabia \n" -"Language-Team: Italian \n" +"Language-Team: Italian " +"\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -167,6 +167,9 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Abbiamo inviato l'e-mail di verifica a" +msgid "common.publish" +msgstr "Pubblica" + msgid "common.share-link.all-users" msgstr "Tutti gli utenti Penpot" @@ -222,6 +225,40 @@ msgstr "Condividi i prototipi" msgid "common.share-link.view-all" msgstr "Seleziona tutto" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Gestisci team" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot è studiato per i team. Invita membri per lavorare insieme a file e " +"progetti" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Impara le basi di Penpot divertendoti con questo tutorial pratico." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Inizia il tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial pratico" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Esplora Penpot e scopri le sue principali funzionalità." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Inizia il tour" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Spiegazione dell'interfaccia passo per passo" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Aggiungi una libreria condivisa" @@ -428,6 +465,17 @@ msgstr "Invita nel team" msgid "dashboard.leave-team" msgstr "Abbandona il team" +msgid "dashboard.libraries-and-templates" +msgstr "Librerie e template" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Esplorane di più e scopri come contribuire" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "" +"Si è verificato un problema nell'importazione del template. Il template non " +"è stato importato." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Librerie condivise" @@ -512,6 +560,10 @@ msgstr "Opzioni" msgid "dashboard.password-change" msgstr "Cambia password" +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Appunta/Rimuovi" + #: src/app/main/ui/dashboard/projects.cljs msgid "dashboard.projects-title" msgstr "Progetti" @@ -604,6 +656,10 @@ msgstr "Risultati della ricerca" msgid "dashboard.type-something" msgstr "Scrivi per cercare" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Spubblicare la libreria" + #: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "Aggiorna le impostazioni" @@ -624,6 +680,14 @@ msgstr "Il tuo nome" msgid "dashboard.your-penpot" msgstr "Il tuo Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Attenzione" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Componenti da aggiornare:" @@ -677,6 +741,10 @@ msgstr "L'e-mail \"%s\" è stata riportata come spam o respinta in modo permanen msgid "errors.generic" msgstr "Si è verificato un problema." +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +msgstr "Autenticazione con Google disabilitata nel backend" + #: src/app/main/ui/components/color_input.cljs msgid "errors.invalid-color" msgstr "Colore non valido" @@ -949,6 +1017,10 @@ msgstr "Stile del carattere" msgid "handoff.attributes.typography.letter-spacing" msgstr "Spaziatura delle lettere" +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Altezza Linea" + msgid "handoff.attributes.typography.text-decoration.none" msgstr "Nessuno" @@ -958,6 +1030,10 @@ msgstr "Barrato" msgid "handoff.attributes.typography.text-decoration.underline" msgstr "Sottolineato" +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Trasforma Testo" + msgid "handoff.attributes.typography.text-transform.lowercase" msgstr "Minuscolo" @@ -1428,6 +1504,10 @@ msgstr "Tutorial" msgid "labels.update" msgstr "Aggiorna" +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Aggiorna team" + msgid "labels.upload" msgstr "Caricare" @@ -1437,6 +1517,10 @@ msgstr "Caricare font personalizzati" msgid "labels.uploading" msgstr "Caricamento…" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "" + msgid "labels.workspace" msgstr "Spazio di lavoro" @@ -1470,6 +1554,10 @@ msgstr "" msgid "modals.add-shared-confirm.message" msgstr "Aggiungere \"%s\" come libreria condivisa" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.big-nudge" +msgstr "Grande scatto" + #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.confirm-email" msgstr "Verificare il nuovo indirizzo e-mail" @@ -1506,38 +1594,93 @@ msgstr "Annulla e mantieni il mio account" msgid "modals.delete-account.confirm" msgstr "Sì, cancellare il mio account" -msgid "common.publish" -msgstr "Pubblica" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Gestisci team" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.info" msgstr "" -"Penpot è studiato per i team. Invita membri per lavorare insieme a file e " -"progetti" +"Cancellando il tuo account, perderai tutti i tuoi archivi e progetti " +"attuali." -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Inizia il tutorial" +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.title" +msgstr "Eliminare il proprio account?" -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Inizia il tour" +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Elimina conversazione" -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.pin-unpin" -msgstr "Appunta/Rimuovi" +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.message" +msgstr "" +"Eliminare questa conversazione? Tutti i commenti in questo thread saranno " +"cancellati." + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.title" +msgstr "Elimina conversazione" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Elimina file" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "Eliminare questo file?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Eliminazione file" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.accept" +msgstr "Elimina files" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "Eliminare %s files?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "Eliminazione %s files" + +msgid "modals.delete-font-variant.message" +msgstr "" +"Eliminare questo stile del carattere? Se è usato in un file, non verrà " +"caricato." + +msgid "modals.delete-font-variant.title" +msgstr "Eliminazione stile del carattere" + +msgid "modals.delete-font.message" +msgstr "Eliminare questo carattere? Se è usato in un file, non verrà caricato." + +msgid "modals.delete-font.title" +msgstr "Eliminazione carattere" #: src/app/main/ui/workspace/sidebar/sitemap.cljs msgid "modals.delete-page.body" msgstr "Eliminare questa pagina?" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.title" -msgstr "Elimina membro del team" +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Elimina pagina" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Elimina progetto" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Eliminare questo progetto?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Elimina progetto" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Elimina file" +msgstr[1] "Elimina i file" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.message" @@ -1557,95 +1700,19 @@ msgid_plural "modals.delete-shared-confirm.scd-message-plural" msgstr[0] "Questi file contengono librerie usate nel file:" msgstr[1] "Questi file contengono librerie usate nei file:" -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.viewer" -msgstr "" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Eliminazione del file" +msgstr[1] "Eliminazione dei file" -#: src/app/main/ui/settings/delete_account.cljs -msgid "modals.delete-account.info" -msgstr "" -"Cancellando il tuo account, perderai tutti i tuoi archivi e progetti attuali." - -#: src/app/main/ui/settings/delete_account.cljs -msgid "modals.delete-account.title" -msgstr "Eliminare il proprio account?" - -#: src/app/main/ui/comments.cljs -msgid "modals.delete-comment-thread.accept" -msgstr "Elimina conversazione" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-confirm.title" -msgstr "Eliminazione file" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.accept" -msgstr "Elimina files" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.message" -msgstr "Eliminare %s files?" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.title" -msgstr "Eliminazione %s files" - -msgid "modals.delete-font-variant.title" -msgstr "Eliminazione stile del carattere" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.message" -msgstr "Eliminare questo progetto?" - -msgid "modals.delete-font.title" -msgstr "Eliminazione carattere" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.title" -msgstr "Elimina progetto" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.accept" -msgstr "Elimina progetto" +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Eliminazione file in corso" #: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint1" -msgstr "" -"Sei il proprietario di questo team. Per favore seleziona un altro membro da " -"promuovere a proprietario prima di uscire." - -msgid "modals.leave-and-reassign.forbiden" -msgstr "" -"Non puoi lasciare il team se non c'è alcun membro da promuovere a " -"proprietario. Potresti voler cancellare il team." - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.promote-and-leave" -msgstr "Promuovi e esci" - -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.nudge-title" -msgstr "Ampiezza scatto" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.title" -msgstr "Nuovo proprietario del team" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-transform" -msgstr "Trasforma Testo" - -#: src/app/main/ui/dashboard/team_form.cljs -msgid "labels.update-team" -msgstr "Aggiorna team" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-confirm.accept" -msgstr "Elimina file" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-confirm.message" -msgstr "Eliminare questo file?" +msgid "modals.delete-team-confirm.accept" +msgstr "Elimina team" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.message" @@ -1653,10 +1720,6 @@ msgstr "" "Eliminare questo team? Tutti i progetti e i file associati con questo team " "verranno cancellati permanentemente." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.accept" -msgstr "Elimina team" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.title" msgstr "Eliminazione del team in corso" @@ -1665,10 +1728,50 @@ msgstr "Eliminazione del team in corso" msgid "modals.delete-team-member-confirm.accept" msgstr "Elimina membro" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Eliminare questo membro dal team?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Elimina membro del team" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Invia invito" + +msgid "modals.invite-member.emails" +msgstr "Email, separate da virgole" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Invita membri al team" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Poiché sei il solo membro di questo team, il team verrà eliminato insieme " +"ai sui file e progetti." + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.message" msgstr "Lasciare il team di %s?" +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"Non puoi lasciare il team se non c'è alcun membro da promuovere a " +"proprietario. Potresti voler cancellare il team." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "" +"Sei il proprietario di questo team. Per favore seleziona un altro membro da " +"promuovere a proprietario prima di uscire." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.promote-and-leave" +msgstr "Promuovi e esci" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.select-member-to-promote" msgstr "Seleziona un membro da promuovere" @@ -1685,6 +1788,10 @@ msgstr "Lascia il team" msgid "modals.leave-confirm.message" msgstr "Vuoi lasciare questo team?" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.nudge-title" +msgstr "Ampiezza scatto" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.accept" msgstr "Trasferisci proprietà" @@ -1701,165 +1808,13 @@ msgstr "" "Sei l'attuale proprietario di questo team. Trasferire la proprietà del team " "a %s?" -msgid "modals.delete-font.message" -msgstr "Eliminare questo carattere? Se è usato in un file, non verrà caricato." - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "modals.delete-page.title" -msgstr "Elimina pagina" - -msgid "modals.invite-member.emails" -msgstr "Email, separate da virgole" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Elimina file" -msgstr[1] "Elimina i file" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "Eliminazione del file" -msgstr[1] "Eliminazione dei file" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "Eliminazione file in corso" - #: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.message" -msgstr "Eliminare questo membro dal team?" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "Invita membri al team" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-close-confirm.hint" -msgstr "" -"Poiché sei il solo membro di questo team, il team verrà eliminato insieme ai " -"sui file e progetti." - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.line-height" -msgstr "Altezza Linea" - -#: src/app/main/ui/comments.cljs -msgid "modals.delete-comment-thread.message" -msgstr "" -"Eliminare questa conversazione? Tutti i commenti in questo thread saranno " -"cancellati." - -#: src/app/main/ui/comments.cljs -msgid "modals.delete-comment-thread.title" -msgstr "Elimina conversazione" - -msgid "modals.delete-font-variant.message" -msgstr "" -"Eliminare questo stile del carattere? Se è usato in un file, non verrà " -"caricato." - -#: src/app/main/ui/auth/login.cljs -msgid "errors.google-auth-not-enabled" -msgstr "Autenticazione con Google disabilitata nel backend" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "Ok" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Attenzione" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "Impara le basi di Penpot divertendoti con questo tutorial pratico." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Tutorial pratico" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "Esplora Penpot e scopri le sue principali funzionalità." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Spiegazione dell'interfaccia passo per passo" - -msgid "dashboard.libraries-and-templates" -msgstr "Librerie e template" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Esplorane di più e scopri come contribuire" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "" -"Si è verificato un problema nell'importazione del template. Il template non " -"è stato importato." +msgid "modals.promote-owner-confirm.title" +msgstr "Nuovo proprietario del team" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Spubblicare la libreria" - -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.small-nudge" -msgstr "Piccolo scatto" - -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.big-nudge" -msgstr "Grande scatto" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component-in-bulk.message" -msgstr "Aggiorna componenti in una libreria condivisa" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.cancel" -msgstr "Cancella" - -#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs -msgid "notifications.profile-saved" -msgstr "Profilo salvato con successo!" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.accept" -msgstr "Aggiorna" - -msgid "onboarding.choice.team-up.create-team" -msgstr "Il nome del tuo team" - -msgid "onboarding.choice.team-up.create-team-desc" -msgstr "" -"Dopo aver nominato il tuo team, potrai invitare persone ad unirsi ad esso." - -msgid "onboarding.choice.team-up.create-team-placeholder" -msgstr "Inserisci il nome del team" - -msgid "onboarding.choice.team-up.invite-members" -msgstr "Invita membri" - -msgid "onboarding.choice.team-up.invite-members-submit" -msgstr "Crea un team e manda inviti" - -msgid "onboarding.choice.title" -msgstr "Benvenuti su Penpot" - -msgid "onboarding.contrib.alt" -msgstr "Open Source" - -msgid "onboarding.contrib.desc2.1" -msgstr "Puoi accedere al" - -msgid "onboarding.contrib.title" -msgstr "Contribuente Open Source?" - -msgid "onboarding.newsletter.title" -msgstr "Vuoi ricevere le news di Pentot?" - -msgid "onboarding.slide.1.alt" -msgstr "Prototipi interattivi" +msgid "modals.remove-shared-confirm.accept" +msgstr "Elimina come Libreria Condivisa" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.hint" @@ -1868,72 +1823,90 @@ msgstr "" "file smetterà di essere a disposizione per essere usata con il resto dei " "tuoi file." -#: src/app/main/ui/dashboard/team.cljs -msgid "notifications.invitation-email-sent" -msgstr "Invito inviato con successo" - -msgid "onboarding.newsletter.accept" -msgstr "Si, iscrivimi" - -msgid "onboarding.newsletter.decline" -msgstr "No, grazie" - -msgid "onboarding.newsletter.desc" -msgstr "" -"Iscriviti alla nostra newsletter per rimanere aggiornato con le news e i " -"progressi di sviluppo del prodotto." - -msgid "onboarding.newsletter.acceptance-message" -msgstr "" -"La tua richiesta di iscrizione è stata invita, ti invieremo un'email di " -"conferma." - -msgid "onboarding.newsletter.policy" -msgstr "Condizioni sulla Privacy." - -msgid "onboarding.newsletter.privacy1" -msgstr "In quanto ci teniamo alla privacy, qui puoi vedere la nostra " - -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Ti invieremo solamente email per te rilevanti. Puoi cancellare l'iscrizione " -"in qualsiasi momento nel tuo profilo utente o tramite il link presente in " -"qualsiasi delle nostre newsletter." - -msgid "onboarding.slide.1.desc1" -msgstr "Crea interazioni complete per imitare al meglio il prodotto finale." - -#: src/app/main/ui/settings/delete_account.cljs -msgid "notifications.profile-deletion-not-allowed" -msgstr "" -"Non puoi eliminare il tuo profilo. Riassegna i tuoi team prima di procedere." - -#: src/app/main/ui/settings/change_email.cljs -msgid "notifications.validation-email-sent" -msgstr "Email di verifica inviata a %s. Controlla la tua email!" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-member-confirm.accept" -msgstr "Invia invito" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.remove-shared-confirm.accept" -msgstr "Elimina come Libreria Condivisa" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.message" msgstr "Elimina \"%s\" come Libreria Condivisa" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "Piccolo scatto" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.accept" msgstr "Annulla pubblicazione" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Se annulli la pubblicazione, gli elementi diventeranno parte della libreria " +"di questo file." +msgstr[1] "" +"Se annulli la pubblicazione, gli elementi diventeranno parte della libreria " +"di questi file." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Annullare la pubblicazione di questa libreria?" +msgstr[1] "Annullare la pubblicazione di queste librerie?" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" msgstr[0] "È utilizzata in questo file:" msgstr[1] "È utilizzata in questi file:" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Annulla pubblicazione libreria" +msgstr[1] "Annulla pubblicazione librerie" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"Stai per aggiornare i componenti in una libreria condivisa. Questo potrebbe " +"causare modifiche nei file che la utilizzano." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.message" +msgstr "Aggiorna componenti in una libreria condivisa" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.accept" +msgstr "Aggiorna" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.cancel" +msgstr "Cancella" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.hint" +msgstr "" +"Stai per aggiornare un componente in una libreria condivisa. Questo " +"potrebbe causare modifiche nei file che la utilizzano." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.message" +msgstr "Aggiorna un componente in una libreria condivisa" + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Invito inviato con successo" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "Non puoi eliminare il tuo profilo. Riassegna i tuoi team prima di procedere." + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Profilo salvato con successo!" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "Email di verifica inviata a %s. Controlla la tua email!" + msgid "onboarding-v2.before-start.desc1" msgstr "" "Suggerimento: ci sono tantissime risorse disponibili per aiutarti nei tuoi " @@ -1964,8 +1937,8 @@ msgstr "" msgid "onboarding-v2.welcome.desc2" msgstr "" -"Uno spazio pubblico dove imparare, condividere e discutere su Penpot, il suo " -"presente e futuro con l'intera Comunità e con il team di Penpot." +"Uno spazio pubblico dove imparare, condividere e discutere su Penpot, il " +"suo presente e futuro con l'intera Comunità e con il team di Penpot." msgid "onboarding-v2.welcome.desc2.title" msgstr "Partecipando nella Comunità" @@ -1984,53 +1957,93 @@ msgstr "Benvenuti su Penpot!" msgid "onboarding.choice.team-up.create-later" msgstr "Crea un team più tardi" +msgid "onboarding.choice.team-up.create-team" +msgstr "Il nome del tuo team" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "Dopo aver nominato il tuo team, potrai invitare persone ad unirsi ad esso." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Inserisci il nome del team" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Invita membri" + msgid "onboarding.choice.team-up.invite-members-info" msgstr "" "Non dimenticarti di includere ogni tipo di persona. Programmatori, " "designers, responsabili... la diversità si somma :)" +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Crea un team e invita più tardi" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Crea un team e manda inviti" + msgid "onboarding.choice.team-up.roles" msgstr "Invita con il ruolo:" +msgid "onboarding.choice.title" +msgstr "Benvenuti su Penpot" + +msgid "onboarding.contrib.alt" +msgstr "Open Source" + msgid "onboarding.contrib.desc1" msgstr "" "Penpot è Open Source, creato dalla comunità per la comunità. Se vuoi " "collaborare, sei più che benvenuto/a!" +msgid "onboarding.contrib.desc2.1" +msgstr "Puoi accedere al" + +msgid "onboarding.contrib.desc2.2" +msgstr "e seguire le istruzioni di contribuzione :)" + +msgid "onboarding.contrib.link" +msgstr "progetto su github" + +msgid "onboarding.contrib.title" +msgstr "Contribuente Open Source?" + +msgid "onboarding.newsletter.accept" +msgstr "Si, iscrivimi" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "" +"La tua richiesta di iscrizione è stata invita, ti invieremo un'email di " +"conferma." + +msgid "onboarding.newsletter.decline" +msgstr "No, grazie" + +msgid "onboarding.newsletter.desc" +msgstr "" +"Iscriviti alla nostra newsletter per rimanere aggiornato con le news e i " +"progressi di sviluppo del prodotto." + +msgid "onboarding.newsletter.policy" +msgstr "Condizioni sulla Privacy." + +msgid "onboarding.newsletter.privacy1" +msgstr "In quanto ci teniamo alla privacy, qui puoi vedere la nostra " + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Ti invieremo solamente email per te rilevanti. Puoi cancellare l'iscrizione " +"in qualsiasi momento nel tuo profilo utente o tramite il link presente in " +"qualsiasi delle nostre newsletter." + +msgid "onboarding.newsletter.title" +msgstr "Vuoi ricevere le news di Pentot?" + msgid "onboarding.slide.0.alt" msgstr "Crea design" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component-in-bulk.hint" +msgid "onboarding.slide.0.desc1" msgstr "" -"Stai per aggiornare i componenti in una libreria condivisa. Questo potrebbe " -"causare modifiche nei file che la utilizzano." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "Annulla pubblicazione libreria" -msgstr[1] "Annulla pubblicazione librerie" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.message" -msgstr "Aggiorna un componente in una libreria condivisa" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "Annullare la pubblicazione di questa libreria?" -msgstr[1] "Annullare la pubblicazione di queste librerie?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "" -"Se annulli la pubblicazione, gli elementi diventeranno parte della libreria " -"di questo file." -msgstr[1] "" -"Se annulli la pubblicazione, gli elementi diventeranno parte della libreria " -"di questi file." +"Crea splendide interfacce utente in collaborazione con tutti i membri del " +"team." msgid "onboarding.slide.0.desc2" msgstr "" @@ -2040,26 +2053,12 @@ msgstr "" msgid "onboarding.slide.0.title" msgstr "Librerie di design, stili e componenti" -msgid "onboarding.choice.team-up.invite-members-skip" -msgstr "Crea un team e invita più tardi" +msgid "onboarding.slide.1.alt" +msgstr "Prototipi interattivi" -msgid "onboarding.contrib.desc2.2" -msgstr "e seguire le istruzioni di contribuzione :)" - -msgid "onboarding.contrib.link" -msgstr "progetto su github" - -msgid "onboarding.slide.0.desc1" -msgstr "" -"Crea splendide interfacce utente in collaborazione con tutti i membri del " -"team." - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.hint" -msgstr "" -"Stai per aggiornare un componente in una libreria condivisa. Questo potrebbe " -"causare modifiche nei file che la utilizzano." +msgid "onboarding.slide.1.desc1" +msgstr "Crea interazioni complete per imitare al meglio il prodotto finale." #: src/app/main/ui/dashboard/team.cljs msgid "title.team-invitations" -msgstr "Inviti - %s - Penpot" +msgstr "Inviti - %s - Penpot" \ No newline at end of file diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po index 8075e8a062..805c6a6c84 100644 --- a/frontend/translations/pl.po +++ b/frontend/translations/pl.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Kevin Nowald \n" -"Language-Team: Polish \n" +"Language-Team: Polish " +"\n" "Language: pl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -172,11 +172,23 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Wysłaliśmy email weryfikacyjny na adres" +msgid "common.publish" +msgstr "Opublikuj" + +msgid "common.share-link.all-users" +msgstr "Wszyscy użytkownicy Penpot" + msgid "common.share-link.confirm-deletion-link-description" msgstr "" "Czy na pewno chcesz usunąć ten link? Jeśli to zrobisz, nie będzie już " "dostępny dla nikogo" +msgid "common.share-link.current-tag" +msgstr "(aktualne)" + +msgid "common.share-link.destroy-link" +msgstr "Usuń link" + msgid "common.share-link.get-link" msgstr "Uzyskaj link" @@ -186,15 +198,77 @@ msgstr "Link skopiowano pomyślnie" msgid "common.share-link.link-deleted-success" msgstr "Link usunięto pomyślnie" +msgid "common.share-link.manage-ops" +msgstr "Zarządzaj uprawnieniami" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 udostępniona strona" +msgstr[1] "%s udostępnione strony" +msgstr[2] "%s udostępnionych stron" + +msgid "common.share-link.permissions-can-comment" +msgstr "Może komentować" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Może sprawdzać kod" + msgid "common.share-link.permissions-hint" msgstr "Każdy, kto ma link, będzie miał dostęp" +msgid "common.share-link.permissions-pages" +msgstr "Udostępnione strony" + msgid "common.share-link.placeholder" msgstr "Tutaj pojawi się link do udostępniania" +msgid "common.share-link.team-members" +msgstr "Tylko członkowie zespołu" + msgid "common.share-link.title" msgstr "Udostępnij prototypy" +msgid "common.share-link.view-all" +msgstr "Wybierz wszystko" + +msgid "common.unpublish" +msgstr "Cofnij publikację" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Zarządzanie zespołem" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot jest przeznaczony dla zespołów. Zaproś członków do wspólnej pracy " +"nad projektami i plikami" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Połącz siły!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Naucz się podstaw obsługi Penpot, bawiąc się tym praktycznym tutorialem." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Zacznij tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Praktyczny Tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Wybierz się na spacer po Penpot i poznaj jego główne funkcje." + +#: src/app/main/ui/dashboard/projects.cljs +#, fuzzy +msgid "dasboard.walkthrough-hero.title" +msgstr "Przewodnik po interfejsie" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Dodaj jako Udostępnioną Bibliotekę" @@ -219,6 +293,12 @@ msgstr "Twój Penpot" msgid "dashboard.delete-team" msgstr "Usuń zespół" +msgid "dashboard.download-binary-file" +msgstr "Pobierz plik Penpot (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Pobierz plik standardowy (.svg + .json)" + msgid "dashboard.draft-title" msgstr "Szkic" @@ -242,6 +322,9 @@ msgstr "" "przejdź do [Biblioteki i " "szablony](https://penpot.app/libraries-templates.html)" +msgid "dashboard.export-binary-multi" +msgstr "Pobierz %s plików Penpot (.penpot)" + msgid "dashboard.export-frames" msgstr "Eksportuj obszary kompozycji do PDF" @@ -281,6 +364,9 @@ msgstr "Eksportuj wybrane" msgid "dashboard.export-single" msgstr "Eksportuj plik Penpot" +msgid "dashboard.export-standard-multi" +msgstr "Pobierz %s plików standardowych (.svg + .json)" + msgid "dashboard.export.detail" msgstr "* Może zawierać komponenty, grafikę, kolory i/lub typografię." @@ -399,6 +485,15 @@ msgstr "Zaproś do zespołu" msgid "dashboard.leave-team" msgstr "Opuść zespół" +msgid "dashboard.libraries-and-templates" +msgstr "Biblioteki i szablony" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Poznaj więcej z nich i dowiedz się, jak pomóc" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Wystąpił problem z importem szablonu. Szablon nie został zaimportowany." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Biblioteki współdzielone" @@ -579,6 +674,10 @@ msgstr "Wyniki wyszukiwania" msgid "dashboard.type-something" msgstr "Wpisz, aby wyszukać wyniki" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Cofnij publikację biblioteki" + #: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "Aktualizuj ustawienia" @@ -599,6 +698,14 @@ msgstr "Twoje imię" msgid "dashboard.your-penpot" msgstr "Twój Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Uwaga" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Komponenty do aktualizacji:" @@ -763,6 +870,20 @@ msgstr "Masz ochotę porozmawiać? Dołącz do nas na Gitter" msgid "feedback.description" msgstr "Opis" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Przejdź do forum Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"Cieszymy się, że tu jesteś. Jeśli potrzebujesz pomocy, poszukaj jej zanim " +"napiszesz." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Społeczność Penpot" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-go-to" msgstr "Przejdź do dyskusji" @@ -795,6 +916,14 @@ msgstr "" msgid "feedback.title" msgstr "Email" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "Przejdź do Twittera" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "Służymy pomocą w kwestiach technicznych." + #: src/app/main/ui/settings/password.cljs msgid "generic.error" msgstr "Wystąpił błąd" @@ -1068,6 +1197,10 @@ msgstr "Zamknij" msgid "labels.comments" msgstr "Komentarze" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Społeczność" + #: src/app/main/ui/settings/password.cljs msgid "labels.confirm-password" msgstr "Potwierdź hasło" @@ -1081,6 +1214,9 @@ msgstr "Kontynuuj" msgid "labels.continue-with" msgstr "Kontynuuj z" +msgid "labels.continue-with-penpot" +msgstr "Możesz kontynuować z kontem Penpot" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "Stwórz" @@ -1222,6 +1358,9 @@ msgstr "Biblioteki i szablony" msgid "labels.link" msgstr "Link" +msgid "labels.log-or-sign" +msgstr "Zaloguj się lub zarejestruj" + #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" msgstr "Wyloguj" @@ -1401,6 +1540,9 @@ msgstr "Biblioteki Współdzielone" msgid "labels.show-all-comments" msgstr "Pokaż wszystkie komentarze" +msgid "labels.show-comments-list" +msgstr "Pokaż listę komentarzy" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-your-comments" msgstr "Pokaż tylko swoje komentarze" @@ -1599,6 +1741,34 @@ msgstr "Czy na pewno chcesz usunąć ten projekt?" msgid "modals.delete-project-confirm.title" msgstr "Usuń projekt" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Usuń plik" +msgstr[1] "Usuń pliki" +msgstr[2] "Usuń pliki" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Czy na pewno chcesz usunąć ten plik?" +msgstr[1] "Czy na pewno chcesz usunąć te pliki?" +msgstr[2] "Czy na pewno chcesz usunąć te pliki?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Ten plik ma biblioteki używane w tym pliku:" +msgstr[1] "Ten plik ma biblioteki używane w tych plikach:" +msgstr[2] "Ten plik ma biblioteki używane w tych plikach:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Te pliki mają biblioteki, które są używane w tym pliku:" +msgstr[1] "Te pliki mają biblioteki, które są używane w tych plikach:" +msgstr[2] "Te pliki mają biblioteki, które są używane w tych plikach:" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "Usuń zespół" @@ -3450,6 +3620,70 @@ msgstr "Zgrupowane warstwy" msgid "workspace.options.layer-options.title.multiple" msgstr "Wybrane warstwy" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Min.Wysokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Min.Szerokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Maksymalna wysokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Maksymalna szerokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Minimalna wysokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Minimalna szerokość" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Dół" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Kolumna" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "Wiersz" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Margines" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding" +msgstr "Padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "Prawa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Góra" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "dół" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "środek" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "góra" + #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Więcej kolorów" @@ -3903,6 +4137,9 @@ msgstr "Ścieżka" msgid "workspace.shape.menu.reset-overrides" msgstr "Zresetuj nadpisania" +msgid "workspace.shape.menu.restore-main" +msgstr "Przywróć główny komponent" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.select-layer" msgstr "Zaznacz warstwę" @@ -4171,242 +4408,4 @@ msgid "workspace.updates.update" msgstr "Aktualizuj" msgid "workspace.viewport.click-to-close-path" -msgstr "Kliknij, aby zamknąć ścieżkę" - -msgid "common.share-link.current-tag" -msgstr "(aktualne)" - -msgid "common.share-link.destroy-link" -msgstr "Usuń link" - -msgid "common.share-link.view-all" -msgstr "Wybierz wszystko" - -msgid "common.share-link.all-users" -msgstr "Wszyscy użytkownicy Penpot" - -msgid "common.share-link.permissions-can-comment" -msgstr "Może komentować" - -msgid "common.share-link.permissions-can-inspect" -msgstr "Może sprawdzać kod" - -msgid "common.share-link.permissions-pages" -msgstr "Udostępnione strony" - -msgid "common.share-link.team-members" -msgstr "Tylko członkowie zespołu" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-subtitle1" -msgstr "" -"Cieszymy się, że tu jesteś. Jeśli potrzebujesz pomocy, poszukaj jej zanim " -"napiszesz." - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.community" -msgstr "Społeczność" - -msgid "labels.log-or-sign" -msgstr "Zaloguj się lub zarejestruj" - -msgid "labels.show-comments-list" -msgstr "Pokaż listę komentarzy" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.right" -msgstr "Prawa" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.top" -msgstr "Góra" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "środek" - -msgid "dashboard.export-binary-multi" -msgstr "Pobierz %s plików Penpot (.penpot)" - -msgid "dashboard.libraries-and-templates" -msgstr "Biblioteki i szablony" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "Wystąpił problem z importem szablonu. Szablon nie został zaimportowany." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-subtitle1" -msgstr "Służymy pomocą w kwestiach technicznych." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot jest przeznaczony dla zespołów. Zaproś członków do wspólnej pracy nad " -"projektami i plikami" - -msgid "dashboard.export-standard-multi" -msgstr "Pobierz %s plików standardowych (.svg + .json)" - -msgid "dashboard.download-standard-file" -msgstr "Pobierz plik standardowy (.svg + .json)" - -msgid "common.share-link.manage-ops" -msgstr "Zarządzaj uprawnieniami" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-title" -msgstr "Społeczność Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-go-to" -msgstr "Przejdź do Twittera" - -msgid "labels.continue-with-penpot" -msgstr "Możesz kontynuować z kontem Penpot" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "Czy na pewno chcesz usunąć ten plik?" -msgstr[1] "Czy na pewno chcesz usunąć te pliki?" -msgstr[2] "Czy na pewno chcesz usunąć te pliki?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "Te pliki mają biblioteki, które są używane w tym pliku:" -msgstr[1] "Te pliki mają biblioteki, które są używane w tych plikach:" -msgstr[2] "Te pliki mają biblioteki, które są używane w tych plikach:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Ten plik ma biblioteki używane w tym pliku:" -msgstr[1] "Ten plik ma biblioteki używane w tych plikach:" -msgstr[2] "Ten plik ma biblioteki używane w tych plikach:" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.bottom" -msgstr "Dół" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" -msgstr "Kolumna" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.padding" -msgstr "Padding" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" -msgstr "Wiersz" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "dół" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "góra" - -msgid "common.unpublish" -msgstr "Cofnij publikację" - -#: src/app/main/ui/dashboard/projects.cljs -#, fuzzy -msgid "dasboard.walkthrough-hero.title" -msgstr "Przewodnik po interfejsie" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "Ok" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Uwaga" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "Przejdź do forum Penpot" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" -msgstr "Min.Wysokość" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" -msgstr "Min.Szerokość" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" -msgstr "Maksymalna wysokość" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" -msgstr "Maksymalna szerokość" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" -msgstr "Minimalna wysokość" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" -msgstr "Minimalna szerokość" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin" -msgstr "Margines" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "Wybierz się na spacer po Penpot i poznaj jego główne funkcje." - -msgid "dashboard.download-binary-file" -msgstr "Pobierz plik Penpot (.penpot)" - -msgid "common.publish" -msgstr "Opublikuj" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Zarządzanie zespołem" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Połącz siły!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "" -"Naucz się podstaw obsługi Penpot, bawiąc się tym praktycznym tutorialem." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Zacznij tutorial" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Praktyczny Tutorial" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Poznaj więcej z nich i dowiedz się, jak pomóc" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Cofnij publikację biblioteki" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Usuń plik" -msgstr[1] "Usuń pliki" -msgstr[2] "Usuń pliki" - -msgid "workspace.shape.menu.restore-main" -msgstr "Przywróć główny komponent" - -msgid "common.share-link.page-shared" -msgid_plural "common.share-link.page-shared" -msgstr[0] "1 udostępniona strona" -msgstr[1] "%s udostępnione strony" -msgstr[2] "%s udostępnionych stron" +msgstr "Kliknij, aby zamknąć ścieżkę" \ No newline at end of file diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index ed95fd0756..33d291b7c8 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-24 23:03+0000\n" "Last-Translator: Hugo Figueira \n" -"Language-Team: Portuguese (Brazil) \n" +"Language-Team: Portuguese (Brazil) " +"\n" "Language: pt_BR\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -171,11 +171,23 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Enviamos um e-mail de verificação para" +msgid "common.publish" +msgstr "Publicar" + +msgid "common.share-link.all-users" +msgstr "Todos os usuários do Penpot" + msgid "common.share-link.confirm-deletion-link-description" msgstr "" "Tem certeza de que deseja remover este link? Se você fizer isso, ele não " "estará mais disponível para ninguém" +msgid "common.share-link.current-tag" +msgstr "(atual)" + +msgid "common.share-link.destroy-link" +msgstr "Destruir link" + msgid "common.share-link.get-link" msgstr "Obter link" @@ -185,24 +197,47 @@ msgstr "Link copiado com sucesso" msgid "common.share-link.link-deleted-success" msgstr "Link excluído com sucesso" +msgid "common.share-link.manage-ops" +msgstr "Gerenciar Permissões" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 página compartilhada" +msgstr[1] "%s páginas compartilhadas" + msgid "common.share-link.permissions-can-access" msgstr "Pode acessar" +msgid "common.share-link.permissions-can-comment" +msgstr "Pode comentar" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Pode inspecionar o código" + msgid "common.share-link.permissions-can-view" msgstr "Pode visualizar" msgid "common.share-link.permissions-hint" msgstr "Qualquer pessoa com o link terá acesso" +msgid "common.share-link.permissions-pages" +msgstr "Páginas compartilhadas" + msgid "common.share-link.placeholder" msgstr "O link compartilhável aparecerá aqui" msgid "common.share-link.remove-link" msgstr "Remover link" +msgid "common.share-link.team-members" +msgstr "Apenas membros da equipe" + msgid "common.share-link.title" msgstr "Compartilhar protótipos" +msgid "common.share-link.view-all" +msgstr "Selecionar todos" + msgid "common.share-link.view-all-pages" msgstr "Todas as páginas" @@ -212,6 +247,47 @@ msgstr "Somente esta página" msgid "common.share-link.view-selected-pages" msgstr "Páginas selecionadas" +msgid "common.unpublish" +msgstr "Cancelar publicação" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Gerenciamento de equipe" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot é destinado a equipes. Convide membros para trabalharem juntos em " +"projetos e arquivos" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Juntem-se!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Aprenda o básico no Penpot enquanto se diverte com este tutorial prático." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Começar o tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial prático" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Faça um passeio pelo Penpot e conheça suas principais características." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Comece o passeio" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Passo a passo da interface" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Adicionar à Biblioteca Compartilhada" @@ -236,6 +312,12 @@ msgstr "Sua Penpot" msgid "dashboard.delete-team" msgstr "Deletar equipe" +msgid "dashboard.download-binary-file" +msgstr "Baixar arquivo Penpot (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Baixar arquivo padrão (.svg + .json)" + msgid "dashboard.draft-title" msgstr "Rascunho" @@ -259,6 +341,9 @@ msgstr "" "modelos, vá para [Bibliotecas & " "modelos](https://penpot.app/libraries-templates.html)" +msgid "dashboard.export-binary-multi" +msgstr "Baixar %s arquivos Penpot (.penpot)" + msgid "dashboard.export-frames" msgstr "Exportar pranchetas para PDF" @@ -298,6 +383,9 @@ msgstr "Exportar seleção" msgid "dashboard.export-single" msgstr "Exportar arquivo" +msgid "dashboard.export-standard-multi" +msgstr "Baixar %s arquivos padrões (.svg + .json)" + msgid "dashboard.export.detail" msgstr "* Pode incluir componentes, gráficos, cores e/ou tipografias." @@ -314,12 +402,41 @@ msgstr "" msgid "dashboard.export.options.all.title" msgstr "Exportar bibliotecas compartilhadas" +msgid "dashboard.export.options.detach.message" +msgstr "" +"Bibliotecas compartilhadas não serão incluídas na exportação e nenhum ativo " +"será adicionado à biblioteca. " + +msgid "dashboard.export.options.detach.title" +msgstr "Trate os ativos da biblioteca compartilhada como objetos básicos" + +msgid "dashboard.export.options.merge.message" +msgstr "" +"Seu arquivo será exportado com todos os ativos externos mesclados na " +"biblioteca de arquivos." + +msgid "dashboard.export.options.merge.title" +msgstr "Incluir ativos da biblioteca compartilhada na bibliotecas de arquivos" + +msgid "dashboard.export.title" +msgstr "Exportar arquivos" + msgid "dashboard.fonts.deleted-placeholder" msgstr "Fonte deletada" +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "Recusar tudo" + msgid "dashboard.fonts.empty-placeholder" msgstr "Você ainda não tem nenhuma fonte personalizada instalada." +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "1 fonte adicionada" +msgstr[1] "%s fontes adicionadas" + #, markdown msgid "dashboard.fonts.hero-text1" msgstr "" @@ -337,9 +454,46 @@ msgstr "" "Serviço da Penpot](https://penpot.app/terms.html). Você pode também querer " "ler sobre [licenciamento de fontes](https://www.typography.com/faq)." +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Carregar tudo" + msgid "dashboard.import" msgstr "Importar arquivos" +msgid "dashboard.import.analyze-error" +msgstr "Ops! Não foi possível importar este arquivo" + +msgid "dashboard.import.import-error" +msgstr "Ocorreu um problema ao importar o arquivo. O arquivo não foi importado." + +msgid "dashboard.import.import-message" +msgstr "%s arquivos foram importados com sucesso." + +msgid "dashboard.import.import-warning" +msgstr "Alguns arquivos continham objetos inválidos que foram removidos." + +msgid "dashboard.import.progress.process-colors" +msgstr "Processando cores" + +msgid "dashboard.import.progress.process-components" +msgstr "Processando componentes" + +msgid "dashboard.import.progress.process-media" +msgstr "Processando mídia" + +msgid "dashboard.import.progress.process-page" +msgstr "Processando página: %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "Processando tipografia" + +msgid "dashboard.import.progress.upload-data" +msgstr "Carregando dados para o servidor (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "Carregando arquivo: %s" + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Convidar para a equipe" @@ -348,6 +502,15 @@ msgstr "Convidar para a equipe" msgid "dashboard.leave-team" msgstr "Sair da equipe" +msgid "dashboard.libraries-and-templates" +msgstr "Biblioteca e Modelos" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Explore mais deles e saiba como contribuir" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Ocorreu um problema ao importar o modelo. O modelo não foi importado." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Bibliotecas" @@ -387,6 +550,14 @@ msgstr "+ Novo projeto" msgid "dashboard.new-project-prefix" msgstr "Novo projeto" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "Envie-me notícias, atualizações de produtos e recomendações sobre o Penpot." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Assinatura de Newsletter" + #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" msgstr "Nenhuma correspondência encontrada para “%s“" @@ -442,6 +613,10 @@ msgstr "Quer remover sua conta?" msgid "dashboard.remove-shared" msgstr "Remover da Biblioteca Compartilhada" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "Salvar configurações" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.search-placeholder" msgstr "Pesquisar…" @@ -514,6 +689,10 @@ msgstr "Resultados da pesquisa" msgid "dashboard.type-something" msgstr "Digite para pesquisar resultados" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Cancelar publicação da biblioteca" + #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "Atualizar configurações" @@ -534,6 +713,18 @@ msgstr "Seu nome" msgid "dashboard.your-penpot" msgstr "Sua Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ok" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Atenção" + +#: src/app/main/ui/confirm.cljs +msgid "ds.component-subtitle" +msgstr "Componentes para atualizar:" + #: src/app/main/ui/confirm.cljs msgid "ds.confirm-cancel" msgstr "Cancelar" @@ -550,10 +741,21 @@ msgstr "Tem certeza?" msgid "ds.updated-at" msgstr "Atualizado: %s" +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Provedor de autenticação não configurado." + +msgid "errors.auth.unable-to-login" +msgstr "Parece que você não está autenticado ou a sessão expirou." + #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" msgstr "Seu navegador não pode fazer esta operação" +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Este arquivo já foi usado com componentes V2 habilitado." + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" msgstr "E-mail já utilizado" @@ -562,10 +764,20 @@ msgstr "E-mail já utilizado" msgid "errors.email-already-validated" msgstr "E-mail já validado." +msgid "errors.email-as-password" +msgstr "Você não pode usar seu e-mail como senha" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.email-has-permanent-bounces" +msgstr "O e-mail «%s» tem muitos relatórios de devolução permanentes." + #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-invalid-confirmation" msgstr "E-mail de confirmação deve ser o mesmo" +msgid "errors.email-spam-or-permanent-bounces" +msgstr "O e-mail «%s» foi denunciado como spam ou devolvido permanentemente." + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.generic" msgstr "Algo errado aconteceu." @@ -578,6 +790,13 @@ msgstr "Autenticação com google desativada no backend" msgid "errors.invalid-color" msgstr "Cor inválida" +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.invite-invalid" +msgstr "Convite inválido" + +msgid "errors.invite-invalid.info" +msgstr "Este convite pode ter sido cancelado ou pode ter expirado." + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "Autenticação por LDAP está desativada." @@ -597,6 +816,12 @@ msgstr "Parece que o conteúdo da imagem não corresponde à extensão do arquiv msgid "errors.media-type-not-allowed" msgstr "Parece que esta não é uma imagem válida." +#: src/app/main/ui/dashboard/team.cljs +msgid "errors.member-is-muted" +msgstr "" +"O perfil que você está convidando tem e-mails silenciados (relatos de spam " +"ou altas devoluções)." + msgid "errors.network" msgstr "Não foi possível conectar ao servidor backend." @@ -608,10 +833,27 @@ msgstr "A senha de confirmação deve ser a mesma" msgid "errors.password-too-short" msgstr "A senha deve ter pelo menos 8 caracteres" +#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.profile-is-muted" +msgstr "Seu perfil tem e-mails silenciados (relatos de spam ou altas devoluções)." + #: src/app/main/ui/auth/register.cljs msgid "errors.registration-disabled" msgstr "O registro de contas está desativado no momento." +msgid "errors.team-leave.insufficient-members" +msgstr "" +"Membros insuficientes para deixar a equipe, você provavelmente deseja " +"excluí-la." + +msgid "errors.team-leave.member-does-not-exists" +msgstr "O membro que você tentou atribuir não existe." + +msgid "errors.team-leave.owner-cant-leave" +msgstr "" +"O proprietário não pode sair da equipe, você deve reatribuir a função de " +"proprietário." + msgid "errors.terms-privacy-agreement-invalid" msgstr "Você deve aceitar nossos termos de serviço e política de privacidade." @@ -643,6 +885,20 @@ msgstr "Com vontade de falar? Converse conosco no Gitter" msgid "feedback.description" msgstr "Descrição" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Vá para o fórum do Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"Estamos felizes em tê-lo aqui. Se precisar de ajuda, pesquise antes de " +"postar." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Comunidade Penpot" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" msgstr "Assunto" @@ -658,6 +914,18 @@ msgstr "" msgid "feedback.title" msgstr "E-mail" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "Vá para o Twitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "Aqui para ajuda com suas dúvidas técnicas." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-title" +msgstr "Conta de suporte no Twitter" + #: src/app/main/ui/settings/password.cljs msgid "generic.error" msgstr "Um erro ocorreu" @@ -746,6 +1014,10 @@ msgstr "Y" msgid "handoff.attributes.shadow.shorthand.spread" msgstr "S" +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Traço" + #, permanent msgid "handoff.attributes.stroke.alignment.center" msgstr "Centro" @@ -834,6 +1106,9 @@ msgstr "Código" msgid "handoff.tabs.code.selected.circle" msgstr "Círculo" +msgid "handoff.tabs.code.selected.component" +msgstr "Componente" + msgid "handoff.tabs.code.selected.curve" msgstr "Curva" @@ -846,6 +1121,9 @@ msgstr "Grupo" msgid "handoff.tabs.code.selected.image" msgstr "Imagem" +msgid "handoff.tabs.code.selected.mask" +msgstr "Máscara" + #: src/app/main/ui/handoff/right_sidebar.cljs msgid "handoff.tabs.code.selected.multiple" msgstr "%s selecionados" @@ -869,6 +1147,14 @@ msgstr "Informação" msgid "history.alert-message" msgstr "Você está vendo a versão %s" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Atalhos" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.about-penpot" +msgstr "Sobre Penpot" + msgid "labels.accept" msgstr "Aceitar" @@ -883,6 +1169,12 @@ msgstr "Administrador" msgid "labels.all" msgstr "Todos" +msgid "labels.and" +msgstr "e" + +msgid "labels.back" +msgstr "Voltar" + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "" @@ -900,10 +1192,17 @@ msgstr "Cancelar" msgid "labels.centered" msgstr "Centro" +msgid "labels.close" +msgstr "Fechar" + #: src/app/main/ui/dashboard/comments.cljs msgid "labels.comments" msgstr "Comentários" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Comunidade" + #: src/app/main/ui/settings/password.cljs msgid "labels.confirm-password" msgstr "Confirmar senha" @@ -911,6 +1210,15 @@ msgstr "Confirmar senha" msgid "labels.content" msgstr "Conteúdo" +msgid "labels.continue" +msgstr "Continuar" + +msgid "labels.continue-with" +msgstr "Continue com" + +msgid "labels.continue-with-penpot" +msgstr "Você pode continuar com a conta Penpot" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "Criar" @@ -930,6 +1238,9 @@ msgstr "Fontes personalizadas" msgid "labels.dashboard" msgstr "Painel" +msgid "labels.default" +msgstr "padrão" + #: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete" msgstr "Excluir" @@ -942,6 +1253,10 @@ msgstr "Excluir comentário" msgid "labels.delete-comment-thread" msgstr "Excluir tópico" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.delete-invitation" +msgstr "Excluir convite" + #: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete-multi-files" msgstr "Excluir %s arquivos" @@ -954,6 +1269,9 @@ msgstr "Rascunhos" msgid "labels.edit" msgstr "Editar" +msgid "labels.edit-file" +msgstr "Editar arquivo" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.editor" msgstr "Editor" @@ -962,6 +1280,13 @@ msgstr "Editor" msgid "labels.email" msgstr "E-mail" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Expirado" + +msgid "labels.export" +msgstr "Exportar" + #: src/app/main/ui/settings/feedback.cljs msgid "labels.feedback-disabled" msgstr "Feedback desativado" @@ -985,6 +1310,10 @@ msgstr "Estilos" msgid "labels.fonts" msgstr "Fontes" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Repositório Github" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.give-feedback" msgstr "Enviar feedback" @@ -992,6 +1321,10 @@ msgstr "Enviar feedback" msgid "labels.go-back" msgstr "Voltar" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.help-center" +msgstr "Central de Ajuda" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" msgstr "Ocultar comentários resolvidos" @@ -1015,10 +1348,24 @@ msgstr "" msgid "labels.internal-error.main-message" msgstr "Erro interno" +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.invitations" +msgstr "Convites" + #: src/app/main/ui/settings/options.cljs msgid "labels.language" msgstr "Linguagem" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.libraries-and-templates" +msgstr "Biblioteca & Modelos" + +msgid "labels.link" +msgstr "Link" + +msgid "labels.log-or-sign" +msgstr "Entrar ou cadastrar-se" + #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" msgstr "Sair" @@ -1026,6 +1373,10 @@ msgstr "Sair" msgid "labels.manage-fonts" msgstr "Gerenciar fontes" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Membro" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.members" msgstr "Membros" @@ -1038,10 +1389,23 @@ msgstr "Nome" msgid "labels.new-password" msgstr "Nova senha" +msgid "labels.next" +msgstr "Próximo" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs msgid "labels.no-comments-available" msgstr "Você não tem notificações de comentários pendentes" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "Não há convites." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "" +"Pressione o botão \"Convidar para equipe\" para convidar mais membros para " +"esta equipe." + #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" msgstr "Você está conectado como" @@ -1090,6 +1454,10 @@ msgstr "Proprietário" msgid "labels.password" msgstr "Senha" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "Pendente" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.permissions" msgstr "Permissões" @@ -1113,6 +1481,10 @@ msgstr "Notas de lançamento" msgid "labels.remove" msgstr "Excluir" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "Remover membro" + #: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "labels.rename" msgstr "Renomear" @@ -1121,6 +1493,10 @@ msgstr "Renomear" msgid "labels.rename-team" msgstr "Renomear equipe" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "Reenviar convite" + #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Tentar novamente" @@ -1155,6 +1531,9 @@ msgstr "Serviço indisponível" msgid "labels.settings" msgstr "Configurações" +msgid "labels.share-prototype" +msgstr "Compartilhar protótipo" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.shared-libraries" msgstr "Bibliotecas" @@ -1163,6 +1542,9 @@ msgstr "Bibliotecas" msgid "labels.show-all-comments" msgstr "Mostrar todos os comentários" +msgid "labels.show-comments-list" +msgstr "Mostrar lista de comentários" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-your-comments" msgstr "Mostrar apenas seus comentários" @@ -1171,6 +1553,20 @@ msgstr "Mostrar apenas seus comentários" msgid "labels.sign-out" msgstr "Sair" +msgid "labels.skip" +msgstr "Pular" + +msgid "labels.start" +msgstr "Começar" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Status" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Tutorial" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "Atualizar" @@ -1192,10 +1588,21 @@ msgstr "Carregando…" msgid "labels.viewer" msgstr "Visualizador" +msgid "labels.workspace" +msgstr "Área de trabalho" + #: src/app/main/ui/comments.cljs msgid "labels.write-new-comment" msgstr "Escrever novo comentário" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(você)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.your-account" +msgstr "Sua conta" + #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" msgstr "Carregando imagem…" @@ -1215,6 +1622,10 @@ msgstr "" msgid "modals.add-shared-confirm.message" msgstr "Adicionar “%s” à Biblioteca Compartilhada" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.big-nudge" +msgstr "Grande deslocamento" + #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.confirm-email" msgstr "Verificar o novo e-mail" @@ -1237,6 +1648,12 @@ msgstr "Alterar e-mail" msgid "modals.change-email.title" msgstr "Alterar seu e-mail" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"Você é o dono desta equipe. Selecione outro membro para promover a " +"proprietário antes de sair." + #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" msgstr "Cancelar e manter minha conta" @@ -1291,6 +1708,14 @@ msgstr "Tem certeza de que deseja excluir %s arquivos?" msgid "modals.delete-file-multi-confirm.title" msgstr "Excluindo %s arquivos" +msgid "modals.delete-font-variant.message" +msgstr "" +"Tem certeza de que deseja excluir este estilo de fonte? Ele não será " +"carregado se for usado em um arquivo." + +msgid "modals.delete-font-variant.title" +msgstr "Excluindo estilo de fonte" + msgid "modals.delete-font.message" msgstr "" "Tem certeza que deseja excluir essa fonte? Ela não será carregada se for " @@ -1299,6 +1724,78 @@ msgstr "" msgid "modals.delete-font.title" msgstr "Excluindo fonte" +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Tem certeza de que deseja excluir esta página?" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Excluir página" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Excluir projeto" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Tem certeza de que deseja excluir este projeto?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Excluir projeto" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Excluir arquivo" +msgstr[1] "Excluir arquivos" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Tem certeza de que deseja excluir este arquivo?" +msgstr[1] "Tem certeza de que deseja excluir estes arquivos?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Este arquivo possui bibliotecas que estão sendo usadas neste arquivo:" +msgstr[1] "Este arquivo possui bibliotecas que estão sendo usadas nestes arquivos:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Esses arquivos possuem bibliotecas que estão sendo usadas neste arquivo:" +msgstr[1] "Esses arquivos têm bibliotecas que estão sendo usadas nesses arquivos:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Excluindo arquivo" +msgstr[1] "Excluindo arquivos" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Excluindo arquivo" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Excluir equipe" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Tem certeza de que deseja excluir esta equipe? Todos os projetos e arquivos " +"associados à equipe serão excluídos permanentemente." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Excluindo equipe" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Excluir membro" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.delete-team-member-confirm.message" msgstr "Tem certeza de que deseja excluir este membro da equipe?" @@ -1311,6 +1808,23 @@ msgstr "Excluir membro da equipe" msgid "modals.invite-member-confirm.accept" msgstr "Enviar convite" +msgid "modals.invite-member.emails" +msgstr "E-mails, separados por vírgulas" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Convite membros para a equipe" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Como você é o único membro desta equipe, a equipe será excluída junto com " +"seus projetos e arquivos." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "Tem certeza de que deseja sair da equipe %s?" + msgid "modals.leave-and-reassign.forbidden" msgstr "" "Você não pode deixar a equipe se não houver outro membro para promover a " @@ -1348,10 +1862,20 @@ msgstr "Tem certeza de que deseja sair deste equipe?" msgid "modals.leave-confirm.title" msgstr "Saindo da equipe" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.nudge-title" +msgstr "Quantidade de deslocamento" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.accept" msgstr "Promover" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.hint" +msgstr "" +"Se você transferir a propriedade, mudará sua função para Admin, perdendo " +"algumas permissões sobre essa equipe. " + #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.message" msgstr "Tem certeza de que deseja promover este usuário a proprietário?" @@ -1375,6 +1899,52 @@ msgstr "" msgid "modals.remove-shared-confirm.message" msgstr "Remover “%s” da Biblioteca Compartilhada" +#: src/app/main/ui/workspace/nudge.cljs +msgid "modals.small-nudge" +msgstr "Pequeno deslocamento" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Cancelar publicação" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Se você cancelar a publicação, os ativos nele se tornarão uma biblioteca " +"desse arquivo." +msgstr[1] "" +"Se você cancelar a publicação, os ativos nele se tornarão uma biblioteca " +"desses arquivos." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Tem certeza de que deseja cancelar a publicação desta biblioteca?" +msgstr[1] "Tem certeza de que deseja cancelar a publicação dessas bibliotecas?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Está em uso neste arquivo:" +msgstr[1] "Está em uso nestes arquivos:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Cancelar publicação da biblioteca" +msgstr[1] "Cancelar publicação de bibliotecas" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"Você está prestes a atualizar componentes em uma biblioteca compartilhada. " +"Isso pode afetar outros arquivos que o utilizam." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.message" +msgstr "Atualizar componentes em uma biblioteca compartilhada" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.accept" msgstr "Atualizar componente" @@ -1386,8 +1956,8 @@ msgstr "Cancelar" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" msgstr "" -"Você está prestes a atualizar um componente em uma biblioteca compartilhada. " -"Isso pode afetar outros arquivos que a utilizam." +"Você está prestes a atualizar um componente em uma biblioteca " +"compartilhada. Isso pode afetar outros arquivos que a utilizam." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.message" @@ -1411,6 +1981,230 @@ msgstr "Perfil salvo com sucesso!" msgid "notifications.validation-email-sent" msgstr "E-mail de verificação enviado para %s. Verifique seu e-mail!" +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Você deve saber que existem muitos recursos disponíveis para ajudá-lo a " +"começar a usar o Penpot, como o Guia do Usuário e nosso canal no Youtube." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Informações detalhadas sobre como usar o Penpot. Da prototipagem à " +"organização ou compartilhamento de projetos." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Guia do Usuário" + +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"Você pode assistir nossos tutoriais e os tutoriais feitos por nossa " +"comunidade." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Tutoriais em vídeo" + +msgid "onboarding-v2.before-start.title" +msgstr "Antes de começar" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"O Penpot é Open Source e é feito pela Kaleidos e também pela comunidade, " +"onde muitas pessoas já se ajudam. Todos podem colaborar:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Um espaço público para aprender, compartilhar e discutir sobre o Penpot, " +"seu presente e futuro com toda a Comunidade e a equipe principal do Penpot." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "Participando da Comunidade" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Onde você poderá colaborar com traduções, solicitações de recursos, " +"contribuições principais, caça a bugs…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Guia do contribuidor" + +msgid "onboarding-v2.welcome.title" +msgstr "Bem-vindo ao Penpot!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Crie uma equipe depois" + +msgid "onboarding.choice.team-up.create-team" +msgstr "O nome da sua equipe" + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "Depois de nomear sua equipe, você poderá convidar pessoas para participar." + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Digite o nome da equipe" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Convide membros" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Lembre-se de incluir todos. Desenvolvedores, designers, gerentes... a " +"diversidade se soma :)" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Crie a equipe e convide depois" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Crie uma equipe e envie convites" + +msgid "onboarding.choice.team-up.roles" +msgstr "Convide com a função:" + +msgid "onboarding.choice.title" +msgstr "Bem-vindo ao Penpot" + +msgid "onboarding.contrib.alt" +msgstr "Open Source" + +msgid "onboarding.contrib.desc1" +msgstr "" +"Penpot é Open Source, feito por e para a comunidade. Se quiser colaborar, é " +"mais que bem-vindo!" + +msgid "onboarding.contrib.desc2.1" +msgstr "Você pode acessar o" + +msgid "onboarding.contrib.desc2.2" +msgstr "e siga as instruções de contribuição :)" + +msgid "onboarding.contrib.link" +msgstr "projeto no github" + +msgid "onboarding.contrib.title" +msgstr "Contribuidor Open Source?" + +msgid "onboarding.newsletter.accept" +msgstr "Sim, assinar" + +msgid "onboarding.newsletter.acceptance-message" +msgstr "" +"Sua solicitação de inscrição foi enviada, enviaremos um e-mail para " +"confirmá-la." + +msgid "onboarding.newsletter.decline" +msgstr "Não, obrigado" + +msgid "onboarding.newsletter.desc" +msgstr "" +"Subscreva a nossa newsletter para se manter atualizado com o progresso e as " +"novidades do desenvolvimento de produtos." + +msgid "onboarding.newsletter.policy" +msgstr "Politica de privacidade." + +msgid "onboarding.newsletter.privacy1" +msgstr "Porque nos preocupamos com a privacidade, aqui está o nosso " + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Enviaremos apenas e-mails relevantes para você. Você pode cancelar a " +"inscrição a qualquer momento em seu perfil de usuário ou por meio do link " +"de cancelamento de inscrição em qualquer um de nossos boletins informativos." + +msgid "onboarding.newsletter.title" +msgstr "Deseja receber novidades sobre o Penpot?" + +msgid "onboarding.slide.0.alt" +msgstr "Criar designs" + +msgid "onboarding.slide.0.desc1" +msgstr "" +"Criar lindas interfaces de usuários em colaboração com todos os membros da " +"equipe." + +msgid "onboarding.slide.0.desc2" +msgstr "" +"Mantenha a consistência em escala com componentes, bibliotecas e sistemas " +"de design." + +msgid "onboarding.slide.0.title" +msgstr "Bibliotecas de design, estilos e componentes" + +msgid "onboarding.slide.1.alt" +msgstr "Protótipos interativos" + +msgid "onboarding.slide.1.desc1" +msgstr "Crie interações completas para imitar o comportamento do produto." + +msgid "onboarding.slide.1.desc2" +msgstr "" +"Compartilhe com stakeholders, apresente propostas ao seu time e comece a " +"testar seus designs com usuários, tudo isso em um só lugar." + +msgid "onboarding.slide.1.title" +msgstr "Dê vida aos seus projetos com interações" + +msgid "onboarding.slide.2.alt" +msgstr "Obter feedback" + +msgid "onboarding.slide.2.desc1" +msgstr "" +"Todos os membros da equipe trabalhando simultaneamente com design " +"multiusuário em tempo real e comentários, ideias e feedback centralizados " +"diretamente sobre os designs." + +msgid "onboarding.slide.2.title" +msgstr "Obtenha feedback, apresente e compartilhe seu trabalho" + +msgid "onboarding.slide.3.alt" +msgstr "Handoff e lowcode" + +msgid "onboarding.slide.3.desc1" +msgstr "" +"Sincronize o design e o código de todos os seus componentes e estilos e " +"obtenha trechos de código." + +msgid "onboarding.slide.3.desc2" +msgstr "" +"Obtenha e forneça especificações de código como marcação (SVG, HTML) ou " +"estilos (CSS, Less, Stylus…)." + +msgid "onboarding.slide.3.title" +msgstr "Uma fonte compartilhada de verdade" + +msgid "onboarding.team-modal.create-team" +msgstr "Crie uma equipe" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Uma equipe permite que você colabore com outros usuários do Penpot " +"trabalhando nos mesmos arquivos e projetos." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Arquivos e projetos ilimitados" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Edição multiusuário" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Gerenciamento de funções" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Membros ilimitados" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% gratuito!" + +msgid "onboarding.templates.subtitle" +msgstr "Aqui estão alguns templates." + +msgid "onboarding.templates.title" +msgstr "Começar a desenhar" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +msgid "onboarding.welcome.title" +msgstr "Bem-vindo ao Penpot" + #: src/app/main/ui/auth/recovery.cljs msgid "profile.recovery.go-to-login" msgstr "Ir para a página de login" @@ -1419,6 +2213,426 @@ msgstr "Ir para a página de login" msgid "settings.multiple" msgstr "Misto" +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Fundamentos" + +msgid "shortcut-section.dashboard" +msgstr "Painel" + +msgid "shortcut-section.viewer" +msgstr "Espectador" + +msgid "shortcut-section.workspace" +msgstr "Espaço de trabalho" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Alinhamento" + +msgid "shortcut-subsection.edit" +msgstr "Editar" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Geral" + +msgid "shortcut-subsection.general-viewer" +msgstr "Geral" + +msgid "shortcut-subsection.main-menu" +msgstr "Menu principal" + +msgid "shortcut-subsection.modify-layers" +msgstr "Modificar camadas" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Navegação" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Navegação" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Navegação" + +msgid "shortcut-subsection.panels" +msgstr "Painéis" + +msgid "shortcut-subsection.path-editor" +msgstr "Curvas" + +msgid "shortcut-subsection.shape" +msgstr "Formas" + +msgid "shortcut-subsection.tools" +msgstr "Ferramentas" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Zoom" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Zoom" + +msgid "shortcuts.add-comment" +msgstr "Comentários" + +msgid "shortcuts.add-node" +msgstr "Adicionar ponto" + +msgid "shortcuts.align-bottom" +msgstr "Alinhar à base" + +msgid "shortcuts.align-hcenter" +msgstr "Alinhar ao centro horizontalmente" + +msgid "shortcuts.align-left" +msgstr "Alinhar à esquerda" + +msgid "shortcuts.align-right" +msgstr "Alinhar à direita" + +msgid "shortcuts.align-top" +msgstr "Alinhar ao topo" + +msgid "shortcuts.align-vcenter" +msgstr "Alinhar ao centro verticalmente" + +msgid "shortcuts.artboard-selection" +msgstr "Criar quadro da seleção" + +msgid "shortcuts.bool-difference" +msgstr "Subtrair seleção" + +msgid "shortcuts.bool-exclude" +msgstr "Excluir seleção" + +msgid "shortcuts.bool-intersection" +msgstr "Intersecção Booleana" + +msgid "shortcuts.bool-union" +msgstr "União boleana" + +msgid "shortcuts.bring-back" +msgstr "Enviar para trás" + +msgid "shortcuts.bring-backward" +msgstr "Mover para trás" + +msgid "shortcuts.bring-forward" +msgstr "Mover para a frente" + +msgid "shortcuts.bring-front" +msgstr "Trazer para a frente" + +msgid "shortcuts.clear-undo" +msgstr "Limpar desfazer" + +msgid "shortcuts.copy" +msgstr "Copiar" + +msgid "shortcuts.create-component" +msgstr "Criar componente" + +msgid "shortcuts.create-new-project" +msgstr "Criar novo" + +msgid "shortcuts.cut" +msgstr "Cortar" + +msgid "shortcuts.decrease-zoom" +msgstr "Reduzir zoom" + +msgid "shortcuts.delete" +msgstr "Apagar" + +msgid "shortcuts.delete-node" +msgstr "Apagar ponto" + +msgid "shortcuts.detach-component" +msgstr "Quebrar componente" + +msgid "shortcuts.draw-curve" +msgstr "Curva" + +msgid "shortcuts.draw-ellipse" +msgstr "Elipse" + +msgid "shortcuts.draw-frame" +msgstr "Prancheta" + +msgid "shortcuts.draw-nodes" +msgstr "Desenhar caminho" + +msgid "shortcuts.draw-path" +msgstr "Caminho" + +msgid "shortcuts.draw-rect" +msgstr "Retângulo" + +msgid "shortcuts.draw-text" +msgstr "Texto" + +msgid "shortcuts.duplicate" +msgstr "Duplicar" + +msgid "shortcuts.escape" +msgstr "Cancelar" + +msgid "shortcuts.export-shapes" +msgstr "Exportar formas" + +msgid "shortcuts.fit-all" +msgstr "Ajustar à janela" + +msgid "shortcuts.flip-horizontal" +msgstr "Refletir horizontalmente" + +msgid "shortcuts.flip-vertical" +msgstr "Refletir verticalmente" + +msgid "shortcuts.go-to-drafts" +msgstr "Ir para rascunhos" + +msgid "shortcuts.go-to-libs" +msgstr "Ir para bibliotecas partilhadas" + +msgid "shortcuts.go-to-search" +msgstr "Pesquisa" + +msgid "shortcuts.group" +msgstr "Agrupar" + +msgid "shortcuts.h-distribute" +msgstr "Distribuir horizontalmente" + +msgid "shortcuts.hide-ui" +msgstr "Mostrar/ocultar interface" + +msgid "shortcuts.increase-zoom" +msgstr "Mais zoom" + +msgid "shortcuts.insert-image" +msgstr "Inserir imagem" + +msgid "shortcuts.join-nodes" +msgstr "Unir pontos" + +msgid "shortcuts.make-corner" +msgstr "Criar canto" + +msgid "shortcuts.make-curve" +msgstr "Criar curva" + +msgid "shortcuts.mask" +msgstr "Máscara" + +msgid "shortcuts.merge-nodes" +msgstr "Unir pontos" + +msgid "shortcuts.move" +msgstr "Mover" + +msgid "shortcuts.move-fast-down" +msgstr "Mover para baixo rápido" + +msgid "shortcuts.move-fast-left" +msgstr "Mover para a esquerda rápido" + +msgid "shortcuts.move-fast-right" +msgstr "Mover para a direita rápido" + +msgid "shortcuts.move-fast-up" +msgstr "Mover para cima rápido" + +msgid "shortcuts.move-nodes" +msgstr "Mover ponto" + +msgid "shortcuts.move-unit-down" +msgstr "Mover para baixo" + +msgid "shortcuts.move-unit-left" +msgstr "Mover para a esquerda" + +msgid "shortcuts.move-unit-right" +msgstr "Mover para a direita" + +msgid "shortcuts.move-unit-up" +msgstr "Mover para cima" + +msgid "shortcuts.next-frame" +msgstr "Próximo quadro" + +msgid "shortcuts.not-found" +msgstr "Não foram encontrados atalhos" + +msgid "shortcuts.opacity-0" +msgstr "Definir opacidade para 100%" + +msgid "shortcuts.opacity-1" +msgstr "Definir opacidade para 100%" + +msgid "shortcuts.opacity-2" +msgstr "Definir opacidade para 20%" + +msgid "shortcuts.opacity-3" +msgstr "Definir opacidade para 10%" + +msgid "shortcuts.opacity-4" +msgstr "Definir opacidade para 40%" + +msgid "shortcuts.opacity-5" +msgstr "Definir opacidade para 50%" + +msgid "shortcuts.opacity-6" +msgstr "Definir opacidade para 60%" + +msgid "shortcuts.opacity-7" +msgstr "Definir opacidade para 70%" + +msgid "shortcuts.opacity-8" +msgstr "Definir opacidade para 80%" + +msgid "shortcuts.opacity-9" +msgstr "Definir opacidade para 90%" + +msgid "shortcuts.open-color-picker" +msgstr "Selector de cores" + +msgid "shortcuts.open-comments" +msgstr "Ir para secção de comentários" + +msgid "shortcuts.open-dashboard" +msgstr "Ir para o painel" + +msgid "shortcuts.open-handoff" +msgstr "Ir para seção de entrega" + +msgid "shortcuts.open-interactions" +msgstr "Ir para seção de interação" + +msgid "shortcuts.open-viewer" +msgstr "Ir para seção de interação" + +msgid "shortcuts.open-workspace" +msgstr "Ir para espaço de trabalho" + +msgid "shortcuts.or" +msgstr " ou " + +msgid "shortcuts.paste" +msgstr "Colar" + +msgid "shortcuts.prev-frame" +msgstr "Quadro anterior" + +msgid "shortcuts.redo" +msgstr "Refazer" + +msgid "shortcuts.reset-zoom" +msgstr "Redefenir zoom" + +msgid "shortcuts.search-placeholder" +msgstr "Procurar atalhos" + +msgid "shortcuts.select-all" +msgstr "Selecionar todos" + +msgid "shortcuts.separate-nodes" +msgstr "Separar pontos" + +msgid "shortcuts.show-pixel-grid" +msgstr "Mostrar/ocultar grade de pixeis" + +msgid "shortcuts.show-shortcuts" +msgstr "Mostrar/ocultar atalhos" + +msgid "shortcuts.snap-nodes" +msgstr "Ajustar ao ponto" + +msgid "shortcuts.snap-pixel-grid" +msgstr "Ajustar à grade" + +msgid "shortcuts.start-editing" +msgstr "Começar a editar" + +msgid "shortcuts.start-measure" +msgstr "Iniciar medição" + +msgid "shortcuts.stop-measure" +msgstr "Parar medição" + +msgid "shortcuts.thumbnail-set" +msgstr "Definir imagem de destaque" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Atalhos do teclado" + +msgid "shortcuts.toggle-alignment" +msgstr "Alternar alinhamento dinâmico" + +msgid "shortcuts.toggle-assets" +msgstr "Alternar recursos" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Alternar paleta de cor" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Alternar modo de foco" + +msgid "shortcuts.toggle-grid" +msgstr "Mostrar/ocultar grade" + +msgid "shortcuts.toggle-history" +msgstr "Alternar histórico" + +msgid "shortcuts.toggle-layers" +msgstr "Alternar camadas" + +msgid "shortcuts.toggle-lock" +msgstr "Bloquear selecionado" + +msgid "shortcuts.toggle-lock-size" +msgstr "Bloquear proporções" + +msgid "shortcuts.toggle-rules" +msgstr "Mostrar/ocultar réguas" + +msgid "shortcuts.toggle-scale-text" +msgstr "Alternar escala de texto" + +msgid "shortcuts.toggle-snap-grid" +msgstr "Encaixar na grade" + +msgid "shortcuts.toggle-snap-guide" +msgstr "Encaixar nos guias" + +msgid "shortcuts.toggle-textpalette" +msgstr "Alternar palete de texto" + +msgid "shortcuts.toggle-visibility" +msgstr "Alternar visibilidade" + +msgid "shortcuts.toggle-zoom-style" +msgstr "Alternar estilo de zoom" + +msgid "shortcuts.toogle-fullscreen" +msgstr "Alternar tela cheia" + +msgid "shortcuts.undo" +msgstr "Desfazer" + +msgid "shortcuts.ungroup" +msgstr "Desagrupar" + +msgid "shortcuts.unmask" +msgstr "Retirar máscara" + +msgid "shortcuts.v-distribute" +msgstr "Distribuir verticalmente" + +msgid "shortcuts.zoom-selected" +msgstr "Ajustar zoom à seleção" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -1463,6 +2677,10 @@ msgstr "Senha - Penpot" msgid "title.settings.profile" msgstr "Perfil - Penpot" +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-invitations" +msgstr "Convites - %s - Penpot" + #: src/app/main/ui/dashboard/team.cljs msgid "title.team-members" msgstr "Membros - %s - Penpot" @@ -1471,10 +2689,33 @@ msgstr "Membros - %s - Penpot" msgid "title.team-settings" msgstr "Configurações - %s - Penpot" +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Modo de visualização - Penpot" + +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + +msgid "viewer.breaking-change.description" +msgstr "" +"Este link compartilhável não é mais válido. Crie ou peça ao proprietário um " +"novo." + +msgid "viewer.breaking-change.message" +msgstr "Desculpe!" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "Não foram encontrados quadros na página." + #: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs msgid "viewer.frame-not-found" msgstr "Prancheta não encontrada." +msgid "viewer.header.comments-section" +msgstr "Comentários (%s)" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.dont-show-interactions" msgstr "Não mostrar interações" @@ -1487,10 +2728,16 @@ msgstr "Editar arquivo" msgid "viewer.header.fullscreen" msgstr "Tela cheia" +msgid "viewer.header.handoff-section" +msgstr "Entrega (%s)" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.interactions" msgstr "Interações" +msgid "viewer.header.interactions-section" +msgstr "Interações (%s)" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.copy-link" msgstr "Copiar link" @@ -1559,6 +2806,14 @@ msgstr "Distribuir espaçamento vertical (%s)" msgid "workspace.align.vtop" msgstr "Alinhar ao topo (%s)" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Recursos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Todos os recursos" + msgid "workspace.assets.box-filter-graphics" msgstr "Gráficos" @@ -1592,6 +2847,367 @@ msgstr "Duplicar" msgid "workspace.assets.edit" msgstr "Editar" +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Imagens" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group-name" +msgstr "Nome do grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Bibliotecas" + +msgid "workspace.assets.local-library" +msgstr "biblioteca local" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "Não foram encontrados arquivos" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename" +msgstr "Renomear" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename-group" +msgstr "Renomear grupo" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Procurar arquivos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.selected-count" +msgid_plural "workspace.assets.selected-count" +msgstr[0] "%s item selecionado" +msgstr[1] "%s itens selecionados" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "Partilhado" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografias" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Fonte" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Tamanho" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Variante" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Ir para biblioteca de estilo para editar" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Espaçamento de letra" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Altura entrelinha" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs +msgid "workspace.assets.typography.sample" +msgstr "Ag" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +msgstr "Transformar Texto" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.ungroup" +msgstr "Desagrupar" + +msgid "workspace.focus.focus-mode" +msgstr "Modo de foco" + +msgid "workspace.focus.focus-off" +msgstr "Foco desligado" + +msgid "workspace.focus.focus-on" +msgstr "Foco ligado" + +msgid "workspace.focus.selection" +msgstr "Seleção" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Gradiente linear" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Gradiente radial" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Desabilitar alinhamento dinâmico" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-scale-text" +msgstr "Desabilitar escala de texto" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Desabilitar encaixar à grade" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-guides" +msgstr "Desativar ajuste às guias" + +msgid "workspace.header.menu.disable-snap-pixel-grid" +msgstr "Desativar ajuste ao pixel" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Habilitar alinhamento dinâmico" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-scale-text" +msgstr "Ativar texto de escala" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Encaixar na grade" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-guides" +msgstr "Encaixar nos guias" + +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Habilitar encaixe por pixel" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-artboard-names" +msgstr "Esconder nomes das pranchetas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-grid" +msgstr "Ocultar grade" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Esconder paleta de cores" + +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Ocultar grade de pixel" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Ocultar réguas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-textpalette" +msgstr "Ocultar palete de fontes" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Editar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Ficheiro" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Ajuda & informação" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.preferences" +msgstr "Preferências" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.view" +msgstr "Visualizar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.select-all" +msgstr "Selecionar tudo" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-artboard-names" +msgstr "Mostrar nomes das pranchetas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-grid" +msgstr "Mostrar grade" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Mostrar paleta de cor" + +msgid "workspace.header.menu.show-pixel-grid" +msgstr "Mostrar grade de pixel" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-rules" +msgstr "Mostrar réguas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-textpalette" +msgstr "Mostrar palete de fontes" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.reset-zoom" +msgstr "Redefenir" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Erro ao salvar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Salvo" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Salvando" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Alterações não salvas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Modo de visualização (%s)" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fill" +msgstr "Ajustar para preencher" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit" +msgstr "Ajustar para encaixar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit-all" +msgstr "Ajustar tudo à janela" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-full-screen" +msgstr "Tela cheia" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-selected" +msgstr "Zoom até selecionados" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Adicionar" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s cores" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Imagens de destaque grandes" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Biblioteca de ficheiros" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.hsv" +msgstr "HSV" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Cores recentes" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgb-complementary" +msgstr "RGB Complementar" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Salvar estilo de cor" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Pré-visualizações pequenas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s componentes" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "Biblioteca de ficheiros" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s imagens" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "BIBILIOTECAS NESTE FICHEIRO" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "BIBLIOTECAS" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "BIBLIOTECA" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "Não há Bibliotecas Partilhadas por atualizar" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "Não foram encontrados resultados para “%s“" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "Não há Bibliotecas Partilhadas disponíveis" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Procurar bibliotecas partilhadas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "BIBLIOTECAS PARTILHADAS" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography" +msgstr "Tipografias múltiplas" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Desassociar todas as tipografias" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s tipografias" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Atualizar" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "ATUALIZAÇÔES" + msgid "workspace.library.all" msgstr "Todas bibliotecas" @@ -1604,6 +3220,10 @@ msgstr "Minhas bibliotecas" msgid "workspace.library.store" msgstr "Bibliotecas da loja" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.add-interaction" +msgstr "Clique no botão + para adicionar interações." + msgid "workspace.options.blur-options.background-blur" msgstr "Fundo" @@ -1614,10 +3234,65 @@ msgstr "Camada" msgid "workspace.options.blur-options.title" msgstr "Desfoque" +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.group" +msgstr "Desfoque agrupado" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title.multiple" +msgstr "Desfoque selecionado" + +#: src/app/main/ui/workspace/sidebar/options/page.cljs +msgid "workspace.options.canvas-background" +msgstr "Fundo da tela" + +msgid "workspace.options.clip-content" +msgstr "Cortar conteúdo" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs msgid "workspace.options.component" msgstr "Componente" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints" +msgstr "Restrições" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.bottom" +msgstr "Inferior" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.fix-when-scrolling" +msgstr "Fixar no scroll" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.leftright" +msgstr "Esquerda & Direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.right" +msgstr "Direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.scale" +msgstr "Escala" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.top" +msgstr "Topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.topbottom" +msgstr "Topo & Fundo" + #: src/app/main/ui/workspace/sidebar/options.cljs msgid "workspace.options.design" msgstr "Design" @@ -1626,24 +3301,50 @@ msgstr "Design" msgid "workspace.options.export" msgstr "Exportar" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-multiple" +msgstr "Exportar seleção" + #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "Exportar 1 elemento" -msgstr[1] "Exportar %s elementos" +msgstr "Exportar 1 elemento" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" msgstr "Sufixo" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "Exportação completa" + #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.exporting-object" msgstr "Exportando…" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "Exportação falhada" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-slow" +msgstr "Exportação inesperadamente lenta" + #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.fill" msgstr "Preencher" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.add-flow-start" +msgstr "Adicionar início de fluxo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-start" +msgstr "Início do fluxo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-starts" +msgstr "Início de fluxo" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.auto" msgstr "Automático" @@ -1652,10 +3353,21 @@ msgstr "Automático" msgid "workspace.options.grid.column" msgstr "Colunas" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "Grade" + +msgid "workspace.options.grid.params.color" +msgstr "Cor" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.columns" msgstr "Colunas" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.gutter" +msgstr "Goteira" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.height" msgstr "Altura" @@ -1720,10 +3432,293 @@ msgstr "Linhas" msgid "workspace.options.grid.square" msgstr "Quadrado" +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.group-fill" +msgstr "Preenchimento de grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.group-stroke" +msgstr "Traço do grupo" + +msgid "workspace.options.height" +msgstr "Altura" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-action" +msgstr "Ação" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-after-delay" +msgstr "Após atraso" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation" +msgstr "Animação" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-dissolve" +msgstr "Dissolver" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-none" +msgstr "Nenhuma" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-push" +msgstr "Empurrar" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-slide" +msgstr "Deslizar" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-background" +msgstr "Adicionar sobreposição de fundo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-outside" +msgstr "Fechar quando clicar fora do diálogo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay" +msgstr "Fechar diálogo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay-dest" +msgstr "Fechar diálogo: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-delay" +msgstr "Atraso" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-destination" +msgstr "Destino" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-duration" +msgstr "Duração" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing" +msgstr "Atenuação" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease" +msgstr "Ease" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease-in" +msgstr "Ease in" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease-in-out" +msgstr "Ease in out" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-ease-out" +msgstr "Ease out" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-linear" +msgstr "Linear" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-in" +msgstr "Dentro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-mouse-enter" +msgstr "Mouse entra" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-mouse-leave" +msgstr "Mouse sai" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-ms" +msgstr "ms" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to" +msgstr "Navegar para" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to-dest" +msgstr "Navegar para: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-none" +msgstr "(não especificado)" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-offset-effect" +msgstr "Efeito de offset" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-on-click" +msgstr "No clique" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-overlay" +msgstr "Abrir diálogo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-overlay-dest" +msgstr "Abrir diálogo: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-url" +msgstr "Abrir url" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-out" +msgstr "Sair" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-center" +msgstr "Centro inferior" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-left" +msgstr "Inferior esquerdo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-right" +msgstr "Inferior direito" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-manual" +msgstr "Manual" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-center" +msgstr "Superior centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-left" +msgstr "Superior esquerdo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-right" +msgstr "Superior direito" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-position" +msgstr "Posição" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-preserve-scroll" +msgstr "Preservar posição do scroll" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-prev-screen" +msgstr "Ecrã anterior" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-self" +msgstr "próprio" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-toggle-overlay" +msgstr "Alternar sobreposição" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-toggle-overlay-dest" +msgstr "Alternar sobreposição: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-trigger" +msgstr "Ativador" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-url" +msgstr "URL" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-while-hovering" +msgstr "Durante o hover" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-while-pressing" +msgstr "Durante o premir" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interactions" +msgstr "Interações" + #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.blend-mode.color" msgstr "Cor" +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-burn" +msgstr "Queimar cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-dodge" +msgstr "Subexposição de cores" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.darken" +msgstr "Escurecer" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.difference" +msgstr "Diferença" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.exclusion" +msgstr "Exclusão" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hard-light" +msgstr "Luz direta" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.hue" +msgstr "Matiz" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.lighten" +msgstr "Clarear" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.luminosity" +msgstr "Luminusidade" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.multiply" +msgstr "Multiplicação" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.normal" +msgstr "Normal" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.overlay" +msgstr "Sobreposição" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.saturation" +msgstr "Saturação" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.screen" +msgstr "Tela" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.soft-light" +msgstr "Luz difusa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Camada" + #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.title.group" msgstr "Camadas do grupo" @@ -1732,6 +3727,165 @@ msgstr "Camadas do grupo" msgid "workspace.options.layer-options.title.multiple" msgstr "Camadas selecionadas" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "Opções avançadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Altura.Máx" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Largura.Máx" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Altura.Min" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Altura.Min" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Redimensionar elemento" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Altura máxima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Largura máxima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Altura mínima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Largura mínima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Inferior" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Coluna" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "Linha" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.right" +msgstr "Linha inversa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "Coluna inversa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.gap" +msgstr "Espaço" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Margem" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "Todos os lados" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Margem simples" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.no-wrap" +msgstr "no wrap" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.packed" +msgstr "embalado" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding" +msgstr "Padding" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "Todos os lados" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-simple" +msgstr "Padding simples" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "Direito" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "espaço em volta" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "espaço interno" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.title" +msgstr "Layout" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "abaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.wrap" +msgstr "quebrado" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "Mais cores" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-lib-colors" +msgstr "Mais cores da biblioteca" + +msgid "workspace.options.opacity" +msgstr "Opacidade" + #: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.position" msgstr "Posição" @@ -1751,10 +3905,20 @@ msgstr "Todos cantos" msgid "workspace.options.radius.single-corners" msgstr "Cantos individuais" +msgid "workspace.options.recent-fonts" +msgstr "Recente" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.retry" +msgstr "Tentar de novo" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.rotation" msgstr "Rotação" +msgid "workspace.options.search-font" +msgstr "Procurar fonte" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.select-a-shape" msgstr "" @@ -1765,14 +3929,25 @@ msgstr "" msgid "workspace.options.select-artboard" msgstr "Selecionar prancheta" +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "Cores selecionadas" + #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.selection-fill" msgstr "Preenchimento da seleção" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.selection-stroke" +msgstr "Selecionar traçado" + #: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs msgid "workspace.options.shadow-options.blur" msgstr "Borrar" +msgid "workspace.options.shadow-options.color" +msgstr "Cor da sombra" + #: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs msgid "workspace.options.shadow-options.drop-shadow" msgstr "Sombra projetada" @@ -1789,6 +3964,10 @@ msgstr "X" msgid "workspace.options.shadow-options.offsety" msgstr "Y" +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.spread" +msgstr "Espalhar" + #: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs msgid "workspace.options.shadow-options.title" msgstr "Sombra" @@ -1801,6 +3980,13 @@ msgstr "Sombra do grupo" msgid "workspace.options.shadow-options.title.multiple" msgstr "Sombras da seleção" +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.show-fill-on-export" +msgstr "Mostrar nas exportações" + +msgid "workspace.options.show-in-viewer" +msgstr "Mostrar no modo de visualização" + #: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.size" msgstr "Tamanho" @@ -1809,6 +3995,48 @@ msgstr "Tamanho" msgid "workspace.options.size-presets" msgstr "Predefinições de tamanho" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke" +msgstr "Traçado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.circle-marker" +msgstr "Marcador de círculo" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.diamond-marker" +msgstr "Marcador diamante" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.line-arrow" +msgstr "Seta de linha" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "Nenhum" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.round" +msgstr "Redondo" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square" +msgstr "Quadrado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square-marker" +msgstr "Marcador quadrado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.triangle-arrow" +msgstr "Seta triangular" + +msgid "workspace.options.stroke-color" +msgstr "Cor do traçado" + +msgid "workspace.options.stroke-width" +msgstr "Largura do traçado" + #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke.center" msgstr "Centro" @@ -1837,6 +4065,10 @@ msgstr "Fora" msgid "workspace.options.stroke.solid" msgstr "Sólido" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Alinhar à base" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.align-center" msgstr "Alinhar ao centro" @@ -1864,6 +4096,14 @@ msgstr "Alinhar ao topo" msgid "workspace.options.text-options.decoration" msgstr "Decoração" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "Esquerda para a direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "Direita para a esquerda" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.options.text-options.google" msgstr "Google" @@ -1884,6 +4124,220 @@ msgstr "Fixado" msgid "workspace.options.text-options.letter-spacing" msgstr "Espaçamento entre letras" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Altura de linha" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Minúsculo" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.none" +msgstr "Nenhum" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.preset" +msgstr "Predefinição" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Tachado" + +msgid "workspace.options.text-options.text-case" +msgstr "Capitalização" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Texto" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Texto do grupo" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-selection" +msgstr "Texto de seleção" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.titlecase" +msgstr "Capitalização de Título" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Sublinhado" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Maiúsculo" + +msgid "workspace.options.text-options.vertical-align" +msgstr "Alinhamento vertical" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.use-play-button" +msgstr "Use o botão play no cabeçalho para executar a visualização do protótipo." + +msgid "workspace.options.width" +msgstr "Largura" + +msgid "workspace.options.x" +msgstr "X" + +msgid "workspace.options.y" +msgstr "Y" + +msgid "workspace.path.actions.add-node" +msgstr "Adicionar ponto (%s)" + +msgid "workspace.path.actions.delete-node" +msgstr "Remover ponto (%s)" + +msgid "workspace.path.actions.draw-nodes" +msgstr "Desenhar ponto (%s)" + +msgid "workspace.path.actions.join-nodes" +msgstr "Unir pontos (%s)" + +msgid "workspace.path.actions.make-corner" +msgstr "Em cantos (%s)" + +msgid "workspace.path.actions.make-curve" +msgstr "Para curvar (%s)" + +msgid "workspace.path.actions.merge-nodes" +msgstr "Mesclar pontos (%s)" + +msgid "workspace.path.actions.move-nodes" +msgstr "Mover pontos (%s)" + +msgid "workspace.path.actions.separate-nodes" +msgstr "Separar pontos (%s)" + +msgid "workspace.path.actions.snap-nodes" +msgstr "Ajustar ao ponto (%s)" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "Enviar para trás" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "Enviar para trás" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Copiar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-artboard-from-selection" +msgstr "Selecionar o board" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Criar componente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Cortar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Remover" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete-flow-start" +msgstr "Remover início de fluxo" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instance" +msgstr "Quebrar instância" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "Quebrar instâncias" + +msgid "workspace.shape.menu.difference" +msgstr "Diferença" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Duplicar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Editar" + +msgid "workspace.shape.menu.exclude" +msgstr "Excluir" + +msgid "workspace.shape.menu.flatten" +msgstr "Achatar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Virar horizontalmente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-vertical" +msgstr "Virar verticalmente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flow-start" +msgstr "Início do fluxo" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.forward" +msgstr "Trazer para a frente" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.front" +msgstr "Trazer para a frente" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.go-main" +msgstr "Ir para o ficheiro do componente principal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.group" +msgstr "Grupo" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Esconder" + +msgid "workspace.shape.menu.hide-ui" +msgstr "Mostrar/ocultar UI" + +msgid "workspace.shape.menu.intersection" +msgstr "Interseção" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Bloquear" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Máscara" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Colar" + +msgid "workspace.shape.menu.path" +msgstr "Caminho" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.reset-overrides" +msgstr "Redefinir substituições" + +msgid "workspace.shape.menu.restore-main" +msgstr "Restaurar componente principal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Selecionar camada" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.show" msgstr "Mostrar" @@ -1892,10 +4346,22 @@ msgstr "Mostrar" msgid "workspace.shape.menu.show-main" msgstr "Mostrar componente principal" +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "Remover miniatura" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "Definir como miniatura" + +msgid "workspace.shape.menu.transform-to-path" +msgstr "Transformar em caminho" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.ungroup" msgstr "Desagrupar" +msgid "workspace.shape.menu.union" +msgstr "Unir" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.unlock" msgstr "Desbloquear" @@ -1904,6 +4370,10 @@ msgstr "Desbloquear" msgid "workspace.shape.menu.unmask" msgstr "Remover máscara" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "Atualizar componentes principais" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.update-main" msgstr "Atualizar o componente principal" @@ -1916,6 +4386,30 @@ msgstr "Histórico (%s)" msgid "workspace.sidebar.layers" msgstr "Camadas" +msgid "workspace.sidebar.layers.components" +msgstr "Componentes" + +msgid "workspace.sidebar.layers.frames" +msgstr "Painéis" + +msgid "workspace.sidebar.layers.groups" +msgstr "Grupos" + +msgid "workspace.sidebar.layers.images" +msgstr "Imagens" + +msgid "workspace.sidebar.layers.masks" +msgstr "Máscaras" + +msgid "workspace.sidebar.layers.search" +msgstr "Procurar camadas" + +msgid "workspace.sidebar.layers.shapes" +msgstr "Formas" + +msgid "workspace.sidebar.layers.texts" +msgstr "Textos" + #: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs msgid "workspace.sidebar.options.svg-attrs.title" msgstr "Atributos SVG importados" @@ -1968,14 +4462,26 @@ msgstr "Caminho (%s)" msgid "workspace.toolbar.rect" msgstr "Retângulo (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Atalhos (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.text" msgstr "Texto (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text-palette" +msgstr "Tipografias (%s)" + #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.empty" msgstr "Não há mudanças no histórico até agora" +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "Removido %s" + #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.modify" msgstr "Modificado (%s)" @@ -2072,10 +4578,21 @@ msgstr "forma" msgid "workspace.undo.entry.single.text" msgstr "texto" +msgid "workspace.undo.entry.single.typography" +msgstr "recurso de tipografia" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.unknown" +msgstr "Operação em %s" + #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.title" msgstr "Histórico" +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Dispensar" + #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.there-are-updates" msgstr "Existem atualizações nas bibliotecas compartilhadas" @@ -2085,2529 +4602,4 @@ msgid "workspace.updates.update" msgstr "Atualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Clique para fechar o caminho" - -msgid "labels.export" -msgstr "Exportar" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.your-account" -msgstr "Sua conta" - -msgid "errors.team-leave.insufficient-members" -msgstr "" -"Membros insuficientes para deixar a equipe, você provavelmente deseja " -"excluí-la." - -msgid "errors.team-leave.member-does-not-exists" -msgstr "O membro que você tentou atribuir não existe." - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-snap-grid" -msgstr "Encaixar na grade" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke" -msgstr "Traço" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.accept" -msgstr "Excluir projeto" - -msgid "onboarding.contrib.link" -msgstr "projeto no github" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "" -"Aprenda o básico no Penpot enquanto se diverte com este tutorial prático." - -msgid "dashboard.export.options.detach.message" -msgstr "" -"Bibliotecas compartilhadas não serão incluídas na exportação e nenhum ativo " -"será adicionado à biblioteca. " - -msgid "dashboard.export.options.detach.title" -msgstr "Trate os ativos da biblioteca compartilhada como objetos básicos" - -msgid "errors.team-leave.owner-cant-leave" -msgstr "" -"O proprietário não pode sair da equipe, você deve reatribuir a função de " -"proprietário." - -msgid "labels.share-prototype" -msgstr "Compartilhar protótipo" - -msgid "labels.edit-file" -msgstr "Editar arquivo" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.pending-invitation" -msgstr "Pendente" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.resend-invitation" -msgstr "Reenviar convite" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.hint" -msgstr "" -"Se você transferir a propriedade, mudará sua função para Admin, perdendo " -"algumas permissões sobre essa equipe. " - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "Tem certeza de que deseja cancelar a publicação desta biblioteca?" -msgstr[1] "Tem certeza de que deseja cancelar a publicação dessas bibliotecas?" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component-in-bulk.message" -msgstr "Atualizar componentes em uma biblioteca compartilhada" - -msgid "onboarding-v2.welcome.desc1" -msgstr "" -"O Penpot é Open Source e é feito pela Kaleidos e também pela comunidade, " -"onde muitas pessoas já se ajudam. Todos podem colaborar:" - -msgid "onboarding-v2.welcome.desc2" -msgstr "" -"Um espaço público para aprender, compartilhar e discutir sobre o Penpot, seu " -"presente e futuro com toda a Comunidade e a equipe principal do Penpot." - -msgid "common.share-link.all-users" -msgstr "Todos os usuários do Penpot" - -msgid "common.share-link.current-tag" -msgstr "(atual)" - -msgid "common.share-link.manage-ops" -msgstr "Gerenciar Permissões" - -msgid "common.share-link.destroy-link" -msgstr "Destruir link" - -msgid "common.share-link.permissions-can-comment" -msgstr "Pode comentar" - -msgid "common.share-link.page-shared" -msgid_plural "common.share-link.page-shared" -msgstr[0] "1 página compartilhada" -msgstr[1] "%s páginas compartilhadas" - -msgid "common.share-link.permissions-pages" -msgstr "Páginas compartilhadas" - -msgid "common.share-link.permissions-can-inspect" -msgstr "Pode inspecionar o código" - -msgid "common.share-link.team-members" -msgstr "Apenas membros da equipe" - -msgid "common.share-link.view-all" -msgstr "Selecionar todos" - -msgid "dashboard.download-binary-file" -msgstr "Baixar arquivo Penpot (.penpot)" - -msgid "dashboard.download-standard-file" -msgstr "Baixar arquivo padrão (.svg + .json)" - -msgid "dashboard.export-binary-multi" -msgstr "Baixar %s arquivos Penpot (.penpot)" - -msgid "dashboard.export-standard-multi" -msgstr "Baixar %s arquivos padrões (.svg + .json)" - -msgid "dashboard.import.progress.upload-media" -msgstr "Carregando arquivo: %s" - -#: src/app/main/ui/auth/login.cljs -msgid "errors.auth-provider-not-configured" -msgstr "Provedor de autenticação não configurado." - -#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs -msgid "errors.profile-is-muted" -msgstr "" -"Seu perfil tem e-mails silenciados (relatos de spam ou altas devoluções)." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "Vá para o fórum do Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-subtitle1" -msgstr "" -"Estamos felizes em tê-lo aqui. Se precisar de ajuda, pesquise antes de " -"postar." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-title" -msgstr "Comunidade Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-go-to" -msgstr "Vá para o Twitter" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-subtitle1" -msgstr "Aqui para ajuda com suas dúvidas técnicas." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-title" -msgstr "Conta de suporte no Twitter" - -#: src/app/main/ui/workspace/header.cljs -msgid "label.shortcuts" -msgstr "Atalhos" - -msgid "labels.continue" -msgstr "Continuar" - -msgid "labels.continue-with" -msgstr "Continue com" - -msgid "labels.continue-with-penpot" -msgstr "Você pode continuar com a conta Penpot" - -msgid "labels.log-or-sign" -msgstr "Entrar ou cadastrar-se" - -msgid "labels.next" -msgstr "Próximo" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.tutorials" -msgstr "Tutorial" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.you" -msgstr "(você)" - -msgid "modals.delete-font-variant.message" -msgstr "" -"Tem certeza de que deseja excluir este estilo de fonte? Ele não será " -"carregado se for usado em um arquivo." - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "modals.delete-page.body" -msgstr "Tem certeza de que deseja excluir esta página?" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "modals.delete-page.title" -msgstr "Excluir página" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.message" -msgstr "Tem certeza de que deseja excluir este projeto?" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.title" -msgstr "Excluir projeto" - -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.small-nudge" -msgstr "Pequeno deslocamento" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component-in-bulk.hint" -msgstr "" -"Você está prestes a atualizar componentes em uma biblioteca compartilhada. " -"Isso pode afetar outros arquivos que o utilizam." - -msgid "onboarding.choice.team-up.invite-members" -msgstr "Convide membros" - -msgid "onboarding.choice.team-up.create-team-desc" -msgstr "" -"Depois de nomear sua equipe, você poderá convidar pessoas para participar." - -msgid "onboarding.choice.team-up.invite-members-submit" -msgstr "Crie uma equipe e envie convites" - -msgid "onboarding.choice.title" -msgstr "Bem-vindo ao Penpot" - -msgid "onboarding.contrib.desc1" -msgstr "" -"Penpot é Open Source, feito por e para a comunidade. Se quiser colaborar, é " -"mais que bem-vindo!" - -msgid "onboarding.contrib.desc2.1" -msgstr "Você pode acessar o" - -msgid "onboarding.contrib.desc2.2" -msgstr "e siga as instruções de contribuição :)" - -msgid "onboarding.contrib.title" -msgstr "Contribuidor Open Source?" - -msgid "onboarding.newsletter.acceptance-message" -msgstr "" -"Sua solicitação de inscrição foi enviada, enviaremos um e-mail para " -"confirmá-la." - -msgid "onboarding.newsletter.decline" -msgstr "Não, obrigado" - -msgid "onboarding.newsletter.policy" -msgstr "Politica de privacidade." - -msgid "onboarding.newsletter.privacy1" -msgstr "Porque nos preocupamos com a privacidade, aqui está o nosso " - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-dynamic-alignment" -msgstr "Habilitar alinhamento dinâmico" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-scale-text" -msgstr "Ativar texto de escala" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-snap-guides" -msgstr "Encaixar nos guias" - -msgid "workspace.header.menu.enable-snap-pixel-grid" -msgstr "Habilitar encaixe por pixel" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.saved" -msgstr "Salvo" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.unsaved" -msgstr "Alterações não salvas" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-msg" -msgstr "" -"Envie-me notícias, atualizações de produtos e recomendações sobre o Penpot." - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-title" -msgstr "Assinatura de Newsletter" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.save-settings" -msgstr "Salvar configurações" - -msgid "errors.auth.unable-to-login" -msgstr "Parece que você não está autenticado ou a sessão expirou." - -msgid "errors.email-as-password" -msgstr "Você não pode usar seu e-mail como senha" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs -msgid "errors.email-has-permanent-bounces" -msgstr "O e-mail «%s» tem muitos relatórios de devolução permanentes." - -msgid "errors.email-spam-or-permanent-bounces" -msgstr "O e-mail «%s» foi denunciado como spam ou devolvido permanentemente." - -msgid "handoff.tabs.code.selected.mask" -msgstr "Máscara" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.accept" -msgstr "Excluir equipe" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.title" -msgstr "Excluindo equipe" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.preferences" -msgstr "Preferências" - -msgid "common.unpublish" -msgstr "Cancelar publicação" - -msgid "dashboard.import.progress.process-components" -msgstr "Processando componentes" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot é destinado a equipes. Convide membros para trabalharem juntos em " -"projetos e arquivos" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Juntem-se!" - -msgid "dashboard.libraries-and-templates" -msgstr "Biblioteca e Modelos" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Explore mais deles e saiba como contribuir" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "Ocorreu um problema ao importar o modelo. O modelo não foi importado." - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "Ok" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Atenção" - -#: src/app/main/ui/confirm.cljs -msgid "ds.component-subtitle" -msgstr "Componentes para atualizar:" - -#: src/app/main/ui/dashboard/team.cljs -msgid "errors.member-is-muted" -msgstr "" -"O perfil que você está convidando tem e-mails silenciados (relatos de spam " -"ou altas devoluções)." - -msgid "handoff.tabs.code.selected.component" -msgstr "Componente" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.about-penpot" -msgstr "Sobre Penpot" - -msgid "labels.close" -msgstr "Fechar" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.community" -msgstr "Comunidade" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.no-invitations" -msgstr "Não há convites." - -msgid "labels.show-comments-list" -msgstr "Mostrar lista de comentários" - -msgid "labels.workspace" -msgstr "Área de trabalho" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.status" -msgstr "Status" - -msgid "modals.delete-font-variant.title" -msgstr "Excluindo estilo de fonte" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "Excluindo arquivo" -msgstr[1] "Excluindo arquivos" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.message" -msgstr "" -"Tem certeza de que deseja excluir esta equipe? Todos os projetos e arquivos " -"associados à equipe serão excluídos permanentemente." - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.accept" -msgstr "Excluir membro" - -msgid "modals.invite-member.emails" -msgstr "E-mails, separados por vírgulas" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Excluir arquivo" -msgstr[1] "Excluir arquivos" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "Excluindo arquivo" - -msgid "onboarding.choice.team-up.create-later" -msgstr "Crie uma equipe depois" - -msgid "onboarding.choice.team-up.roles" -msgstr "Convide com a função:" - -msgid "onboarding-v2.before-start.desc1" -msgstr "" -"Você deve saber que existem muitos recursos disponíveis para ajudá-lo a " -"começar a usar o Penpot, como o Guia do Usuário e nosso canal no Youtube." - -msgid "onboarding-v2.before-start.desc2" -msgstr "" -"Informações detalhadas sobre como usar o Penpot. Da prototipagem à " -"organização ou compartilhamento de projetos." - -msgid "onboarding-v2.welcome.title" -msgstr "Bem-vindo ao Penpot!" - -msgid "common.publish" -msgstr "Publicar" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Gerenciamento de equipe" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Começar o tutorial" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Tutorial prático" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "Faça um passeio pelo Penpot e conheça suas principais características." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Comece o passeio" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Passo a passo da interface" - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "Este arquivo já foi usado com componentes V2 habilitado." - -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.invite-invalid" -msgstr "Convite inválido" - -msgid "errors.invite-invalid.info" -msgstr "Este convite pode ter sido cancelado ou pode ter expirado." - -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.nudge-title" -msgstr "Quantidade de deslocamento" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Cancelar publicação da biblioteca" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "Tem certeza de que deseja excluir este arquivo?" -msgstr[1] "Tem certeza de que deseja excluir estes arquivos?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Este arquivo possui bibliotecas que estão sendo usadas neste arquivo:" -msgstr[1] "Este arquivo possui bibliotecas que estão sendo usadas nestes arquivos:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "" -"Esses arquivos possuem bibliotecas que estão sendo usadas neste arquivo:" -msgstr[1] "Esses arquivos têm bibliotecas que estão sendo usadas nesses arquivos:" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "Convite membros para a equipe" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "Cancelar publicação" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "" -"Se você cancelar a publicação, os ativos nele se tornarão uma biblioteca " -"desse arquivo." -msgstr[1] "" -"Se você cancelar a publicação, os ativos nele se tornarão uma biblioteca " -"desses arquivos." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.scd-message" -msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "Está em uso neste arquivo:" -msgstr[1] "Está em uso nestes arquivos:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "Cancelar publicação da biblioteca" -msgstr[1] "Cancelar publicação de bibliotecas" - -msgid "onboarding-v2.before-start.desc2.title" -msgstr "Guia do Usuário" - -msgid "onboarding-v2.before-start.desc3" -msgstr "" -"Você pode assistir nossos tutoriais e os tutoriais feitos por nossa " -"comunidade." - -msgid "onboarding-v2.before-start.desc3.title" -msgstr "Tutoriais em vídeo" - -msgid "onboarding-v2.before-start.title" -msgstr "Antes de começar" - -msgid "onboarding-v2.welcome.desc2.title" -msgstr "Participando da Comunidade" - -msgid "onboarding-v2.welcome.desc3" -msgstr "" -"Onde você poderá colaborar com traduções, solicitações de recursos, " -"contribuições principais, caça a bugs…" - -msgid "onboarding-v2.welcome.desc3.title" -msgstr "Guia do contribuidor" - -msgid "onboarding.choice.team-up.invite-members-info" -msgstr "" -"Lembre-se de incluir todos. Desenvolvedores, designers, gerentes... a " -"diversidade se soma :)" - -msgid "onboarding.newsletter.accept" -msgstr "Sim, assinar" - -msgid "onboarding.newsletter.desc" -msgstr "" -"Subscreva a nossa newsletter para se manter atualizado com o progresso e as " -"novidades do desenvolvimento de produtos." - -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Enviaremos apenas e-mails relevantes para você. Você pode cancelar a " -"inscrição a qualquer momento em seu perfil de usuário ou por meio do link de " -"cancelamento de inscrição em qualquer um de nossos boletins informativos." - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.dismiss-all" -msgstr "Recusar tudo" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.upload-all" -msgstr "Carregar tudo" - -msgid "dashboard.import.import-error" -msgstr "Ocorreu um problema ao importar o arquivo. O arquivo não foi importado." - -msgid "dashboard.import.import-message" -msgstr "%s arquivos foram importados com sucesso." - -msgid "dashboard.import.import-warning" -msgstr "Alguns arquivos continham objetos inválidos que foram removidos." - -msgid "onboarding.contrib.alt" -msgstr "Open Source" - -msgid "labels.and" -msgstr "e" - -msgid "labels.back" -msgstr "Voltar" - -msgid "labels.default" -msgstr "padrão" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.delete-invitation" -msgstr "Excluir convite" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.expired-invitation" -msgstr "Expirado" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.help-center" -msgstr "Central de Ajuda" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.github-repo" -msgstr "Repositório Github" - -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.invitations" -msgstr "Convites" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.libraries-and-templates" -msgstr "Biblioteca & Modelos" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.member" -msgstr "Membro" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.no-invitations-hint" -msgstr "" -"Pressione o botão \"Convidar para equipe\" para convidar mais membros para " -"esta equipe." - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.remove-member" -msgstr "Remover membro" - -msgid "labels.skip" -msgstr "Pular" - -msgid "labels.start" -msgstr "Começar" - -#: src/app/main/ui/workspace/nudge.cljs -msgid "modals.big-nudge" -msgstr "Grande deslocamento" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.change-owner-and-leave-confirm.message" -msgstr "" -"Você é o dono desta equipe. Selecione outro membro para promover a " -"proprietário antes de sair." - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-close-confirm.hint" -msgstr "" -"Como você é o único membro desta equipe, a equipe será excluída junto com " -"seus projetos e arquivos." - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-close-confirm.message" -msgstr "Tem certeza de que deseja sair da equipe %s?" - -msgid "onboarding.choice.team-up.create-team-placeholder" -msgstr "Digite o nome da equipe" - -msgid "onboarding.choice.team-up.invite-members-skip" -msgstr "Crie a equipe e convide depois" - -msgid "onboarding.choice.team-up.create-team" -msgstr "O nome da sua equipe" - -msgid "dashboard.export.options.merge.message" -msgstr "" -"Seu arquivo será exportado com todos os ativos externos mesclados na " -"biblioteca de arquivos." - -msgid "dashboard.export.options.merge.title" -msgstr "Incluir ativos da biblioteca compartilhada na bibliotecas de arquivos" - -msgid "dashboard.export.title" -msgstr "Exportar arquivos" - -msgid "dashboard.import.analyze-error" -msgstr "Ops! Não foi possível importar este arquivo" - -msgid "dashboard.import.progress.process-colors" -msgstr "Processando cores" - -msgid "dashboard.import.progress.process-media" -msgstr "Processando mídia" - -msgid "dashboard.import.progress.process-page" -msgstr "Processando página: %s" - -msgid "dashboard.import.progress.process-typographies" -msgstr "Processando tipografia" - -msgid "dashboard.import.progress.upload-data" -msgstr "Carregando dados para o servidor (%s/%s)" - -msgid "labels.link" -msgstr "Link" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.fonts-added" -msgid_plural "dashboard.fonts.fonts-added" -msgstr[0] "1 fonte adicionada" -msgstr[1] "%s fontes adicionadas" - -msgid "onboarding.newsletter.title" -msgstr "Deseja receber novidades sobre o Penpot?" - -msgid "onboarding.slide.0.desc2" -msgstr "" -"Mantenha a consistência em escala com componentes, bibliotecas e sistemas de " -"design." - -msgid "onboarding.slide.0.title" -msgstr "Bibliotecas de design, estilos e componentes" - -msgid "onboarding.slide.1.alt" -msgstr "Protótipos interativos" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.selected-count" -msgid_plural "workspace.assets.selected-count" -msgstr[0] "%s item selecionado" -msgstr[1] "%s itens selecionados" - -msgid "onboarding.slide.1.desc1" -msgstr "Crie interações completas para imitar o comportamento do produto." - -msgid "onboarding.slide.0.desc1" -msgstr "" -"Criar lindas interfaces de usuários em colaboração com todos os membros da " -"equipe." - -msgid "onboarding.slide.1.desc2" -msgstr "" -"Compartilhe com stakeholders, apresente propostas ao seu time e comece a " -"testar seus designs com usuários, tudo isso em um só lugar." - -msgid "onboarding.slide.0.alt" -msgstr "Criar designs" - -msgid "onboarding.slide.1.title" -msgstr "Dê vida aos seus projetos com interações" - -msgid "onboarding.slide.2.alt" -msgstr "Obter feedback" - -msgid "onboarding.slide.2.desc1" -msgstr "" -"Todos os membros da equipe trabalhando simultaneamente com design " -"multiusuário em tempo real e comentários, ideias e feedback centralizados " -"diretamente sobre os designs." - -msgid "onboarding.slide.3.alt" -msgstr "Handoff e lowcode" - -msgid "onboarding.slide.3.desc2" -msgstr "" -"Obtenha e forneça especificações de código como marcação (SVG, HTML) ou " -"estilos (CSS, Less, Stylus…)." - -msgid "onboarding.slide.3.desc1" -msgstr "" -"Sincronize o design e o código de todos os seus componentes e estilos e " -"obtenha trechos de código." - -msgid "onboarding.slide.3.title" -msgstr "Uma fonte compartilhada de verdade" - -msgid "onboarding.slide.2.title" -msgstr "Obtenha feedback, apresente e compartilhe seu trabalho" - -msgid "onboarding.team-modal.create-team" -msgstr "Crie uma equipe" - -msgid "onboarding.team-modal.create-team-desc" -msgstr "" -"Uma equipe permite que você colabore com outros usuários do Penpot " -"trabalhando nos mesmos arquivos e projetos." - -msgid "onboarding.team-modal.create-team-feature-1" -msgstr "Arquivos e projetos ilimitados" - -msgid "onboarding.team-modal.create-team-feature-2" -msgstr "Edição multiusuário" - -msgid "onboarding.team-modal.create-team-feature-3" -msgstr "Gerenciamento de funções" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "Membros ilimitados" - -msgid "onboarding.welcome.title" -msgstr "Bem-vindo ao Penpot" - -# SECTIONS -msgid "shortcut-section.basics" -msgstr "Fundamentos" - -msgid "onboarding.templates.subtitle" -msgstr "Aqui estão alguns templates." - -msgid "onboarding.templates.title" -msgstr "Começar a desenhar" - -msgid "shortcut-subsection.main-menu" -msgstr "Menu principal" - -msgid "shortcut-subsection.modify-layers" -msgstr "Modificar camadas" - -msgid "shortcut-subsection.navigation-viewer" -msgstr "Navegação" - -msgid "shortcut-subsection.shape" -msgstr "Formas" - -msgid "shortcut-subsection.tools" -msgstr "Ferramentas" - -msgid "shortcut-subsection.zoom-workspace" -msgstr "Zoom" - -msgid "shortcut-section.workspace" -msgstr "Espaço de trabalho" - -# SUBSECTIONS -msgid "shortcut-subsection.alignment" -msgstr "Alinhamento" - -msgid "shortcut-subsection.edit" -msgstr "Editar" - -msgid "shortcut-subsection.navigation-dashboard" -msgstr "Navegação" - -msgid "shortcut-subsection.panels" -msgstr "Painéis" - -msgid "shortcuts.align-right" -msgstr "Alinhar à direita" - -msgid "shortcuts.align-top" -msgstr "Alinhar ao topo" - -msgid "shortcuts.align-vcenter" -msgstr "Alinhar ao centro verticalmente" - -msgid "shortcut-subsection.navigation-workspace" -msgstr "Navegação" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "100% gratuito!" - -msgid "shortcuts.add-comment" -msgstr "Comentários" - -msgid "shortcuts.align-left" -msgstr "Alinhar à esquerda" - -msgid "shortcuts.align-bottom" -msgstr "Alinhar à base" - -msgid "shortcuts.add-node" -msgstr "Adicionar ponto" - -msgid "shortcut-subsection.zoom-viewer" -msgstr "Zoom" - -msgid "onboarding.welcome.alt" -msgstr "Penpot" - -msgid "shortcuts.bring-backward" -msgstr "Mover para trás" - -msgid "shortcuts.bool-union" -msgstr "União boleana" - -msgid "shortcuts.bring-back" -msgstr "Enviar para trás" - -msgid "shortcuts.move-nodes" -msgstr "Mover ponto" - -msgid "shortcuts.open-handoff" -msgstr "Ir para seção de entrega" - -msgid "shortcuts.toggle-snap-guide" -msgstr "Encaixar nos guias" - -msgid "workspace.header.menu.hide-pixel-grid" -msgstr "Ocultar grade de pixel" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-animation-dissolve" -msgstr "Dissolver" - -msgid "workspace.path.actions.move-nodes" -msgstr "Mover pontos (%s)" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.search" -msgstr "Procurar arquivos" - -#: src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.big-thumbnails" -msgstr "Imagens de destaque grandes" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.difference" -msgstr "Diferença" - -msgid "workspace.path.actions.join-nodes" -msgstr "Unir pontos (%s)" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.delete" -msgstr "Remover" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.go-main" -msgstr "Ir para o ficheiro do componente principal" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-grid" -msgstr "Ocultar grade" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-palette" -msgstr "Esconder paleta de cores" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-rules" -msgstr "Ocultar réguas" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.select-all" -msgstr "Selecionar tudo" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-rules" -msgstr "Mostrar réguas" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.save-error" -msgstr "Erro ao salvar" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.add" -msgstr "Adicionar" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.viewer" -msgstr "Modo de visualização (%s)" - -#: src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.small-thumbnails" -msgstr "Pré-visualizações pequenas" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.libraries" -msgstr "BIBLIOTECAS" - -msgid "workspace.options.grid.params.color" -msgstr "Cor" - -msgid "workspace.options.height" -msgstr "Altura" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-preserve-scroll" -msgstr "Preservar posição do scroll" - -msgid "workspace.options.opacity" -msgstr "Opacidade" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.multiply" -msgstr "Multiplicação" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.cut" -msgstr "Cortar" - -#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.mask" -msgstr "Máscara" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-palette" -msgstr "Mostrar paleta de cor" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.components" -msgstr "%s componentes" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.graphics" -msgstr "Imagens" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.group" -msgstr "Grupo" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.ungroup" -msgstr "Desagrupar" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-bottom" -msgstr "Alinhar à base" - -msgid "shortcut-section.viewer" -msgstr "Espectador" - -msgid "shortcut-section.dashboard" -msgstr "Painel" - -msgid "shortcut-subsection.general-viewer" -msgstr "Geral" - -msgid "shortcut-subsection.path-editor" -msgstr "Curvas" - -msgid "shortcuts.bool-intersection" -msgstr "Intersecção Booleana" - -msgid "shortcuts.draw-frame" -msgstr "Prancheta" - -msgid "shortcuts.draw-nodes" -msgstr "Desenhar caminho" - -msgid "shortcuts.draw-path" -msgstr "Caminho" - -msgid "shortcuts.move-unit-left" -msgstr "Mover para a esquerda" - -msgid "shortcuts.flip-horizontal" -msgstr "Refletir horizontalmente" - -msgid "shortcuts.move-unit-up" -msgstr "Mover para cima" - -msgid "shortcuts.next-frame" -msgstr "Próximo quadro" - -msgid "shortcuts.not-found" -msgstr "Não foram encontrados atalhos" - -msgid "shortcuts.opacity-0" -msgstr "Definir opacidade para 100%" - -msgid "shortcuts.opacity-3" -msgstr "Definir opacidade para 10%" - -msgid "shortcuts.open-color-picker" -msgstr "Selector de cores" - -msgid "shortcuts.open-comments" -msgstr "Ir para secção de comentários" - -msgid "shortcuts.open-dashboard" -msgstr "Ir para o painel" - -msgid "shortcuts.open-viewer" -msgstr "Ir para seção de interação" - -msgid "shortcuts.opacity-1" -msgstr "Definir opacidade para 100%" - -msgid "shortcuts.opacity-2" -msgstr "Definir opacidade para 20%" - -msgid "shortcuts.open-workspace" -msgstr "Ir para espaço de trabalho" - -msgid "shortcuts.or" -msgstr " ou " - -msgid "shortcuts.prev-frame" -msgstr "Quadro anterior" - -msgid "shortcuts.toggle-history" -msgstr "Alternar histórico" - -msgid "shortcuts.show-shortcuts" -msgstr "Mostrar/ocultar atalhos" - -msgid "shortcuts.toggle-scale-text" -msgstr "Alternar escala de texto" - -msgid "shortcuts.toggle-snap-grid" -msgstr "Encaixar na grade" - -msgid "shortcuts.toggle-visibility" -msgstr "Alternar visibilidade" - -msgid "shortcuts.toggle-zoom-style" -msgstr "Alternar estilo de zoom" - -msgid "shortcuts.unmask" -msgstr "Retirar máscara" - -msgid "shortcuts.v-distribute" -msgstr "Distribuir verticalmente" - -msgid "shortcuts.ungroup" -msgstr "Desagrupar" - -msgid "shortcuts.zoom-selected" -msgstr "Ajustar zoom à seleção" - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "title.viewer" -msgstr "%s - Modo de visualização - Penpot" - -msgid "viewer.breaking-change.description" -msgstr "" -"Este link compartilhável não é mais válido. Crie ou peça ao proprietário um " -"novo." - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "viewer.empty-state" -msgstr "Não foram encontrados quadros na página." - -msgid "viewer.breaking-change.message" -msgstr "Desculpe!" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.box-filter-all" -msgstr "Todos os recursos" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.libraries" -msgstr "Bibliotecas" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.not-found" -msgstr "Não foram encontrados arquivos" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.typography" -msgstr "Tipografias" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-id" -msgstr "Fonte" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.rename-group" -msgstr "Renomear grupo" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.letter-spacing" -msgstr "Espaçamento de letra" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-size" -msgstr "Tamanho" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.line-height" -msgstr "Altura entrelinha" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.text-transform" -msgstr "Transformar Texto" - -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs -msgid "workspace.gradients.linear" -msgstr "Gradiente linear" - -msgid "workspace.focus.focus-on" -msgstr "Foco ligado" - -msgid "workspace.focus.selection" -msgstr "Seleção" - -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs -msgid "workspace.gradients.radial" -msgstr "Gradiente radial" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-snap-grid" -msgstr "Desabilitar encaixar à grade" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-dynamic-alignment" -msgstr "Desabilitar alinhamento dinâmico" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-scale-text" -msgstr "Desabilitar escala de texto" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-snap-guides" -msgstr "Desativar ajuste às guias" - -msgid "workspace.header.menu.disable-snap-pixel-grid" -msgstr "Desativar ajuste ao pixel" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-textpalette" -msgstr "Ocultar palete de fontes" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.edit" -msgstr "Editar" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.file" -msgstr "Ficheiro" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.help-info" -msgstr "Ajuda & informação" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.view" -msgstr "Visualizar" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.reset-zoom" -msgstr "Redefenir" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.zoom-full-screen" -msgstr "Tela cheia" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.zoom-fill" -msgstr "Ajustar para preencher" - -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.file-library" -msgstr "Biblioteca de ficheiros" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.rgba" -msgstr "RGBA" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.save-color" -msgstr "Salvar estilo de cor" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.zoom-selected" -msgstr "Zoom até selecionados" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.colors" -msgstr "%s cores" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.hsv" -msgstr "HSV" - -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.recent-colors" -msgstr "Cores recentes" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.file-library" -msgstr "Biblioteca de ficheiros" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.library" -msgstr "BIBLIOTECA" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.graphics" -msgstr "%s imagens" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.shared-libraries" -msgstr "BIBLIOTECAS PARTILHADAS" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.libraries.text.multiple-typography-tooltip" -msgstr "Desassociar todas as tipografias" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.typography" -msgstr "%s tipografias" - -#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs -msgid "workspace.options.blur-options.title.multiple" -msgstr "Desfoque selecionado" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.fix-when-scrolling" -msgstr "Fixar no scroll" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.right" -msgstr "Direita" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.flows.flow-starts" -msgstr "Início de fluxo" - -#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs -msgid "workspace.options.group-fill" -msgstr "Preenchimento de grupo" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.group-stroke" -msgstr "Traço do grupo" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-action" -msgstr "Ação" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-animation-slide" -msgstr "Deslizar" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-background" -msgstr "Adicionar sobreposição de fundo" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-after-delay" -msgstr "Após atraso" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-duration" -msgstr "Duração" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing" -msgstr "Atenuação" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing-ease" -msgstr "Ease" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-mouse-enter" -msgstr "Mouse entra" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-mouse-leave" -msgstr "Mouse sai" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-ms" -msgstr "ms" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-out" -msgstr "Sair" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-top-right" -msgstr "Superior direito" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interactions" -msgstr "Interações" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-self" -msgstr "próprio" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.darken" -msgstr "Escurecer" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.exclusion" -msgstr "Exclusão" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.lighten" -msgstr "Clarear" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.luminosity" -msgstr "Luminusidade" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.hue" -msgstr "Matiz" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.normal" -msgstr "Normal" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.advanced-ops" -msgstr "Opções avançadas" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title" -msgstr "Redimensionar elemento" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.bottom" -msgstr "Inferior" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" -msgstr "Linha" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" -msgstr "Linha inversa" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" -msgstr "Coluna inversa" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.gap" -msgstr "Espaço" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "esquerda" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "direita" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin-simple" -msgstr "Margem simples" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.padding-all" -msgstr "Todos os lados" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.title" -msgstr "Layout" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.top" -msgstr "Topo" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.retry" -msgstr "Tentar de novo" - -msgid "workspace.options.search-font" -msgstr "Procurar fonte" - -msgid "workspace.options.text-options.text-case" -msgstr "Capitalização" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.delete-flow-start" -msgstr "Remover início de fluxo" - -msgid "workspace.shape.menu.difference" -msgstr "Diferença" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.front" -msgstr "Trazer para a frente" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.group" -msgstr "Grupo" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.lock" -msgstr "Bloquear" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.shortcuts" -msgstr "Atalhos (%s)" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.delete" -msgstr "Removido %s" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.assets" -msgstr "Recursos" - -msgid "viewer.header.handoff-section" -msgstr "Entrega (%s)" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-grid" -msgstr "Mostrar grade" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.export-multiple" -msgstr "Exportar seleção" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-object-slow" -msgstr "Exportação inesperadamente lenta" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-offset-effect" -msgstr "Efeito de offset" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-in" -msgstr "Dentro" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-center" -msgstr "Centro inferior" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-while-hovering" -msgstr "Durante o hover" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-while-pressing" -msgstr "Durante o premir" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.copy" -msgstr "Copiar" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.detach-instances-in-bulk" -msgstr "Quebrar instâncias" - -msgid "workspace.sidebar.layers.frames" -msgstr "Painéis" - -msgid "workspace.sidebar.layers.groups" -msgstr "Grupos" - -msgid "workspace.sidebar.layers.images" -msgstr "Imagens" - -msgid "workspace.sidebar.layers.masks" -msgstr "Máscaras" - -msgid "workspace.sidebar.layers.shapes" -msgstr "Formas" - -msgid "workspace.sidebar.layers.search" -msgstr "Procurar camadas" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.text-palette" -msgstr "Tipografias (%s)" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.zoom-fit" -msgstr "Ajustar para encaixar" - -msgid "shortcuts.artboard-selection" -msgstr "Criar quadro da seleção" - -msgid "shortcuts.bool-exclude" -msgstr "Excluir seleção" - -msgid "shortcuts.bool-difference" -msgstr "Subtrair seleção" - -msgid "shortcuts.bring-front" -msgstr "Trazer para a frente" - -msgid "shortcuts.bring-forward" -msgstr "Mover para a frente" - -msgid "shortcuts.decrease-zoom" -msgstr "Reduzir zoom" - -msgid "shortcuts.delete" -msgstr "Apagar" - -msgid "shortcuts.draw-curve" -msgstr "Curva" - -msgid "shortcuts.draw-ellipse" -msgstr "Elipse" - -msgid "shortcuts.fit-all" -msgstr "Ajustar à janela" - -msgid "shortcuts.go-to-drafts" -msgstr "Ir para rascunhos" - -msgid "shortcuts.go-to-libs" -msgstr "Ir para bibliotecas partilhadas" - -msgid "shortcuts.delete-node" -msgstr "Apagar ponto" - -msgid "shortcuts.make-curve" -msgstr "Criar curva" - -msgid "shortcuts.clear-undo" -msgstr "Limpar desfazer" - -msgid "shortcuts.move-unit-down" -msgstr "Mover para baixo" - -msgid "shortcuts.select-all" -msgstr "Selecionar todos" - -msgid "shortcuts.separate-nodes" -msgstr "Separar pontos" - -msgid "shortcuts.show-pixel-grid" -msgstr "Mostrar/ocultar grade de pixeis" - -msgid "shortcut-subsection.general-dashboard" -msgstr "Geral" - -msgid "shortcuts.open-interactions" -msgstr "Ir para seção de interação" - -msgid "shortcuts.reset-zoom" -msgstr "Redefenir zoom" - -msgid "shortcuts.toggle-lock" -msgstr "Bloquear selecionado" - -msgid "shortcuts.toggle-lock-size" -msgstr "Bloquear proporções" - -msgid "shortcuts.toggle-layers" -msgstr "Alternar camadas" - -msgid "shortcuts.toggle-textpalette" -msgstr "Alternar palete de texto" - -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-invitations" -msgstr "Convites - %s - Penpot" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.shared" -msgstr "Partilhado" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-variant-id" -msgstr "Variante" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.go-to-edit" -msgstr "Ir para biblioteca de estilo para editar" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs -msgid "workspace.assets.typography.sample" -msgstr "Ag" - -msgid "workspace.assets.local-library" -msgstr "biblioteca local" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-artboard-names" -msgstr "Esconder nomes das pranchetas" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.in-this-file" -msgstr "BIBILIOTECAS NESTE FICHEIRO" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-artboard-names" -msgstr "Mostrar nomes das pranchetas" - -msgid "workspace.header.menu.show-pixel-grid" -msgstr "Mostrar grade de pixel" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-textpalette" -msgstr "Mostrar palete de fontes" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-libraries-need-sync" -msgstr "Não há Bibliotecas Partilhadas por atualizar" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.update" -msgstr "Atualizar" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.add-interaction" -msgstr "Clique no botão + para adicionar interações." - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.left" -msgstr "Esquerda" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.updates" -msgstr "ATUALIZAÇÔES" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" -msgstr "Altura máxima" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" -msgstr "Largura máxima" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" -msgstr "Altura mínima" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" -msgstr "Largura mínima" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-center" -msgstr "Centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-close-outside" -msgstr "Fechar quando clicar fora do diálogo" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.color-burn" -msgstr "Queimar cor" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.title" -msgstr "Camada" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-manual" -msgstr "Manual" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin" -msgstr "Margem" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-position" -msgstr "Posição" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-prev-screen" -msgstr "Ecrã anterior" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-top-center" -msgstr "Superior centro" - -msgid "workspace.path.actions.separate-nodes" -msgstr "Separar pontos (%s)" - -msgid "workspace.path.actions.snap-nodes" -msgstr "Ajustar ao ponto (%s)" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.selection-color" -msgstr "Cores selecionadas" - -msgid "workspace.sidebar.layers.components" -msgstr "Componentes" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.hide" -msgstr "Esconder" - -msgid "workspace.shape.menu.intersection" -msgstr "Interseção" - -msgid "workspace.sidebar.layers.texts" -msgstr "Textos" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.zoom-fit-all" -msgstr "Ajustar tudo à janela" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-matches-for" -msgstr "Não foram encontrados resultados para “%s“" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.search-shared-libraries" -msgstr "Procurar bibliotecas partilhadas" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.top" -msgstr "Topo" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing-ease-in-out" -msgstr "Ease in out" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-navigate-to-dest" -msgstr "Navegar para: %s" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-toggle-overlay" -msgstr "Alternar sobreposição" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.gutter" -msgstr "Goteira" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.soft-light" -msgstr "Luz difusa" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing-ease-in" -msgstr "Ease in" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-none" -msgstr "(não especificado)" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-on-click" -msgstr "No clique" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing-ease-out" -msgstr "Ease out" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-navigate-to" -msgstr "Navegar para" - -msgid "shortcuts.create-component" -msgstr "Criar componente" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-top-left" -msgstr "Superior esquerdo" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-url" -msgstr "URL" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.hard-light" -msgstr "Luz direta" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.overlay" -msgstr "Sobreposição" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.saturation" -msgstr "Saturação" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "topo" - -msgid "shortcuts.copy" -msgstr "Copiar" - -msgid "shortcuts.create-new-project" -msgstr "Criar novo" - -msgid "shortcuts.cut" -msgstr "Cortar" - -msgid "shortcuts.draw-rect" -msgstr "Retângulo" - -msgid "shortcuts.detach-component" -msgstr "Quebrar componente" - -msgid "shortcuts.duplicate" -msgstr "Duplicar" - -msgid "shortcuts.draw-text" -msgstr "Texto" - -msgid "shortcuts.escape" -msgstr "Cancelar" - -msgid "shortcuts.export-shapes" -msgstr "Exportar formas" - -msgid "shortcuts.flip-vertical" -msgstr "Refletir verticalmente" - -#: src/app/main/ui/workspace.cljs -msgid "title.workspace" -msgstr "%s - Penpot" - -msgid "shortcuts.toggle-rules" -msgstr "Mostrar/ocultar réguas" - -msgid "shortcuts.move-unit-right" -msgstr "Mover para a direita" - -msgid "shortcuts.redo" -msgstr "Refazer" - -msgid "shortcuts.paste" -msgstr "Colar" - -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.dismiss" -msgstr "Dispensar" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" -msgstr "Altura.Min" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" -msgstr "Altura.Máx" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" -msgstr "Largura.Máx" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" -msgstr "Altura.Min" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-close-overlay" -msgstr "Fechar diálogo" - -msgid "workspace.options.clip-content" -msgstr "Cortar conteúdo" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-close-overlay-dest" -msgstr "Fechar diálogo: %s" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-left" -msgstr "Inferior esquerdo" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.create-component" -msgstr "Criar componente" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.flows.add-flow-start" -msgstr "Adicionar início de fluxo" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin-all" -msgstr "Todos os lados" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.bottom" -msgstr "Inferior" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "abaixo" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-right" -msgstr "Inferior direito" - -#: src/app/main/ui/workspace/sidebar/options/page.cljs -msgid "workspace.options.canvas-background" -msgstr "Fundo da tela" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.forward" -msgstr "Trazer para a frente" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.center" -msgstr "Centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-destination" -msgstr "Destino" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.detach-instance" -msgstr "Quebrar instância" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.duplicate" -msgstr "Duplicar" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.grid-title" -msgstr "Grade" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" -msgstr "Coluna" - -msgid "viewer.header.comments-section" -msgstr "Comentários (%s)" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints" -msgstr "Restrições" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.edit" -msgstr "Editar" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-object-error" -msgstr "Exportação falhada" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.flow-start" -msgstr "Início do fluxo" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.rgb-complementary" -msgstr "RGB Complementar" - -msgid "viewer.header.interactions-section" -msgstr "Interações (%s)" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.left" -msgstr "Esquerda" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.line-height" -msgstr "Altura de linha" - -msgid "shortcuts.toogle-fullscreen" -msgstr "Alternar tela cheia" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.scale" -msgstr "Escala" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.topbottom" -msgstr "Topo & Fundo" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-shared-libraries-available" -msgstr "Não há Bibliotecas Partilhadas disponíveis" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.libraries.text.multiple-typography" -msgstr "Tipografias múltiplas" - -msgid "workspace.shape.menu.restore-main" -msgstr "Restaurar componente principal" - -msgid "workspace.focus.focus-mode" -msgstr "Modo de foco" - -msgid "workspace.shape.menu.exclude" -msgstr "Excluir" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-complete" -msgstr "Exportação completa" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-animation" -msgstr "Animação" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-animation-none" -msgstr "Nenhuma" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-animation-push" -msgstr "Empurrar" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-open-url" -msgstr "Abrir url" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing-linear" -msgstr "Linear" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-open-overlay" -msgstr "Abrir diálogo" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-open-overlay-dest" -msgstr "Abrir diálogo: %s" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-toggle-overlay-dest" -msgstr "Alternar sobreposição: %s" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-trigger" -msgstr "Ativador" - -msgid "workspace.options.recent-fonts" -msgstr "Recente" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.circle-marker" -msgstr "Marcador de círculo" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.direction-ltr" -msgstr "Esquerda para a direita" - -msgid "shortcuts.search-placeholder" -msgstr "Procurar atalhos" - -#: src/app/main/ui/workspace/sidebar/shortcuts.cljs -msgid "shortcuts.title" -msgstr "Atalhos do teclado" - -msgid "shortcuts.align-hcenter" -msgstr "Alinhar ao centro horizontalmente" - -msgid "shortcuts.join-nodes" -msgstr "Unir pontos" - -msgid "shortcuts.go-to-search" -msgstr "Pesquisa" - -msgid "shortcuts.group" -msgstr "Agrupar" - -msgid "shortcuts.h-distribute" -msgstr "Distribuir horizontalmente" - -msgid "shortcuts.hide-ui" -msgstr "Mostrar/ocultar interface" - -msgid "shortcuts.increase-zoom" -msgstr "Mais zoom" - -msgid "shortcuts.insert-image" -msgstr "Inserir imagem" - -msgid "shortcuts.make-corner" -msgstr "Criar canto" - -msgid "shortcuts.move-fast-right" -msgstr "Mover para a direita rápido" - -msgid "shortcuts.move-fast-up" -msgstr "Mover para cima rápido" - -msgid "shortcuts.merge-nodes" -msgstr "Unir pontos" - -msgid "shortcuts.mask" -msgstr "Máscara" - -msgid "shortcuts.move" -msgstr "Mover" - -msgid "shortcuts.move-fast-down" -msgstr "Mover para baixo rápido" - -msgid "shortcuts.move-fast-left" -msgstr "Mover para a esquerda rápido" - -msgid "shortcuts.opacity-4" -msgstr "Definir opacidade para 40%" - -msgid "shortcuts.opacity-5" -msgstr "Definir opacidade para 50%" - -msgid "shortcuts.opacity-6" -msgstr "Definir opacidade para 60%" - -msgid "shortcuts.opacity-8" -msgstr "Definir opacidade para 80%" - -msgid "shortcuts.opacity-9" -msgstr "Definir opacidade para 90%" - -msgid "shortcuts.opacity-7" -msgstr "Definir opacidade para 70%" - -msgid "shortcuts.snap-nodes" -msgstr "Ajustar ao ponto" - -msgid "shortcuts.snap-pixel-grid" -msgstr "Ajustar à grade" - -msgid "shortcuts.start-editing" -msgstr "Começar a editar" - -msgid "shortcuts.start-measure" -msgstr "Iniciar medição" - -msgid "shortcuts.stop-measure" -msgstr "Parar medição" - -msgid "shortcuts.toggle-colorpalette" -msgstr "Alternar paleta de cor" - -msgid "shortcuts.thumbnail-set" -msgstr "Definir imagem de destaque" - -msgid "shortcuts.toggle-focus-mode" -msgstr "Alternar modo de foco" - -msgid "shortcuts.toggle-grid" -msgstr "Mostrar/ocultar grade" - -msgid "shortcuts.toggle-alignment" -msgstr "Alternar alinhamento dinâmico" - -msgid "shortcuts.toggle-assets" -msgstr "Alternar recursos" - -msgid "shortcuts.undo" -msgstr "Desfazer" - -msgid "workspace.focus.focus-off" -msgstr "Foco desligado" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.group-name" -msgstr "Nome do grupo" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.rename" -msgstr "Renomear" - -#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs -msgid "workspace.options.blur-options.title.group" -msgstr "Desfoque agrupado" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.saving" -msgstr "Salvando" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.leftright" -msgstr "Esquerda & Direita" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.flows.flow-start" -msgstr "Início do fluxo" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-delay" -msgstr "Atraso" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.color-dodge" -msgstr "Subexposição de cores" - -msgid "workspace.path.actions.add-node" -msgstr "Adicionar ponto (%s)" - -msgid "workspace.path.actions.delete-node" -msgstr "Remover ponto (%s)" - -msgid "workspace.path.actions.draw-nodes" -msgstr "Desenhar ponto (%s)" - -msgid "workspace.path.actions.merge-nodes" -msgstr "Mesclar pontos (%s)" - -msgid "workspace.options.width" -msgstr "Largura" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.selection-stroke" -msgstr "Selecionar traçado" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.underline" -msgstr "Sublinhado" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.square" -msgstr "Quadrado" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.strikethrough" -msgstr "Tachado" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.title" -msgstr "Texto" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.packed" -msgstr "embalado" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.no-wrap" -msgstr "no wrap" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.padding" -msgstr "Padding" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.right" -msgstr "Direito" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.padding-simple" -msgstr "Padding simples" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.space-between" -msgstr "espaço interno" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.space-around" -msgstr "espaço em volta" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.more-colors" -msgstr "Mais cores" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.wrap" -msgstr "quebrado" - -msgid "workspace.options.shadow-options.color" -msgstr "Cor da sombra" - -#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs -msgid "workspace.options.show-fill-on-export" -msgstr "Mostrar nas exportações" - -msgid "workspace.options.show-in-viewer" -msgstr "Mostrar no modo de visualização" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.line-arrow" -msgstr "Seta de linha" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.none" -msgstr "Nenhum" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.round" -msgstr "Redondo" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.lowercase" -msgstr "Minúsculo" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.title-group" -msgstr "Texto do grupo" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.title-selection" -msgstr "Texto de seleção" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.none" -msgstr "Nenhum" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.back" -msgstr "Enviar para trás" - -msgid "workspace.shape.menu.flatten" -msgstr "Achatar" - -msgid "workspace.shape.menu.hide-ui" -msgstr "Mostrar/ocultar UI" - -msgid "workspace.shape.menu.path" -msgstr "Caminho" - -msgid "workspace.shape.menu.thumbnail-remove" -msgstr "Remover miniatura" - -msgid "workspace.options.x" -msgstr "X" - -#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.paste" -msgstr "Colar" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.screen" -msgstr "Tela" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.uppercase" -msgstr "Maiúsculo" - -msgid "workspace.options.text-options.vertical-align" -msgstr "Alinhamento vertical" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.select-layer" -msgstr "Selecionar camada" - -msgid "workspace.shape.menu.union" -msgstr "Unir" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.more-lib-colors" -msgstr "Mais cores da biblioteca" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.reset-overrides" -msgstr "Redefinir substituições" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke" -msgstr "Traçado" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.preset" -msgstr "Predefinição" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.backward" -msgstr "Enviar para trás" - -msgid "workspace.shape.menu.thumbnail-set" -msgstr "Definir como miniatura" - -msgid "workspace.shape.menu.transform-to-path" -msgstr "Transformar em caminho" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.update-components-in-bulk" -msgstr "Atualizar componentes principais" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.square-marker" -msgstr "Marcador quadrado" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.triangle-arrow" -msgstr "Seta triangular" - -msgid "workspace.options.stroke-color" -msgstr "Cor do traçado" - -msgid "workspace.options.stroke-width" -msgstr "Largura do traçado" - -msgid "workspace.options.y" -msgstr "Y" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.direction-rtl" -msgstr "Direita para a esquerda" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.spread" -msgstr "Espalhar" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.use-play-button" -msgstr "" -"Use o botão play no cabeçalho para executar a visualização do protótipo." - -msgid "workspace.path.actions.make-curve" -msgstr "Para curvar (%s)" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.flip-horizontal" -msgstr "Virar horizontalmente" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.flip-vertical" -msgstr "Virar verticalmente" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.titlecase" -msgstr "Capitalização de Título" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.diamond-marker" -msgstr "Marcador diamante" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.unknown" -msgstr "Operação em %s" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.create-artboard-from-selection" -msgstr "Selecionar o board" - -msgid "workspace.undo.entry.single.typography" -msgstr "recurso de tipografia" - -msgid "workspace.path.actions.make-corner" -msgstr "Em cantos (%s)" +msgstr "Clique para fechar o caminho" \ No newline at end of file diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index dd58bfcf8d..9e6f63396a 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -2,15 +2,25 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-27 15:18+0000\n" "Last-Translator: Dário \n" -"Language-Team: Portuguese (Portugal) \n" +"Language-Team: Portuguese (Portugal) " +"\n" "Language: pt_PT\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n > 1;\n" "X-Generator: Weblate 4.14.1\n" +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Já tens uma conta?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"Verifica o teu e‑mail e clica no link de verificação para começares a " +"utilizar o Penpot." + #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" msgstr "Confirmar palavra-passe" @@ -24,19 +34,19 @@ msgid "auth.create-demo-profile" msgstr "Só queres experimentar?" #: src/app/main/ui/auth/register.cljs -msgid "auth.already-have-account" -msgstr "Já tens uma conta?" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.check-your-email" +msgid "auth.demo-warning" msgstr "" -"Verifica o teu e‑mail e clica no link de verificação para começares a " -"utilizar o Penpot." +"Este é um serviço de DEMONSTRAÇÃO, NÃO UTILIZES para trabalhos reais. Os " +"projetos serão eliminados periodicamente." #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs msgid "auth.email" msgstr "E-mail" +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Esqueceste a tua palavra-passe?" + #: src/app/main/ui/auth/register.cljs msgid "auth.fullname" msgstr "Nome completo" @@ -46,8 +56,24 @@ msgid "auth.login-here" msgstr "Iniciar sessão" #: src/app/main/ui/auth/login.cljs -msgid "auth.forgot-password" -msgstr "Esqueceste a tua palavra-passe?" +msgid "auth.login-submit" +msgstr "Iniciar sessão" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Que bom voltar a ver-te!" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Github" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "GitLab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Google" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" @@ -100,6 +126,47 @@ msgstr "Política de privacidade" msgid "auth.recovery-request-submit" msgstr "Recuperar palavra-passe" +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Vamos enviar-te um e-mail com as instruções" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Não te lembras da tua palavra-passe?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Altera a palavra-passe" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Não tens conta?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Criar conta" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "É gratuito, é Open Source" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Cria uma conta" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "A solução código aberto para design e prototipar." + +msgid "auth.terms-of-service" +msgstr "Termos de serviço" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Ao criar uma nova conta, concordas com os nossos termos de serviço e " +"política de privacidade." + #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "Enviámos um email de verificação para" @@ -112,12 +179,59 @@ msgstr "Todos os utilizadores Penpot" msgid "common.share-link.confirm-deletion-link-description" msgstr "" -"Tens a certeza de que queres remover este link? Se o fizeres, deixa de ficar " -"disponível para ninguém" +"Tens a certeza de que queres remover este link? Se o fizeres, deixa de " +"ficar disponível para ninguém" + +msgid "common.share-link.current-tag" +msgstr "(atual)" + +msgid "common.share-link.destroy-link" +msgstr "Eliminar link" + +msgid "common.share-link.get-link" +msgstr "Obter link" + +msgid "common.share-link.link-copied-success" +msgstr "Link copiado com sucesso" + +msgid "common.share-link.link-deleted-success" +msgstr "Link eliminado com sucesso" + +msgid "common.share-link.manage-ops" +msgstr "Gerir permissões" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 página partilhada" +msgstr[1] "%s páginas partilhadas" msgid "common.share-link.permissions-can-comment" msgstr "Pode comentar" +msgid "common.share-link.permissions-can-inspect" +msgstr "Pode inspecionar o código" + +msgid "common.share-link.permissions-hint" +msgstr "Qualquer pessoa com o link terá acesso" + +msgid "common.share-link.permissions-pages" +msgstr "Páginas partilhadas" + +msgid "common.share-link.placeholder" +msgstr "O link partilhável será apresentado aqui" + +msgid "common.share-link.team-members" +msgstr "Apenas membros da equipa" + +msgid "common.share-link.title" +msgstr "Partilha protótipos" + +msgid "common.share-link.view-all" +msgstr "Seleciona tudo" + +msgid "common.unpublish" +msgstr "Cancelar publicação" + #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.management" msgstr "Gestão da equipa" @@ -125,13 +239,33 @@ msgstr "Gestão da equipa" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.text" msgstr "" -"O Penpot é destinado a equipas. Convida colegas para colaborarem em projetos " -"e ficheiros" +"O Penpot é destinado a equipas. Convida colegas para colaborarem em " +"projetos e ficheiros" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.title" msgstr "Trabalho de equipa!" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Aprende os básicos no Penpot enquanto divertes-te a praticar neste tutorial." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Iniciar tutorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Tutorial prático" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Explora o Penpot e conhece as suas principais características." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Inicia a tour" + #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.walkthrough-hero.title" msgstr "Passo a passo na interface" @@ -156,6 +290,23 @@ msgstr "+ Criar nova equipa" msgid "dashboard.default-team-name" msgstr "O teu Penpot" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Eliminar equipa" + +msgid "dashboard.download-binary-file" +msgstr "Descarregar ficheiro Penpot (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Descarregar ficheiro standard (svg + json)" + +msgid "dashboard.draft-title" +msgstr "Rascunho" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplicar" + #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.duplicate-multi" msgstr "Duplicar %s ficheiros" @@ -164,6 +315,14 @@ msgstr "Duplicar %s ficheiros" msgid "dashboard.empty-files" msgstr "Ainda não tens ficheiros aqui" +#: src/app/main/ui/dashboard/grid.cljs +#, markdown +msgid "dashboard.empty-placeholder-drafts" +msgstr "" +"Oh não! Ainda não tens ficheiros! Se quiseres experimentar podes começar " +"com os nossos templates em [Libraries & " +"templates](https://penpot.app/libraries-templates.html)" + msgid "dashboard.export-binary-multi" msgstr "Descarrega %s ficheiros Penpot (.penpot)" @@ -185,6 +344,20 @@ msgstr "%s de %s elementos selecionados" msgid "dashboard.export-shapes" msgstr "Exportar" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Podes adicionar definições de exportação em elementos a partir das " +"propriedades de design (na parte inferior da barra lateral direita)." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Informações sobre como definir exportações no Penpot." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Não existem elementos com definições de exportação." + #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.title" msgstr "Exportar seleção" @@ -195,9 +368,13 @@ msgstr "Exportar ficheiro Penpot" msgid "dashboard.export-standard-multi" msgstr "Descarregar %s ficheiros standard (svg + json)" -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.no-elements" -msgstr "Não existem elementos com definições de exportação." +msgid "dashboard.export.detail" +msgstr "* Pode incluir componentes, gráficos, cores e/ou tipografia." + +msgid "dashboard.export.explain" +msgstr "" +"Um ou mais ficheiros que queres exportar estão a utilizar bibliotecas " +"partilhadas. O que queres fazer com os recursos*?" msgid "dashboard.export.options.all.message" msgstr "" @@ -207,6 +384,98 @@ msgstr "" msgid "dashboard.export.options.all.title" msgstr "Exportar bibliotecas partilhadas" +msgid "dashboard.export.options.detach.message" +msgstr "" +"Bibliotecas partilhadas não serão incluídas na exportação e nenhum recurso " +"será adicionado à biblioteca. " + +msgid "dashboard.export.options.detach.title" +msgstr "Trata os recursos da biblioteca partilhada como objetos básicos" + +msgid "dashboard.export.options.merge.message" +msgstr "" +"Os teus ficheiros serão exportados com todos os recursos externos " +"incorporados na biblioteca de ficheiros." + +msgid "dashboard.export.options.merge.title" +msgstr "Incluir recursos da biblioteca partilhada em bibliotecas de ficheiros" + +msgid "dashboard.export.title" +msgstr "Exportar ficheiros" + +msgid "dashboard.fonts.deleted-placeholder" +msgstr "Tipo de letra eliminado" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.dismiss-all" +msgstr "Ignorar todas" + +msgid "dashboard.fonts.empty-placeholder" +msgstr "Ainda não tens tipos de letra personalizados instalados." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.fonts-added" +msgid_plural "dashboard.fonts.fonts-added" +msgstr[0] "1 tipo de letra adicionado" +msgstr[1] "%s tipos de letra adicionados" + +#, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"Qualquer web font que carregares aqui será adicionada à família de fontes " +"disponível nas propriedades de texto dos ficheiros desta equipa. Tipos de " +"letra com a mesma família serão agrupadas como uma **única família**. Podes " +"carregar tipos de letra com os seguintes formatos: **TTF, OTF e WOFF** " +"(apenas um será necessário)." + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"Deves carregar tipos de letra que possuas or tenhas licença para utilizar " +"no Penpot. Sabe mais na secção de Direitos de Conteúdos dos [Termos de " +"serviço do Penpot](https://penpot.app/terms.html). Podes também ler mais " +"sobre [licenciamento de fontes](https://www.typography.com/faq)." + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "dashboard.fonts.upload-all" +msgstr "Carregar tudo" + +msgid "dashboard.import" +msgstr "Importar ficheiros Penpot" + +msgid "dashboard.import.analyze-error" +msgstr "Oops! Não conseguimos importar este ficheiro" + +msgid "dashboard.import.import-error" +msgstr "Ocorreu um problema na importação do ficheiro. O ficheiro não foi importado." + +msgid "dashboard.import.import-message" +msgstr "%s ficheiros importados com sucesso." + +msgid "dashboard.import.import-warning" +msgstr "Alguns ficheiros continham objetos inválidos que foram removidos." + +msgid "dashboard.import.progress.process-colors" +msgstr "Processando cores" + +msgid "dashboard.import.progress.process-components" +msgstr "Processando componentes" + +msgid "dashboard.import.progress.process-media" +msgstr "Processando media" + +msgid "dashboard.import.progress.process-page" +msgstr "Processando página: %s" + +msgid "dashboard.import.progress.process-typographies" +msgstr "Processando tipografias" + +msgid "dashboard.import.progress.upload-data" +msgstr "A carregar dados para o servidor (%s/%s)" + +msgid "dashboard.import.progress.upload-media" +msgstr "A carregar ficheiro: %s" + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Convidar para a equipa" @@ -257,6 +526,30 @@ msgstr "+ Novo Ficheiro" msgid "dashboard.new-file-prefix" msgstr "Novo Ficheiro" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Novo projeto" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Novo Projeto" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "Envia-me notícias, atualizações de produto e recomendações sobre o Penpot." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Subscrição de Newsletter" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "Não há resultados para \"%s\"" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "Projetos fixos aparecerão aqui" + #: src/app/main/ui/auth/verify_token.cljs msgid "dashboard.notifications.email-changed-successfully" msgstr "O teu endereço de e-mail foi atualizado com sucesso" @@ -300,6 +593,10 @@ msgstr "Promover para proprietário" msgid "dashboard.remove-account" msgstr "Queres remover a tua conta?" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.remove-shared" +msgstr "Remover como Biblioteca Partilhada" + #: src/app/main/ui/settings/profile.cljs msgid "dashboard.save-settings" msgstr "Guardar definições" @@ -436,6 +733,13 @@ msgstr "Atualizado: %s" msgid "errors.auth-provider-not-configured" msgstr "Provedor de autenticação não configurado." +msgid "errors.auth.unable-to-login" +msgstr "Parece que não estás autenticado ou a sessão expirou." + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "O teu browser não pode fazer esta operação" + #: src/app/main/data/workspace/persistence.cljs msgid "errors.components-v2" msgstr "Este ficheiro já foi usado com Componentes V2 ativos." @@ -503,8 +807,8 @@ msgstr "Parece que esta não é uma imagem válida." #: src/app/main/ui/dashboard/team.cljs msgid "errors.member-is-muted" msgstr "" -"O perfil que estás a convidar tem e-mails silenciados (relatórios de spam ou " -"devoluções altas)." +"O perfil que estás a convidar tem e-mails silenciados (relatórios de spam " +"ou devoluções altas)." msgid "errors.network" msgstr "Não é possível conectar ao servidor do back-end." @@ -513,13 +817,22 @@ msgstr "Não é possível conectar ao servidor do back-end." msgid "errors.password-invalid-confirmation" msgstr "A palavra-passe de confirmação tem de corresponder" +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "A palavra-passe deverá conter no mínimo 8 caracteres" + +#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +msgid "errors.profile-is-muted" +msgstr "" +"O teu perfil tem e-mails silenciados (relatórios de spam ou devoluções " +"altas)." + #: src/app/main/ui/auth/register.cljs msgid "errors.registration-disabled" msgstr "A criação de contas está atualmente desativada." msgid "errors.team-leave.insufficient-members" -msgstr "" -"Membros insuficientes para deixar a equipa, provavelmente queres eliminá-la." +msgstr "Membros insuficientes para deixar a equipa, provavelmente queres eliminá-la." msgid "errors.team-leave.member-does-not-exists" msgstr "O membro que tentas atribuir não existe." @@ -560,6 +873,10 @@ msgstr "Sentes vontade de falar? Conversa connosco no Gitter" msgid "feedback.description" msgstr "Descrição" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Ir para o fórum Penpot" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discourse-subtitle1" msgstr "" @@ -578,12 +895,8 @@ msgstr "Assunto" msgid "feedback.subtitle" msgstr "" "Por favor descreve o motivo do teu e-mail, especificando se é um problema, " -"uma ideia, ou uma dúvida. Um membro da nossa equipa tentará responder o mais " -"rápido possível." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "Ir para o fórum Penpot" +"uma ideia, ou uma dúvida. Um membro da nossa equipa tentará responder o " +"mais rápido possível." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" @@ -645,6 +958,14 @@ msgstr "Largura" msgid "handoff.attributes.layout" msgstr "Layout" +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Altura" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Esquerda" + #: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.radius" msgstr "Raio" @@ -766,6 +1087,13 @@ msgstr "Capitalização de Título" msgid "handoff.attributes.typography.text-transform.uppercase" msgstr "Maiúsculas" +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Código" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Círculo" + msgid "handoff.tabs.code.selected.component" msgstr "Componente" @@ -775,6 +1103,19 @@ msgstr "Curva" msgid "handoff.tabs.code.selected.frame" msgstr "Prancheta" +msgid "handoff.tabs.code.selected.group" +msgstr "Grupo" + +msgid "handoff.tabs.code.selected.image" +msgstr "Imagem" + +msgid "handoff.tabs.code.selected.mask" +msgstr "Máscara" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s Selecionados" + msgid "handoff.tabs.code.selected.path" msgstr "Caminho" @@ -822,6 +1163,12 @@ msgstr "e" msgid "labels.back" msgstr "Voltar" +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.desc-message" +msgstr "" +"Parece que tens de aguardar um pouco e tentar novamente; estamos a realizar " +"pequenas manutenções nos nossos servidores." + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.main-message" msgstr "Error de Servidor (Bad Gateway)" @@ -840,14 +1187,14 @@ msgstr "Fechar" msgid "labels.comments" msgstr "Comentários" -#: src/app/main/ui/settings/password.cljs -msgid "labels.confirm-password" -msgstr "Confirmar palavra-passe" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.community" msgstr "Comunidade" +#: src/app/main/ui/settings/password.cljs +msgid "labels.confirm-password" +msgstr "Confirmar palavra-passe" + msgid "labels.content" msgstr "Conteúdo" @@ -906,6 +1253,10 @@ msgstr "Eliminar %s ficheiros" msgid "labels.drafts" msgstr "Rascunhos" +#: src/app/main/ui/comments.cljs +msgid "labels.edit" +msgstr "Editar" + msgid "labels.edit-file" msgstr "Editar ficheiro" @@ -938,6 +1289,20 @@ msgstr "Família da Fonte" msgid "labels.font-providers" msgstr "Provedores de fontes" +msgid "labels.font-variants" +msgstr "Variantes" + +msgid "labels.fonts" +msgstr "Fontes" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Repositório Github" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.give-feedback" +msgstr "Dá feedback" + msgid "labels.go-back" msgstr "Voltar atrás" @@ -980,10 +1345,6 @@ msgstr "Idioma" msgid "labels.libraries-and-templates" msgstr "Bibliotecas e Templates" -#: src/app/main/ui/settings/password.cljs -msgid "labels.new-password" -msgstr "Palavra-passe nova" - msgid "labels.link" msgstr "Link" @@ -997,10 +1358,22 @@ msgstr "Sair" msgid "labels.manage-fonts" msgstr "Gerir fontes" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Membro" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.members" +msgstr "Membros" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.name" msgstr "Nome" +#: src/app/main/ui/settings/password.cljs +msgid "labels.new-password" +msgstr "Palavra-passe nova" + msgid "labels.next" msgstr "Próximo" @@ -1022,10 +1395,6 @@ msgstr "" msgid "labels.not-found.auth-info" msgstr "Estás autenticado como" -#: src/app/main/ui/settings/password.cljs -msgid "labels.old-password" -msgstr "Palavra-passe antiga" - #: src/app/main/ui/static.cljs msgid "labels.not-found.desc-message" msgstr "Esta página não existe ou não tens permissões para a aceder." @@ -1051,9 +1420,9 @@ msgid_plural "labels.num-of-projects" msgstr[0] "1 projeto" msgstr[1] "%s projetos" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.password" -msgstr "Palavra-passe" +#: src/app/main/ui/settings/password.cljs +msgid "labels.old-password" +msgstr "Palavra-passe antiga" #: src/app/main/ui/workspace/comments.cljs msgid "labels.only-yours" @@ -1066,6 +1435,10 @@ msgstr "ou" msgid "labels.owner" msgstr "Proprietário" +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Palavra-passe" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.pending-invitation" msgstr "Pendente" @@ -1101,6 +1474,10 @@ msgstr "Remover membro" msgid "labels.rename" msgstr "Renomear" +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.rename-team" +msgstr "Renomear equipa" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.resend-invitation" msgstr "Reenviar convite" @@ -1127,17 +1504,25 @@ msgstr "Enviar" msgid "labels.sending" msgstr "Enviando…" +#: src/app/main/ui/static.cljs +msgid "labels.service-unavailable.desc-message" +msgstr "Estamos numa manutenção programada dos nossos sistemas." + #: src/app/main/ui/static.cljs msgid "labels.service-unavailable.main-message" msgstr "Serviço Indisponível" +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.settings" +msgstr "Definições" + +msgid "labels.share-prototype" +msgstr "Partilhar protótipo" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.shared-libraries" msgstr "Bibliotecas" -msgid "labels.share-prototype" -msgstr "Partilhar protótipo" - #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-all-comments" msgstr "Mostrar todos os comentários" @@ -1195,10 +1580,18 @@ msgstr "Área de trabalho" msgid "labels.write-new-comment" msgstr "Escrever novo comentário" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(tu)" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.your-account" msgstr "A tua conta" +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "A carregar imagem…" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.accept" msgstr "Adicionar como Biblioteca Partilhada" @@ -1224,8 +1617,8 @@ msgstr "Verificar o novo e-mail" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.info" msgstr "" -"Vamos enviar um e‑mail para o teu endereço atual \"%s\" para verificar a tua " -"identidade." +"Vamos enviar um e‑mail para o teu endereço atual \"%s\" para verificar a " +"tua identidade." #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.new-email" @@ -1239,6 +1632,12 @@ msgstr "Alterar e-mail" msgid "modals.change-email.title" msgstr "Altera o teu e-mail" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"És o proprietário desta equipa. Seleciona outro membro para promover para " +"proprietário antes de saíres." + #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" msgstr "Cancelar e manter a minha conta" @@ -1262,8 +1661,8 @@ msgstr "Eliminar conversa" #: src/app/main/ui/comments.cljs msgid "modals.delete-comment-thread.message" msgstr "" -"Tens a certeza de que pretender eliminar esta conversa? Todos os comentários " -"neste tópico serão eliminados." +"Tens a certeza de que pretender eliminar esta conversa? Todos os " +"comentários neste tópico serão eliminados." #: src/app/main/ui/comments.cljs msgid "modals.delete-comment-thread.title" @@ -1293,13 +1692,18 @@ msgstr "Tens a certeza de que pretendes eliminar %s ficheiros?" msgid "modals.delete-file-multi-confirm.title" msgstr "Eliminando %s ficheiros" +msgid "modals.delete-font-variant.message" +msgstr "" +"Tens a certeza de que pretendes eliminar este estilo de fonte? Não " +"carregará se estiver a ser utilizado num ficheiro." + msgid "modals.delete-font-variant.title" msgstr "Eliminando estilo de fonte" -msgid "modals.delete-font-variant.message" +msgid "modals.delete-font.message" msgstr "" -"Tens a certeza de que pretendes eliminar este estilo de fonte? Não carregará " -"se estiver a ser utilizado num ficheiro." +"Tens a certeza de que pretendes eliminar esta fonte? Não carregará se " +"estiver a ser utilizada num ficheiro." msgid "modals.delete-font.title" msgstr "Eliminando fonte" @@ -1324,12 +1728,6 @@ msgstr "Tens a certeza de que pretendes eliminar este projeto?" msgid "modals.delete-project-confirm.title" msgstr "Eliminar projeto" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Este ficheiro tem bibliotecas utilizadas neste ficheiro:" -msgstr[1] "Este ficheiro tem bibliotecas utilizadas nestes ficheiros:" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.accept" msgid_plural "modals.delete-shared-confirm.accept" @@ -1342,6 +1740,18 @@ msgid_plural "modals.delete-shared-confirm.message" msgstr[0] "Tens a certeza de que pretendes eliminar este ficheiro?" msgstr[1] "Tens a certeza de que pretendes eliminar estes ficheiros?" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Este ficheiro tem bibliotecas utilizadas neste ficheiro:" +msgstr[1] "Este ficheiro tem bibliotecas utilizadas nestes ficheiros:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Estes ficheiros têm bibliotecas utilizadas neste ficheiro:" +msgstr[1] "Estes ficheiros têm bibliotecas utilizadas nestes ficheiros:" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" msgid_plural "modals.delete-shared-confirm.title" @@ -1370,14 +1780,14 @@ msgstr "Eliminando equipa" msgid "modals.delete-team-member-confirm.accept" msgstr "Eliminar membro" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.title" -msgstr "Eliminar membro da equipa" - #: src/app/main/ui/dashboard/team.cljs msgid "modals.delete-team-member-confirm.message" msgstr "Tens a certeza de que pretendes eliminar este membro da equipa?" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Eliminar membro da equipa" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-member-confirm.accept" msgstr "Enviar convite" @@ -1445,8 +1855,8 @@ msgstr "Transferir propriedade" #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.hint" msgstr "" -"Se transferires a propriedade, vais alterar a tua função para Administrador, " -"perdendo algumas permissões sobre esta equipa. " +"Se transferires a propriedade, vais alterar a tua função para " +"Administrador, perdendo algumas permissões sobre esta equipa. " #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.message" @@ -1465,9 +1875,9 @@ msgstr "Remover como Biblioteca Partilhada" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.hint" msgstr "" -"Uma vez removida como Biblioteca Partilhada, a Biblioteca de Ficheiros deste " -"ficheiro deixarão de estar disponíveis para serem utilizados com o resto dos " -"teus ficheiros." +"Uma vez removida como Biblioteca Partilhada, a Biblioteca de Ficheiros " +"deste ficheiro deixarão de estar disponíveis para serem utilizados com o " +"resto dos teus ficheiros." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.message" @@ -1488,8 +1898,14 @@ msgstr[0] "" "Se cancelares a publicação, os recursos nele tornam-se uma biblioteca deste " "ficheiro." msgstr[1] "" -"Se cancelares a publicação, os recursos nele tornam-se uma biblioteca destes " -"ficheiros." +"Se cancelares a publicação, os recursos nele tornam-se uma biblioteca " +"destes ficheiros." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Tens a certeza de que queres cancelar a publicação desta biblioteca?" +msgstr[1] "Tens a certeza de que queres cancelar a publicação destas bibliotecas?" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" @@ -1497,6 +1913,12 @@ msgid_plural "modals.unpublish-shared-confirm.scd-message" msgstr[0] "Está em uso neste ficheiro:" msgstr[1] "Está em uso nestes ficheiros:" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Cancelar publicação da biblioteca" +msgstr[1] "Cancelar publicação das bibliotecas" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" msgstr "" @@ -1507,6 +1929,10 @@ msgstr "" msgid "modals.update-remote-component-in-bulk.message" msgstr "Atualizar componentes numa biblioteca partilhada" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.accept" +msgstr "Atualizar" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.cancel" msgstr "Cancelar" @@ -1552,11 +1978,6 @@ msgstr "" msgid "onboarding-v2.before-start.desc2.title" msgstr "Guia de Utilizador" -msgid "onboarding-v2.welcome.desc1" -msgstr "" -"O Penpot é um software de código aberto criado pela Kaleidos e também pela " -"comunidade, onde muitas pessoas já colaboram. Todos podem colaborar:" - msgid "onboarding-v2.before-start.desc3" msgstr "Poderás ver os nossos tutoriais e os criados pela nossa comunidade." @@ -1566,6 +1987,11 @@ msgstr "Tutoriais em vídeo" msgid "onboarding-v2.before-start.title" msgstr "Antes de começares" +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"O Penpot é um software de código aberto criado pela Kaleidos e também pela " +"comunidade, onde muitas pessoas já colaboram. Todos podem colaborar:" + msgid "onboarding-v2.welcome.desc2" msgstr "" "Um espaço público para aprender, partilhar e discutir sobre o Penpot, o " @@ -1597,16 +2023,13 @@ msgstr "Depois de nomeares a tua equipa, poderás convidar pessoas para entrar." msgid "onboarding.choice.team-up.create-team-placeholder" msgstr "Escreve o nome da equipa" -msgid "onboarding.contrib.alt" -msgstr "Código aberto" - msgid "onboarding.choice.team-up.invite-members" msgstr "Convida membros" msgid "onboarding.choice.team-up.invite-members-info" msgstr "" -"Lembra-te em incluir todos. Programadores, designers, gestores... acrescenta " -"diversidade :)" +"Lembra-te em incluir todos. Programadores, designers, gestores... " +"acrescenta diversidade :)" msgid "onboarding.choice.team-up.invite-members-skip" msgstr "Criar equipa e convidar mais tarde" @@ -1620,14 +2043,14 @@ msgstr "Convidar com a função:" msgid "onboarding.choice.title" msgstr "Bem-vindo ao Penpot" +msgid "onboarding.contrib.alt" +msgstr "Código aberto" + msgid "onboarding.contrib.desc1" msgstr "" "O Penpot é de código aberto, feito por e para a comunidade. Se queres " "colaborar, és mais que bem-vindo!" -msgid "onboarding.contrib.title" -msgstr "Contribuidor Open Source?" - msgid "onboarding.contrib.desc2.1" msgstr "Podes aceder o" @@ -1637,9 +2060,17 @@ msgstr "e seguir as instruções de contribuição :)" msgid "onboarding.contrib.link" msgstr "projeto no GitHub" +msgid "onboarding.contrib.title" +msgstr "Contribuidor Open Source?" + msgid "onboarding.newsletter.accept" msgstr "Sim, subscreve" +msgid "onboarding.newsletter.acceptance-message" +msgstr "" +"A tua solicitação de inscrição foi enviada, iremos enviar-te um e-mail para " +"confirmá-la." + msgid "onboarding.newsletter.decline" msgstr "Não, obrigado" @@ -1667,8 +2098,7 @@ msgid "onboarding.slide.0.alt" msgstr "Cria designs" msgid "onboarding.slide.0.desc1" -msgstr "" -"Cria interfaces maravilhosas em colaboração com todos os membros da equipa." +msgstr "Cria interfaces maravilhosas em colaboração com todos os membros da equipa." msgid "onboarding.slide.0.desc2" msgstr "" @@ -1708,18 +2138,13 @@ msgstr "Handoff e low code" msgid "onboarding.slide.3.desc1" msgstr "" -"Sincroniza o design e código de todos os teus componentes e estilos, e obtém " -"os snippets de código." - -msgid "onboarding.team-modal.create-team-desc" -msgstr "" -"Uma equipa permite que colabores com outros utilizadores do Penpot " -"trabalhando nos mesmos ficheiros e projetos." +"Sincroniza o design e código de todos os teus componentes e estilos, e " +"obtém os snippets de código." msgid "onboarding.slide.3.desc2" msgstr "" -"Recebe e fornece especificações de código como markup (SVG, HTML) ou estilos " -"(CSS, Less, Stylus...)." +"Recebe e fornece especificações de código como markup (SVG, HTML) ou " +"estilos (CSS, Less, Stylus...)." msgid "onboarding.slide.3.title" msgstr "Uma origem partilhada de verdade" @@ -1727,31 +2152,42 @@ msgstr "Uma origem partilhada de verdade" msgid "onboarding.team-modal.create-team" msgstr "Cria uma equipa" +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Uma equipa permite que colabores com outros utilizadores do Penpot " +"trabalhando nos mesmos ficheiros e projetos." + msgid "onboarding.team-modal.create-team-feature-1" msgstr "Ficheiros e projetos ilimitados" msgid "onboarding.team-modal.create-team-feature-2" msgstr "Edição Multiplayer" +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Gestão de funções" + msgid "onboarding.team-modal.create-team-feature-4" msgstr "Membros ilimitados" +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% grátis!" + msgid "onboarding.templates.subtitle" msgstr "Aqui tens alguns templates." msgid "onboarding.templates.title" msgstr "Começa a desenhar" -#: src/app/main/ui/auth/recovery.cljs -msgid "profile.recovery.go-to-login" -msgstr "Ir para login" - msgid "onboarding.welcome.alt" msgstr "Penpot" msgid "onboarding.welcome.title" msgstr "Bem-vindo ao Penpot" +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Ir para login" + #: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs msgid "settings.multiple" msgstr "Misturado" @@ -1806,6 +2242,15 @@ msgstr "Caminhos" msgid "shortcut-subsection.shape" msgstr "Formas" +msgid "shortcut-subsection.tools" +msgstr "Ferramentas" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Zoom" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Zoom" + msgid "shortcuts.add-comment" msgstr "Comentários" @@ -1848,12 +2293,12 @@ msgstr "União Booleana" msgid "shortcuts.bring-back" msgstr "Enviar para trás" -msgid "shortcuts.bring-forward" -msgstr "Mover para a frente" - msgid "shortcuts.bring-backward" msgstr "Mover para trás" +msgid "shortcuts.bring-forward" +msgstr "Mover para a frente" + msgid "shortcuts.bring-front" msgstr "Enviar para a frente" @@ -1920,9 +2365,15 @@ msgstr "Ajustar tudo à janela" msgid "shortcuts.flip-horizontal" msgstr "Virar horizontalmente" +msgid "shortcuts.flip-vertical" +msgstr "Virar verticalmente" + msgid "shortcuts.go-to-drafts" msgstr "Ir para rascunhos" +msgid "shortcuts.go-to-libs" +msgstr "Ir para bibliotecas partilhadas" + msgid "shortcuts.go-to-search" msgstr "Pesquisa" @@ -1932,6 +2383,9 @@ msgstr "Grupo" msgid "shortcuts.h-distribute" msgstr "Distribuir horizontalmente" +msgid "shortcuts.hide-ui" +msgstr "Mostrar/ocultar interface" + msgid "shortcuts.increase-zoom" msgstr "Mais zoom" @@ -1956,6 +2410,9 @@ msgstr "Fundir nós" msgid "shortcuts.move" msgstr "Mover" +msgid "shortcuts.move-fast-down" +msgstr "Mover para baixo rápido" + msgid "shortcuts.move-fast-left" msgstr "Mover para a esquerda rápido" @@ -1989,9 +2446,6 @@ msgstr "Nenhum atalho encontrado" msgid "shortcuts.opacity-0" msgstr "Definir opacidade a 100%" -msgid "shortcuts.open-comments" -msgstr "Ir para secção de comentários" - msgid "shortcuts.opacity-1" msgstr "Definir opacidade a 10%" @@ -2022,6 +2476,9 @@ msgstr "Definir opacidade a 90%" msgid "shortcuts.open-color-picker" msgstr "Selector de cores" +msgid "shortcuts.open-comments" +msgstr "Ir para secção de comentários" + msgid "shortcuts.open-dashboard" msgstr "Ir para painel" @@ -2031,6 +2488,9 @@ msgstr "Ir para secção de transferências" msgid "shortcuts.open-interactions" msgstr "Ir para secção de interações" +msgid "shortcuts.open-viewer" +msgstr "Ir para secção de interações" + msgid "shortcuts.open-workspace" msgstr "Ir para a área de trabalho" @@ -2052,6 +2512,9 @@ msgstr "Redefinir zoom" msgid "shortcuts.search-placeholder" msgstr "Pesquisar atalhos" +msgid "shortcuts.select-all" +msgstr "Selecionar tudo" + msgid "shortcuts.separate-nodes" msgstr "Separar nós" @@ -2113,10 +2576,6 @@ msgstr "Bloquear proporções" msgid "shortcuts.toggle-rules" msgstr "Mostrar/ocultar regras" -#: src/app/main/ui/dashboard/files.cljs -msgid "title.dashboard.files" -msgstr "%s - Penpot" - msgid "shortcuts.toggle-scale-text" msgstr "Alternar escala de texto" @@ -2153,6 +2612,10 @@ msgstr "Distribuir verticalmente" msgid "shortcuts.zoom-selected" msgstr "Zoom para selecionados" +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - Penpot" + #: src/app/main/ui/dashboard/fonts.cljs msgid "title.dashboard.font-providers" msgstr "Provedores de fonte - %s - Penpot" @@ -2161,18 +2624,6 @@ msgstr "Provedores de fonte - %s - Penpot" msgid "title.dashboard.fonts" msgstr "Fontes - %s - Penpot" -#: src/app/main/ui/dashboard/libraries.cljs -msgid "title.dashboard.shared-libraries" -msgstr "Bibliotecas partilhadas - %s - Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "title.settings.feedback" -msgstr "Dá feedback - Penpot" - -#: src/app/main/ui/settings/password.cljs -msgid "title.settings.password" -msgstr "Palavra-passe - Penpot" - #: src/app/main/ui/dashboard/projects.cljs msgid "title.dashboard.projects" msgstr "Projetos - %s - Penpot" @@ -2181,18 +2632,30 @@ msgstr "Projetos - %s - Penpot" msgid "title.dashboard.search" msgstr "Pesquisa - %s - Penpot" +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Bibliotecas partilhadas - %s - Penpot" + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs msgid "title.default" msgstr "Penpot - Liberdade de Design para Equipas" +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Dá feedback - Penpot" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Definições - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Palavra-passe - Penpot" + #: src/app/main/ui/settings/profile.cljs msgid "title.settings.profile" msgstr "Perfil - Penpot" -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-settings" -msgstr "Definições - %s - Penpot" - #: src/app/main/ui/dashboard/team.cljs msgid "title.team-invitations" msgstr "Convites - %s - Penpot" @@ -2201,6 +2664,10 @@ msgstr "Convites - %s - Penpot" msgid "title.team-members" msgstr "Membros - %s - Penpot" +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Definições - %s - Penpot" + #: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs msgid "title.viewer" msgstr "%s - Modo visualizador - Penpot" @@ -2211,12 +2678,20 @@ msgstr "%s - Penpot" msgid "viewer.breaking-change.description" msgstr "" -"Este link partilhável já não é válido. Cria uma nova ou pede ao proprietário " -"para um novo." +"Este link partilhável já não é válido. Cria uma nova ou pede ao " +"proprietário para um novo." msgid "viewer.breaking-change.message" msgstr "Desculpa!" +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "Não foram encontrados quadros na página." + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.frame-not-found" +msgstr "Quadro não encontrado." + msgid "viewer.header.comments-section" msgstr "Comentários (%s)" @@ -2250,6 +2725,18 @@ msgstr "Criar link" msgid "viewer.header.share.placeholder" msgstr "Links partilhados aparecerão aqui" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Remover link" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Qualquer pessoa com o link terá acesso" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "Mostrar interações" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions-on-click" msgstr "Mostrar interações com click" @@ -2335,6 +2822,10 @@ msgstr "Editar" msgid "workspace.assets.graphics" msgstr "Gráficos" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Agrupar" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.group-name" msgstr "Nome do grupo" @@ -2350,6 +2841,10 @@ msgstr "biblioteca local" msgid "workspace.assets.not-found" msgstr "Recursos não encontrados" +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename" +msgstr "Renomear" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.rename-group" msgstr "Renomear grupo" @@ -2396,6 +2891,10 @@ msgstr "Espaço entre letras" msgid "workspace.assets.typography.line-height" msgstr "Altura da Linha" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs +msgid "workspace.assets.typography.sample" +msgstr "Ag" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.assets.typography.text-transform" msgstr "Transformar Texto" @@ -2420,10 +2919,18 @@ msgstr "Seleção" msgid "workspace.gradients.linear" msgstr "Gradiente linear" +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Gradiente radial" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.disable-dynamic-alignment" msgstr "Desativar alinhamento dinâmico" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-scale-text" +msgstr "Desativar escala de texto" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.disable-snap-grid" msgstr "Desativar ajuste à grade" @@ -2432,542 +2939,6 @@ msgstr "Desativar ajuste à grade" msgid "workspace.header.menu.disable-snap-guides" msgstr "Desativar ajuste às guias" -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.shared-libraries" -msgstr "BIBLIOTECAS PARTILHADAS" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.go-main" -msgstr "Ir para ficheiro do componente principal" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.demo-warning" -msgstr "" -"Este é um serviço de DEMONSTRAÇÃO, NÃO UTILIZES para trabalhos reais. Os " -"projetos serão eliminados periodicamente." - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-submit" -msgstr "Iniciar sessão" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-title" -msgstr "Que bom voltar a ver-te!" - -msgid "common.share-link.destroy-link" -msgstr "Eliminar link" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-github-submit" -msgstr "Github" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-gitlab-submit" -msgstr "GitLab" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-google-submit" -msgstr "Google" - -#: src/app/main/ui/auth.cljs -msgid "auth.sidebar-tagline" -msgstr "A solução código aberto para design e prototipar." - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.recovery-submit" -msgstr "Altera a palavra-passe" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.register" -msgstr "Não tens conta?" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.register-submit" -msgstr "Criar conta" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.register-subtitle" -msgstr "É gratuito, é Open Source" - -msgid "auth.terms-of-service" -msgstr "Termos de serviço" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.terms-privacy-agreement" -msgstr "" -"Ao criar uma nova conta, concordas com os nossos termos de serviço e " -"política de privacidade." - -#: src/app/main/ui/auth/register.cljs -msgid "auth.register-title" -msgstr "Cria uma conta" - -msgid "common.share-link.current-tag" -msgstr "(atual)" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.recovery-request-subtitle" -msgstr "Vamos enviar-te um e-mail com as instruções" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.recovery-request-title" -msgstr "Não te lembras da tua palavra-passe?" - -msgid "common.share-link.get-link" -msgstr "Obter link" - -msgid "common.share-link.link-copied-success" -msgstr "Link copiado com sucesso" - -msgid "common.share-link.page-shared" -msgid_plural "common.share-link.page-shared" -msgstr[0] "1 página partilhada" -msgstr[1] "%s páginas partilhadas" - -msgid "common.share-link.permissions-can-inspect" -msgstr "Pode inspecionar o código" - -msgid "common.share-link.link-deleted-success" -msgstr "Link eliminado com sucesso" - -msgid "common.share-link.permissions-pages" -msgstr "Páginas partilhadas" - -msgid "common.share-link.manage-ops" -msgstr "Gerir permissões" - -msgid "common.share-link.permissions-hint" -msgstr "Qualquer pessoa com o link terá acesso" - -msgid "common.share-link.view-all" -msgstr "Seleciona tudo" - -msgid "common.share-link.placeholder" -msgstr "O link partilhável será apresentado aqui" - -msgid "common.share-link.team-members" -msgstr "Apenas membros da equipa" - -msgid "common.share-link.title" -msgstr "Partilha protótipos" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "" -"Aprende os básicos no Penpot enquanto divertes-te a praticar neste tutorial." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Tutorial prático" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "Explora o Penpot e conhece as suas principais características." - -msgid "common.unpublish" -msgstr "Cancelar publicação" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Iniciar tutorial" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Inicia a tour" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.delete-team" -msgstr "Eliminar equipa" - -msgid "dashboard.download-standard-file" -msgstr "Descarregar ficheiro standard (svg + json)" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate" -msgstr "Duplicar" - -msgid "dashboard.download-binary-file" -msgstr "Descarregar ficheiro Penpot (.penpot)" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.how-to" -msgstr "" -"Podes adicionar definições de exportação em elementos a partir das " -"propriedades de design (na parte inferior da barra lateral direita)." - -msgid "dashboard.draft-title" -msgstr "Rascunho" - -msgid "dashboard.export.detail" -msgstr "* Pode incluir componentes, gráficos, cores e/ou tipografia." - -msgid "dashboard.export.explain" -msgstr "" -"Um ou mais ficheiros que queres exportar estão a utilizar bibliotecas " -"partilhadas. O que queres fazer com os recursos*?" - -#: src/app/main/ui/dashboard/grid.cljs -#, markdown -msgid "dashboard.empty-placeholder-drafts" -msgstr "" -"Oh não! Ainda não tens ficheiros! Se quiseres experimentar podes começar com " -"os nossos templates em [Libraries & templates](https://penpot.app/libraries-" -"templates.html)" - -msgid "dashboard.export.options.merge.message" -msgstr "" -"Os teus ficheiros serão exportados com todos os recursos externos " -"incorporados na biblioteca de ficheiros." - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.how-to-link" -msgstr "Informações sobre como definir exportações no Penpot." - -msgid "dashboard.export.options.detach.message" -msgstr "" -"Bibliotecas partilhadas não serão incluídas na exportação e nenhum recurso " -"será adicionado à biblioteca. " - -msgid "dashboard.export.options.detach.title" -msgstr "Trata os recursos da biblioteca partilhada como objetos básicos" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-scale-text" -msgstr "Desativar escala de texto" - -msgid "dashboard.export.options.merge.title" -msgstr "Incluir recursos da biblioteca partilhada em bibliotecas de ficheiros" - -msgid "dashboard.fonts.deleted-placeholder" -msgstr "Tipo de letra eliminado" - -msgid "shortcuts.select-all" -msgstr "Selecionar tudo" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.subtitle" -msgstr "Qualquer pessoa com o link terá acesso" - -msgid "dashboard.export.title" -msgstr "Exportar ficheiros" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.remove-link" -msgstr "Remover link" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.dismiss-all" -msgstr "Ignorar todas" - -msgid "dashboard.fonts.empty-placeholder" -msgstr "Ainda não tens tipos de letra personalizados instalados." - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.fonts-added" -msgid_plural "dashboard.fonts.fonts-added" -msgstr[0] "1 tipo de letra adicionado" -msgstr[1] "%s tipos de letra adicionados" - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "viewer.empty-state" -msgstr "Não foram encontrados quadros na página." - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.show-interactions" -msgstr "Mostrar interações" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.group" -msgstr "Agrupar" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs -msgid "workspace.assets.typography.sample" -msgstr "Ag" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.rename" -msgstr "Renomear" - -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs -msgid "workspace.gradients.radial" -msgstr "Gradiente radial" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "Estes ficheiros têm bibliotecas utilizadas neste ficheiro:" -msgstr[1] "Estes ficheiros têm bibliotecas utilizadas nestes ficheiros:" - -msgid "dashboard.import.progress.process-components" -msgstr "Processando componentes" - -msgid "shortcuts.go-to-libs" -msgstr "Ir para bibliotecas partilhadas" - -msgid "dashboard.import.import-message" -msgstr "%s ficheiros importados com sucesso." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "Tens a certeza de que queres cancelar a publicação desta biblioteca?" -msgstr[1] "Tens a certeza de que queres cancelar a publicação destas bibliotecas?" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.search-shared-libraries" -msgstr "Pesquisar bibliotecas partilhadas" - -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.there-are-updates" -msgstr "Existem atualizações nas bibliotecas partilhadas" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "Cancelar publicação da biblioteca" -msgstr[1] "Cancelar publicação das bibliotecas" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-libraries-need-sync" -msgstr "Não há bibliotecas partilhadas que precisem de atualização" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-shared-libraries-available" -msgstr "Não há bibliotecas partilhadas disponíveis" - -#, markdown -msgid "dashboard.fonts.hero-text1" -msgstr "" -"Qualquer web font que carregares aqui será adicionada à família de fontes " -"disponível nas propriedades de texto dos ficheiros desta equipa. Tipos de " -"letra com a mesma família serão agrupadas como uma **única família**. Podes " -"carregar tipos de letra com os seguintes formatos: **TTF, OTF e WOFF** (" -"apenas um será necessário)." - -#, markdown -msgid "dashboard.fonts.hero-text2" -msgstr "" -"Deves carregar tipos de letra que possuas or tenhas licença para utilizar no " -"Penpot. Sabe mais na secção de Direitos de Conteúdos dos [Termos de serviço " -"do Penpot](https://penpot.app/terms.html). Podes também ler mais sobre [" -"licenciamento de fontes](https://www.typography.com/faq)." - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "dashboard.fonts.upload-all" -msgstr "Carregar tudo" - -msgid "dashboard.import.analyze-error" -msgstr "Oops! Não conseguimos importar este ficheiro" - -#: src/app/main/data/workspace.cljs -msgid "errors.clipboard-not-implemented" -msgstr "O teu browser não pode fazer esta operação" - -msgid "dashboard.import" -msgstr "Importar ficheiros Penpot" - -msgid "dashboard.import.import-error" -msgstr "" -"Ocorreu um problema na importação do ficheiro. O ficheiro não foi importado." - -msgid "dashboard.import.progress.upload-media" -msgstr "A carregar ficheiro: %s" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dashboard.new-project" -msgstr "+ Novo projeto" - -msgid "errors.auth.unable-to-login" -msgstr "Parece que não estás autenticado ou a sessão expirou." - -msgid "shortcuts.open-viewer" -msgstr "Ir para secção de interações" - -msgid "dashboard.import.import-warning" -msgstr "Alguns ficheiros continham objetos inválidos que foram removidos." - -msgid "dashboard.import.progress.process-colors" -msgstr "Processando cores" - -msgid "dashboard.import.progress.process-media" -msgstr "Processando media" - -msgid "dashboard.import.progress.process-page" -msgstr "Processando página: %s" - -msgid "dashboard.import.progress.process-typographies" -msgstr "Processando tipografias" - -msgid "dashboard.import.progress.upload-data" -msgstr "A carregar dados para o servidor (%s/%s)" - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-project-prefix" -msgstr "Novo Projeto" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-msg" -msgstr "" -"Envia-me notícias, atualizações de produto e recomendações sobre o Penpot." - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-title" -msgstr "Subscrição de Newsletter" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.no-matches-for" -msgstr "Não há resultados para \"%s\"" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.remove-shared" -msgstr "Remover como Biblioteca Partilhada" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.no-projects-placeholder" -msgstr "Projetos fixos aparecerão aqui" - -#: src/app/main/ui/settings/password.cljs -msgid "errors.password-too-short" -msgstr "A palavra-passe deverá conter no mínimo 8 caracteres" - -#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs -msgid "errors.profile-is-muted" -msgstr "" -"O teu perfil tem e-mails silenciados (relatórios de spam ou devoluções " -"altas)." - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.height" -msgstr "Altura" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.give-feedback" -msgstr "Dá feedback" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.left" -msgstr "Esquerda" - -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.settings" -msgstr "Definições" - -#: src/app/main/ui/settings/options.cljs -msgid "title.settings.options" -msgstr "Definições - Penpot" - -msgid "handoff.tabs.code.selected.image" -msgstr "Imagem" - -msgid "handoff.tabs.code.selected.mask" -msgstr "Máscara" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code.selected.multiple" -msgstr "%s Selecionados" - -msgid "handoff.tabs.code.selected.circle" -msgstr "Círculo" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code" -msgstr "Código" - -msgid "handoff.tabs.code.selected.group" -msgstr "Grupo" - -#: src/app/main/ui/static.cljs -msgid "labels.bad-gateway.desc-message" -msgstr "" -"Parece que tens de aguardar um pouco e tentar novamente; estamos a realizar " -"pequenas manutenções nos nossos servidores." - -msgid "labels.fonts" -msgstr "Fontes" - -#: src/app/main/ui/comments.cljs -msgid "labels.edit" -msgstr "Editar" - -#: src/app/main/ui/static.cljs -msgid "labels.service-unavailable.desc-message" -msgstr "Estamos numa manutenção programada dos nossos sistemas." - -msgid "labels.font-variants" -msgstr "Variantes" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.github-repo" -msgstr "Repositório Github" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.member" -msgstr "Membro" - -msgid "modals.delete-font.message" -msgstr "" -"Tens a certeza de que pretendes eliminar esta fonte? Não carregará se " -"estiver a ser utilizada num ficheiro." - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.members" -msgstr "Membros" - -#: src/app/main/ui/dashboard/team_form.cljs -msgid "labels.rename-team" -msgstr "Renomear equipa" - -#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs -msgid "media.loading" -msgstr "A carregar imagem…" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.change-owner-and-leave-confirm.message" -msgstr "" -"És o proprietário desta equipa. Seleciona outro membro para promover para " -"proprietário antes de saíres." - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.you" -msgstr "(tu)" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.accept" -msgstr "Atualizar" - -msgid "onboarding.newsletter.acceptance-message" -msgstr "" -"A tua solicitação de inscrição foi enviada, iremos enviar-te um e-mail para " -"confirmá-la." - -msgid "onboarding.team-modal.create-team-feature-3" -msgstr "Gestão de funções" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "100% grátis!" - -msgid "shortcuts.flip-vertical" -msgstr "Virar verticalmente" - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "viewer.frame-not-found" -msgstr "Quadro não encontrado." - -msgid "shortcut-subsection.tools" -msgstr "Ferramentas" - -msgid "shortcut-subsection.zoom-viewer" -msgstr "Zoom" - -msgid "shortcut-subsection.zoom-workspace" -msgstr "Zoom" - -msgid "shortcuts.hide-ui" -msgstr "Mostrar/ocultar interface" - -msgid "shortcuts.move-fast-down" -msgstr "Mover para baixo rápido" - msgid "workspace.header.menu.disable-snap-pixel-grid" msgstr "Desativar ajuste ao pixel" @@ -2987,6 +2958,9 @@ msgstr "Ajustar à grade" msgid "workspace.header.menu.enable-snap-guides" msgstr "Ajustar às guias" +msgid "workspace.header.menu.enable-snap-pixel-grid" +msgstr "Ativar ajuste ao pixel" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-artboard-names" msgstr "Ocultar nome das pranchetas" @@ -3014,6 +2988,18 @@ msgstr "Ocultar paleta de texto" msgid "workspace.header.menu.option.edit" msgstr "Editar" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Ficheiro" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Ajuda e Informações" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.preferences" +msgstr "Preferências" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.option.view" msgstr "Visualização" @@ -3022,20 +3008,36 @@ msgstr "Visualização" msgid "workspace.header.menu.select-all" msgstr "Selecionar tudo" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-artboard-names" +msgstr "Mostrar nomes das pranchetas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-grid" +msgstr "Mostrar grade" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Mostrar paleta de cor" + msgid "workspace.header.menu.show-pixel-grid" msgstr "Mostrar grade de píxeis" #: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.save-error" -msgstr "Erro ao salvar" +msgid "workspace.header.menu.show-rules" +msgstr "Mostrar regras" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-textpalette" +msgstr "Mostrar paleta de texto" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" msgstr "Ampliar em 100%" #: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-textpalette" -msgstr "Mostrar paleta de texto" +msgid "workspace.header.save-error" +msgstr "Erro ao salvar" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.saved" @@ -3045,6 +3047,14 @@ msgstr "Guardado" msgid "workspace.header.saving" msgstr "A salvar" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Alterações não guardadas" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Modo de visualização (%s)" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.zoom-fill" msgstr "Ajustar para preencher" @@ -3061,14 +3071,6 @@ msgstr "Ajustar para mostrar tudo" msgid "workspace.header.zoom-full-screen" msgstr "Tela cheia" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.unsaved" -msgstr "Alterações não guardadas" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.viewer" -msgstr "Modo de visualização (%s)" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.zoom-selected" msgstr "Aumentar para seleção" @@ -3093,10 +3095,6 @@ msgstr "Biblioteca de ficheiros" msgid "workspace.libraries.colors.hsv" msgstr "HSV" -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.save-color" -msgstr "Guardar estilo de cor" - #: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs msgid "workspace.libraries.colors.recent-colors" msgstr "Cores recentes" @@ -3109,6 +3107,10 @@ msgstr "RGB Complementar" msgid "workspace.libraries.colors.rgba" msgstr "RGBA" +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Guardar estilo de cor" + #: src/app/main/ui/workspace/colorpalette.cljs msgid "workspace.libraries.colors.small-thumbnails" msgstr "Miniaturas pequenas" @@ -3137,10 +3139,26 @@ msgstr "BIBLIOTECAS" msgid "workspace.libraries.library" msgstr "BIBLIOTECA" +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "Não há bibliotecas partilhadas que precisem de atualização" + #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-matches-for" msgstr "Não há resultados para \"%s\"" +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "Não há bibliotecas partilhadas disponíveis" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Pesquisar bibliotecas partilhadas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "BIBLIOTECAS PARTILHADAS" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.libraries.text.multiple-typography" msgstr "Múltiplas tipografias" @@ -3218,6 +3236,10 @@ msgstr "Abaixo" msgid "workspace.options.constraints.center" msgstr "Centro" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.fix-when-scrolling" +msgstr "Fixar no scroll" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.left" msgstr "Esquerda" @@ -3234,6 +3256,10 @@ msgstr "Direita" msgid "workspace.options.constraints.scale" msgstr "Escala" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.top" +msgstr "Topo" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.topbottom" msgstr "Topo e Abaixo" @@ -3332,12 +3358,20 @@ msgid "workspace.options.grid.params.set-default" msgstr "Definir como padrão" #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.top" -msgstr "Topo" +msgid "workspace.options.grid.params.size" +msgstr "Tamanho" #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.use-default" -msgstr "Utilizar padrão" +msgid "workspace.options.grid.params.type" +msgstr "Tipo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Abaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Centro" #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.type.left" @@ -3351,6 +3385,14 @@ msgstr "Direita" msgid "workspace.options.grid.params.type.stretch" msgstr "Esticar" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Utilizar padrão" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.width" msgstr "Largura" @@ -3363,14 +3405,6 @@ msgstr "Linhas" msgid "workspace.options.grid.square" msgstr "Quadrado" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-action" -msgstr "Ação" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-after-delay" -msgstr "Após atraso" - #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.group-fill" msgstr "Preenchimento de grupo" @@ -3382,6 +3416,14 @@ msgstr "Traço de grupo" msgid "workspace.options.height" msgstr "Altura" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-action" +msgstr "Ação" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-after-delay" +msgstr "Após atraso" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-animation" msgstr "Animação" @@ -3431,16 +3473,16 @@ msgid "workspace.options.interaction-duration" msgstr "Duração" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing-ease-in" -msgstr "Ease in" +msgid "workspace.options.interaction-easing" +msgstr "Easing" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing-ease" msgstr "Ease" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing" -msgstr "Easing" +msgid "workspace.options.interaction-easing-ease-in" +msgstr "Ease in" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing-ease-in-out" @@ -3450,14 +3492,38 @@ msgstr "Ease in out" msgid "workspace.options.interaction-easing-ease-out" msgstr "Ease out" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-easing-linear" +msgstr "Linear" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-in" +msgstr "Dentro" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-mouse-enter" +msgstr "Cursor entra" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-mouse-leave" +msgstr "Cursor sai" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-ms" msgstr "ms" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-navigate-to" +msgstr "Navegar para" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-navigate-to-dest" msgstr "Navegar para: %s" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-none" +msgstr "(indefinido)" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-offset-effect" msgstr "Efeito Offset" @@ -3467,20 +3533,32 @@ msgid "workspace.options.interaction-on-click" msgstr "No Clique" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-left" -msgstr "Inferior esquerdo" +msgid "workspace.options.interaction-open-overlay" +msgstr "Abrir sobreposição" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-bottom-right" -msgstr "Inferior direito" +msgid "workspace.options.interaction-open-overlay-dest" +msgstr "Abrir sobreposição: %s" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-open-url" +msgstr "Abrir url" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-out" +msgstr "Fora" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-pos-bottom-center" msgstr "Inferior centro" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-out" -msgstr "Fora" +msgid "workspace.options.interaction-pos-bottom-left" +msgstr "Inferior esquerdo" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-bottom-right" +msgstr "Inferior direito" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-pos-center" @@ -3490,14 +3568,14 @@ msgstr "Centro" msgid "workspace.options.interaction-pos-manual" msgstr "Manual" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-pos-top-left" -msgstr "Superior esquerdo" - #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-pos-top-center" msgstr "Superior centro" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-top-left" +msgstr "Superior esquerdo" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-pos-top-right" msgstr "Superior direito" @@ -3554,6 +3632,22 @@ msgstr "Cor" msgid "workspace.options.layer-options.blend-mode.color-burn" msgstr "Superexposição de cor" +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-dodge" +msgstr "Subexposição Linear (Adicionar)" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.darken" +msgstr "Escurecer" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.difference" +msgstr "Diferença" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.exclusion" +msgstr "Exclusão" + #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.blend-mode.hard-light" msgstr "Luz direta" @@ -3562,6 +3656,10 @@ msgstr "Luz direta" msgid "workspace.options.layer-options.blend-mode.hue" msgstr "Matiz" +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.lighten" +msgstr "Clarear" + #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.blend-mode.luminosity" msgstr "Luminosidade" @@ -3582,6 +3680,22 @@ msgstr "Sobrepor" msgid "workspace.options.layer-options.blend-mode.saturation" msgstr "Saturação" +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.screen" +msgstr "Tela" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.soft-light" +msgstr "Luz indireta" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Camada" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title.group" +msgstr "Grupo de camadas" + #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.title.multiple" msgstr "Camadas selecionadas" @@ -3590,6 +3704,14 @@ msgstr "Camadas selecionadas" msgid "workspace.options.layout-item.advanced-ops" msgstr "Opções avançadas" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Altura.Máx" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Largura.Máx" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.layout-min-h" msgstr "Altura.Min" @@ -3598,14 +3720,78 @@ msgstr "Altura.Min" msgid "workspace.options.layout-item.layout-min-w" msgstr "Largura.Min" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "Redimensionar elementos" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Altura máxima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Largura máxima" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.layout-min-h" msgstr "Altura mínima" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Largura mínima" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Abaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "Coluna" + #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.direction.left" msgstr "Linha" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.right" +msgstr "Linha inversa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "Coluna inversa" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.gap" +msgstr "Espaço" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "direita" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Margem" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "Todos os lados" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "Margem simples" + #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.no-wrap" msgstr "sem envolver" @@ -3634,10 +3820,22 @@ msgstr "Direita" msgid "workspace.options.layout.space-around" msgstr "espaço à volta" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "espaço entre" + #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.title" msgstr "Layout" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Topo" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "abaixo" + #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.v.center" msgstr "centro" @@ -3654,13 +3852,28 @@ msgstr "envolver" msgid "workspace.options.more-colors" msgstr "Mais cores" +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-lib-colors" +msgstr "Mais bibliotecas de cor" + msgid "workspace.options.opacity" msgstr "Opacidade" +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.position" +msgstr "Posição" + #: src/app/main/ui/workspace/sidebar/options.cljs msgid "workspace.options.prototype" msgstr "Protótipo" +msgid "workspace.options.radius" +msgstr "Raio" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Todos os cantos" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.radius.single-corners" msgstr "Cantos individuais" @@ -3679,14 +3892,47 @@ msgstr "Rotação" msgid "workspace.options.search-font" msgstr "Pesquisar fonte" -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.selection-stroke" -msgstr "Traço da seleção" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-a-shape" +msgstr "" +"Selecionar a forma, prancheta ou grupo para arrastar uma conexão para outra " +"prancheta." #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.select-artboard" msgstr "Selecionar prancheta" +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "Cores selecionadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.selection-fill" +msgstr "Preenchimento de seleção" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.selection-stroke" +msgstr "Traço da seleção" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Desfoque" + +msgid "workspace.options.shadow-options.color" +msgstr "Cor da sombra" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Sombra" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Sombra interna" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.offsetx" +msgstr "X" + #: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs msgid "workspace.options.shadow-options.offsety" msgstr "Y" @@ -3703,6 +3949,14 @@ msgstr "Sombra" msgid "workspace.options.shadow-options.title.group" msgstr "Grupo de sombras" +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Seleção de sombras" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.show-fill-on-export" +msgstr "Mostrar na exportação" + msgid "workspace.options.show-in-viewer" msgstr "Mostrar no modo de visualização" @@ -3714,6 +3968,22 @@ msgstr "Tamanho" msgid "workspace.options.size-presets" msgstr "Tamanho pré-definido" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke" +msgstr "Traço" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.circle-marker" +msgstr "Marcador circular" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.diamond-marker" +msgstr "Marcador em diamante" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.line-arrow" +msgstr "Seta de linha" + #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke-cap.none" msgstr "Nenhum" @@ -3722,6 +3992,10 @@ msgstr "Nenhum" msgid "workspace.options.stroke-cap.round" msgstr "Arredondado" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square" +msgstr "Quadrado" + #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke-cap.square-marker" msgstr "Marcador quadrado" @@ -3730,13 +4004,28 @@ msgstr "Marcador quadrado" msgid "workspace.options.stroke-cap.triangle-arrow" msgstr "Seta triangular" +msgid "workspace.options.stroke-color" +msgstr "Cor do traço" + msgid "workspace.options.stroke-width" msgstr "Largura do traço" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Centro" + #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke.dashed" msgstr "Tracejado" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Pontilhado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Interior" + #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke.mixed" msgstr "Misto" @@ -3745,6 +4034,10 @@ msgstr "Misto" msgid "workspace.options.stroke.outer" msgstr "Exterior" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.solid" +msgstr "Sólido" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.align-bottom" msgstr "Alinhar abaixo" @@ -3753,6 +4046,14 @@ msgstr "Alinhar abaixo" msgid "workspace.options.text-options.align-center" msgstr "Alinhar ao centro" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-justify" +msgstr "Justificar" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-left" +msgstr "Alinhar à esquerda" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.align-middle" msgstr "Alinhar ao meio" @@ -3788,16 +4089,36 @@ msgstr "Altura automática" msgid "workspace.options.text-options.grow-auto-width" msgstr "Largura automática" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Fixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.letter-spacing" +msgstr "Espaço entre letras" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.options.text-options.line-height" msgstr "Altura entre linhas" -msgid "workspace.options.text-options.text-case" -msgstr "Capitalizar" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Minúsculas" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.none" +msgstr "Nenhum" #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.titlecase" -msgstr "Capitalizar iniciais" +msgid "workspace.options.text-options.preset" +msgstr "Pré-definido" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Rasurado" + +msgid "workspace.options.text-options.text-case" +msgstr "Capitalizar" #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.title" @@ -3811,6 +4132,10 @@ msgstr "Grupo de texto" msgid "workspace.options.text-options.title-selection" msgstr "Texto selecionado" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.titlecase" +msgstr "Capitalizar iniciais" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.underline" msgstr "Sublinhado" @@ -3831,9 +4156,39 @@ msgstr "" msgid "workspace.options.width" msgstr "Largura" +msgid "workspace.options.x" +msgstr "X" + +msgid "workspace.options.y" +msgstr "Y" + +msgid "workspace.path.actions.add-node" +msgstr "Adicionar nó (%s)" + +msgid "workspace.path.actions.delete-node" +msgstr "Eliminar nó (%s)" + +msgid "workspace.path.actions.draw-nodes" +msgstr "Desenhar nós (%s)" + +msgid "workspace.path.actions.join-nodes" +msgstr "Unir nós (%s)" + +msgid "workspace.path.actions.make-corner" +msgstr "Em cantos (%s)" + msgid "workspace.path.actions.make-curve" msgstr "Em curvas (%s)" +msgid "workspace.path.actions.merge-nodes" +msgstr "Fundir nós (%s)" + +msgid "workspace.path.actions.move-nodes" +msgstr "Mover nós (%s)" + +msgid "workspace.path.actions.separate-nodes" +msgstr "Separar nós (%s)" + msgid "workspace.path.actions.snap-nodes" msgstr "Ajustar nós (%s)" @@ -3841,6 +4196,18 @@ msgstr "Ajustar nós (%s)" msgid "workspace.shape.menu.back" msgstr "Enviar para trás" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "Mover para trás" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Copiar" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-artboard-from-selection" +msgstr "Seleção para a prancheta" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.create-component" msgstr "Criar componente" @@ -3849,10 +4216,22 @@ msgstr "Criar componente" msgid "workspace.shape.menu.cut" msgstr "Recortar" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Eliminar" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.delete-flow-start" msgstr "Eliminar início de fluxo" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instance" +msgstr "Desprender instância" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "Desprender instâncias" + msgid "workspace.shape.menu.difference" msgstr "Diferença" @@ -3882,13 +4261,6 @@ msgstr "Virar na vertical" msgid "workspace.shape.menu.flow-start" msgstr "Início de fluxo" -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.hide" -msgstr "Ocultar" - -msgid "workspace.shape.menu.hide-ui" -msgstr "Mostrar/Ocultar interface" - #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.forward" msgstr "Mover para a frente" @@ -3897,13 +4269,32 @@ msgstr "Mover para a frente" msgid "workspace.shape.menu.front" msgstr "Enviar para a frente" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.go-main" +msgstr "Ir para ficheiro do componente principal" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.group" msgstr "Agrupar" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Ocultar" + +msgid "workspace.shape.menu.hide-ui" +msgstr "Mostrar/Ocultar interface" + msgid "workspace.shape.menu.intersection" msgstr "Interseção" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Bloquear" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Máscara" + #: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.paste" msgstr "Colar" @@ -3915,6 +4306,27 @@ msgstr "Curvas" msgid "workspace.shape.menu.reset-overrides" msgstr "Anular alterações" +msgid "workspace.shape.menu.restore-main" +msgstr "Restaurar componente principal" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Selecionar camada" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Mostrar" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-main" +msgstr "Mostrar componente principal" + +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "Remover miniatura" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "Definir como miniatura" + msgid "workspace.shape.menu.transform-to-path" msgstr "Transformar em vector" @@ -3922,36 +4334,81 @@ msgstr "Transformar em vector" msgid "workspace.shape.menu.ungroup" msgstr "Desagrupar" +msgid "workspace.shape.menu.union" +msgstr "União" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unlock" +msgstr "Desbloquear" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.unmask" msgstr "Retirar máscara" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "Atualizar componentes principais" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-main" +msgstr "Atualizar componente principal" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "Histórico (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.layers" +msgstr "Camadas" + +msgid "workspace.sidebar.layers.components" +msgstr "Componentes" + msgid "workspace.sidebar.layers.frames" msgstr "Pranchetas" msgid "workspace.sidebar.layers.groups" msgstr "Grupos" -msgid "workspace.sidebar.layers.masks" -msgstr "Máscaras" - msgid "workspace.sidebar.layers.images" msgstr "Imagens" +msgid "workspace.sidebar.layers.masks" +msgstr "Máscaras" + msgid "workspace.sidebar.layers.search" msgstr "Pesquisar camadas" msgid "workspace.sidebar.layers.shapes" msgstr "Formas" +msgid "workspace.sidebar.layers.texts" +msgstr "Textos" + +#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs +msgid "workspace.sidebar.options.svg-attrs.title" +msgstr "Importar atributos do SVG" + #: src/app/main/ui/workspace/sidebar/sitemap.cljs msgid "workspace.sidebar.sitemap" msgstr "Páginas" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.sitemap" +msgstr "Mapa do site" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Recursos" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.color-palette" msgstr "Paleta de cores (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Comentários (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.curve" msgstr "Curvas (%s)" @@ -3960,6 +4417,42 @@ msgstr "Curvas (%s)" msgid "workspace.toolbar.ellipse" msgstr "Elipse (%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Prancheta (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Imagem (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Mover (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.path" +msgstr "Caminho (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Rectângulo (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "Atalhos (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Texto (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text-palette" +msgstr "Tipografias (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.empty" +msgstr "Não há histórico de mudanças até agora" + #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.delete" msgstr "%s eliminado" @@ -3968,15 +4461,62 @@ msgstr "%s eliminado" msgid "workspace.undo.entry.modify" msgstr "%s modificado" +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.move" +msgstr "Objectos movidos" + msgid "workspace.undo.entry.multiple.circle" msgstr "círculos" msgid "workspace.undo.entry.multiple.color" msgstr "recursos de cor" +msgid "workspace.undo.entry.multiple.component" +msgstr "componentes" + +msgid "workspace.undo.entry.multiple.curve" +msgstr "curvas" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "prancheta" + msgid "workspace.undo.entry.multiple.group" msgstr "grupos" +msgid "workspace.undo.entry.multiple.media" +msgstr "recursos gráficos" + +msgid "workspace.undo.entry.multiple.multiple" +msgstr "objectos" + +msgid "workspace.undo.entry.multiple.page" +msgstr "páginas" + +msgid "workspace.undo.entry.multiple.path" +msgstr "caminhos" + +msgid "workspace.undo.entry.multiple.rect" +msgstr "rectângulos" + +msgid "workspace.undo.entry.multiple.shape" +msgstr "formas" + +msgid "workspace.undo.entry.multiple.text" +msgstr "textos" + +msgid "workspace.undo.entry.multiple.typography" +msgstr "recursos tipográficos" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.new" +msgstr "Novo %s" + +msgid "workspace.undo.entry.single.circle" +msgstr "círculo" + +msgid "workspace.undo.entry.single.color" +msgstr "recurso de cor" + msgid "workspace.undo.entry.single.component" msgstr "componente" @@ -3989,6 +4529,15 @@ msgstr "prancheta" msgid "workspace.undo.entry.single.group" msgstr "grupo" +msgid "workspace.undo.entry.single.image" +msgstr "imagem" + +msgid "workspace.undo.entry.single.media" +msgstr "recurso gráfico" + +msgid "workspace.undo.entry.single.multiple" +msgstr "objecto" + msgid "workspace.undo.entry.single.page" msgstr "página" @@ -4015,571 +4564,17 @@ msgstr "Operação sobre %s" msgid "workspace.undo.title" msgstr "Histórico" -msgid "workspace.undo.entry.multiple.page" -msgstr "páginas" - -msgid "workspace.undo.entry.multiple.rect" -msgstr "rectângulos" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.empty" -msgstr "Não há histórico de mudanças até agora" - -msgid "workspace.undo.entry.multiple.multiple" -msgstr "objectos" - -msgid "workspace.undo.entry.multiple.path" -msgstr "caminhos" - -msgid "workspace.undo.entry.multiple.media" -msgstr "recursos gráficos" - -msgid "workspace.undo.entry.multiple.shape" -msgstr "formas" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.move" -msgstr "Objectos movidos" - -msgid "workspace.undo.entry.multiple.text" -msgstr "textos" - -msgid "workspace.undo.entry.multiple.typography" -msgstr "recursos tipográficos" - -msgid "workspace.undo.entry.multiple.component" -msgstr "componentes" - -msgid "workspace.undo.entry.multiple.curve" -msgstr "curvas" - -msgid "workspace.undo.entry.single.color" -msgstr "recurso de cor" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.new" -msgstr "Novo %s" - -msgid "workspace.undo.entry.single.circle" -msgstr "círculo" - -msgid "workspace.undo.entry.single.image" -msgstr "imagem" - -msgid "workspace.undo.entry.single.media" -msgstr "recurso gráfico" - -msgid "workspace.undo.entry.single.multiple" -msgstr "objecto" - -msgid "workspace.undo.entry.multiple.frame" -msgstr "prancheta" - -msgid "workspace.header.menu.enable-snap-pixel-grid" -msgstr "Ativar ajuste ao pixel" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-grid" -msgstr "Mostrar grade" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-palette" -msgstr "Mostrar paleta de cor" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-rules" -msgstr "Mostrar regras" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.file" -msgstr "Ficheiro" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.help-info" -msgstr "Ajuda e Informações" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.preferences" -msgstr "Preferências" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-artboard-names" -msgstr "Mostrar nomes das pranchetas" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.center" -msgstr "Centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.more-lib-colors" -msgstr "Mais bibliotecas de cor" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.bottom" -msgstr "Abaixo" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.fix-when-scrolling" -msgstr "Fixar no scroll" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.blur" -msgstr "Desfoque" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.top" -msgstr "Topo" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.size" -msgstr "Tamanho" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type" -msgstr "Tipo" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" -msgstr "Coluna" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "abaixo" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-mouse-enter" -msgstr "Cursor entra" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-navigate-to" -msgstr "Navegar para" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-open-url" -msgstr "Abrir url" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.bottom" -msgstr "Abaixo" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.circle-marker" -msgstr "Marcador circular" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-mouse-leave" -msgstr "Cursor sai" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke" -msgstr "Traço" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.center" -msgstr "Centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.solid" -msgstr "Sólido" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.diamond-marker" -msgstr "Marcador em diamante" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.line-arrow" -msgstr "Seta de linha" - -msgid "workspace.options.stroke-color" -msgstr "Cor do traço" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.square" -msgstr "Quadrado" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dotted" -msgstr "Pontilhado" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.inner" -msgstr "Interior" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-easing-linear" -msgstr "Linear" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-open-overlay-dest" -msgstr "Abrir sobreposição: %s" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-in" -msgstr "Dentro" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-none" -msgstr "(indefinido)" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-open-overlay" -msgstr "Abrir sobreposição" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.color-dodge" -msgstr "Subexposição Linear (Adicionar)" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.darken" -msgstr "Escurecer" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.difference" -msgstr "Diferença" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.exclusion" -msgstr "Exclusão" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.screen" -msgstr "Tela" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.soft-light" -msgstr "Luz indireta" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.title.group" -msgstr "Grupo de camadas" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.lighten" -msgstr "Clarear" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.title" -msgstr "Camada" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" -msgstr "Altura.Máx" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" -msgstr "Largura.Máx" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title" -msgstr "Redimensionar elementos" - -#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs -msgid "workspace.options.selection-fill" -msgstr "Preenchimento de seleção" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" -msgstr "Altura máxima" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" -msgstr "Linha inversa" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin" -msgstr "Margem" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin-simple" -msgstr "Margem simples" - -msgid "workspace.options.shadow-options.color" -msgstr "Cor da sombra" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" -msgstr "Largura máxima" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" -msgstr "Coluna inversa" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "direita" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin-all" -msgstr "Todos os lados" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" -msgstr "Largura mínima" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.left" -msgstr "Esquerda" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.gap" -msgstr "Espaço" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "esquerda" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.space-between" -msgstr "espaço entre" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.top" -msgstr "Topo" - -#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.position" -msgstr "Posição" - -msgid "workspace.options.radius" -msgstr "Raio" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.radius.all-corners" -msgstr "Todos os cantos" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title.multiple" -msgstr "Seleção de sombras" - -msgid "workspace.path.actions.draw-nodes" -msgstr "Desenhar nós (%s)" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.detach-instance" -msgstr "Desprender instância" - -msgid "workspace.path.actions.join-nodes" -msgstr "Unir nós (%s)" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.select-a-shape" -msgstr "" -"Selecionar a forma, prancheta ou grupo para arrastar uma conexão para outra " -"prancheta." - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.selection-color" -msgstr "Cores selecionadas" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-left" -msgstr "Alinhar à esquerda" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.drop-shadow" -msgstr "Sombra" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.offsetx" -msgstr "X" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.inner-shadow" -msgstr "Sombra interna" - -#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs -msgid "workspace.options.show-fill-on-export" -msgstr "Mostrar na exportação" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.grow-fixed" -msgstr "Fixo" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.lowercase" -msgstr "Minúsculas" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-justify" -msgstr "Justificar" - -msgid "workspace.path.actions.make-corner" -msgstr "Em cantos (%s)" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.letter-spacing" -msgstr "Espaço entre letras" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.none" -msgstr "Nenhum" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.preset" -msgstr "Pré-definido" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.strikethrough" -msgstr "Rasurado" - -msgid "workspace.options.y" -msgstr "Y" - -msgid "workspace.path.actions.merge-nodes" -msgstr "Fundir nós (%s)" - -msgid "workspace.path.actions.delete-node" -msgstr "Eliminar nó (%s)" - -msgid "workspace.options.x" -msgstr "X" - -msgid "workspace.path.actions.add-node" -msgstr "Adicionar nó (%s)" - -msgid "workspace.path.actions.move-nodes" -msgstr "Mover nós (%s)" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.copy" -msgstr "Copiar" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.create-artboard-from-selection" -msgstr "Seleção para a prancheta" - -msgid "workspace.path.actions.separate-nodes" -msgstr "Separar nós (%s)" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.backward" -msgstr "Mover para trás" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.delete" -msgstr "Eliminar" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.select-layer" -msgstr "Selecionar camada" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.detach-instances-in-bulk" -msgstr "Desprender instâncias" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.lock" -msgstr "Bloquear" - -msgid "workspace.shape.menu.restore-main" -msgstr "Restaurar componente principal" - -msgid "workspace.shape.menu.thumbnail-remove" -msgstr "Remover miniatura" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.unlock" -msgstr "Desbloquear" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.update-components-in-bulk" -msgstr "Atualizar componentes principais" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.sidebar.history" -msgstr "Histórico (%s)" - -msgid "workspace.sidebar.layers.texts" -msgstr "Textos" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show-main" -msgstr "Mostrar componente principal" - -#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.mask" -msgstr "Máscara" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show" -msgstr "Mostrar" - -msgid "workspace.shape.menu.thumbnail-set" -msgstr "Definir como miniatura" - -msgid "workspace.shape.menu.union" -msgstr "União" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.update-main" -msgstr "Atualizar componente principal" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.sidebar.layers" -msgstr "Camadas" - -#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs -msgid "workspace.sidebar.options.svg-attrs.title" -msgstr "Importar atributos do SVG" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.sitemap" -msgstr "Mapa do site" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.assets" -msgstr "Recursos" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.comments" -msgstr "Comentários (%s)" - -msgid "workspace.sidebar.layers.components" -msgstr "Componentes" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.move" -msgstr "Mover (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.rect" -msgstr "Rectângulo (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.text" -msgstr "Texto (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.text-palette" -msgstr "Tipografias (%s)" - #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.dismiss" msgstr "Ignorar" -msgid "workspace.viewport.click-to-close-path" -msgstr "Clica para fechar o caminho" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.shortcuts" -msgstr "Atalhos (%s)" +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "Existem atualizações nas bibliotecas partilhadas" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.update" msgstr "Atualizar" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.path" -msgstr "Caminho (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.frame" -msgstr "Prancheta (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.image" -msgstr "Imagem (%s)" +msgid "workspace.viewport.click-to-close-path" +msgstr "Clica para fechar o caminho" \ No newline at end of file diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index a7365095a9..b39ae2c3eb 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-23 20:54+0000\n" "Last-Translator: Stas Haas \n" -"Language-Team: Russian \n" +"Language-Team: Russian " +"\n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -169,11 +169,23 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Мы отправили эл. письмо с подтверждением на" +msgid "common.publish" +msgstr "Опубликовать" + +msgid "common.share-link.all-users" +msgstr "Все пользователи Penpot" + msgid "common.share-link.confirm-deletion-link-description" msgstr "" "Вы точно хотите удалить эту ссылку? После этого она перестанет быть " "доступной" +msgid "common.share-link.current-tag" +msgstr "(текущее)" + +msgid "common.share-link.destroy-link" +msgstr "Удалить ссылку" + msgid "common.share-link.get-link" msgstr "Получить ссылку" @@ -183,15 +195,79 @@ msgstr "Ссылка скопирована" msgid "common.share-link.link-deleted-success" msgstr "Ссылка удалена" +msgid "common.share-link.manage-ops" +msgstr "Управлять разрешениями" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 общая страница" +msgstr[1] "%s общих страниц" + +msgid "common.share-link.permissions-can-comment" +msgstr "Может комментировать" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Может проверять код" + msgid "common.share-link.permissions-hint" msgstr "Доступ открыт для получателей ссылки" +msgid "common.share-link.permissions-pages" +msgstr "Общие страницы" + msgid "common.share-link.placeholder" msgstr "Ссылка появится здесь" +msgid "common.share-link.team-members" +msgstr "Только участники команды" + msgid "common.share-link.title" msgstr "Поделиться прототипами" +msgid "common.share-link.view-all" +msgstr "Выбрать все" + +msgid "common.unpublish" +msgstr "Снять с публикации" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Управление командой" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot предназначен для команд. Приглашайте участников к совместной работе " +"над проектами и файлами" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Объединяйтесь!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "Изучите основы в Penpot весело с этим практическим руководством." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Начать обучение" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Практическое руководство" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Прогуляйтесь по возможностям Penpot и познакомьтесь с основными функциями." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Начать тур" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Руководство по интерфейсу" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Добавить как общую библиотеку" @@ -216,6 +292,12 @@ msgstr "Ваш Penpot" msgid "dashboard.delete-team" msgstr "Удалить команду" +msgid "dashboard.download-binary-file" +msgstr "Скачать файл Penpot (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Скачать стандартный файл (.svg + .json)" + msgid "dashboard.draft-title" msgstr "Черновик" @@ -235,18 +317,54 @@ msgstr "Здесь пока нет файлов" #, markdown msgid "dashboard.empty-placeholder-drafts" msgstr "" -"Ой! Файлов пока нет. Вы можете испытать готовые шаблоны в разделе [" -"Библиотеки и шаблоны](https://penpot.app/libraries-templates.html)" +"Ой! Файлов пока нет. Вы можете испытать готовые шаблоны в разделе " +"[Библиотеки и шаблоны](https://penpot.app/libraries-templates.html)" + +msgid "dashboard.export-binary-multi" +msgstr "Скачать файлы Penpot (.penpot) (%s)" msgid "dashboard.export-frames" msgstr "Экспорт кадров в PDF" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Экспорт в PDF" + msgid "dashboard.export-multi" msgstr "Экспорт файлов Penpot (%s)" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "Выбрано %s из %s элементов" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Экспорт" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Вы можете добавить настройки экспорта элементам из свойств дизайна (в " +"нижней части правой боковой панели)." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to-link" +msgstr "Информация о настройке экспорта в Penpot." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Нет элементов с настройками экспорта." + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.title" +msgstr "Выбор экспорта" + msgid "dashboard.export-single" msgstr "Экспорт файла Penpot" +msgid "dashboard.export-standard-multi" +msgstr "Скачать стандартные файлы (.svg + .json) (%s)" + msgid "dashboard.export.detail" msgstr "* Могут содержать компоненты, цвета, графику, и/или типографику." @@ -299,19 +417,20 @@ msgstr[1] "Шрифты добавлены (%s)" #, markdown msgid "dashboard.fonts.hero-text1" msgstr "" -"Любой загружаемый сюда шрифт будет добавлен в семейство шрифтов и доступен в " -"свойствах файлов, редактируемых командой. Шрифты из одного и того же " -"семейства будут сгруппированы по **названию семейства шрифта**. Для загрузки " -"допустимы следующие форматы: **TTF, OTF и WOFF** (используйте один из них)." +"Любой загружаемый сюда шрифт будет добавлен в семейство шрифтов и доступен " +"в свойствах файлов, редактируемых командой. Шрифты из одного и того же " +"семейства будут сгруппированы по **названию семейства шрифта**. Для " +"загрузки допустимы следующие форматы: **TTF, OTF и WOFF** (используйте один " +"из них)." #, markdown msgid "dashboard.fonts.hero-text2" msgstr "" -"Вам следует загружать только собственные шрифты, или у которых есть лицензия " -"на использование в Penpot. Больше информации в разделе \"Content rights\" в [" -"Условиях использования Penpot](https://penpot.app/terms.html). Также можете " -"прочитать о [лицензированием шрифтов](https://www.typography.com/faq) в " -"целом." +"Вам следует загружать только собственные шрифты, или у которых есть " +"лицензия на использование в Penpot. Больше информации в разделе \"Content " +"rights\" в [Условиях использования Penpot](https://penpot.app/terms.html). " +"Также можете прочитать о [лицензированием " +"шрифтов](https://www.typography.com/faq) в целом." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -361,6 +480,15 @@ msgstr "Пригласить в команду" msgid "dashboard.leave-team" msgstr "Покинуть команду" +msgid "dashboard.libraries-and-templates" +msgstr "Библиотеки и шаблоны" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Узнайте больше о них и о том, как внести свой вклад" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Возникла проблема с импортом шаблона. Шаблон не был импортирован." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Библиотеки" @@ -400,6 +528,16 @@ msgstr "+ Новый проект" msgid "dashboard.new-project-prefix" msgstr "Новый проект" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-msg" +msgstr "" +"Присылайте мне новости, информацию об обновлениях продуктов и рекомендации " +"о Penpot." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Подписка на рассылку" + #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" msgstr "Совпадений для “%s“ не найдено" @@ -455,6 +593,10 @@ msgstr "Хотите удалить свой аккаунт?" msgid "dashboard.remove-shared" msgstr "Снять статус общей библиотеки" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.save-settings" +msgstr "Сохранить настройки" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.search-placeholder" msgstr "Поиск…" @@ -527,6 +669,10 @@ msgstr "Результаты поиска" msgid "dashboard.type-something" msgstr "Введите для поиска" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Снять библиотеку с публикации" + #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "Обновить настройки" @@ -547,6 +693,14 @@ msgstr "Ваше имя" msgid "dashboard.your-penpot" msgstr "Ваш Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Ок" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Внимание" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Обновляемые компоненты:" @@ -605,6 +759,9 @@ msgstr "Вход с Google пока не доступен" msgid "errors.invalid-color" msgstr "Неверный цвет" +msgid "errors.invite-invalid.info" +msgstr "Возможно, это приглашение отменено или истёк срок его действия." + #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" msgstr "Вход c LDAP отключён." @@ -647,12 +804,19 @@ msgstr "Ваш адрес электронной почты не доступе msgid "errors.registration-disabled" msgstr "Регистрация сейчас отключена." +msgid "errors.team-leave.insufficient-members" +msgstr "" +"Недостаточно участников, чтобы покинуть команду, вероятно, вы хотите её " +"удалить." + +msgid "errors.team-leave.member-does-not-exists" +msgstr "Участник, которого вы пытаетесь назначить, не существует." + msgid "errors.team-leave.owner-cant-leave" msgstr "Нужно переназначить роль владельца перед тем, как покинуть команду." msgid "errors.terms-privacy-agreement-invalid" -msgstr "" -"Вы должны принять наши условия использования и политику конфиденциальности." +msgstr "Вы должны принять наши условия использования и политику конфиденциальности." #: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "errors.unexpected-error" @@ -682,6 +846,20 @@ msgstr "Хотите поговорить? Заходите в наш чат Git msgid "feedback.description" msgstr "Описание" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "Перейти на форум Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "" +"Мы рады вас здесь видеть. Если вам нужна помощь, пожалуйста, поищите ответ, " +"возможно он уже есть." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Сообщество Penpot" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" msgstr "Краткое описание" @@ -689,13 +867,25 @@ msgstr "Краткое описание" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"Пожалуйста, опишите причину обращения: проблема в работе, идея или сомнение. " -"Участник нашей команды даст ответ в ближайшее время." +"Пожалуйста, опишите причину обращения: проблема в работе, идея или " +"сомнение. Участник нашей команды даст ответ в ближайшее время." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" msgstr "Эл. почта" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "Перейти в Twitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "Здесь, чтобы помочь с вашими техническими запросами." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-title" +msgstr "Аккаунт поддержки в Twitter" + #: src/app/main/ui/settings/password.cljs msgid "generic.error" msgstr "Произошла ошибка" @@ -917,6 +1107,10 @@ msgstr "Информация" msgid "history.alert-message" msgstr "Ваша версия %s" +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Сочетания клавиш" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.about-penpot" msgstr "О Penpot" @@ -935,6 +1129,12 @@ msgstr "Администратор" msgid "labels.all" msgstr "Все" +msgid "labels.and" +msgstr "и" + +msgid "labels.back" +msgstr "Назад" + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "Возможны технические работы. Пожалуйста, зайдите чуть позже." @@ -957,6 +1157,10 @@ msgstr "Закрыть" msgid "labels.comments" msgstr "Комментарии" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Сообщество" + #: src/app/main/ui/settings/password.cljs msgid "labels.confirm-password" msgstr "Подтвердите пароль" @@ -967,6 +1171,12 @@ msgstr "Содержимое" msgid "labels.continue" msgstr "Продолжить" +msgid "labels.continue-with" +msgstr "Продолжить с" + +msgid "labels.continue-with-penpot" +msgstr "Вы можете продолжить с аккаунтом Penpot" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "Создать" @@ -1001,6 +1211,10 @@ msgstr "Удалить комментарий" msgid "labels.delete-comment-thread" msgstr "Удалить обсуждение" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.delete-invitation" +msgstr "Удалить приглашение" + #: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete-multi-files" msgstr "Удалить файлы (%s)" @@ -1024,6 +1238,10 @@ msgstr "Редактор" msgid "labels.email" msgstr "Эл. почта" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Истекло" + msgid "labels.export" msgstr "Экспорт" @@ -1031,6 +1249,10 @@ msgstr "Экспорт" msgid "labels.feedback-disabled" msgstr "Обратная связь отключена" +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.feedback-sent" +msgstr "Отзыв отправлен" + msgid "labels.font-family" msgstr "Семейство шрифтов" @@ -1043,6 +1265,10 @@ msgstr "Начертания" msgid "labels.fonts" msgstr "Шрифты" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.github-repo" +msgstr "Репозиторий на Github" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.give-feedback" msgstr "Оставить отзыв" @@ -1075,6 +1301,10 @@ msgstr "Что-то пошло не так. Пожалуйста, повтори msgid "labels.internal-error.main-message" msgstr "Внутренняя ошибка" +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.invitations" +msgstr "Приглашения" + #: src/app/main/ui/settings/options.cljs msgid "labels.language" msgstr "Язык" @@ -1086,6 +1316,9 @@ msgstr "Библиотеки и шаблоны" msgid "labels.link" msgstr "Ссылка" +msgid "labels.log-or-sign" +msgstr "Войти или зарегистрироваться" + #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" msgstr "Выйти" @@ -1093,6 +1326,10 @@ msgstr "Выйти" msgid "labels.manage-fonts" msgstr "Управление шрифтами" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Участник" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.members" msgstr "Участники" @@ -1112,6 +1349,16 @@ msgstr "Далее" msgid "labels.no-comments-available" msgstr "Уведомлений о новых комментариях нет" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations" +msgstr "Приглашений нет." + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.no-invitations-hint" +msgstr "" +"Нажмите кнопку «Пригласить в команду», чтобы пригласить в эту команду " +"больше участников." + #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" msgstr "Вы вошли как" @@ -1160,6 +1407,10 @@ msgstr "Владелец" msgid "labels.password" msgstr "Пароль" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "Ожидание" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.permissions" msgstr "Разрешения" @@ -1183,6 +1434,10 @@ msgstr "Примечания к выпуску" msgid "labels.remove" msgstr "Удалить" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove-member" +msgstr "Удалить участника" + #: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "labels.rename" msgstr "Переименовать" @@ -1191,6 +1446,10 @@ msgstr "Переименовать" msgid "labels.rename-team" msgstr "Переименовать команду" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.resend-invitation" +msgstr "Снова отправить приглашение" + #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Заново" @@ -1237,6 +1496,9 @@ msgstr "Библиотеки" msgid "labels.show-all-comments" msgstr "Показать все" +msgid "labels.show-comments-list" +msgstr "Показать список комментариев" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-your-comments" msgstr "Показать только ваши" @@ -1251,6 +1513,14 @@ msgstr "Пропустить" msgid "labels.start" msgstr "Начать" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Состояние" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Руководства" + #: src/app/main/ui/settings/profile.cljs msgid "labels.update" msgstr "Обновить" @@ -1279,6 +1549,10 @@ msgstr "Рабочая область" msgid "labels.write-new-comment" msgstr "Написать комментарий" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(вы)" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.your-account" msgstr "Ваш аккаунт" @@ -1312,8 +1586,8 @@ msgstr "Подтвердить новую эл. почту" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.info" msgstr "" -"Мы отправим эл. письмо для подтверждения личности на текущую эл. почту \"%s\"" -"." +"Мы отправим эл. письмо для подтверждения личности на текущую эл. почту " +"\"%s\"." #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.new-email" @@ -1327,6 +1601,12 @@ msgstr "Изменить эл. почту" msgid "modals.change-email.title" msgstr "Изменить эл. почту" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.change-owner-and-leave-confirm.message" +msgstr "" +"Вы являетесь владельцем этой команды. Прежде чем уйти, выберите участника, " +"чтобы сделать его владельцем." + #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" msgstr "Отменить и сохранить мой аккаунт" @@ -1417,6 +1697,40 @@ msgstr "Вы уверены, что хотите удалить этот про msgid "modals.delete-project-confirm.title" msgstr "Удаление проекта" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Удалить файл" +msgstr[1] "Удалить файлы" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Вы уверены, что хотите удалить этот файл?" +msgstr[1] "Вы уверены, что хотите удалить эти файлы?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Файл содержит библиотеки, которые используются в этом файле:" +msgstr[1] "Файл содержит библиотеки, которые используются в этих файлах:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Файлы содержат библиотеки, которые используются в этом файле:" +msgstr[1] "Файлы содержат библиотеки, которые используются в этих файлах:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Удаление файла" +msgstr[1] "Удаление файлов" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Удаление файла" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "Удалить команду" @@ -1424,13 +1738,46 @@ msgstr "Удалить команду" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.message" msgstr "" -"Вы уверены, что хотите удалить эту команду? Все проекты и файлы этой команды " -"также будут безвозвратно удалены." +"Вы уверены, что хотите удалить эту команду? Все проекты и файлы этой " +"команды также будут безвозвратно удалены." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.title" msgstr "Удаление команды" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Удалить участника" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Вы уверены, что хотите удалить этого участника из команды?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Удалить участника команды" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Отправить приглашение" + +msgid "modals.invite-member.emails" +msgstr "Эл. почты, разделённые запятой" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Пригласить участников в команду" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.hint" +msgstr "" +"Так как вы единственный участник этой команды, она будет удалена вместе с " +"проектами и файлами." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-close-confirm.message" +msgstr "Вы уверены, что хотите покинуть команду %s?" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.accept" msgstr "" @@ -1443,6 +1790,30 @@ msgstr "" msgid "modals.remove-shared-confirm.message" msgstr "" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Вы уверены, что хотите снять с публикации эту библиотеку?" +msgstr[1] "Вы уверены, что хотите снять с публикации эти библиотеки?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Используется в этом файле:" +msgstr[1] "Используется в этих файлах:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Снять библиотеку с публикации" +msgstr[1] "Снять библиотеки с публикации" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component-in-bulk.hint" +msgstr "" +"Вы собираетесь обновить компоненты в общей библиотеке. Это может повлиять " +"на другие файлы, которые её используют." + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.accept" msgstr "Обновить" @@ -1469,16 +1840,222 @@ msgstr "Вы не можете удалить профиль. Сначала п msgid "notifications.profile-saved" msgstr "Профиль успешно сохранён!" +msgid "onboarding-v2.before-start.desc3" +msgstr "" +"Вы можете посмотреть наши руководства и руководства, созданные нашим " +"сообществом." + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot — проект с открытым исходным кодом, созданный Kaleidos и " +"сообществом, где многие люди уже помогают друг другу. Каждый может начать " +"сотрудничество:" + +msgid "onboarding.choice.team-up.create-team" +msgstr "Название вашей команды" + +msgid "onboarding.choice.team-up.create-team-placeholder" +msgstr "Введите название команды" + +msgid "onboarding.choice.team-up.invite-members" +msgstr "Пригласить участников" + +msgid "onboarding.choice.team-up.invite-members-skip" +msgstr "Создать команду и пригласить позже" + +msgid "onboarding.choice.team-up.invite-members-submit" +msgstr "Создать команду и отправить приглашения" + +msgid "onboarding.contrib.desc1" +msgstr "" +"Penpot — это проект с открытым исходным кодом, созданный сообществом и для " +"него. Если вы хотите сотрудничать, добро пожаловать!" + +msgid "onboarding.contrib.link" +msgstr "проект на Github" + +msgid "onboarding.newsletter.privacy2" +msgstr "" +"Мы будем отправлять только акутальные эл. письма. Вы можете отказаться от " +"подписки в любое время в своём профиле пользователя или по ссылке отказа от " +"подписки в любом из наших писем информационной рассылки." + +msgid "onboarding.slide.1.alt" +msgstr "Интерактивные прототипы" + msgid "onboarding.slide.1.desc1" msgstr "Создайте различные взаимодействия для имитации поведения продукта." msgid "onboarding.slide.1.title" msgstr "Оживите свои работы с помощью интерактива" +msgid "onboarding.slide.2.desc1" +msgstr "" +"Все участники команды работают одновременно в режиме реального времени и " +"централизованно комментируют, отправляют идеи и отзывы напрямую в проект " +"дизайна." + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Команда позволяет вам сотрудничать с другими пользователями Penpot, " +"работающими над одними файлами и проектами." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Неограниченное количество файлов и проектов" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Многопользовательская версия" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Управление ролями" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Неограниченное количество участников" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "100% бесплатно!" + +msgid "onboarding.templates.subtitle" +msgstr "Вот несколько шаблонов." + +msgid "onboarding.templates.title" +msgstr "Заняться дизайном" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +msgid "onboarding.welcome.title" +msgstr "Добро пожаловать в Penpot" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Перейти к входу" + #: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs msgid "settings.multiple" msgstr "Смешаный" +msgid "shortcut-subsection.edit" +msgstr "Редактировать" + +msgid "shortcut-subsection.main-menu" +msgstr "Главное меню" + +msgid "shortcut-subsection.modify-layers" +msgstr "Изменить слои" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Навигация" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Навигация" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Навигация" + +msgid "shortcut-subsection.panels" +msgstr "Панели" + +msgid "shortcuts.copy" +msgstr "Скопировать" + +msgid "shortcuts.create-component" +msgstr "Создать компонент" + +msgid "shortcuts.delete" +msgstr "Удалить" + +msgid "shortcuts.go-to-libs" +msgstr "Перейти к общим библиотекам" + +msgid "shortcuts.go-to-search" +msgstr "Поиск" + +msgid "shortcuts.hide-ui" +msgstr "Показать/скрыть UI" + +msgid "shortcuts.opacity-5" +msgstr "Установить непрозрачность на 50%" + +msgid "shortcuts.opacity-6" +msgstr "Установить непрозрачность на 60%" + +msgid "shortcuts.opacity-7" +msgstr "Установить непрозрачность на 70%" + +msgid "shortcuts.opacity-8" +msgstr "Установить непрозрачность на 80%" + +msgid "shortcuts.opacity-9" +msgstr "Установить непрозрачность на 90%" + +msgid "shortcuts.open-color-picker" +msgstr "Выбор цвета" + +msgid "shortcuts.open-workspace" +msgstr "Перейти к рабочей области" + +msgid "shortcuts.or" +msgstr " или " + +msgid "shortcuts.paste" +msgstr "Вставить" + +msgid "shortcuts.select-all" +msgstr "Выбрать все" + +msgid "shortcuts.show-pixel-grid" +msgstr "Показать/скрыть сетку пикселей" + +msgid "shortcuts.show-shortcuts" +msgstr "Показать/скрыть горячие клавиши" + +msgid "shortcuts.start-editing" +msgstr "Начать редактирование" + +msgid "shortcuts.start-measure" +msgstr "Начать измерение" + +msgid "shortcuts.stop-measure" +msgstr "Остановить измерение" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "Горячие клавиши" + +msgid "shortcuts.toggle-assets" +msgstr "Переключить ресурсы" + +msgid "shortcuts.toggle-colorpalette" +msgstr "Переключить палитру цветов" + +msgid "shortcuts.toggle-focus-mode" +msgstr "Переключить режим фокуса" + +msgid "shortcuts.toggle-grid" +msgstr "Показать/скрыть сетку" + +msgid "shortcuts.toggle-history" +msgstr "Переключить историю" + +msgid "shortcuts.toggle-layers" +msgstr "Переключить слои" + +msgid "shortcuts.toggle-rules" +msgstr "Показать/скрыть линейки" + +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - Penpot" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.font-providers" +msgstr "Поставщики шрифтов - %s - Penpot" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.fonts" +msgstr "Шрифты - %s - Penpot" + #: src/app/main/ui/dashboard/projects.cljs msgid "title.dashboard.projects" msgstr "Проекты - %s - Penpot" @@ -1491,6 +2068,10 @@ msgstr "Поиск - %s - Penpot" msgid "title.dashboard.shared-libraries" msgstr "Общие библиотеки - %s - Penpot" +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot — свобода в дизайне для команд" + #: src/app/main/ui/settings/feedback.cljs msgid "title.settings.feedback" msgstr "Оставить отзыв - Penpot" @@ -1507,6 +2088,14 @@ msgstr "Пароль - Penpot" msgid "title.settings.profile" msgstr "Профиль - Penpot" +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-invitations" +msgstr "Приглашения - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Участники - %s - Penpot" + #: src/app/main/ui/dashboard/team.cljs msgid "title.team-settings" msgstr "Настройки - %s - Penpot" @@ -1515,6 +2104,18 @@ msgstr "Настройки - %s - Penpot" msgid "title.viewer" msgstr "%s - Режим просмотра - Penpot" +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + +msgid "viewer.breaking-change.description" +msgstr "" +"Эта общая ссылка больше не действительна. Создайте новую или попросите об " +"этом владельца." + +msgid "viewer.breaking-change.message" +msgstr "Извините!" + #: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs msgid "viewer.empty-state" msgstr "На странице не найдено ни одного кадра." @@ -1710,6 +2311,10 @@ msgstr "Размер" msgid "workspace.assets.typography.font-variant-id" msgstr "Начертание" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Перейти к файлу библиотеки стилей для редактирования" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.assets.typography.letter-spacing" msgstr "Кернинг" @@ -1754,10 +2359,25 @@ msgstr "Скрыть сетки" msgid "workspace.header.menu.hide-palette" msgstr "Скрыть палитру цветов" +msgid "workspace.header.menu.hide-pixel-grid" +msgstr "Скрыть сетку пикселей" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-rules" msgstr "Скрыть линейки" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-textpalette" +msgstr "Скрыть палитру шрифтов" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Файл" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "Помощь и информация" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.select-all" msgstr "" @@ -1778,6 +2398,10 @@ msgstr "Показать палитру цветов" msgid "workspace.header.menu.show-rules" msgstr "Показать линейки" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.reset-zoom" +msgstr "Сброс" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.viewer" msgstr "Режим просмотра (%s)" @@ -1790,6 +2414,30 @@ msgstr "Добавить" msgid "workspace.libraries.colors" msgstr "" +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Библиотека файлов" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.hsv" +msgstr "HSV" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Недавние цвета" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgb-complementary" +msgstr "Дополнительный цвет RGB" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Сохранить стиль цвета" + #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.components" msgstr "" @@ -1870,6 +2518,12 @@ msgstr "Сохраненные библиотеки" msgid "workspace.options.add-interaction" msgstr "Нажмите кнопку \"+\" для добавления интерактива." +msgid "workspace.options.blur-options.background-blur" +msgstr "Фон" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Слой" + #: src/app/main/ui/workspace/sidebar/options/page.cljs msgid "workspace.options.canvas-background" msgstr "Фон холста" @@ -1884,14 +2538,20 @@ msgstr "Экспорт" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "Экспорт элемента" -msgstr[1] "Экспорт %s элементов" +msgstr "Экспорт элемента" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "Экспорт завершён" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.exporting-object" msgstr "Экспортирование…" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "Экспорт не удался" + #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.fill" msgstr "Заливка" @@ -2000,6 +2660,14 @@ msgstr "Обводка для группы" msgid "workspace.options.interactions" msgstr "Интерактив" +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Цвет" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Слой" + #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.title.group" msgstr "Группировать слои" @@ -2023,6 +2691,9 @@ msgstr "Радиус" msgid "workspace.options.rotation" msgstr "Вращение" +msgid "workspace.options.search-font" +msgstr "Искать шрифт" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.select-a-shape" msgstr "Выберите фигуру, кадр или группу для соединения с другим кадром." @@ -2039,6 +2710,24 @@ msgstr "Заливка выбранного" msgid "workspace.options.selection-stroke" msgstr "Обводка выбранного" +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Размытие" + +msgid "workspace.options.shadow-options.color" +msgstr "Цвет тени" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Внутренняя тень" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Тень" + +msgid "workspace.options.show-in-viewer" +msgstr "Показать в режиме просмотра" + #: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.size" msgstr "Размер" @@ -2110,6 +2799,30 @@ msgstr "Выравнивание по верхнему краю" msgid "workspace.options.text-options.decoration" msgstr "Оформление" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "Слева направо" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "Справа налево" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.google" +msgstr "Google" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-height" +msgstr "Автовысота" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Автоширина" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Фиксированно" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.options.text-options.letter-spacing" msgstr "Межсимвольный интервал" @@ -2126,6 +2839,10 @@ msgstr "Нижний регистр" msgid "workspace.options.text-options.none" msgstr "Не задано" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.preset" +msgstr "Предустановка" + #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.strikethrough" msgstr "Перечеркнутый" @@ -2164,6 +2881,12 @@ msgstr "Вертикальное выравнивание" msgid "workspace.options.use-play-button" msgstr "Нажмите кнопку воспроизведения вверху для показа прототипа." +msgid "workspace.options.x" +msgstr "X" + +msgid "workspace.options.y" +msgstr "Y" + msgid "workspace.path.actions.add-node" msgstr "Добавить узел (%s)" @@ -2194,28 +2917,116 @@ msgstr "Разделить узлы (%s)" msgid "workspace.path.actions.snap-nodes" msgstr "Прилипать узлами (%s)" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Скопировать" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.create-artboard-from-selection" msgstr "Выделенное в кадр" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Создать компонент" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Вырезать" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Удалить" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.delete-flow-start" msgstr "Удалить начало потока" +msgid "workspace.shape.menu.difference" +msgstr "Разница" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Дублировать" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Редактировать" + +msgid "workspace.shape.menu.exclude" +msgstr "Исключить" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.flow-start" msgstr "Начало потока" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.go-main" +msgstr "Перейти к основному файлу компонента" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Скрыть" + +msgid "workspace.shape.menu.hide-ui" +msgstr "Показать/скрыть UI" + msgid "workspace.shape.menu.path" msgstr "Контур" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.reset-overrides" +msgstr "Сбросить переопределения" + +msgid "workspace.shape.menu.restore-main" +msgstr "Восстановить основной компонент" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "Выбрать слой" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Показать" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-main" +msgstr "Показать основной компонент" + +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "Удалить миниатюру" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "Сделать миниатюрой" + msgid "workspace.shape.menu.transform-to-path" msgstr "Преобразовать в контур" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Разгруппировать" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "Обновить основные компоненты" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-main" +msgstr "Обновить основной компонент" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "История (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.sidebar.layers" msgstr "Слои" +msgid "workspace.sidebar.layers.components" +msgstr "Компоненты" + +msgid "workspace.sidebar.layers.search" +msgstr "Искать слои" + #: src/app/main/ui/workspace/sidebar/sitemap.cljs msgid "workspace.sidebar.sitemap" msgstr "Страницы" @@ -2275,6 +3086,9 @@ msgstr "Типографики (%s)" msgid "workspace.undo.entry.multiple.color" msgstr "цветовые ресурсы" +msgid "workspace.undo.entry.multiple.component" +msgstr "компоненты" + msgid "workspace.undo.entry.multiple.frame" msgstr "кадры" @@ -2290,9 +3104,19 @@ msgstr "типографические ресурсы" msgid "workspace.undo.entry.single.frame" msgstr "кадр" +msgid "workspace.undo.entry.single.group" +msgstr "группа" + msgid "workspace.undo.entry.single.path" msgstr "контур" +msgid "workspace.undo.entry.single.rect" +msgstr "прямоугольник" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.title" +msgstr "История" + #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.dismiss" msgstr "Отклонить" @@ -2306,830 +3130,4 @@ msgid "workspace.updates.update" msgstr "Обновить" msgid "workspace.viewport.click-to-close-path" -msgstr "Нажмите для замыкания контура" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.file" -msgstr "Файл" - -msgid "workspace.options.y" -msgstr "Y" - -#: src/app/main/ui/auth/recovery.cljs -msgid "profile.recovery.go-to-login" -msgstr "Перейти к входу" - -msgid "workspace.options.shadow-options.color" -msgstr "Цвет тени" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.rgb-complementary" -msgstr "Дополнительный цвет RGB" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.hide" -msgstr "Скрыть" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.inner-shadow" -msgstr "Внутренняя тень" - -msgid "common.share-link.view-all" -msgstr "Выбрать все" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Управление командой" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot предназначен для команд. Приглашайте участников к совместной работе " -"над проектами и файлами" - -msgid "onboarding.choice.team-up.invite-members-submit" -msgstr "Создать команду и отправить приглашения" - -msgid "onboarding-v2.welcome.desc1" -msgstr "" -"Penpot — проект с открытым исходным кодом, созданный Kaleidos и сообществом, " -"где многие люди уже помогают друг другу. Каждый может начать сотрудничество:" - -msgid "onboarding-v2.before-start.desc3" -msgstr "" -"Вы можете посмотреть наши руководства и руководства, созданные нашим " -"сообществом." - -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.file-library" -msgstr "Библиотека файлов" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Файл содержит библиотеки, которые используются в этом файле:" -msgstr[1] "Файл содержит библиотеки, которые используются в этих файлах:" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.blur" -msgstr "Размытие" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.update-components-in-bulk" -msgstr "Обновить основные компоненты" - -msgid "onboarding.team-modal.create-team-feature-1" -msgstr "Неограниченное количество файлов и проектов" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "Вы уверены, что хотите удалить этот файл?" -msgstr[1] "Вы уверены, что хотите удалить эти файлы?" - -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Мы будем отправлять только акутальные эл. письма. Вы можете отказаться от " -"подписки в любое время в своём профиле пользователя или по ссылке отказа от " -"подписки в любом из наших писем информационной рассылки." - -msgid "shortcuts.toggle-history" -msgstr "Переключить историю" - -msgid "shortcuts.open-color-picker" -msgstr "Выбор цвета" - -msgid "common.share-link.all-users" -msgstr "Все пользователи Penpot" - -msgid "common.share-link.current-tag" -msgstr "(текущее)" - -msgid "common.share-link.destroy-link" -msgstr "Удалить ссылку" - -msgid "common.share-link.permissions-can-comment" -msgstr "Может комментировать" - -msgid "common.share-link.manage-ops" -msgstr "Управлять разрешениями" - -msgid "common.share-link.permissions-can-inspect" -msgstr "Может проверять код" - -msgid "common.share-link.team-members" -msgstr "Только участники команды" - -msgid "dashboard.download-binary-file" -msgstr "Скачать файл Penpot (.penpot)" - -msgid "dashboard.download-standard-file" -msgstr "Скачать стандартный файл (.svg + .json)" - -msgid "dashboard.export-binary-multi" -msgstr "Скачать файлы Penpot (.penpot) (%s)" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.how-to-link" -msgstr "Информация о настройке экспорта в Penpot." - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.no-elements" -msgstr "Нет элементов с настройками экспорта." - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.title" -msgstr "Выбор экспорта" - -msgid "dashboard.export-standard-multi" -msgstr "Скачать стандартные файлы (.svg + .json) (%s)" - -msgid "errors.team-leave.member-does-not-exists" -msgstr "Участник, которого вы пытаетесь назначить, не существует." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-go-to" -msgstr "Перейти в Twitter" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-title" -msgstr "Сообщество Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "Перейти на форум Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-subtitle1" -msgstr "" -"Мы рады вас здесь видеть. Если вам нужна помощь, пожалуйста, поищите ответ, " -"возможно он уже есть." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-subtitle1" -msgstr "Здесь, чтобы помочь с вашими техническими запросами." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-title" -msgstr "Аккаунт поддержки в Twitter" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.community" -msgstr "Сообщество" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.github-repo" -msgstr "Репозиторий на Github" - -msgid "labels.show-comments-list" -msgstr "Показать список комментариев" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.you" -msgstr "(вы)" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component-in-bulk.hint" -msgstr "" -"Вы собираетесь обновить компоненты в общей библиотеке. Это может повлиять на " -"другие файлы, которые её используют." - -msgid "onboarding.slide.1.alt" -msgstr "Интерактивные прототипы" - -msgid "shortcut-subsection.main-menu" -msgstr "Главное меню" - -msgid "shortcut-subsection.modify-layers" -msgstr "Изменить слои" - -msgid "shortcut-subsection.navigation-dashboard" -msgstr "Навигация" - -msgid "shortcut-subsection.navigation-viewer" -msgstr "Навигация" - -msgid "shortcut-subsection.navigation-workspace" -msgstr "Навигация" - -msgid "shortcut-subsection.panels" -msgstr "Панели" - -msgid "shortcuts.create-component" -msgstr "Создать компонент" - -msgid "shortcuts.delete" -msgstr "Удалить" - -msgid "shortcuts.go-to-libs" -msgstr "Перейти к общим библиотекам" - -msgid "shortcuts.go-to-search" -msgstr "Поиск" - -msgid "shortcuts.hide-ui" -msgstr "Показать/скрыть UI" - -msgid "shortcuts.select-all" -msgstr "Выбрать все" - -msgid "shortcuts.or" -msgstr " или " - -msgid "shortcuts.show-pixel-grid" -msgstr "Показать/скрыть сетку пикселей" - -msgid "shortcuts.open-workspace" -msgstr "Перейти к рабочей области" - -#: src/app/main/ui/workspace/sidebar/shortcuts.cljs -msgid "shortcuts.title" -msgstr "Горячие клавиши" - -msgid "shortcuts.start-measure" -msgstr "Начать измерение" - -msgid "shortcuts.stop-measure" -msgstr "Остановить измерение" - -msgid "shortcuts.start-editing" -msgstr "Начать редактирование" - -msgid "shortcuts.toggle-grid" -msgstr "Показать/скрыть сетку" - -msgid "shortcuts.toggle-focus-mode" -msgstr "Переключить режим фокуса" - -msgid "shortcuts.toggle-colorpalette" -msgstr "Переключить палитру цветов" - -msgid "shortcuts.toggle-assets" -msgstr "Переключить ресурсы" - -#: src/app/main/ui/dashboard/files.cljs -msgid "title.dashboard.files" -msgstr "%s - Penpot" - -msgid "workspace.header.menu.hide-pixel-grid" -msgstr "Скрыть сетку пикселей" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.reset-zoom" -msgstr "Сброс" - -msgid "workspace.options.blur-options.background-blur" -msgstr "Фон" - -msgid "workspace.options.blur-options.layer-blur" -msgstr "Слой" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-complete" -msgstr "Экспорт завершён" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.color" -msgstr "Цвет" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.title" -msgstr "Слой" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title" -msgstr "Тень" - -msgid "workspace.options.show-in-viewer" -msgstr "Показать в режиме просмотра" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.grow-auto-height" -msgstr "Автовысота" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.grow-fixed" -msgstr "Фиксированно" - -msgid "workspace.options.x" -msgstr "X" - -msgid "workspace.shape.menu.hide-ui" -msgstr "Показать/скрыть UI" - -msgid "workspace.sidebar.layers.components" -msgstr "Компоненты" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.update-main" -msgstr "Обновить основной компонент" - -#: src/app/main/ui/workspace/header.cljs -msgid "dashboard.export-shapes" -msgstr "Экспорт" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.rgba" -msgstr "RGBA" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.hsv" -msgstr "HSV" - -msgid "common.unpublish" -msgstr "Снять с публикации" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-frames.title" -msgstr "Экспорт в PDF" - -msgid "errors.team-leave.insufficient-members" -msgstr "" -"Недостаточно участников, чтобы покинуть команду, вероятно, вы хотите её " -"удалить." - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "Ок" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Внимание" - -#: src/app/main/ui/workspace/header.cljs -msgid "label.shortcuts" -msgstr "Сочетания клавиш" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.change-owner-and-leave-confirm.message" -msgstr "" -"Вы являетесь владельцем этой команды. Прежде чем уйти, выберите участника, " -"чтобы сделать его владельцем." - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.pending-invitation" -msgstr "Ожидание" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.tutorials" -msgstr "Руководства" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "Вы уверены, что хотите снять с публикации эту библиотеку?" -msgstr[1] "Вы уверены, что хотите снять с публикации эти библиотеки?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "Снять библиотеку с публикации" -msgstr[1] "Снять библиотеки с публикации" - -msgid "onboarding.welcome.title" -msgstr "Добро пожаловать в Penpot" - -msgid "shortcut-subsection.edit" -msgstr "Редактировать" - -msgid "shortcuts.show-shortcuts" -msgstr "Показать/скрыть горячие клавиши" - -msgid "shortcuts.paste" -msgstr "Вставить" - -msgid "shortcuts.opacity-9" -msgstr "Установить непрозрачность на 90%" - -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs -msgid "title.default" -msgstr "Penpot — свобода в дизайне для команд" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "title.dashboard.fonts" -msgstr "Шрифты - %s - Penpot" - -msgid "shortcuts.toggle-rules" -msgstr "Показать/скрыть линейки" - -msgid "shortcuts.toggle-layers" -msgstr "Переключить слои" - -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-invitations" -msgstr "Приглашения - %s - Penpot" - -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-members" -msgstr "Участники - %s - Penpot" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.help-info" -msgstr "Помощь и информация" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-textpalette" -msgstr "Скрыть палитру шрифтов" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.go-to-edit" -msgstr "Перейти к файлу библиотеки стилей для редактирования" - -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.recent-colors" -msgstr "Недавние цвета" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.save-color" -msgstr "Сохранить стиль цвета" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-object-error" -msgstr "Экспорт не удался" - -msgid "workspace.options.search-font" -msgstr "Искать шрифт" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.grow-auto-width" -msgstr "Автоширина" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.preset" -msgstr "Предустановка" - -msgid "workspace.sidebar.layers.search" -msgstr "Искать слои" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.select-layer" -msgstr "Выбрать слой" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.go-main" -msgstr "Перейти к основному файлу компонента" - -msgid "common.publish" -msgstr "Опубликовать" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.how-to" -msgstr "" -"Вы можете добавить настройки экспорта элементам из свойств дизайна (в нижней " -"части правой боковой панели)." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Объединяйтесь!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "Изучите основы в Penpot весело с этим практическим руководством." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Начать обучение" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Практическое руководство" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "" -"Прогуляйтесь по возможностям Penpot и познакомьтесь с основными функциями." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Начать тур" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Руководство по интерфейсу" - -msgid "dashboard.libraries-and-templates" -msgstr "Библиотеки и шаблоны" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.save-settings" -msgstr "Сохранить настройки" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Удалить файл" -msgstr[1] "Удалить файлы" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "Файлы содержат библиотеки, которые используются в этом файле:" -msgstr[1] "Файлы содержат библиотеки, которые используются в этих файлах:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "Удаление файла" -msgstr[1] "Удаление файлов" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "Удаление файла" - -msgid "onboarding.slide.2.desc1" -msgstr "" -"Все участники команды работают одновременно в режиме реального времени и " -"централизованно комментируют, отправляют идеи и отзывы напрямую в проект " -"дизайна." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.scd-message" -msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "Используется в этом файле:" -msgstr[1] "Используется в этих файлах:" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "Неограниченное количество участников" - -msgid "onboarding.team-modal.create-team-feature-3" -msgstr "Управление ролями" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "100% бесплатно!" - -msgid "onboarding.templates.subtitle" -msgstr "Вот несколько шаблонов." - -msgid "onboarding.team-modal.create-team-feature-2" -msgstr "Многопользовательская версия" - -msgid "onboarding.team-modal.create-team-desc" -msgstr "" -"Команда позволяет вам сотрудничать с другими пользователями Penpot, " -"работающими над одними файлами и проектами." - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.sidebar.history" -msgstr "История (%s)" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.resend-invitation" -msgstr "Снова отправить приглашение" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.remove-member" -msgstr "Удалить участника" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.status" -msgstr "Состояние" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-close-confirm.hint" -msgstr "" -"Так как вы единственный участник этой команды, она будет удалена вместе с " -"проектами и файлами." - -msgid "onboarding.choice.team-up.invite-members" -msgstr "Пригласить участников" - -msgid "onboarding.choice.team-up.invite-members-skip" -msgstr "Создать команду и пригласить позже" - -msgid "onboarding.templates.title" -msgstr "Заняться дизайном" - -msgid "onboarding.welcome.alt" -msgstr "Penpot" - -msgid "shortcuts.copy" -msgstr "Скопировать" - -msgid "shortcuts.opacity-8" -msgstr "Установить непрозрачность на 80%" - -msgid "shortcuts.opacity-7" -msgstr "Установить непрозрачность на 70%" - -msgid "shortcuts.opacity-5" -msgstr "Установить непрозрачность на 50%" - -msgid "shortcuts.opacity-6" -msgstr "Установить непрозрачность на 60%" - -msgid "onboarding.contrib.link" -msgstr "проект на Github" - -msgid "onboarding.contrib.desc1" -msgstr "" -"Penpot — это проект с открытым исходным кодом, созданный сообществом и для " -"него. Если вы хотите сотрудничать, добро пожаловать!" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "title.dashboard.font-providers" -msgstr "Поставщики шрифтов - %s - Penpot" - -#: src/app/main/ui/workspace.cljs -msgid "title.workspace" -msgstr "%s - Penpot" - -msgid "viewer.breaking-change.message" -msgstr "Извините!" - -msgid "viewer.breaking-change.description" -msgstr "" -"Эта общая ссылка больше не действительна. Создайте новую или попросите об " -"этом владельца." - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.google" -msgstr "Google" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.direction-ltr" -msgstr "Слева направо" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.direction-rtl" -msgstr "Справа налево" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.delete" -msgstr "Удалить" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.duplicate" -msgstr "Дублировать" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.edit" -msgstr "Редактировать" - -msgid "workspace.undo.entry.single.rect" -msgstr "прямоугольник" - -msgid "common.share-link.page-shared" -msgid_plural "common.share-link.page-shared" -msgstr[0] "1 общая страница" -msgstr[1] "%s общих страниц" - -msgid "common.share-link.permissions-pages" -msgstr "Общие страницы" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-title" -msgstr "Подписка на рассылку" - -msgid "errors.invite-invalid.info" -msgstr "Возможно, это приглашение отменено или истёк срок его действия." - -msgid "labels.continue-with-penpot" -msgstr "Вы можете продолжить с аккаунтом Penpot" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.expired-invitation" -msgstr "Истекло" - -#: src/app/main/ui/settings/feedback.cljs -msgid "labels.feedback-sent" -msgstr "Отзыв отправлен" - -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.invitations" -msgstr "Приглашения" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show" -msgstr "Показать" - -msgid "workspace.shape.menu.exclude" -msgstr "Исключить" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.ungroup" -msgstr "Разгруппировать" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show-main" -msgstr "Показать основной компонент" - -msgid "workspace.undo.entry.single.group" -msgstr "группа" - -msgid "labels.log-or-sign" -msgstr "Войти или зарегистрироваться" - -msgid "labels.back" -msgstr "Назад" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.delete-invitation" -msgstr "Удалить приглашение" - -msgid "workspace.shape.menu.difference" -msgstr "Разница" - -msgid "workspace.shape.menu.restore-main" -msgstr "Восстановить основной компонент" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.reset-overrides" -msgstr "Сбросить переопределения" - -msgid "workspace.undo.entry.multiple.component" -msgstr "компоненты" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.title" -msgstr "История" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Узнайте больше о них и о том, как внести свой вклад" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "Возникла проблема с импортом шаблона. Шаблон не был импортирован." - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-msg" -msgstr "" -"Присылайте мне новости, информацию об обновлениях продуктов и рекомендации о " -"Penpot." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Снять библиотеку с публикации" - -msgid "onboarding.choice.team-up.create-team-placeholder" -msgstr "Введите название команды" - -msgid "workspace.shape.menu.thumbnail-set" -msgstr "Сделать миниатюрой" - -msgid "workspace.shape.menu.thumbnail-remove" -msgstr "Удалить миниатюру" - -msgid "labels.and" -msgstr "и" - -msgid "labels.continue-with" -msgstr "Продолжить с" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.no-invitations" -msgstr "Приглашений нет." - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.member" -msgstr "Участник" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.no-invitations-hint" -msgstr "" -"Нажмите кнопку «Пригласить в команду», чтобы пригласить в эту команду больше " -"участников." - -msgid "onboarding.choice.team-up.create-team" -msgstr "Название вашей команды" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.copy" -msgstr "Скопировать" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.create-component" -msgstr "Создать компонент" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.accept" -msgstr "Удалить участника" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-member-confirm.accept" -msgstr "Отправить приглашение" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.title" -msgstr "Удалить участника команды" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.message" -msgstr "Вы уверены, что хотите удалить этого участника из команды?" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "Пригласить участников в команду" - -msgid "modals.invite-member.emails" -msgstr "Эл. почты, разделённые запятой" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-close-confirm.message" -msgstr "Вы уверены, что хотите покинуть команду %s?" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.cut" -msgstr "Вырезать" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-multiple.selected" -msgstr "Выбрано %s из %s элементов" +msgstr "Нажмите для замыкания контура" \ No newline at end of file diff --git a/frontend/translations/ta.po b/frontend/translations/ta.po index 187429d501..e594f441d0 100644 --- a/frontend/translations/ta.po +++ b/frontend/translations/ta.po @@ -2,15 +2,25 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-07 02:17+0000\n" "Last-Translator: K.B.Dharun Krishna \n" -"Language-Team: Tamil \n" +"Language-Team: Tamil " +"\n" "Language: ta\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.14.1\n" +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "ஏற்கனவே ஒரு கணக்கு உள்ளதா?" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"உங்கள் மின்னஞ்சலைச் சரிபார்த்து, இணைப்பைக் கிளிக் செய்து சரிபார்த்து, " +"Penpot ஐப் பயன்படுத்தத் தொடங்குங்கள்." + #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" msgstr "கடவுச்சொல்லை உறுதிப்படுத்தவும்" @@ -23,25 +33,15 @@ msgstr "டெமோ கணக்கை உருவாக்கவும்" msgid "auth.create-demo-profile" msgstr "அதை முயற்சி செய்ய வேண்டுமா?" -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.email" -msgstr "மின்னஞ்சல்" - #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" msgstr "" "இது ஒரு டெமோ சேவை, உண்மையான வேலைக்கு பயன்படுத்த வேண்டாம், திட்டங்கள் " "அவ்வப்போது அழிக்கப்படும்." -#: src/app/main/ui/auth/register.cljs -msgid "auth.check-your-email" -msgstr "" -"உங்கள் மின்னஞ்சலைச் சரிபார்த்து, இணைப்பைக் கிளிக் செய்து சரிபார்த்து, Penpot " -"ஐப் பயன்படுத்தத் தொடங்குங்கள்." - -#: src/app/main/ui/auth/register.cljs -msgid "auth.already-have-account" -msgstr "ஏற்கனவே ஒரு கணக்கு உள்ளதா?" +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.email" +msgstr "மின்னஞ்சல்" #: src/app/main/ui/auth/login.cljs msgid "auth.forgot-password" @@ -87,6 +87,18 @@ msgstr "ஓப்பன் ஐடி" msgid "auth.new-password" msgstr "புதிய கடவுச்சொல்லை உள்ளிடவும்" +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "பென்பாட் அஞ்சல் பட்டியலில் குழுசேர ஒப்புக்கொள்கிறேன்." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "மீட்பு டோக்கன் செல்லுபடியாகாது." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-successfully" +msgstr "கடவுச்சொல் வெற்றிகரமாக மாற்றப்பட்டது" + #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.profile-not-verified" msgstr "" @@ -102,22 +114,10 @@ msgstr "கடவுச்சொல் மீட்பு இணைப்பு msgid "auth.notifications.team-invitation-accepted" msgstr "அணியில் வெற்றிகரமாக இணைந்தார்" -#: src/app/main/ui/auth/register.cljs -msgid "auth.newsletter-subscription" -msgstr "பென்பாட் அஞ்சல் பட்டியலில் குழுசேர ஒப்புக்கொள்கிறேன்." - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.invalid-token-error" -msgstr "மீட்பு டோக்கன் செல்லுபடியாகாது." - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.password-changed-successfully" -msgstr "கடவுச்சொல் வெற்றிகரமாக மாற்றப்பட்டது" - #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.password" msgstr "கடவுச்சொல்" #: src/app/main/ui/auth/register.cljs msgid "auth.password-length-hint" -msgstr "குறைந்தது 8 எழுத்துகள்" +msgstr "குறைந்தது 8 எழுத்துகள்" \ No newline at end of file diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 1c3b71a2b4..1ce32b6dad 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-27 15:18+0000\n" "Last-Translator: Oğuz Ersen \n" -"Language-Team: Turkish \n" +"Language-Team: Turkish " +"\n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -172,6 +172,9 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Onay e-postanı şu adrese gönderdik" +msgid "common.publish" +msgstr "Yayınla" + msgid "common.share-link.all-users" msgstr "Tüm Penpot kullanıcıları" @@ -227,6 +230,49 @@ msgstr "Prototipleri paylaş" msgid "common.share-link.view-all" msgstr "Tümünü Seç" +msgid "common.unpublish" +msgstr "Yayından kaldır" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Takım yönetimi" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot takımlar içindir. Üyeleri projeler ve dosyalar üzerinde birlikte " +"çalışmaya davet edin" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Takım olun!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Bu uygulamalı öğretici ile biraz eğlenirken Penpot'taki temel bilgileri " +"öğrenin." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Öğreticiyi başlat" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Uygulamalı Öğretici" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Penpot'ta bir gezintiye çıkın ve temel özelliklerini öğrenin." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Gezintiyi başlat" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "Arayüz İncelemesi" + #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -444,6 +490,15 @@ msgstr "Takıma davet et" msgid "dashboard.leave-team" msgstr "Takımdan ayrıl" +msgid "dashboard.libraries-and-templates" +msgstr "Kütüphaneler ve Şablonlar" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Daha fazlasını keşfedin ve nasıl katkıda bulunacağınızı öğrenin" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Şablon içe aktarılırken bir sorun oluştu. Şablon içe aktarılmadı." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Paylaşılan Kütüphaneler" @@ -626,6 +681,10 @@ msgstr "Arama sonuçları" msgid "dashboard.type-something" msgstr "Aramak için yazın" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "Kütüphaneyi Yayından Kaldır" + #: src/app/main/ui/settings/profile.cljs, #: src/app/main/ui/settings/password.cljs, #: src/app/main/ui/settings/options.cljs @@ -652,6 +711,14 @@ msgstr "Adın" msgid "dashboard.your-penpot" msgstr "Penpot'un" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "Tamam" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "Dikkat" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "Güncellenecek bileşenler:" @@ -683,6 +750,10 @@ msgstr "Kimliğiniz doğrulanmamış veya oturumun süresi dolmuş gibi görün msgid "errors.clipboard-not-implemented" msgstr "Tarayıcın bu işlemi gerçekleştiremiyor" +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "Bu dosya zaten Bileşenler V2 etkinken kullanıldı." + #: src/app/main/ui/auth/verify_token.cljs, #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" @@ -1693,6 +1764,40 @@ msgstr "Bu projeyi silmek istediğinden emin misin?" msgid "modals.delete-project-confirm.title" msgstr "Projeyi sil" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Dosyayı sil" +msgstr[1] "Dosyaları sil" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "Bu dosyayı silmek istediğinizden emin misiniz?" +msgstr[1] "Bu dosyaları silmek istediğinizden emin misiniz?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Bu dosyada, şu dosyada kullanılmakta olan kütüphaneler var:" +msgstr[1] "Bu dosyada, şu dosyalarda kullanılmakta olan kütüphaneler var:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "Bu dosyalarda, şu dosyada kullanılmakta olan kütüphaneler var:" +msgstr[1] "Bu dosyalarda, şu dosyalarda kullanılmakta olan kütüphaneler var:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Dosya siliniyor" +msgstr[1] "Dosyalar siliniyor" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "Dosya siliniyor" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "Takımı sil" @@ -1726,6 +1831,10 @@ msgstr "Davet gönder" msgid "modals.invite-member.emails" msgstr "E-posta adresleri, virgülle ayrılmış" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "Üyeleri takıma davet et" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" msgstr "" @@ -1820,6 +1929,38 @@ msgstr "“%s” Paylaşılan Kütüphanesini Kaldır" msgid "modals.small-nudge" msgstr "Küçük dürtme" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "Yayından kaldır" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Yayından kaldırırsanız, içindeki varlıklar bu dosyanın bir kütüphanesi " +"haline gelir." +msgstr[1] "" +"Yayından kaldırırsanız, içindeki varlıklar bu dosyaların bir kütüphanesi " +"haline gelir." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "Bu kütüphaneyi yayından kaldırmak istediğinizden emin misiniz?" +msgstr[1] "Bu kütüphaneleri yayından kaldırmak istediğinizden emin misiniz?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "Bu dosyada kullanılıyor:" +msgstr[1] "Bu dosyalarda kullanılıyor:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Kütüphaneyi yayından kaldır" +msgstr[1] "Kütüphaneleri yayından kaldır" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" @@ -1870,6 +2011,56 @@ msgstr "Profil başarıyla kaydedildi!" msgid "notifications.validation-email-sent" msgstr "%s adresine doğrulama e-postası gönderildi. E-posta kutunuza bakın!" +msgid "onboarding-v2.before-start.desc1" +msgstr "" +"Kullanıcı Kılavuzu ve Youtube kanalımız gibi Penpot'u kullanmaya " +"başlamanıza yardımcı olacak birçok kaynak olduğunu bilmelisiniz." + +msgid "onboarding-v2.before-start.desc2" +msgstr "" +"Penpot'un nasıl kullanılacağı hakkında ayrıntılı bilgi. Prototiplemeden " +"tasarımları düzenlemeye veya paylaşmaya kadar." + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "Kullanıcı kılavuzu" + +msgid "onboarding-v2.before-start.desc3" +msgstr "Bizim ve topluluğumuz tarafından hazırlanan öğreticileri izleyebilirsiniz." + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "Video öğreticiler" + +msgid "onboarding-v2.before-start.title" +msgstr "Başlamadan önce" + +msgid "onboarding-v2.welcome.desc1" +msgstr "" +"Penpot açık kaynaklıdır ve Kaleidos'un yanı sıra birçok insanın birbirine " +"yardım ettiği topluluk tarafından yapılmıştır. Herkes işbirliğine " +"katılabilir:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "" +"Tüm topluluk ve Penpot çekirdek takımı ile Penpot, bugünü ve geleceği " +"hakkında bilgi edinmek, paylaşmak ve tartışmak için herkese açık bir alan." + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "Topluluğa Katılım" + +msgid "onboarding-v2.welcome.desc3" +msgstr "" +"Çeviriler, özellik istekleri, temel katkılar, hata avı ile nasıl işbirliği " +"yapacağınızı bulacağınız yer…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Katkıda bulunma kılavuzu" + +msgid "onboarding-v2.welcome.title" +msgstr "Penpot'a hoş geldiniz!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Daha sonra bir takım oluştur" + msgid "onboarding.choice.team-up.create-team" msgstr "Takımınızın adı" @@ -1882,12 +2073,20 @@ msgstr "Takımın adını girin" msgid "onboarding.choice.team-up.invite-members" msgstr "Üyeleri davet edin" +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Herkesi dahil etmeyi unutmayın. Geliştiriciler, tasarımcılar, " +"yöneticiler... çeşitlilik iyidir :)" + msgid "onboarding.choice.team-up.invite-members-skip" msgstr "Takım oluştur ve daha sonra davet et" msgid "onboarding.choice.team-up.invite-members-submit" msgstr "Takım oluştur ve davet gönder" +msgid "onboarding.choice.team-up.roles" +msgstr "Rol ile davet et:" + msgid "onboarding.choice.title" msgstr "Penpot'a Hoş Geldiniz" @@ -1998,6 +2197,29 @@ msgstr "Paylaşılan bir doğruluk kaynağı" msgid "onboarding.team-input-placeholder" msgstr "Yeni takım adı gir" +msgid "onboarding.team-modal.create-team" +msgstr "Bir takım oluştur" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "" +"Takım, aynı dosya ve projelerde çalışan diğer Penpot kullanıcılarıyla " +"işbirliği yapmanıza olanak tanır." + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "Sınırsız dosya ve proje" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "Çok oyunculu sürüm" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "Rol yönetimi" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "Sınırsız üye" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "%100 özgür!" + msgid "onboarding.team.create.button" msgstr "Takım oluştur" @@ -2724,6 +2946,9 @@ msgstr "Grup adı" msgid "workspace.assets.libraries" msgstr "Kütüphaneler" +msgid "workspace.assets.local-library" +msgstr "yerel kütüphane" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.not-found" msgstr "Varlık bulunmadı" @@ -3192,9 +3417,7 @@ msgstr "Seçimi dışa aktar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "1 ögeyi dışa aktar" -msgstr[1] "%s ögeyi dışa aktar" +msgstr "1 ögeyi dışa aktar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -3620,6 +3843,22 @@ msgstr "Seçili katmanlar" msgid "workspace.options.layout-item.advanced-ops" msgstr "Gelişmiş seçenekler" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "Azami Yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "Azami Genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "Asgari Yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "Asgari Genişlik" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.max-h" msgstr "Azami Yükseklik" @@ -3640,6 +3879,22 @@ msgstr "Asgari Genişlik" msgid "workspace.options.layout-item.title" msgstr "Öge yeniden boyutlandırma" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "Azami yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "Azami genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "Asgari yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "Asgari genişlik" + #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-h" msgstr "Azami yükseklik" @@ -4240,6 +4495,9 @@ msgstr "Yol" msgid "workspace.shape.menu.reset-overrides" msgstr "Geçersiz kılmaları sıfırla" +msgid "workspace.shape.menu.restore-main" +msgstr "Ana bileşeni geri yükle" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.select-layer" msgstr "Katman seç" @@ -4513,265 +4771,4 @@ msgid "workspace.updates.update" msgstr "Güncelle" msgid "workspace.viewport.click-to-close-path" -msgstr "Yolu kapatmak için tıklayın" - -msgid "common.publish" -msgstr "Yayınla" - -msgid "common.unpublish" -msgstr "Yayından kaldır" - -msgid "dashboard.libraries-and-templates" -msgstr "Kütüphaneler ve Şablonlar" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Daha fazlasını keşfedin ve nasıl katkıda bulunacağınızı öğrenin" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "Tamam" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "Dikkat" - -msgid "onboarding.choice.team-up.invite-members-info" -msgstr "" -"Herkesi dahil etmeyi unutmayın. Geliştiriciler, tasarımcılar, yöneticiler... " -"çeşitlilik iyidir :)" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "Dosya siliniyor" - -msgid "onboarding.choice.team-up.create-later" -msgstr "Daha sonra bir takım oluştur" - -msgid "onboarding.choice.team-up.roles" -msgstr "Rol ile davet et:" - -msgid "onboarding.team-modal.create-team" -msgstr "Bir takım oluştur" - -msgid "onboarding.team-modal.create-team-desc" -msgstr "" -"Takım, aynı dosya ve projelerde çalışan diğer Penpot kullanıcılarıyla " -"işbirliği yapmanıza olanak tanır." - -msgid "onboarding.team-modal.create-team-feature-1" -msgstr "Sınırsız dosya ve proje" - -msgid "onboarding-v2.welcome.desc2" -msgstr "" -"Tüm topluluk ve Penpot çekirdek takımı ile Penpot, bugünü ve geleceği " -"hakkında bilgi edinmek, paylaşmak ve tartışmak için herkese açık bir alan." - -msgid "onboarding-v2.welcome.desc3" -msgstr "" -"Çeviriler, özellik istekleri, temel katkılar, hata avı ile nasıl işbirliği " -"yapacağınızı bulacağınız yer…" - -msgid "onboarding-v2.welcome.desc1" -msgstr "" -"Penpot açık kaynaklıdır ve Kaleidos'un yanı sıra birçok insanın birbirine " -"yardım ettiği topluluk tarafından yapılmıştır. Herkes işbirliğine " -"katılabilir:" - -msgid "onboarding-v2.welcome.desc2.title" -msgstr "Topluluğa Katılım" - -msgid "onboarding-v2.before-start.desc1" -msgstr "" -"Kullanıcı Kılavuzu ve Youtube kanalımız gibi Penpot'u kullanmaya başlamanıza " -"yardımcı olacak birçok kaynak olduğunu bilmelisiniz." - -msgid "onboarding-v2.before-start.desc2" -msgstr "" -"Penpot'un nasıl kullanılacağı hakkında ayrıntılı bilgi. Prototiplemeden " -"tasarımları düzenlemeye veya paylaşmaya kadar." - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" -msgstr "Asgari Genişlik" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" -msgstr "Azami yükseklik" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" -msgstr "Asgari yükseklik" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" -msgstr "Asgari genişlik" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Takım yönetimi" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot takımlar içindir. Üyeleri projeler ve dosyalar üzerinde birlikte " -"çalışmaya davet edin" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Takım olun!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "" -"Bu uygulamalı öğretici ile biraz eğlenirken Penpot'taki temel bilgileri " -"öğrenin." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Öğreticiyi başlat" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Uygulamalı Öğretici" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "Penpot'ta bir gezintiye çıkın ve temel özelliklerini öğrenin." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Gezintiyi başlat" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "Arayüz İncelemesi" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "Şablon içe aktarılırken bir sorun oluştu. Şablon içe aktarılmadı." - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "Bu dosya zaten Bileşenler V2 etkinken kullanıldı." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Dosyayı sil" -msgstr[1] "Dosyaları sil" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "Üyeleri takıma davet et" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "Yayından kaldır" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "" -"Yayından kaldırırsanız, içindeki varlıklar bu dosyanın bir kütüphanesi " -"haline gelir." -msgstr[1] "" -"Yayından kaldırırsanız, içindeki varlıklar bu dosyaların bir kütüphanesi " -"haline gelir." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "Bu kütüphaneyi yayından kaldırmak istediğinizden emin misiniz?" -msgstr[1] "Bu kütüphaneleri yayından kaldırmak istediğinizden emin misiniz?" - -msgid "onboarding-v2.before-start.desc3" -msgstr "" -"Bizim ve topluluğumuz tarafından hazırlanan öğreticileri izleyebilirsiniz." - -msgid "onboarding-v2.before-start.desc3.title" -msgstr "Video öğreticiler" - -msgid "onboarding-v2.before-start.title" -msgstr "Başlamadan önce" - -msgid "onboarding-v2.welcome.desc3.title" -msgstr "Katkıda bulunma kılavuzu" - -msgid "onboarding-v2.welcome.title" -msgstr "Penpot'a hoş geldiniz!" - -msgid "onboarding.team-modal.create-team-feature-2" -msgstr "Çok oyunculu sürüm" - -msgid "onboarding.team-modal.create-team-feature-3" -msgstr "Rol yönetimi" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "Sınırsız üye" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "%100 özgür!" - -msgid "workspace.assets.local-library" -msgstr "yerel kütüphane" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "Kütüphaneyi Yayından Kaldır" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "Dosya siliniyor" -msgstr[1] "Dosyalar siliniyor" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Bu dosyada, şu dosyada kullanılmakta olan kütüphaneler var:" -msgstr[1] "Bu dosyada, şu dosyalarda kullanılmakta olan kütüphaneler var:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "Bu dosyayı silmek istediğinizden emin misiniz?" -msgstr[1] "Bu dosyaları silmek istediğinizden emin misiniz?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "Bu dosyalarda, şu dosyada kullanılmakta olan kütüphaneler var:" -msgstr[1] "Bu dosyalarda, şu dosyalarda kullanılmakta olan kütüphaneler var:" - -msgid "onboarding-v2.before-start.desc2.title" -msgstr "Kullanıcı kılavuzu" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.scd-message" -msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "Bu dosyada kullanılıyor:" -msgstr[1] "Bu dosyalarda kullanılıyor:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "Kütüphaneyi yayından kaldır" -msgstr[1] "Kütüphaneleri yayından kaldır" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" -msgstr "Asgari Yükseklik" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" -msgstr "Azami Genişlik" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" -msgstr "Azami Yükseklik" - -msgid "workspace.shape.menu.restore-main" -msgstr "Ana bileşeni geri yükle" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" -msgstr "Azami genişlik" +msgstr "Yolu kapatmak için tıklayın" \ No newline at end of file diff --git a/frontend/translations/ukr_UA.po b/frontend/translations/ukr_UA.po index f37ada8f3d..88b27f71a3 100644 --- a/frontend/translations/ukr_UA.po +++ b/frontend/translations/ukr_UA.po @@ -2,116 +2,16 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Denys M. \n" -"Language-Team: Ukrainian \n" +"Language-Team: Ukrainian " +"\n" "Language: ukr_UA\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=UTF-8\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n" -"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" +"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && " +"n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.14.1\n" -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.password-changed-successfully" -msgstr "Пароль успішно змінено" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.about-penpot" -msgstr "Про Penpot" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.newsletter-subscription" -msgstr "Я хочу підписатися на розсилку Penpot." - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code.selected.multiple" -msgstr "Виділено: %s" - -msgid "labels.content" -msgstr "Вміст" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.invalid-token-error" -msgstr "Невірний код відновлення." - -#: src/app/main/ui/workspace/comments.cljs -msgid "labels.all" -msgstr "Всі" - -msgid "labels.back" -msgstr "Назад" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.cancel" -msgstr "Відміна" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.community" -msgstr "Спільнота" - -msgid "labels.continue" -msgstr "Продовжити" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "labels.create" -msgstr "Створити" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.expired-invitation" -msgstr "Протерміновано" - -msgid "labels.export" -msgstr "Експорт" - -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.password" -msgstr "Пароль" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.description" -msgstr "Опис" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.width" -msgstr "Ширина" - -#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.radius" -msgstr "Радіус" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.subject" -msgstr "Тема" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.title" -msgstr "Електронна пошта" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.height" -msgstr "Висота" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.left" -msgstr "Зліва" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow" -msgstr "Тінь" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow.shorthand.blur" -msgstr "Р" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow.shorthand.offset-x" -msgstr "X" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow.shorthand.offset-y" -msgstr "Y" - #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" msgstr "Уже маєте аккаунт?" @@ -164,6 +64,18 @@ msgstr "OpenID" msgid "auth.new-password" msgstr "Введіть новий пароль" +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "Я хочу підписатися на розсилку Penpot." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "Невірний код відновлення." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-successfully" +msgstr "Пароль успішно змінено" + #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.password" msgstr "Пароль" @@ -172,10 +84,24 @@ msgstr "Пароль" msgid "auth.password-length-hint" msgstr "Щонайменше 8 символів" +msgid "common.publish" +msgstr "Опублікувати" + +#, fuzzy +msgid "common.share-link.current-tag" +msgstr "(поточне)" + +msgid "common.unpublish" +msgstr "Зняти з публікації" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Додати як Спільну Бібліотеку" +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(копія)" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.create-new-team" msgstr "+ Створити нову команду" @@ -183,9 +109,24 @@ msgstr "+ Створити нову команду" msgid "dashboard.download-binary-file" msgstr "Завантажити файл Penpot (.penpot)" +msgid "dashboard.draft-title" +msgstr "Чорновик" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Створити дублікат" + msgid "dashboard.export-multi" msgstr "Експорт файлів Penpot (%s)" +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Експорт" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "Бібліотеки" + #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.loading-files" msgstr "завантажую ваші файли…" @@ -201,14 +142,33 @@ msgstr "Перемістити файли (%s)" msgid "dashboard.move-to-other-team" msgstr "Перенести в іншу команду" +msgid "dashboard.options" +msgstr "Опції" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Закріпити/Відчепити" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Проекти" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.remove-shared" msgstr "Видалити Спільну Бібліотеку" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +msgstr "Пошук…" + #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.type-something" msgstr "Введіть для пошуку" +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "Електронна пошта" + #: src/app/main/ui/alert.cljs msgid "ds.alert-ok" msgstr "Ок" @@ -221,13 +181,25 @@ msgstr "Увага" msgid "ds.confirm-cancel" msgstr "Відміна" +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "Ок" + #: src/app/main/ui/auth/login.cljs msgid "errors.auth-provider-not-configured" msgstr "Провайдер для автентифікації не налаштований." -#: src/app/main/ui/confirm.cljs -msgid "ds.confirm-ok" -msgstr "Ок" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "Опис" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +msgstr "Тема" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "Електронна пошта" #: src/app/main/ui/handoff/attributes/blur.cljs msgid "handoff.attributes.blur" @@ -257,10 +229,26 @@ msgstr "Заливка" msgid "handoff.attributes.image.height" msgstr "Висота" +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Ширина" + #: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout" msgstr "Розміщення" +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Висота" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Зліва" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Радіус" + #: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.rotation" msgstr "Обертання" @@ -273,13 +261,136 @@ msgstr "Зверху" msgid "handoff.attributes.layout.width" msgstr "Ширина" +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Тінь" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.blur" +msgstr "Р" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.offset-x" +msgstr "X" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.offset-y" +msgstr "Y" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Центр" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Всередину" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Назовні" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Точковий" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Змішаний" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Немає" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Суцільний" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Товщина" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Текст" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Немає" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Перечеркнутий" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Підчеркнутий" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Який є" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Код" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Коло" + +msgid "handoff.tabs.code.selected.component" +msgstr "Компонент" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Крива" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Кадр" + +msgid "handoff.tabs.code.selected.group" +msgstr "Група" + +msgid "handoff.tabs.code.selected.image" +msgstr "Зображення" + +msgid "handoff.tabs.code.selected.mask" +msgstr "Маска" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "Виділено: %s" + +msgid "handoff.tabs.code.selected.path" +msgstr "Контур" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Прямокутник" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.text" +msgstr "Текст" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Інформація" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.about-penpot" +msgstr "Про Penpot" + +msgid "labels.accept" +msgstr "Прийняти" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.admin" msgstr "Адміністратор" +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.all" +msgstr "Всі" + msgid "labels.and" msgstr "і" +msgid "labels.back" +msgstr "Назад" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "Відміна" + msgid "labels.centered" msgstr "По центру" @@ -290,6 +401,27 @@ msgstr "Закрити" msgid "labels.comments" msgstr "Коментарі" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "Спільнота" + +msgid "labels.content" +msgstr "Вміст" + +msgid "labels.continue" +msgstr "Продовжити" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "labels.create" +msgstr "Створити" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.dashboard" +msgstr "Панель управління" + +msgid "labels.default" +msgstr "за умовчуванням" + #: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete" msgstr "Видалити" @@ -310,6 +442,16 @@ msgstr "Редактор" msgid "labels.email" msgstr "Електронна пошта" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Протерміновано" + +msgid "labels.export" +msgstr "Експорт" + +msgid "labels.font-variants" +msgstr "Стилі" + msgid "labels.fonts" msgstr "Шрифти" @@ -360,424 +502,9 @@ msgstr "або" msgid "labels.owner" msgstr "Власник" -msgid "handoff.tabs.code.selected.image" -msgstr "Зображення" - -msgid "labels.save" -msgstr "Зберегти" - -msgid "handoff.tabs.code.selected.frame" -msgstr "Кадр" - -msgid "handoff.tabs.code.selected.curve" -msgstr "Крива" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code" -msgstr "Код" - -msgid "handoff.tabs.code.selected.component" -msgstr "Компонент" - -msgid "handoff.tabs.code.selected.circle" -msgstr "Коло" - -msgid "handoff.tabs.code.selected.group" -msgstr "Група" - -#: src/app/main/ui/settings/feedback.cljs -msgid "labels.send" -msgstr "Надіслати" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.status" -msgstr "Статус" - -#: src/app/main/ui/settings/profile.cljs -msgid "labels.update" -msgstr "Оновити" - -#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs -msgid "labels.remove" -msgstr "Видалити" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.projects" -msgstr "Проекти" - -# SUBSECTIONS -msgid "shortcut-subsection.alignment" -msgstr "Вирівнювання" - -msgid "shortcut-subsection.edit" -msgstr "Редагувати" - -msgid "labels.recent" -msgstr "Нещодавні" - -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "labels.rename" -msgstr "Перейменувати" - -#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs -msgid "labels.retry" -msgstr "Повторити" - -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.settings" -msgstr "Налаштування" - -msgid "shortcut-subsection.general-viewer" -msgstr "Загальний" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.role" -msgstr "Роль" - -#: src/app/main/ui/settings/feedback.cljs -msgid "labels.sending" -msgstr "Надсилаю…" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.shared-libraries" -msgstr "Бібліотеки" - -msgid "labels.skip" -msgstr "Пропустити" - -msgid "labels.start" -msgstr "Почати" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.tutorials" -msgstr "Посібники" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.accept" -msgstr "Оновити" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.cancel" -msgstr "Відмінити" - -msgid "onboarding.welcome.alt" -msgstr "Penpot" - -# SECTIONS -msgid "shortcut-section.basics" -msgstr "Основи" - -#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs -msgid "settings.multiple" -msgstr "Змішаний" - -msgid "shortcut-section.dashboard" -msgstr "Панель управління" - -msgid "shortcut-section.viewer" -msgstr "Переглядач" - -msgid "shortcut-subsection.navigation-dashboard" -msgstr "Навігація" - -msgid "shortcut-subsection.path-editor" -msgstr "Контури" - -msgid "shortcut-section.workspace" -msgstr "Робоче поле" - -msgid "shortcut-subsection.general-dashboard" -msgstr "Загальний" - -msgid "shortcut-subsection.panels" -msgstr "Панелі" - -msgid "shortcuts.delete" -msgstr "Видалити" - -msgid "shortcut-subsection.navigation-viewer" -msgstr "Навігація" - -msgid "shortcut-subsection.navigation-workspace" -msgstr "Навігація" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.delete" -msgstr "Видалити" - -msgid "shortcuts.go-to-search" -msgstr "Пошук" - -msgid "shortcut-subsection.shape" -msgstr "Форми" - -msgid "shortcuts.cut" -msgstr "Вирізати" - -msgid "shortcut-subsection.tools" -msgstr "Інструменти" - -msgid "shortcuts.copy" -msgstr "Скопіювати" - -msgid "shortcuts.draw-curve" -msgstr "Крива" - -msgid "shortcuts.draw-frame" -msgstr "Рамка" - -msgid "shortcuts.draw-ellipse" -msgstr "Еліпс" - -msgid "shortcuts.draw-text" -msgstr "Текст" - -msgid "shortcuts.draw-path" -msgstr "Контур" - -msgid "shortcuts.draw-rect" -msgstr "Прямокутник" - -msgid "shortcuts.group" -msgstr "Група" - -msgid "shortcuts.or" -msgstr " або " - -msgid "shortcuts.duplicate" -msgstr "Дублікат" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.duplicate" -msgstr "Створити дуплікат" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.group" -msgstr "Група" - -msgid "shortcuts.escape" -msgstr "Відмінити" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.preferences" -msgstr "Налаштування" - -msgid "shortcuts.paste" -msgstr "Вставити" - -msgid "shortcuts.ungroup" -msgstr "Розбити групу" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.assets" -msgstr "Ресурси" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.colors" -msgstr "Кольори" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.libraries" -msgstr "Бібліотеки" - -msgid "workspace.assets.box-filter-graphics" -msgstr "Графіка" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.components" -msgstr "Компоненти" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.edit" -msgstr "Редагувати" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.graphics" -msgstr "Графіка" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.reset-zoom" -msgstr "Скинути" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.rename" -msgstr "Перейменувати" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.typography" -msgstr "Типографіка" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.file" -msgstr "Файл" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.shared" -msgstr "СПІЛЬНІ" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.edit" -msgstr "Редагувати" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.view" -msgstr "Вид" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.add" -msgstr "Додати" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.hsv" -msgstr "HSV" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.saved" -msgstr "Збережено" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.saving" -msgstr "Збереження" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.rgba" -msgstr "RGBA" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.libraries" -msgstr "БІБЛІОТЕКИ" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.library" -msgstr "БІБЛІОТЕКА" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs -msgid "workspace.options.component" -msgstr "Компонент" - -msgid "common.publish" -msgstr "Опублікувати" - -#, fuzzy -msgid "common.share-link.current-tag" -msgstr "(поточне)" - -msgid "common.unpublish" -msgstr "Зняти з публікації" - -#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs -msgid "dashboard.copy-suffix" -msgstr "(копія)" - -msgid "dashboard.draft-title" -msgstr "Чорновик" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate" -msgstr "Створити дублікат" - -#: src/app/main/ui/workspace/header.cljs -msgid "dashboard.export-shapes" -msgstr "Експорт" - -#: src/app/main/ui/dashboard/libraries.cljs -msgid "dashboard.libraries-title" -msgstr "Бібліотеки" - -msgid "dashboard.options" -msgstr "Опції" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.pin-unpin" -msgstr "Закріпити/Відчепити" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dashboard.projects-title" -msgstr "Проекти" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.search-placeholder" -msgstr "Пошук…" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.your-email" -msgstr "Електронна пошта" - -#, permanent -msgid "handoff.attributes.stroke.alignment.center" -msgstr "Центр" - -#, permanent -msgid "handoff.attributes.stroke.alignment.inner" -msgstr "Всередину" - -#, permanent -msgid "handoff.attributes.stroke.alignment.outer" -msgstr "Назовні" - -msgid "handoff.attributes.stroke.style.dotted" -msgstr "Точковий" - -msgid "handoff.attributes.stroke.style.mixed" -msgstr "Змішаний" - -msgid "handoff.attributes.stroke.style.none" -msgstr "Немає" - -msgid "handoff.attributes.stroke.style.solid" -msgstr "Суцільний" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke.width" -msgstr "Товщина" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography" -msgstr "Текст" - -msgid "handoff.attributes.typography.text-decoration.none" -msgstr "Немає" - -msgid "handoff.attributes.typography.text-decoration.strikethrough" -msgstr "Перечеркнутий" - -msgid "handoff.attributes.typography.text-decoration.underline" -msgstr "Підчеркнутий" - -msgid "handoff.attributes.typography.text-transform.none" -msgstr "Який є" - -msgid "handoff.tabs.code.selected.mask" -msgstr "Маска" - -msgid "handoff.tabs.code.selected.path" -msgstr "Контур" - -msgid "handoff.tabs.code.selected.rect" -msgstr "Прямокутник" - -msgid "handoff.tabs.code.selected.svg-raw" -msgstr "SVG" - -msgid "handoff.tabs.code.selected.text" -msgstr "Текст" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.info" -msgstr "Інформація" - -msgid "labels.accept" -msgstr "Прийняти" - -#: src/app/main/ui/settings/sidebar.cljs -msgid "labels.dashboard" -msgstr "Панель управління" - -msgid "labels.default" -msgstr "за умовчуванням" - -msgid "labels.font-variants" -msgstr "Стилі" +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Пароль" #: src/app/main/ui/dashboard/team.cljs msgid "labels.pending-invitation" @@ -791,6 +518,66 @@ msgstr "Дозволи" msgid "labels.profile" msgstr "Профіль" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.projects" +msgstr "Проекти" + +msgid "labels.recent" +msgstr "Нещодавні" + +#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.remove" +msgstr "Видалити" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.rename" +msgstr "Перейменувати" + +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +msgid "labels.retry" +msgstr "Повторити" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.role" +msgstr "Роль" + +msgid "labels.save" +msgstr "Зберегти" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.send" +msgstr "Надіслати" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.sending" +msgstr "Надсилаю…" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.settings" +msgstr "Налаштування" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "Бібліотеки" + +msgid "labels.skip" +msgstr "Пропустити" + +msgid "labels.start" +msgstr "Почати" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Статус" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Посібники" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +msgstr "Оновити" + msgid "labels.upload" msgstr "Завантаження" @@ -812,6 +599,68 @@ msgstr "(ви)" msgid "modals.unpublish-shared-confirm.accept" msgstr "Зняти з публікації" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.accept" +msgstr "Оновити" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.cancel" +msgstr "Відмінити" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "settings.multiple" +msgstr "Змішаний" + +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Основи" + +msgid "shortcut-section.dashboard" +msgstr "Панель управління" + +msgid "shortcut-section.viewer" +msgstr "Переглядач" + +msgid "shortcut-section.workspace" +msgstr "Робоче поле" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Вирівнювання" + +msgid "shortcut-subsection.edit" +msgstr "Редагувати" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Загальний" + +msgid "shortcut-subsection.general-viewer" +msgstr "Загальний" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Навігація" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Навігація" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Навігація" + +msgid "shortcut-subsection.panels" +msgstr "Панелі" + +msgid "shortcut-subsection.path-editor" +msgstr "Контури" + +msgid "shortcut-subsection.shape" +msgstr "Форми" + +msgid "shortcut-subsection.tools" +msgstr "Інструменти" + msgid "shortcut-subsection.zoom-viewer" msgstr "Масштабування" @@ -821,12 +670,60 @@ msgstr "Масштабування" msgid "shortcuts.add-comment" msgstr "Коментарі" +msgid "shortcuts.copy" +msgstr "Скопіювати" + +msgid "shortcuts.cut" +msgstr "Вирізати" + +msgid "shortcuts.delete" +msgstr "Видалити" + +msgid "shortcuts.draw-curve" +msgstr "Крива" + +msgid "shortcuts.draw-ellipse" +msgstr "Еліпс" + +msgid "shortcuts.draw-frame" +msgstr "Рамка" + +msgid "shortcuts.draw-path" +msgstr "Контур" + +msgid "shortcuts.draw-rect" +msgstr "Прямокутник" + +msgid "shortcuts.draw-text" +msgstr "Текст" + +msgid "shortcuts.duplicate" +msgstr "Дублікат" + +msgid "shortcuts.escape" +msgstr "Відмінити" + +msgid "shortcuts.go-to-search" +msgstr "Пошук" + +msgid "shortcuts.group" +msgstr "Група" + msgid "shortcuts.mask" msgstr "Маска" msgid "shortcuts.move" msgstr "Перемістити" +msgid "shortcuts.or" +msgstr " або " + +msgid "shortcuts.paste" +msgstr "Вставити" + +msgid "shortcuts.ungroup" +msgstr "Розбити групу" + msgid "viewer.breaking-change.message" msgstr "Упс!" @@ -838,6 +735,57 @@ msgstr "Інтеракції" msgid "viewer.header.sitemap" msgstr "Мапа сайту" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Ресурси" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Графіка" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Кольори" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Компоненти" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.delete" +msgstr "Видалити" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Створити дуплікат" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.edit" +msgstr "Редагувати" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Графіка" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Група" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Бібліотеки" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename" +msgstr "Перейменувати" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "СПІЛЬНІ" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Типографіка" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.assets.typography.font-id" msgstr "Шрифт" @@ -857,6 +805,54 @@ msgstr "Розгрупувати" msgid "workspace.focus.selection" msgstr "Вибір" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Редагувати" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Файл" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.preferences" +msgstr "Налаштування" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.view" +msgstr "Вид" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.reset-zoom" +msgstr "Скинути" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Збережено" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Збереження" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Додати" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.hsv" +msgstr "HSV" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "БІБЛІОТЕКИ" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "БІБЛІОТЕКА" + #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.update" msgstr "Оновити" @@ -877,3 +873,7 @@ msgstr "Шар" #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs msgid "workspace.options.blur-options.title" msgstr "Розмиття" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Компонент" \ No newline at end of file diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 3f3a0ca17e..39922ac455 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-20 13:46+0000\n" "Last-Translator: Semon Xue \n" -"Language-Team: Chinese (Simplified) \n" +"Language-Team: Chinese (Simplified) " +"\n" "Language: zh_CN\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -165,9 +165,21 @@ msgstr "创建账号意味着您认可我们的服务条例和隐私政策。" msgid "auth.verification-email-sent" msgstr "我们已经发送了一封验证邮件到" +msgid "common.publish" +msgstr "发布" + +msgid "common.share-link.all-users" +msgstr "所有Penpot用户" + msgid "common.share-link.confirm-deletion-link-description" msgstr "你确定要移除链接?那么任何人都无法再访问它" +msgid "common.share-link.current-tag" +msgstr "(当前)" + +msgid "common.share-link.destroy-link" +msgstr "去除链接" + msgid "common.share-link.get-link" msgstr "获取链接" @@ -177,21 +189,83 @@ msgstr "链接已复制" msgid "common.share-link.link-deleted-success" msgstr "链接已移除" +msgid "common.share-link.manage-ops" +msgstr "权限管理" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1页已共享" +msgstr[1] "%s页已共享" + msgid "common.share-link.permissions-can-access" msgstr "可访问" +msgid "common.share-link.permissions-can-comment" +msgstr "可评论" + +msgid "common.share-link.permissions-can-inspect" +msgstr "可审查代码" + msgid "common.share-link.permissions-can-view" msgstr "可浏览" msgid "common.share-link.permissions-hint" msgstr "任何人通过此链接都可访问" +msgid "common.share-link.permissions-pages" +msgstr "页面已共享" + msgid "common.share-link.placeholder" msgstr "可分享的链接会在此处显示" +msgid "common.share-link.team-members" +msgstr "只团队成员" + msgid "common.share-link.title" msgstr "分享原型" +msgid "common.share-link.view-all" +msgstr "选择所有" + +msgid "common.unpublish" +msgstr "未发布" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "团队管理" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "Penpot是为团队协作而设计,邀请成员合作处理项目和文件" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "组建团队!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "通过有趣的实践教程学习Penpot的基础知识。" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "开始教程" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "实践教程" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "浏览Penpot, 了解其主要功能。" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "开始浏览" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.title" +msgstr "界面浏览" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "添加为共享库" @@ -216,6 +290,12 @@ msgstr "你的Penpot" msgid "dashboard.delete-team" msgstr "删除团队" +msgid "dashboard.download-binary-file" +msgstr "下载Penpot文件 (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "下载标准文件(.svg + .json)" + msgid "dashboard.draft-title" msgstr "草稿" @@ -236,6 +316,9 @@ msgstr "暂无文档" msgid "dashboard.empty-placeholder-drafts" msgstr "现在尚无文件。如果你想尝试一些模板,请点击[库和模板](https://penpot.app/libraries-templates.html)" +msgid "dashboard.export-binary-multi" +msgstr "下载 %s Penpot文件 (.penpot)" + msgid "dashboard.export-frames" msgstr "导出画板到PDF" @@ -273,6 +356,9 @@ msgstr "导出已选中" msgid "dashboard.export-single" msgstr "导出文件" +msgid "dashboard.export-standard-multi" +msgstr "下载 %s 标准文件 (.svg + .json)" + msgid "dashboard.export.detail" msgstr "* 可能包含组件、图形、颜色和/或排版。" @@ -376,6 +462,15 @@ msgstr "邀请加入团队" msgid "dashboard.leave-team" msgstr "退出团队" +msgid "dashboard.libraries-and-templates" +msgstr "库和模板" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "探索更多内容,了解如何做出贡献" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "导入模板时发生错误。模板未导入成功。" + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "共享库" @@ -554,6 +649,10 @@ msgstr "搜索结果" msgid "dashboard.type-something" msgstr "输入关键词进行搜索" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.unpublish-shared" +msgstr "未发布库" + #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "保存设置" @@ -574,6 +673,14 @@ msgstr "你的姓名" msgid "dashboard.your-penpot" msgstr "你的Penpot" +#: src/app/main/ui/alert.cljs +msgid "ds.alert-ok" +msgstr "好" + +#: src/app/main/ui/alert.cljs +msgid "ds.alert-title" +msgstr "注意" + #: src/app/main/ui/confirm.cljs msgid "ds.component-subtitle" msgstr "待更新的组件:" @@ -594,6 +701,10 @@ msgstr "你确定?" msgid "ds.updated-at" msgstr "最后更新:%s" +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "没有配置身份认证服务源." + msgid "errors.auth.unable-to-login" msgstr "你似乎还没有登录或会话已过期" @@ -601,6 +712,10 @@ msgstr "你似乎还没有登录或会话已过期" msgid "errors.clipboard-not-implemented" msgstr "你的浏览器不支持该操作" +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.components-v2" +msgstr "此文件已经在组件V2版本下使用。" + #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" msgstr "电子邮件已被占用" @@ -724,6 +839,18 @@ msgstr "想说两句?来Gitter和我们聊聊" msgid "feedback.description" msgstr "描述" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-go-to" +msgstr "前往Penpot论坛" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-subtitle1" +msgstr "我们很欢迎你的到来。请在发布帮助请求前搜索你所需要的帮助内容。" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discourse-title" +msgstr "Penpot社区" + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" msgstr "话题" @@ -736,6 +863,18 @@ msgstr "请说明你发邮件的原因,详细说明这是一个问题反馈、 msgid "feedback.title" msgstr "电子邮件" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-go-to" +msgstr "前往Twtter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-subtitle1" +msgstr "这里可以帮助您解决技术问题。" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.twitter-title" +msgstr "Twtter支持帐号" + #: src/app/main/ui/settings/password.cljs msgid "generic.error" msgstr "发生了一个错误" @@ -1007,6 +1146,10 @@ msgstr "关闭" msgid "labels.comments" msgstr "评论" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.community" +msgstr "社区" + #: src/app/main/ui/settings/password.cljs msgid "labels.confirm-password" msgstr "确认密码" @@ -1020,6 +1163,9 @@ msgstr "继续" msgid "labels.continue-with" msgstr "继续" +msgid "labels.continue-with-penpot" +msgstr "你可以使用Penpot帐号继续" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "创建" @@ -1159,6 +1305,9 @@ msgstr "库&模板" msgid "labels.link" msgstr "链接" +msgid "labels.log-or-sign" +msgstr "登录或注册" + #: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.logout" msgstr "登出" @@ -1333,6 +1482,9 @@ msgstr "共享库" msgid "labels.show-all-comments" msgstr "显示所有评论" +msgid "labels.show-comments-list" +msgstr "显示评论列表" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.show-your-comments" msgstr "只显示你的评论" @@ -1519,6 +1671,40 @@ msgstr "你确定想要删除这个项目?" msgid "modals.delete-project-confirm.title" msgstr "删除项目" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "删除文件" +msgstr[1] "批量删除文件" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "你是否确认要删除这个文件?" +msgstr[1] "你是否确认要删除这些文件?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "此文件中使用了以下库:" +msgstr[1] "这些文件中使用了以下库:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-plural" +msgid_plural "modals.delete-shared-confirm.scd-message-plural" +msgstr[0] "这些文件中的库在此文件中使用:" +msgstr[1] "这些文件中的库在此批文件中使用:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "删除文件" +msgstr[1] "批量删除文件" + +#: src/app/main/ui/delete_shared.cljs +msgid "modals.delete-shared.title" +msgstr "正在删除文件" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "删除团队" @@ -1550,6 +1736,10 @@ msgstr "发送邀请" msgid "modals.invite-member.emails" msgstr "电子邮件,以逗号分隔" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-team-member.title" +msgstr "邀请成员加入团队" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" msgstr "由于你是这个团队的唯一成员,这个团队将连同其项目和文件一起被删除。" @@ -1629,6 +1819,34 @@ msgstr "不再将“%s”作为共享库" msgid "modals.small-nudge" msgstr "小幅微调" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgstr "取消发布" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "如果取消发布,则其中的资源将成为此文件的一个库。" +msgstr[1] "如果取消发布,则其中的资源将成为这些文件的一个库。" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.message" +msgid_plural "modals.unpublish-shared-confirm.message" +msgstr[0] "你是否确认取消发布这个库?" +msgstr[1] "你是否确认取消发布这些库?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message" +msgid_plural "modals.unpublish-shared-confirm.scd-message" +msgstr[0] "在这个文件中被使用:" +msgstr[1] "在这些文件中被使用:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "取消发布库" +msgstr[1] "批量取消发布库" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" msgstr "你即将更新共享库中的组件,这可能会影响使用这些组件的其他文档" @@ -1669,6 +1887,45 @@ msgstr "个人资料保存成功!" msgid "notifications.validation-email-sent" msgstr "验证邮件已发至%s。请检查电子邮箱!" +msgid "onboarding-v2.before-start.desc1" +msgstr "有很多资源可以帮助你开始使用Penpot,如用户指南和我们的Youtube频道。" + +msgid "onboarding-v2.before-start.desc2" +msgstr "有关如何使用Penpot的详细信息。从原型设计到组织或共享设计。" + +msgid "onboarding-v2.before-start.desc2.title" +msgstr "用户指南" + +msgid "onboarding-v2.before-start.desc3" +msgstr "您可以观看我们的官方教程以及社区制作的教程。" + +msgid "onboarding-v2.before-start.desc3.title" +msgstr "视频教程" + +msgid "onboarding-v2.before-start.title" +msgstr "在开始之前" + +msgid "onboarding-v2.welcome.desc1" +msgstr "Penpot是由Kaleidos及社区共同开发的开源软件,许多人已经在社区中互相帮助。每个人都可以通过以下方式进行协作:" + +msgid "onboarding-v2.welcome.desc2" +msgstr "一个与整个社区和Penpot核心团队学习、分享和讨论Penpot及其现在和未来的公共空间。" + +msgid "onboarding-v2.welcome.desc2.title" +msgstr "参与到社区中" + +msgid "onboarding-v2.welcome.desc3" +msgstr "在这里,您将了解如何协作进行翻译、功能需求提出、核心代码贡献、BUG修复等…" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "贡献指南" + +msgid "onboarding-v2.welcome.title" +msgstr "欢迎来到Penpot!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "稍后创建团队" + msgid "onboarding.choice.team-up.create-team" msgstr "团队名称" @@ -1681,12 +1938,18 @@ msgstr "输入团队名称" msgid "onboarding.choice.team-up.invite-members" msgstr "邀请成员" +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "记得将开发人员、设计师、经理……等各类人员都加进来:)" + msgid "onboarding.choice.team-up.invite-members-skip" msgstr "创建团队并稍后邀请" msgid "onboarding.choice.team-up.invite-members-submit" msgstr "创建团队并发送邀请" +msgid "onboarding.choice.team-up.roles" +msgstr "邀请角色:" + msgid "onboarding.choice.title" msgstr "欢迎来到Penpot" @@ -1777,6 +2040,27 @@ msgstr "获取并提供代码规范,如标记(SVG、HTML)或样式(CSS msgid "onboarding.slide.3.title" msgstr "一个共享的事实来源" +msgid "onboarding.team-modal.create-team" +msgstr "创建一个团队" + +msgid "onboarding.team-modal.create-team-desc" +msgstr "团队能够让你与其它Penpot用户协作处理相同的文件和项目。" + +msgid "onboarding.team-modal.create-team-feature-1" +msgstr "无限制的文件和项目" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "多用户编辑" + +msgid "onboarding.team-modal.create-team-feature-3" +msgstr "角色管理" + +msgid "onboarding.team-modal.create-team-feature-4" +msgstr "无限制成员" + +msgid "onboarding.team-modal.create-team-feature-5" +msgstr "完全免费!" + msgid "onboarding.templates.subtitle" msgstr "这里有一些模板。" @@ -1853,6 +2137,150 @@ msgstr "面板" msgid "shortcut-subsection.path-editor" msgstr "路径" +msgid "shortcut-subsection.shape" +msgstr "形状" + +msgid "shortcut-subsection.tools" +msgstr "工具" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "缩放" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "缩放" + +msgid "shortcuts.add-comment" +msgstr "评论" + +msgid "shortcuts.add-node" +msgstr "添加节点" + +msgid "shortcuts.align-bottom" +msgstr "底部对齐" + +msgid "shortcuts.align-hcenter" +msgstr "水平居中对齐" + +msgid "shortcuts.align-left" +msgstr "左对齐" + +msgid "shortcuts.align-right" +msgstr "右对齐" + +msgid "shortcuts.align-top" +msgstr "顶部对齐" + +msgid "shortcuts.align-vcenter" +msgstr "垂直居中对齐" + +msgid "shortcuts.artboard-selection" +msgstr "以所选内容创建画板" + +msgid "shortcuts.bool-difference" +msgstr "布尔差" + +msgid "shortcuts.bool-exclude" +msgstr "布尔排除" + +msgid "shortcuts.bool-intersection" +msgstr "布尔交集" + +msgid "shortcuts.bool-union" +msgstr "布尔合并" + +msgid "shortcuts.bring-back" +msgstr "移至最下层" + +msgid "shortcuts.bring-backward" +msgstr "移至下一层" + +msgid "shortcuts.bring-forward" +msgstr "移至上一层" + +msgid "shortcuts.bring-front" +msgstr "移至最上层" + +msgid "shortcuts.clear-undo" +msgstr "清除回退内容" + +msgid "shortcuts.copy" +msgstr "拷贝" + +msgid "shortcuts.create-component" +msgstr "创建组件" + +msgid "shortcuts.create-new-project" +msgstr "创建新的" + +msgid "shortcuts.cut" +msgstr "剪切" + +msgid "shortcuts.decrease-zoom" +msgstr "缩小" + +msgid "shortcuts.delete" +msgstr "删除" + +msgid "shortcuts.delete-node" +msgstr "删除节点" + +msgid "shortcuts.detach-component" +msgstr "拆分组件" + +msgid "shortcuts.draw-curve" +msgstr "曲线" + +msgid "shortcuts.draw-ellipse" +msgstr "椭圆" + +msgid "shortcuts.draw-frame" +msgstr "画板" + +msgid "shortcuts.draw-nodes" +msgstr "绘制路径" + +msgid "shortcuts.draw-path" +msgstr "路径" + +msgid "shortcuts.draw-rect" +msgstr "长方形" + +msgid "shortcuts.draw-text" +msgstr "文本" + +msgid "shortcuts.duplicate" +msgstr "复制" + +msgid "shortcuts.escape" +msgstr "取消" + +msgid "shortcuts.export-shapes" +msgstr "导出形状" + +msgid "shortcuts.fit-all" +msgstr "缩放至适应所有" + +msgid "shortcuts.flip-horizontal" +msgstr "水平翻转" + +msgid "shortcuts.flip-vertical" +msgstr "垂直翻转" + +msgid "shortcuts.go-to-drafts" +msgstr "前往草稿" + +msgid "shortcuts.go-to-libs" +msgstr "前往共享库" + +msgid "shortcuts.go-to-search" +msgstr "搜索" + +msgid "shortcuts.group" +msgstr "组" + +msgid "shortcuts.h-distribute" +msgstr "水平分布" + msgid "shortcuts.hide-ui" msgstr "显示/隐藏UI" @@ -1898,6 +2326,190 @@ msgstr "移动节点" msgid "shortcuts.move-unit-down" msgstr "向下移动" +msgid "shortcuts.move-unit-left" +msgstr "左移" + +msgid "shortcuts.move-unit-right" +msgstr "右移" + +msgid "shortcuts.move-unit-up" +msgstr "上移" + +msgid "shortcuts.next-frame" +msgstr "下个画板" + +msgid "shortcuts.not-found" +msgstr "没找到快捷方式" + +msgid "shortcuts.opacity-0" +msgstr "设置不透明度为100%" + +msgid "shortcuts.opacity-1" +msgstr "设置不透明度为10%" + +msgid "shortcuts.opacity-2" +msgstr "设置不透明度为20%" + +msgid "shortcuts.opacity-3" +msgstr "设置不透明度为30%" + +msgid "shortcuts.opacity-4" +msgstr "设置不透明度为40%" + +msgid "shortcuts.opacity-5" +msgstr "设置不透明度为50%" + +msgid "shortcuts.opacity-6" +msgstr "设置不透明度为60%" + +msgid "shortcuts.opacity-7" +msgstr "设置不透明度为70%" + +msgid "shortcuts.opacity-8" +msgstr "设置不透明度为80%" + +msgid "shortcuts.opacity-9" +msgstr "设置不透明度为90%" + +msgid "shortcuts.open-color-picker" +msgstr "色彩拾取器" + +msgid "shortcuts.open-comments" +msgstr "前往查阅者评论区" + +msgid "shortcuts.open-dashboard" +msgstr "前往看板" + +msgid "shortcuts.open-handoff" +msgstr "前往阅读器切换部分" + +msgid "shortcuts.open-interactions" +msgstr "转往阅读器交互部分" + +msgid "shortcuts.open-viewer" +msgstr "转往阅读器交互部分" + +msgid "shortcuts.open-workspace" +msgstr "前往工作区" + +msgid "shortcuts.or" +msgstr " 或 " + +msgid "shortcuts.paste" +msgstr "粘贴" + +msgid "shortcuts.prev-frame" +msgstr "前一画板" + +msgid "shortcuts.redo" +msgstr "重做" + +msgid "shortcuts.reset-zoom" +msgstr "重置缩放" + +msgid "shortcuts.search-placeholder" +msgstr "搜索快捷方式" + +msgid "shortcuts.select-all" +msgstr "选择所有" + +msgid "shortcuts.separate-nodes" +msgstr "分离节点" + +msgid "shortcuts.show-pixel-grid" +msgstr "显示/隐藏像素网格" + +msgid "shortcuts.show-shortcuts" +msgstr "显示/隐藏快捷方式" + +msgid "shortcuts.snap-nodes" +msgstr "对齐到节点" + +msgid "shortcuts.snap-pixel-grid" +msgstr "对齐像素网格" + +msgid "shortcuts.start-editing" +msgstr "启用编辑" + +msgid "shortcuts.start-measure" +msgstr "启用测量" + +msgid "shortcuts.stop-measure" +msgstr "停止测量" + +msgid "shortcuts.thumbnail-set" +msgstr "设置缩略图" + +#: src/app/main/ui/workspace/sidebar/shortcuts.cljs +msgid "shortcuts.title" +msgstr "快捷键" + +msgid "shortcuts.toggle-alignment" +msgstr "切换动态对齐" + +msgid "shortcuts.toggle-assets" +msgstr "切换资产" + +msgid "shortcuts.toggle-colorpalette" +msgstr "切换调色板" + +msgid "shortcuts.toggle-focus-mode" +msgstr "切换焦点模式" + +msgid "shortcuts.toggle-grid" +msgstr "显示/隐藏网格" + +msgid "shortcuts.toggle-history" +msgstr "切换历史" + +msgid "shortcuts.toggle-layers" +msgstr "切换层" + +msgid "shortcuts.toggle-lock" +msgstr "锁定所选" + +msgid "shortcuts.toggle-lock-size" +msgstr "锁定比例" + +msgid "shortcuts.toggle-rules" +msgstr "显示/隐藏规则" + +msgid "shortcuts.toggle-scale-text" +msgstr "切换缩放文本" + +msgid "shortcuts.toggle-snap-grid" +msgstr "网络对齐" + +msgid "shortcuts.toggle-snap-guide" +msgstr "辅助线对齐" + +msgid "shortcuts.toggle-textpalette" +msgstr "切换文本调色板" + +msgid "shortcuts.toggle-visibility" +msgstr "切换可见度" + +msgid "shortcuts.toggle-zoom-style" +msgstr "切换缩放样式" + +msgid "shortcuts.toogle-fullscreen" +msgstr "切换全屏" + +msgid "shortcuts.undo" +msgstr "回退" + +msgid "shortcuts.ungroup" +msgstr "取消组合" + +msgid "shortcuts.unmask" +msgstr "取消遮罩" + +msgid "shortcuts.v-distribute" +msgstr "垂直分布" + +msgid "shortcuts.zoom-selected" +msgstr "缩放到选定对象" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -2128,6 +2740,9 @@ msgstr "组名" msgid "workspace.assets.libraries" msgstr "库" +msgid "workspace.assets.local-library" +msgstr "本地库" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.not-found" msgstr "未找到素材" @@ -2283,6 +2898,10 @@ msgstr "编辑" msgid "workspace.header.menu.option.file" msgstr "文件" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.help-info" +msgstr "帮助和信息" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.option.preferences" msgstr "首选项" @@ -2346,6 +2965,10 @@ msgstr "预览模式(%s)" msgid "workspace.header.zoom-fill" msgstr "填充 - 填充比例" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-fit" +msgstr "适合 - 缩小以适合" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.zoom-fit-all" msgstr "缩放以适应所有" @@ -2354,6 +2977,10 @@ msgstr "缩放以适应所有" msgid "workspace.header.zoom-full-screen" msgstr "全屏" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.zoom-selected" +msgstr "缩放到选定的位置" + #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.add" msgstr "添加" @@ -2496,6 +3123,9 @@ msgstr "选项模糊" msgid "workspace.options.canvas-background" msgstr "画布背景" +msgid "workspace.options.clip-content" +msgstr "剪辑内容" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs msgid "workspace.options.component" msgstr "组件" @@ -2554,14 +3184,16 @@ msgstr "导出已选择" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "导出 1 个元素" -msgstr[1] "导出 %s 个元素" +msgstr "导出 1 个元素" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" msgstr "后缀" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "导出完成" + #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "workspace.options.exporting-object" msgstr "正在导出…" @@ -2578,6 +3210,18 @@ msgstr "导出速度意外缓慢" msgid "workspace.options.fill" msgstr "填充" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.add-flow-start" +msgstr "增加流程起点" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-start" +msgstr "流程起点" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.flows.flow-starts" +msgstr "所有流程起点" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.auto" msgstr "自动" @@ -2586,6 +3230,10 @@ msgstr "自动" msgid "workspace.options.grid.column" msgstr "列" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "网格" + msgid "workspace.options.grid.params.color" msgstr "颜色" @@ -2712,6 +3360,10 @@ msgstr "点击外部时关闭" msgid "workspace.options.interaction-close-overlay" msgstr "关闭覆盖" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-close-overlay-dest" +msgstr "关闭遮罩层: %s" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-delay" msgstr "延迟" @@ -2720,6 +3372,10 @@ msgstr "延迟" msgid "workspace.options.interaction-destination" msgstr "目的地" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-duration" +msgstr "持续时间" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-easing" msgstr "平滑" @@ -2948,6 +3604,158 @@ msgstr "图层成组" msgid "workspace.options.layer-options.title.multiple" msgstr "已选中的图层" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.advanced-ops" +msgstr "高级选项" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-h" +msgstr "最大高度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-max-w" +msgstr "最大宽度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-h" +msgstr "最小高度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.layout-min-w" +msgstr "最小宽度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title" +msgstr "调整大小" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-h" +msgstr "最大高度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-max-w" +msgstr "最大宽度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-h" +msgstr "最小高度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout-item.title.layout-min-w" +msgstr "最小宽度" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "底部" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.bottom" +msgstr "列" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.left" +msgstr "行" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.right" +msgstr "倒排行" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.direction.top" +msgstr "倒排列" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.gap" +msgstr "差距" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.center" +msgstr "居中" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.left" +msgstr "居左" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.h.right" +msgstr "居右" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "左" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "外边距" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-all" +msgstr "所有方向" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin-simple" +msgstr "简易外边距" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.no-wrap" +msgstr "不换行" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding" +msgstr "内边距" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-all" +msgstr "所有方向" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.padding-simple" +msgstr "简易内边距" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "右" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-around" +msgstr "周围留空" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.space-between" +msgstr "间隔留空" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.title" +msgstr "布局" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "顶部" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.bottom" +msgstr "底部" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.center" +msgstr "居中" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.v.top" +msgstr "顶部" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.wrap" +msgstr "底部" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-colors" +msgstr "更多颜色" + +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.more-lib-colors" +msgstr "更多共享库颜色" + msgid "workspace.options.opacity" msgstr "不透明度" @@ -2992,6 +3800,10 @@ msgstr "选择一个形状、画板或编组,拖至另一个画板,以创建 msgid "workspace.options.select-artboard" msgstr "选择画板" +#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs +msgid "workspace.options.selection-color" +msgstr "已选颜色" + #: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs msgid "workspace.options.selection-fill" msgstr "选项填充" @@ -3043,6 +3855,9 @@ msgstr "选项阴影" msgid "workspace.options.show-fill-on-export" msgstr "在导出中显示" +msgid "workspace.options.show-in-viewer" +msgstr "在预览模式显示" + #: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs, src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.size" msgstr "尺寸" @@ -3063,6 +3878,36 @@ msgstr "圆形标记" msgid "workspace.options.stroke-cap.diamond-marker" msgstr "钻石标记" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.line-arrow" +msgstr "箭头" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "无边框" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.round" +msgstr "圆头" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square" +msgstr "方头" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.square-marker" +msgstr "方形标记" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.triangle-arrow" +msgstr "三角箭头" + +msgid "workspace.options.stroke-color" +msgstr "线条颜色" + +msgid "workspace.options.stroke-width" +msgstr "线宽" + #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke.center" msgstr "居中" @@ -3204,6 +4049,15 @@ msgstr "垂直对齐" msgid "workspace.options.use-play-button" msgstr "点击页面顶端的播放按钮预览原型。" +msgid "workspace.options.width" +msgstr "宽度" + +msgid "workspace.options.x" +msgstr "X" + +msgid "workspace.options.y" +msgstr "Y" + msgid "workspace.path.actions.add-node" msgstr "添加节点(%s)" @@ -3216,6 +4070,12 @@ msgstr "绘制节点(%s)" msgid "workspace.path.actions.join-nodes" msgstr "连接节点(%s)" +msgid "workspace.path.actions.make-corner" +msgstr "转锐角 (%s)" + +msgid "workspace.path.actions.make-curve" +msgstr "转圆角 (%s)" + msgid "workspace.path.actions.merge-nodes" msgstr "合并节点(%s)" @@ -3225,6 +4085,9 @@ msgstr "移动节点(%s)" msgid "workspace.path.actions.separate-nodes" msgstr "拆分节点(%s)" +msgid "workspace.path.actions.snap-nodes" +msgstr "对接节点 (%s)" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.back" msgstr "移至底层" @@ -3237,6 +4100,10 @@ msgstr "向下移动一层" msgid "workspace.shape.menu.copy" msgstr "复制" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-artboard-from-selection" +msgstr "转为画板" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.create-component" msgstr "创建组件" @@ -3249,10 +4116,21 @@ msgstr "剪切" msgid "workspace.shape.menu.delete" msgstr "删除" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete-flow-start" +msgstr "删除流程起点" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.detach-instance" msgstr "解绑实例" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.detach-instances-in-bulk" +msgstr "解绑实例" + +msgid "workspace.shape.menu.difference" +msgstr "差集" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.duplicate" msgstr "创建副本" @@ -3261,6 +4139,12 @@ msgstr "创建副本" msgid "workspace.shape.menu.edit" msgstr "编辑" +msgid "workspace.shape.menu.exclude" +msgstr "相减" + +msgid "workspace.shape.menu.flatten" +msgstr "展平" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.flip-horizontal" msgstr "水平翻转" @@ -3269,6 +4153,10 @@ msgstr "水平翻转" msgid "workspace.shape.menu.flip-vertical" msgstr "垂直翻转" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flow-start" +msgstr "流程起点" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.forward" msgstr "向上移动一层" @@ -3289,6 +4177,12 @@ msgstr "编组" msgid "workspace.shape.menu.hide" msgstr "隐藏" +msgid "workspace.shape.menu.hide-ui" +msgstr "显示/隐藏界面" + +msgid "workspace.shape.menu.intersection" +msgstr "差集" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.lock" msgstr "锁定" @@ -3301,10 +4195,20 @@ msgstr "蒙板" msgid "workspace.shape.menu.paste" msgstr "粘贴" +msgid "workspace.shape.menu.path" +msgstr "路径" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.reset-overrides" msgstr "还原自定义选项" +msgid "workspace.shape.menu.restore-main" +msgstr "恢复主要组件" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.select-layer" +msgstr "选择图层" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.show" msgstr "显示" @@ -3313,10 +4217,22 @@ msgstr "显示" msgid "workspace.shape.menu.show-main" msgstr "显示主组件" +msgid "workspace.shape.menu.thumbnail-remove" +msgstr "移除缩略图" + +msgid "workspace.shape.menu.thumbnail-set" +msgstr "设为缩略图" + +msgid "workspace.shape.menu.transform-to-path" +msgstr "转换为路径" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.ungroup" msgstr "取消编组" +msgid "workspace.shape.menu.union" +msgstr "相加" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.unlock" msgstr "取消锁定" @@ -3325,6 +4241,10 @@ msgstr "取消锁定" msgid "workspace.shape.menu.unmask" msgstr "取消蒙版" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.update-components-in-bulk" +msgstr "更新主要组件" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.update-main" msgstr "更新主组件" @@ -3337,6 +4257,30 @@ msgstr "历史(%s)" msgid "workspace.sidebar.layers" msgstr "图层" +msgid "workspace.sidebar.layers.components" +msgstr "组件" + +msgid "workspace.sidebar.layers.frames" +msgstr "画板" + +msgid "workspace.sidebar.layers.groups" +msgstr "编组" + +msgid "workspace.sidebar.layers.images" +msgstr "图片" + +msgid "workspace.sidebar.layers.masks" +msgstr "遮罩" + +msgid "workspace.sidebar.layers.search" +msgstr "搜索图层" + +msgid "workspace.sidebar.layers.shapes" +msgstr "形状" + +msgid "workspace.sidebar.layers.texts" +msgstr "文本" + #: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs msgid "workspace.sidebar.options.svg-attrs.title" msgstr "已导入SVG属性" @@ -3389,6 +4333,10 @@ msgstr "路径(%s)" msgid "workspace.toolbar.rect" msgstr "矩形(%s)" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.shortcuts" +msgstr "快捷键 (%s)" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.text" msgstr "文本(%s)" @@ -3525,956 +4473,4 @@ msgid "workspace.updates.update" msgstr "更新" msgid "workspace.viewport.click-to-close-path" -msgstr "单击以闭合路径" - -msgid "shortcuts.clear-undo" -msgstr "清除回退内容" - -msgid "common.share-link.all-users" -msgstr "所有Penpot用户" - -msgid "common.share-link.current-tag" -msgstr "(当前)" - -msgid "common.share-link.destroy-link" -msgstr "去除链接" - -msgid "common.share-link.permissions-can-comment" -msgstr "可评论" - -msgid "common.share-link.page-shared" -msgid_plural "common.share-link.page-shared" -msgstr[0] "1页已共享" -msgstr[1] "%s页已共享" - -msgid "common.share-link.manage-ops" -msgstr "权限管理" - -msgid "common.share-link.team-members" -msgstr "只团队成员" - -msgid "common.share-link.view-all" -msgstr "选择所有" - -msgid "common.share-link.permissions-pages" -msgstr "页面已共享" - -msgid "dashboard.download-binary-file" -msgstr "下载Penpot文件 (.penpot)" - -msgid "dashboard.export-binary-multi" -msgstr "下载 %s Penpot文件 (.penpot)" - -msgid "dashboard.export-standard-multi" -msgstr "下载 %s 标准文件 (.svg + .json)" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-subtitle1" -msgstr "我们很欢迎你的到来。请在发布帮助请求前搜索你所需要的帮助内容。" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-go-to" -msgstr "前往Penpot论坛" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discourse-title" -msgstr "Penpot社区" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-go-to" -msgstr "前往Twtter" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-subtitle1" -msgstr "这里可以帮助您解决技术问题。" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.twitter-title" -msgstr "Twtter支持帐号" - -msgid "labels.continue-with-penpot" -msgstr "你可以使用Penpot帐号继续" - -msgid "labels.log-or-sign" -msgstr "登录或注册" - -msgid "labels.show-comments-list" -msgstr "显示评论列表" - -msgid "shortcuts.add-comment" -msgstr "评论" - -msgid "shortcuts.add-node" -msgstr "添加节点" - -msgid "shortcuts.align-bottom" -msgstr "底部对齐" - -msgid "shortcuts.align-hcenter" -msgstr "水平居中对齐" - -msgid "shortcuts.align-left" -msgstr "左对齐" - -msgid "shortcuts.align-top" -msgstr "顶部对齐" - -msgid "shortcuts.align-vcenter" -msgstr "垂直居中对齐" - -msgid "shortcuts.artboard-selection" -msgstr "以所选内容创建画板" - -msgid "shortcuts.bool-difference" -msgstr "布尔差" - -msgid "shortcuts.bool-exclude" -msgstr "布尔排除" - -msgid "shortcuts.bool-intersection" -msgstr "布尔交集" - -msgid "shortcuts.bool-union" -msgstr "布尔合并" - -msgid "shortcuts.bring-forward" -msgstr "移至上一层" - -msgid "shortcuts.bring-front" -msgstr "移至最上层" - -msgid "shortcuts.bring-backward" -msgstr "移至下一层" - -msgid "shortcuts.create-component" -msgstr "创建组件" - -msgid "shortcuts.next-frame" -msgstr "下个画板" - -msgid "shortcuts.not-found" -msgstr "没找到快捷方式" - -msgid "shortcuts.opacity-9" -msgstr "设置不透明度为90%" - -msgid "shortcuts.or" -msgstr " 或 " - -msgid "common.publish" -msgstr "发布" - -msgid "common.unpublish" -msgstr "未发布" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "团队管理" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-ok" -msgstr "好" - -#: src/app/main/ui/alert.cljs -msgid "ds.alert-title" -msgstr "注意" - -#: src/app/main/ui/auth/login.cljs -msgid "errors.auth-provider-not-configured" -msgstr "没有配置身份认证服务源." - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.community" -msgstr "社区" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-team-member.title" -msgstr "邀请成员加入团队" - -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "正在删除文件" - -msgid "onboarding-v2.before-start.desc1" -msgstr "有很多资源可以帮助你开始使用Penpot,如用户指南和我们的Youtube频道。" - -msgid "shortcut-subsection.shape" -msgstr "形状" - -msgid "shortcut-subsection.tools" -msgstr "工具" - -msgid "shortcut-subsection.zoom-viewer" -msgstr "缩放" - -msgid "shortcut-subsection.zoom-workspace" -msgstr "缩放" - -msgid "shortcuts.align-right" -msgstr "右对齐" - -msgid "shortcuts.bring-back" -msgstr "移至最下层" - -msgid "shortcuts.cut" -msgstr "剪切" - -msgid "shortcuts.decrease-zoom" -msgstr "缩小" - -msgid "shortcuts.delete" -msgstr "删除" - -msgid "shortcuts.delete-node" -msgstr "删除节点" - -msgid "shortcuts.detach-component" -msgstr "拆分组件" - -msgid "shortcuts.draw-curve" -msgstr "曲线" - -msgid "shortcuts.draw-ellipse" -msgstr "椭圆" - -msgid "shortcuts.draw-frame" -msgstr "画板" - -msgid "shortcuts.move-unit-up" -msgstr "上移" - -msgid "shortcuts.open-interactions" -msgstr "转往阅读器交互部分" - -msgid "shortcuts.open-handoff" -msgstr "前往阅读器切换部分" - -msgid "shortcuts.open-viewer" -msgstr "转往阅读器交互部分" - -msgid "shortcuts.open-workspace" -msgstr "前往工作区" - -msgid "shortcuts.paste" -msgstr "粘贴" - -msgid "shortcuts.prev-frame" -msgstr "前一画板" - -msgid "shortcuts.redo" -msgstr "重做" - -msgid "shortcuts.reset-zoom" -msgstr "重置缩放" - -msgid "shortcuts.search-placeholder" -msgstr "搜索快捷方式" - -msgid "shortcuts.show-pixel-grid" -msgstr "显示/隐藏像素网格" - -msgid "shortcuts.show-shortcuts" -msgstr "显示/隐藏快捷方式" - -msgid "shortcuts.snap-nodes" -msgstr "对齐到节点" - -msgid "common.share-link.permissions-can-inspect" -msgstr "可审查代码" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "组建团队!" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "实践教程" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "浏览Penpot, 了解其主要功能。" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "开始浏览" - -msgid "dashboard.download-standard-file" -msgstr "下载标准文件(.svg + .json)" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.title" -msgstr "界面浏览" - -msgid "dashboard.libraries-and-templates" -msgstr "库和模板" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "导入模板时发生错误。模板未导入成功。" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.unpublish-shared" -msgstr "未发布库" - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "此文件已经在组件V2版本下使用。" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "你是否确认要删除这个文件?" -msgstr[1] "你是否确认要删除这些文件?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "此文件中使用了以下库:" -msgstr[1] "这些文件中使用了以下库:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "删除文件" -msgstr[1] "批量删除文件" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "如果取消发布,则其中的资源将成为此文件的一个库。" -msgstr[1] "如果取消发布,则其中的资源将成为这些文件的一个库。" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.message" -msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "你是否确认取消发布这个库?" -msgstr[1] "你是否确认取消发布这些库?" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.scd-message" -msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "在这个文件中被使用:" -msgstr[1] "在这些文件中被使用:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "取消发布库" -msgstr[1] "批量取消发布库" - -msgid "onboarding-v2.before-start.desc2" -msgstr "有关如何使用Penpot的详细信息。从原型设计到组织或共享设计。" - -msgid "onboarding-v2.before-start.desc2.title" -msgstr "用户指南" - -msgid "onboarding-v2.before-start.desc3" -msgstr "您可以观看我们的官方教程以及社区制作的教程。" - -msgid "onboarding-v2.before-start.desc3.title" -msgstr "视频教程" - -msgid "onboarding-v2.before-start.title" -msgstr "在开始之前" - -msgid "onboarding-v2.welcome.desc2" -msgstr "一个与整个社区和Penpot核心团队学习、分享和讨论Penpot及其现在和未来的公共空间" -"。" - -msgid "onboarding-v2.welcome.desc2.title" -msgstr "参与到社区中" - -msgid "onboarding-v2.welcome.desc3" -msgstr "在这里,您将了解如何协作进行翻译、功能需求提出、核心代码贡献、BUG修复等…" - -msgid "onboarding.choice.team-up.create-later" -msgstr "稍后创建团队" - -msgid "onboarding.team-modal.create-team" -msgstr "创建一个团队" - -msgid "onboarding.team-modal.create-team-desc" -msgstr "团队能够让你与其它Penpot用户协作处理相同的文件和项目。" - -msgid "onboarding.team-modal.create-team-feature-1" -msgstr "无限制的文件和项目" - -msgid "onboarding.team-modal.create-team-feature-4" -msgstr "无限制成员" - -msgid "onboarding.team-modal.create-team-feature-5" -msgstr "完全免费!" - -msgid "onboarding-v2.welcome.desc3.title" -msgstr "贡献指南" - -msgid "onboarding.choice.team-up.roles" -msgstr "邀请角色:" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "开始教程" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "Penpot是为团队协作而设计,邀请成员合作处理项目和文件" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "通过有趣的实践教程学习Penpot的基础知识。" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "探索更多内容,了解如何做出贡献" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "删除文件" -msgstr[1] "批量删除文件" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "这些文件中的库在此文件中使用:" -msgstr[1] "这些文件中的库在此批文件中使用:" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "取消发布" - -msgid "onboarding-v2.welcome.desc1" -msgstr "Penpot是由Kaleidos及社区共同开发的开源软件,许多人已经在社区中互相帮助。每个" -"人都可以通过以下方式进行协作:" - -msgid "onboarding.choice.team-up.invite-members-info" -msgstr "记得将开发人员、设计师、经理……等各类人员都加进来:)" - -msgid "onboarding-v2.welcome.title" -msgstr "欢迎来到Penpot!" - -msgid "onboarding.team-modal.create-team-feature-2" -msgstr "多用户编辑" - -msgid "onboarding.team-modal.create-team-feature-3" -msgstr "角色管理" - -msgid "shortcuts.copy" -msgstr "拷贝" - -msgid "shortcuts.create-new-project" -msgstr "创建新的" - -msgid "shortcuts.escape" -msgstr "取消" - -msgid "shortcuts.fit-all" -msgstr "缩放至适应所有" - -msgid "shortcuts.go-to-libs" -msgstr "前往共享库" - -msgid "shortcuts.draw-nodes" -msgstr "绘制路径" - -msgid "shortcuts.draw-path" -msgstr "路径" - -msgid "shortcuts.draw-rect" -msgstr "长方形" - -msgid "shortcuts.draw-text" -msgstr "文本" - -msgid "shortcuts.flip-horizontal" -msgstr "水平翻转" - -msgid "shortcuts.flip-vertical" -msgstr "垂直翻转" - -msgid "shortcuts.go-to-drafts" -msgstr "前往草稿" - -msgid "shortcuts.go-to-search" -msgstr "搜索" - -msgid "shortcuts.group" -msgstr "组" - -msgid "shortcuts.h-distribute" -msgstr "水平分布" - -msgid "shortcuts.opacity-3" -msgstr "设置不透明度为30%" - -msgid "shortcuts.open-color-picker" -msgstr "色彩拾取器" - -msgid "shortcuts.duplicate" -msgstr "复制" - -msgid "shortcuts.opacity-2" -msgstr "设置不透明度为20%" - -msgid "shortcuts.opacity-5" -msgstr "设置不透明度为50%" - -msgid "shortcuts.opacity-4" -msgstr "设置不透明度为40%" - -msgid "shortcuts.opacity-6" -msgstr "设置不透明度为60%" - -msgid "shortcuts.export-shapes" -msgstr "导出形状" - -msgid "shortcuts.move-unit-right" -msgstr "右移" - -msgid "shortcuts.opacity-0" -msgstr "设置不透明度为100%" - -msgid "shortcuts.opacity-8" -msgstr "设置不透明度为80%" - -msgid "shortcuts.move-unit-left" -msgstr "左移" - -msgid "shortcuts.opacity-1" -msgstr "设置不透明度为10%" - -msgid "shortcuts.opacity-7" -msgstr "设置不透明度为70%" - -msgid "shortcuts.open-comments" -msgstr "前往查阅者评论区" - -msgid "shortcuts.open-dashboard" -msgstr "前往看板" - -msgid "shortcuts.select-all" -msgstr "选择所有" - -msgid "shortcuts.separate-nodes" -msgstr "分离节点" - -msgid "shortcuts.snap-pixel-grid" -msgstr "对齐像素网格" - -msgid "shortcuts.stop-measure" -msgstr "停止测量" - -msgid "shortcuts.start-editing" -msgstr "启用编辑" - -msgid "shortcuts.start-measure" -msgstr "启用测量" - -msgid "shortcuts.thumbnail-set" -msgstr "设置缩略图" - -#: src/app/main/ui/workspace/sidebar/shortcuts.cljs -msgid "shortcuts.title" -msgstr "快捷键" - -msgid "shortcuts.toggle-assets" -msgstr "切换资产" - -msgid "shortcuts.toggle-alignment" -msgstr "切换动态对齐" - -msgid "shortcuts.zoom-selected" -msgstr "缩放到选定对象" - -msgid "shortcuts.ungroup" -msgstr "取消组合" - -msgid "shortcuts.unmask" -msgstr "取消遮罩" - -msgid "shortcuts.v-distribute" -msgstr "垂直分布" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.help-info" -msgstr "帮助和信息" - -msgid "shortcuts.toggle-colorpalette" -msgstr "切换调色板" - -msgid "shortcuts.toggle-focus-mode" -msgstr "切换焦点模式" - -msgid "shortcuts.toggle-grid" -msgstr "显示/隐藏网格" - -msgid "shortcuts.toggle-history" -msgstr "切换历史" - -msgid "shortcuts.toggle-layers" -msgstr "切换层" - -msgid "shortcuts.toggle-lock" -msgstr "锁定所选" - -msgid "shortcuts.toggle-lock-size" -msgstr "锁定比例" - -msgid "shortcuts.toggle-rules" -msgstr "显示/隐藏规则" - -msgid "shortcuts.toggle-scale-text" -msgstr "切换缩放文本" - -msgid "shortcuts.toggle-snap-grid" -msgstr "网络对齐" - -msgid "shortcuts.toggle-snap-guide" -msgstr "辅助线对齐" - -msgid "shortcuts.toggle-textpalette" -msgstr "切换文本调色板" - -msgid "shortcuts.toggle-visibility" -msgstr "切换可见度" - -msgid "shortcuts.toggle-zoom-style" -msgstr "切换缩放样式" - -msgid "shortcuts.toogle-fullscreen" -msgstr "切换全屏" - -msgid "shortcuts.undo" -msgstr "回退" - -msgid "workspace.assets.local-library" -msgstr "本地库" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.zoom-fit" -msgstr "适合 - 缩小以适合" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-duration" -msgstr "持续时间" - -msgid "workspace.options.stroke-width" -msgstr "线宽" - -msgid "workspace.shape.menu.union" -msgstr "相加" - -msgid "workspace.options.width" -msgstr "宽度" - -msgid "workspace.options.x" -msgstr "X" - -msgid "workspace.path.actions.make-curve" -msgstr "转圆角 (%s)" - -msgid "workspace.shape.menu.difference" -msgstr "差集" - -msgid "workspace.path.actions.snap-nodes" -msgstr "对接节点 (%s)" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" -msgstr "最大宽度" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" -msgstr "最小高度" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" -msgstr "最大高度" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" -msgstr "最小宽度" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" -msgstr "最小宽度" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-complete" -msgstr "导出完成" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" -msgstr "最大高度" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" -msgstr "最大宽度" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" -msgstr "最小高度" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.create-artboard-from-selection" -msgstr "转为画板" - -msgid "workspace.options.show-in-viewer" -msgstr "在预览模式显示" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.triangle-arrow" -msgstr "三角箭头" - -msgid "workspace.options.clip-content" -msgstr "剪辑内容" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.grid-title" -msgstr "网格" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.advanced-ops" -msgstr "高级选项" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title" -msgstr "调整大小" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.bottom" -msgstr "底部" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" -msgstr "列" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" -msgstr "行" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" -msgstr "倒排行" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" -msgstr "倒排列" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.gap" -msgstr "差距" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "居中" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "居左" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "居右" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.padding-all" -msgstr "所有方向" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.padding" -msgstr "内边距" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.right" -msgstr "右" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.title" -msgstr "布局" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.space-between" -msgstr "间隔留空" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.space-around" -msgstr "周围留空" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.top" -msgstr "顶部" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "底部" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "居中" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "顶部" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.wrap" -msgstr "底部" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.selection-color" -msgstr "已选颜色" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.none" -msgstr "无边框" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.square" -msgstr "方头" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.square-marker" -msgstr "方形标记" - -msgid "workspace.options.stroke-color" -msgstr "线条颜色" - -msgid "workspace.sidebar.layers.texts" -msgstr "文本" - -msgid "workspace.sidebar.layers.shapes" -msgstr "形状" - -msgid "workspace.sidebar.layers.search" -msgstr "搜索图层" - -msgid "workspace.shape.menu.transform-to-path" -msgstr "转换为路径" - -msgid "workspace.shape.menu.thumbnail-set" -msgstr "设为缩略图" - -msgid "workspace.shape.menu.thumbnail-remove" -msgstr "移除缩略图" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.select-layer" -msgstr "选择图层" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.line-arrow" -msgstr "箭头" - -msgid "workspace.options.y" -msgstr "Y" - -msgid "workspace.path.actions.make-corner" -msgstr "转锐角 (%s)" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.detach-instances-in-bulk" -msgstr "解绑实例" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.flow-start" -msgstr "流程起点" - -msgid "workspace.sidebar.layers.components" -msgstr "组件" - -msgid "workspace.sidebar.layers.frames" -msgstr "画板" - -msgid "workspace.sidebar.layers.images" -msgstr "图片" - -msgid "workspace.sidebar.layers.masks" -msgstr "遮罩" - -msgid "workspace.sidebar.layers.groups" -msgstr "编组" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.zoom-selected" -msgstr "缩放到选定的位置" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.left" -msgstr "左" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin-all" -msgstr "所有方向" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin-simple" -msgstr "简易外边距" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin" -msgstr "外边距" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.padding-simple" -msgstr "简易内边距" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.more-colors" -msgstr "更多颜色" - -#: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs -msgid "workspace.options.more-lib-colors" -msgstr "更多共享库颜色" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.no-wrap" -msgstr "不换行" - -msgid "workspace.shape.menu.exclude" -msgstr "相减" - -msgid "workspace.shape.menu.flatten" -msgstr "展平" - -msgid "workspace.shape.menu.hide-ui" -msgstr "显示/隐藏界面" - -msgid "workspace.shape.menu.intersection" -msgstr "差集" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.shortcuts" -msgstr "快捷键 (%s)" - -msgid "workspace.shape.menu.restore-main" -msgstr "恢复主要组件" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.update-components-in-bulk" -msgstr "更新主要组件" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.round" -msgstr "圆头" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.flows.add-flow-start" -msgstr "增加流程起点" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.flows.flow-start" -msgstr "流程起点" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.flows.flow-starts" -msgstr "所有流程起点" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.interaction-close-overlay-dest" -msgstr "关闭遮罩层: %s" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.delete-flow-start" -msgstr "删除流程起点" - -msgid "workspace.shape.menu.path" -msgstr "路径" +msgstr "单击以闭合路径" \ No newline at end of file diff --git a/frontend/translations/zh_Hant.po b/frontend/translations/zh_Hant.po index 73ce72718d..a5488b9e7e 100644 --- a/frontend/translations/zh_Hant.po +++ b/frontend/translations/zh_Hant.po @@ -1083,4 +1083,4 @@ msgstr "歷史" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.update" -msgstr "更新" +msgstr "更新" \ No newline at end of file From 9915990e101062b41f854c378fc908987a342cad Mon Sep 17 00:00:00 2001 From: Shuaib Zahda Date: Sat, 8 Oct 2022 11:31:28 +0000 Subject: [PATCH 092/682] :globe_with_meridians: Add translations for: Arabic. Currently translated at 66.6% (806 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 345 +++++++++++++++++++++++++----------- 1 file changed, 244 insertions(+), 101 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 80287261db..e62b746e3d 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Youkho \n" -"Language-Team: Arabic " -"\n" +"Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -18,7 +18,8 @@ msgstr "هل لديك حساب؟" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" -msgstr "تحقق من بريدك الإلكتروني وانقر على الرابط للتحقق والبدء في استخدام Penpot." +msgstr "" +"تحقق من بريدك الإلكتروني وانقر على الرابط للتحقق والبدء في استخدام Penpot." #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" @@ -34,9 +35,11 @@ msgstr "ترغب في التجربة فحسب؟" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" -msgstr "هذه خدمة تجريبية ، لا تستخدمها للعمل الحقيقي ، سيتم مسح المشاريع بشكل دوري." +msgstr "" +"هذه خدمة تجريبية ، لا تستخدمها للعمل الحقيقي ، سيتم مسح المشاريع بشكل دوري." -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +#: src/app/main/ui/auth/register.cljs, +#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs msgid "auth.email" msgstr "البريد الالكتروني" @@ -160,7 +163,8 @@ msgstr "شروط الخدمة" #: src/app/main/ui/auth/register.cljs msgid "auth.terms-privacy-agreement" -msgstr "عند إنشاء حساب جديد ، فإنك توافق على شروط الخدمة وسياسة الخصوصية الخاصة بنا." +msgstr "" +"عند إنشاء حساب جديد ، فإنك توافق على شروط الخدمة وسياسة الخصوصية الخاصة بنا." #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" @@ -174,8 +178,7 @@ msgstr "جميع مستخدمي Penpot" msgid "common.share-link.confirm-deletion-link-description" msgstr "" -"هل أنت متأكد أنك تريد إزالة هذا الرابط؟ إذا قمت بذلك ، فلن يكون متاحًا لأي " -"شخص" +"هل أنت متأكد أنك تريد إزالة هذا الرابط؟ إذا قمت بذلك ، فلن يكون متاحًا لأي شخص" msgid "common.share-link.current-tag" msgstr "(الحالي)" @@ -245,7 +248,8 @@ msgstr "إعمل فريق!" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.info" -msgstr "تعلم أساسيات برنامج Penpot بينما تستمع بتجربته في هذه الدورة التعليمية." +msgstr "" +"تعلم أساسيات برنامج Penpot بينما تستمع بتجربته في هذه الدورة التعليمية." #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.start" @@ -267,7 +271,8 @@ msgstr "إبدا الجولة" msgid "dasboard.walkthrough-hero.title" msgstr "جولة في الواجهة" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "أضف كمكتبة مشتركة" @@ -300,7 +305,8 @@ msgstr "تنزيل ملف قياسي (.svg + .json)" msgid "dashboard.draft-title" msgstr "مسودة" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.duplicate" msgstr "تكرير" @@ -313,7 +319,6 @@ msgid "dashboard.empty-files" msgstr "لا يزال لديك 0 ملفات هنا" #: src/app/main/ui/dashboard/grid.cljs -#, markdown msgid "dashboard.empty-placeholder-drafts" msgstr "" "أوه لا! ليس لديك ملفات بعد! إذا كنت تريد تجربة بعض القوالب ، فانتقل إلى " @@ -373,15 +378,15 @@ msgstr "" "أصولهم*؟" msgid "dashboard.export.options.all.message" -msgstr "سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم." +msgstr "" +"سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم." msgid "dashboard.export.options.all.title" msgstr "صدر المكتبات المشتركة" msgid "dashboard.export.options.detach.message" msgstr "" -"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى " -"المكتبة. " +"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى المكتبة. " msgid "dashboard.export.options.detach.title" msgstr "عامل أصول المكتبة المشتركة كعناصر بسيطة" @@ -415,7 +420,6 @@ msgstr[3] "عدد قليل من الخطوط المضافة" msgstr[4] "تمت إضافة العديد من الخطوط" msgstr[5] "" -#, markdown msgid "dashboard.fonts.hero-text1" msgstr "" "ستتم إضافة أي خط ويب تقوم بتحميله هنا إلى قائمة عائلة الخطوط المتوفرة في " @@ -423,13 +427,11 @@ msgstr "" "عائلة الخطوط على أنها ** عائلة خط واحدة **. يمكنك تحميل الخطوط بالتنسيقات " "التالية: ** TTF و OTF و WOFF ** (ستحتاج إلى تنسيق واحد فقط)." -#, markdown msgid "dashboard.fonts.hero-text2" msgstr "" "يجب عليك فقط تحميل الخطوط التي تمتلكها أو لديك ترخيص لاستخدامها في Penpot. " -"اكتشف المزيد في قسم حقوق المحتوى في [شروط خدمة Penpot] " -"(https://penpot.app/terms.html). قد ترغب أيضًا في القراءة عن [ترخيص الخطوط] " -"(2)." +"اكتشف المزيد في قسم حقوق المحتوى في [شروط خدمة Penpot] (https://penpot.app/" +"terms.html). قد ترغب أيضًا في القراءة عن [ترخيص الخطوط] (2)." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -475,7 +477,8 @@ msgstr "تحميل الملف: %s" msgid "dashboard.invite-profile" msgstr "قم بدعوة للفريق" -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.leave-team" msgstr "ترك الفريق" @@ -499,7 +502,8 @@ msgstr "تحميل ملفاتك …" msgid "dashboard.loading-fonts" msgstr "جاري تحميل الخطوط …" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.move-to" msgstr "الانتقال إلى" @@ -511,7 +515,8 @@ msgstr "أنقل %s الملفات إلى" msgid "dashboard.move-to-other-team" msgstr "الانتقال إلى فريق آخر" -#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +#: src/app/main/ui/dashboard/projects.cljs, +#: src/app/main/ui/dashboard/files.cljs msgid "dashboard.new-file" msgstr "+ ملف جديد" @@ -586,7 +591,8 @@ msgstr "الترقية إلى مالك" msgid "dashboard.remove-account" msgstr "هل تريد إزالة حسابك؟" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.remove-shared" msgstr "إزالة كمكتبة مشتركة" @@ -630,7 +636,8 @@ msgstr "تم تكرار ملفك بنجاح" msgid "dashboard.success-duplicate-project" msgstr "تم نسخ مشروعك بنجاح" -#: src/app/main/ui/dashboard/grid.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/grid.cljs, src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.success-move-file" msgstr "تم نقل ملفك بنجاح" @@ -666,11 +673,14 @@ msgstr "نتائج البحث" msgid "dashboard.type-something" msgstr "اكتب لإظهار نتائج البحث" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.unpublish-shared" msgstr "الغاء نشر المكتبة" -#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs +#: src/app/main/ui/settings/profile.cljs, +#: src/app/main/ui/settings/password.cljs, +#: src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "تحديث الإعدادات" @@ -686,7 +696,11 @@ msgstr "البريد الالكتروني" msgid "dashboard.your-name" msgstr "اسمك" -#: src/app/main/ui/dashboard/search.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/libraries.cljs, src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/search.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/libraries.cljs, +#: src/app/main/ui/dashboard/projects.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.your-penpot" msgstr "Penpot الخاص بك" @@ -733,7 +747,8 @@ msgstr "لا يمكن للمتصفح إجراء هذه العملية" msgid "errors.components-v2" msgstr "تم استخدام هذا الملف بالفعل مع تمكين المكونات V2." -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +#: src/app/main/ui/auth/verify_token.cljs, +#: src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" msgstr "البريد الإلكتروني مستخدم بالفعل" @@ -744,7 +759,10 @@ msgstr "تم التحقق من صحة البريد الإلكتروني." msgid "errors.email-as-password" msgstr "لا يمكنك استخدام بريدك الإلكتروني ككلمة مرور" -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/auth/register.cljs, +#: src/app/main/ui/auth/recovery_request.cljs, +#: src/app/main/ui/settings/change_email.cljs, +#: src/app/main/ui/dashboard/team.cljs msgid "errors.email-has-permanent-bounces" msgstr "يحتوي البريد الإلكتروني «%s» على العديد من تقارير الارتداد الدائم." @@ -755,7 +773,8 @@ msgstr "يجب أن يتطابق البريد الإلكتروني للتأكي msgid "errors.email-spam-or-permanent-bounces" msgstr "تم الإبلاغ عن البريد الإلكتروني «٪ s» كبريد عشوائي أو مرتد بشكل دائم." -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/auth/verify_token.cljs, +#: src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.generic" msgstr "حدث خطأ ما." @@ -789,7 +808,7 @@ msgstr "الصورة كبيرة جدا بحيث لا يمكن إدراجها." msgid "errors.media-type-mismatch" msgstr "يبدو أن محتويات الصورة لا تتطابق مع امتداد الملف." -#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-not-allowed" msgstr "يبدو أن هذه ليست صورة صالحة." @@ -810,7 +829,9 @@ msgstr "يجب أن تتطابق كلمة مرور التأكيد" msgid "errors.password-too-short" msgstr "يجب ألا تقل كلمة المرور عن 8 أحرف" -#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/auth/recovery_request.cljs, +#: src/app/main/ui/settings/change_email.cljs, +#: src/app/main/ui/dashboard/team.cljs msgid "errors.profile-is-muted" msgstr "" "يحتوي ملفك الشخصي على رسائل بريد إلكتروني مكتومة (تقارير البريد المزعجة أو " @@ -832,7 +853,9 @@ msgstr "لا يمكن للمالك مغادرة الفريق ، يجب إعاد msgid "errors.terms-privacy-agreement-invalid" msgstr "يجب أن تقبل شروط الخدمة وسياسة الخصوصية الخاصة بنا." -#: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +#: src/app/main/data/media.cljs, +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs msgid "errors.unexpected-error" msgstr "حدث خطأ غير متوقع." @@ -879,8 +902,8 @@ msgstr "موضوع" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"يرجى وصف سبب بريدك الإلكتروني ، وتحديد ما إذا كانت مشكلة أم فكرة أم شك. " -"سيرد أحد أعضاء فريقنا في أسرع وقت ممكن." +"يرجى وصف سبب بريدك الإلكتروني ، وتحديد ما إذا كانت مشكلة أم فكرة أم شك. سيرد " +"أحد أعضاء فريقنا في أسرع وقت ممكن." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" @@ -950,7 +973,8 @@ msgstr "ارتفاع" msgid "handoff.attributes.layout.left" msgstr "يسار" -#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +#: src/app/main/ui/handoff/attributes/layout.cljs, +#: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.radius" msgstr "نصف قطر" @@ -990,15 +1014,12 @@ msgstr "S" msgid "handoff.attributes.stroke" msgstr "لون الحدّ" -#, permanent msgid "handoff.attributes.stroke.alignment.center" msgstr "مركز" -#, permanent msgid "handoff.attributes.stroke.alignment.inner" msgstr "داخل" -#, permanent msgid "handoff.attributes.stroke.alignment.outer" msgstr "خارج" @@ -1133,7 +1154,7 @@ msgstr "إقبل" msgid "labels.add-custom-font" msgstr "إضافة خط مخصص" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.admin" msgstr "مشرف" @@ -1195,7 +1216,8 @@ msgstr "يمكنك المتابعة مع حساب Penpot" msgid "labels.create" msgstr "انشاء" -#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs +#: src/app/main/ui/dashboard/team_form.cljs, +#: src/app/main/ui/dashboard/team_form.cljs msgid "labels.create-team" msgstr "إنشاء فريق جديد" @@ -1213,7 +1235,8 @@ msgstr "لوحة التحكم" msgid "labels.default" msgstr "إفتراضي" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete" msgstr "حذف" @@ -1233,7 +1256,10 @@ msgstr "حذف الدعوة" msgid "labels.delete-multi-files" msgstr "حذف %s ملفات" -#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/projects.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/files.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.drafts" msgstr "المسودات" @@ -1244,7 +1270,7 @@ msgstr "تعديل" msgid "labels.edit-file" msgstr "تعديل ملف" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.editor" msgstr "محرر" @@ -1286,7 +1312,9 @@ msgstr "الخطوط" msgid "labels.github-repo" msgstr "مستودع Github" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/settings/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.give-feedback" msgstr "إعطاء ملاحظات" @@ -1312,13 +1340,15 @@ msgstr "الخطوط المتوفرة" #: src/app/main/ui/static.cljs msgid "labels.internal-error.desc-message" -msgstr "شيء سيء حدث الرجاء إعادة محاولة العملية وإذا استمرت المشكلة، اتصل بالدعم." +msgstr "" +"شيء سيء حدث الرجاء إعادة محاولة العملية وإذا استمرت المشكلة، اتصل بالدعم." #: src/app/main/ui/static.cljs msgid "labels.internal-error.main-message" msgstr "خطأ داخلي" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.invitations" msgstr "الدعوات" @@ -1343,11 +1373,11 @@ msgstr "تسجيل خروج" msgid "labels.manage-fonts" msgstr "إدارة الخطوط" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.member" msgstr "عضو" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.members" msgstr "الأعضاء" @@ -1362,7 +1392,8 @@ msgstr "كلمة مرور جديدة" msgid "labels.next" msgstr "التالي" -#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs +#: src/app/main/ui/workspace/comments.cljs, +#: src/app/main/ui/dashboard/comments.cljs msgid "labels.no-comments-available" msgstr "ليس لديك أي إشعارات تعليق معلقة" @@ -1372,7 +1403,8 @@ msgstr "لا توجد دعوات." #: src/app/main/ui/dashboard/team.cljs msgid "labels.no-invitations-hint" -msgstr "اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." +msgstr "" +"اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" @@ -1430,7 +1462,8 @@ msgstr "أو" msgid "labels.owner" msgstr "مالك" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/settings/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.password" msgstr "كلمة المرور" @@ -1442,7 +1475,8 @@ msgstr "قيد الانتظار" msgid "labels.permissions" msgstr "اذونات" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/settings/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.profile" msgstr "الملف الشخصي" @@ -1457,7 +1491,8 @@ msgstr "الأخيرة" msgid "labels.release-notes" msgstr "ملاحظات الإصدار" -#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/workspace/libraries.cljs, +#: src/app/main/ui/dashboard/team.cljs msgid "labels.remove" msgstr "إزالة" @@ -1465,7 +1500,9 @@ msgstr "إزالة" msgid "labels.remove-member" msgstr "إزالة العضو" -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.rename" msgstr "اعاده تسميه" @@ -1477,7 +1514,7 @@ msgstr "إعادة تسمية الفريق" msgid "labels.resend-invitation" msgstr "إعادة إرسال الدعوة" -#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "أعد المحاولة" @@ -1507,7 +1544,8 @@ msgstr "نحن في صيانة مبرمجة لأنظمتنا." msgid "labels.service-unavailable.main-message" msgstr "الخدمة غير متوفرة" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.settings" msgstr "إعدادات" @@ -1575,7 +1613,7 @@ msgstr "مساحة العمل" msgid "labels.write-new-comment" msgstr "كتابة تعليق جديد" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.you" msgstr "(أنت)" @@ -1583,21 +1621,24 @@ msgstr "(أنت)" msgid "labels.your-account" msgstr "حسابك" -#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" msgstr "جاري تحميل الصورة…" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.accept" msgstr "إضافة كمكتبة مشتركة" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.hint" msgstr "" "بمجرد إضافتها كمكتبة مشتركة، ستكون أصول مكتبة الملفات هذه متاحة للاستخدام " "بين باقي ملفاتك." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.message" msgstr "إضافة “%s” كمكتبة مشتركة" @@ -1627,7 +1668,8 @@ msgstr "تغيير بريدك الإلكتروني" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.change-owner-and-leave-confirm.message" -msgstr "أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." +msgstr "" +"أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" @@ -1685,14 +1727,14 @@ msgstr "حذف %s الملفات" msgid "modals.delete-font-variant.message" msgstr "" -"هل أنت متأكد أنك تريد حذف نمط هذا الخط؟ لن يتم تحميله إذا تم استخدامه في " -"ملف." +"هل أنت متأكد أنك تريد حذف نمط هذا الخط؟ لن يتم تحميله إذا تم استخدامه في ملف." msgid "modals.delete-font-variant.title" msgstr "حذف نمط الخط" msgid "modals.delete-font.message" -msgstr "هل أنت متأكد أنك تريد حذف هذا الخط؟ لن يتم تحميله إذا تم استخدامه في ملف." +msgstr "" +"هل أنت متأكد أنك تريد حذف هذا الخط؟ لن يتم تحميله إذا تم استخدامه في ملف." msgid "modals.delete-font.title" msgstr "حذف الخط" @@ -1760,7 +1802,8 @@ msgstr "ادعُ الأعضاء إلى الفريق" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" -msgstr "نظرًا لأنك العضو الوحيد في هذا الفريق ، فسيتم حذف الفريق مع مشاريعه وملفاته." +msgstr "" +"نظرًا لأنك العضو الوحيد في هذا الفريق ، فسيتم حذف الفريق مع مشاريعه وملفاته." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.message" @@ -1825,17 +1868,20 @@ msgstr "هل أنت متأكد أنك تريد ترقية هذا المستخد msgid "modals.promote-owner-confirm.title" msgstr "الترقية إلى مالك" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.accept" msgstr "إزالة كمكتبة مشتركة" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.hint" msgstr "" -"بمجرد إزالتها كمكتبة مشتركة ، ستتوقف مكتبة الملفات لهذا الملف عن كونها " -"متاحة للاستخدام بين بقية ملفاتك." +"بمجرد إزالتها كمكتبة مشتركة ، ستتوقف مكتبة الملفات لهذا الملف عن كونها متاحة " +"للاستخدام بين بقية ملفاتك." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.message" msgstr "إزالة “%s” كمكتبة مشتركة" @@ -1843,35 +1889,42 @@ msgstr "إزالة “%s” كمكتبة مشتركة" msgid "modals.small-nudge" msgstr "دفعة صغيرة" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.accept" msgstr "إلغاء النشر" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" msgstr "" "أنت على وشك تحديث المكونات في مكتبة مشتركة. قد يؤثر هذا على الملفات الأخرى " "التي تستخدمها." -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.message" msgstr "تحديث المكونات في مكتبة مشتركة" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.accept" msgstr "تحديث المكون" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.cancel" msgstr "إلغاﺀ" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" msgstr "" "أنت على وشك تحديث مكون في مكتبة مشتركة. قد يؤثر هذا على الملفات الأخرى التي " "تستخدمها." -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.message" msgstr "تحديث المكون في المكتبة المشتركة" @@ -1928,8 +1981,8 @@ msgstr "المشاركة في المجتمع" msgid "onboarding-v2.welcome.desc3" msgstr "" -"حيث ستجد كيفية التعاون في الترجمات وطلبات الميزات والمساهمات الأساسية " -"والبحث عن الأخطاء …" +"حيث ستجد كيفية التعاون في الترجمات وطلبات الميزات والمساهمات الأساسية والبحث " +"عن الأخطاء …" msgid "onboarding-v2.welcome.desc3.title" msgstr "دليل المساهمة" @@ -2099,7 +2152,12 @@ msgstr "مرحبًا بك في Penpot" msgid "profile.recovery.go-to-login" msgstr "اذهب إلى تسجيل الدخول" -#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +#: src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs, +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs, +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs, +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs, +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs msgid "settings.multiple" msgstr "مختلط" @@ -2440,7 +2498,7 @@ msgstr "إزالة الرابط" msgid "viewer.header.share.subtitle" msgstr "أي شخص لديه الرابط سيكون لديه حق الوصول" -#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs +#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.title" msgstr "مشاركة النموذج الأولي" @@ -2499,11 +2557,13 @@ msgstr "كل الأصول الرقمية" msgid "workspace.assets.box-filter-graphics" msgstr "الرسومات" -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.colors" msgstr "الألوان" -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.components" msgstr "المكونات" @@ -2515,15 +2575,19 @@ msgstr "أنشئ مجموعة" msgid "workspace.assets.create-group-hint" msgstr "ستتم تسمية عناصرك تلقائيًا باسم \"المجموعة / العنصر\"" -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.delete" msgstr "حذف" -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.duplicate" msgstr "تكرار" -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.edit" msgstr "تعديل" @@ -2531,7 +2595,8 @@ msgstr "تعديل" msgid "workspace.assets.file-library" msgstr "مكتبة الملف" -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" msgstr "الرسومات" @@ -2551,7 +2616,9 @@ msgstr "المكتبات" msgid "workspace.assets.not-found" msgstr "لم يتم العثور على أصول رقمية" -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.rename" msgstr "إعادة تسمية" @@ -2577,7 +2644,8 @@ msgstr[5] "غير ذلك" msgid "workspace.assets.shared" msgstr "متشاركة" -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.typography" msgstr "صياغة الحروف" @@ -2605,7 +2673,9 @@ msgstr "تباعد الحروف" msgid "workspace.assets.typography.line-height" msgstr "ارتفاع الخط" -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, src/app/main/ui/handoff/attributes/text.cljs, src/app/main/ui/handoff/attributes/text.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, +#: src/app/main/ui/handoff/attributes/text.cljs, +#: src/app/main/ui/handoff/attributes/text.cljs msgid "workspace.assets.typography.sample" msgstr "مثال" @@ -2617,11 +2687,13 @@ msgstr "تحويل النص" msgid "workspace.assets.ungroup" msgstr "إلغاء التجميع" -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +#: src/app/main/data/workspace/libraries.cljs, +#: src/app/main/ui/components/color_bullet.cljs msgid "workspace.gradients.linear" msgstr "تدرج خطي" -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +#: src/app/main/data/workspace/libraries.cljs, +#: src/app/main/ui/components/color_bullet.cljs msgid "workspace.gradients.radial" msgstr "تدرج شعاعي" @@ -2709,11 +2781,13 @@ msgstr "%s الألوان" msgid "workspace.libraries.colors.big-thumbnails" msgstr "صور مصغرة كبيرة" -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, +#: src/app/main/ui/workspace/colorpalette.cljs msgid "workspace.libraries.colors.file-library" msgstr "مكتبة الملف" -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, +#: src/app/main/ui/workspace/colorpalette.cljs msgid "workspace.libraries.colors.recent-colors" msgstr "الألوان الحديثة" @@ -2871,11 +2945,13 @@ msgstr "أعلى وأسفل" msgid "workspace.options.design" msgstr "تصميم" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export" msgstr "تصدير" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" msgstr "تصدير 0 عنصر" @@ -2883,7 +2959,8 @@ msgstr "تصدير 0 عنصر" msgid "workspace.options.export.suffix" msgstr "لاحقة" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.exporting-object" msgstr "جارٍ التصدير …" @@ -3004,4 +3081,70 @@ msgid "workspace.updates.update" msgstr "تحديث" msgid "workspace.viewport.click-to-close-path" -msgstr "انقر لإغلاق المسار" \ No newline at end of file +msgstr "انقر لإغلاق المسار" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "تبديل الفريق" + +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"لا يمكنك مغادرة الفريق إذا لم يكن هناك عضو آخر للترقية إلى المالك. قد ترغب " +"في حذف الفريق." + +msgid "shortcuts.bool-union" +msgstr "الاتحاد المنطقي" + +msgid "shortcuts.create-new-project" +msgstr "أضف جديد" + +msgid "shortcuts.not-found" +msgstr "لا يوجد إختصارات" + +msgid "shortcuts.opacity-4" +msgstr "اضبط التعتيم على 40%" + +msgid "shortcuts.opacity-5" +msgstr "اضبط التعتيم على 50%" + +msgid "shortcuts.bool-intersection" +msgstr "تقاطع منطقي" + +msgid "shortcuts.opacity-9" +msgstr "اضبط التعتيم على 90%" + +msgid "shortcuts.open-color-picker" +msgstr "أداة انتقاء اللون" + +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "إصدار متعدد اللاعبين" + +msgid "shortcuts.bool-difference" +msgstr "فرق منطقي" + +msgid "shortcuts.opacity-1" +msgstr "اضبط التعتيم على 10٪" + +msgid "shortcuts.opacity-0" +msgstr "ضبط التعتيم على 100٪" + +msgid "shortcuts.open-dashboard" +msgstr "إذهب إلى لوحة المعلومات" + +msgid "shortcuts.bool-exclude" +msgstr "استبعاد منطقي" + +msgid "shortcuts.opacity-2" +msgstr "اضبط التعتيم على 20٪" + +msgid "shortcuts.opacity-6" +msgstr "اضبط التعتيم على 60%" + +msgid "shortcuts.opacity-3" +msgstr "اضبط التعتيم على 30%" + +msgid "shortcuts.opacity-8" +msgstr "اضبط التعتيم على 80%" + +msgid "shortcuts.opacity-7" +msgstr "اضبط التعتيم على 70%" From 670365acb7f7fe858fb76ed268db9a28f13cb296 Mon Sep 17 00:00:00 2001 From: Valentina Chapellu Date: Fri, 7 Oct 2022 12:59:08 +0000 Subject: [PATCH 093/682] :globe_with_meridians: Add translations for: Italian. Currently translated at 43.5% (526 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/it/ --- frontend/translations/it.po | 262 +++++++++++++++++++++++------------- 1 file changed, 171 insertions(+), 91 deletions(-) diff --git a/frontend/translations/it.po b/frontend/translations/it.po index a19a4dedbb..3f664865dd 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-01 14:17+0000\n" "Last-Translator: Jacopo Lodovico Trabia \n" -"Language-Team: Italian " -"\n" +"Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -39,7 +39,8 @@ msgstr "" "Questo è un servizio di prova, non utilizzare per il lavoro reale, i " "progetti verranno eliminati periodicamente." -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +#: src/app/main/ui/auth/register.cljs, +#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs msgid "auth.email" msgstr "Indirizzo e-mail" @@ -259,7 +260,8 @@ msgstr "Inizia il tour" msgid "dasboard.walkthrough-hero.title" msgstr "Spiegazione dell'interfaccia passo per passo" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Aggiungi una libreria condivisa" @@ -292,7 +294,8 @@ msgstr "Scarica il file standard (.svg + .json)" msgid "dashboard.draft-title" msgstr "Bozza" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.duplicate" msgstr "Duplica" @@ -305,7 +308,6 @@ msgid "dashboard.empty-files" msgstr "Qui non c'è ancora nessun file" #: src/app/main/ui/dashboard/grid.cljs -#, markdown msgid "dashboard.empty-placeholder-drafts" msgstr "" "Oh no! Non hai ancora nessun file! Se desideri provare alcuni template vai " @@ -362,8 +364,8 @@ msgstr "" msgid "dashboard.export.options.all.message" msgstr "" -"I file con librerie condivise verranno inclusi nell'esportazione, " -"mantenendo il loro collegamento." +"I file con librerie condivise verranno inclusi nell'esportazione, mantenendo " +"il loro collegamento." msgid "dashboard.export.options.all.title" msgstr "Esporta le librerie condivise" @@ -397,23 +399,21 @@ msgid_plural "dashboard.fonts.fonts-added" msgstr[0] "1 font aggiunto" msgstr[1] "%s font aggiunti" -#, markdown msgid "dashboard.fonts.hero-text1" msgstr "" "Qualsiasi font web caricato qui verrà aggiunto alla lista dei font family " "disponibile nelle impostazioni testo dei file di questo team. I font che " "arrecano lo stesso nome di font family verranno raggruppati come un " -"**singolo font family**. È possibile caricare font con i seguenti " -"formati:**TTF, OTF e WOFF**(uno solo di questi è necessario)." +"**singolo font family**. È possibile caricare font con i seguenti formati:" +"**TTF, OTF e WOFF**(uno solo di questi è necessario)." -#, markdown msgid "dashboard.fonts.hero-text2" msgstr "" -"È consigliabile caricare unicamente font di cui si è proprietari o dei " -"quali si possiede la licenza d'uso in Penpot. Ulteriori informazioni sui " -"diritti dei contenuti sono disponibili nella sezione [Termini di Servizio " -"di Penpot](https://penpot.app/terms.html). Potresti anche voler " -"approfondire le [licenze per i font](https://www.typography.com/faq)." +"È consigliabile caricare unicamente font di cui si è proprietari o dei quali " +"si possiede la licenza d'uso in Penpot. Ulteriori informazioni sui diritti " +"dei contenuti sono disponibili nella sezione [Termini di Servizio di Penpot]" +"(https://penpot.app/terms.html). Potresti anche voler approfondire le " +"[licenze per i font](https://www.typography.com/faq)." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -461,7 +461,8 @@ msgstr "Caricamento file: %s" msgid "dashboard.invite-profile" msgstr "Invita nel team" -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.leave-team" msgstr "Abbandona il team" @@ -487,7 +488,8 @@ msgstr "caricamento dei file …" msgid "dashboard.loading-fonts" msgstr "caricamento dei font …" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.move-to" msgstr "Sposta verso" @@ -499,7 +501,8 @@ msgstr "Sposta %s file verso" msgid "dashboard.move-to-other-team" msgstr "Sposta verso un altro team" -#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +#: src/app/main/ui/dashboard/projects.cljs, +#: src/app/main/ui/dashboard/files.cljs msgid "dashboard.new-file" msgstr "+ Nuovo File" @@ -576,7 +579,8 @@ msgstr "Promuovi a proprietario" msgid "dashboard.remove-account" msgstr "Desideri eliminare il tuo account?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.remove-shared" msgstr "Elimina come Libreria Condivisa" @@ -620,7 +624,8 @@ msgstr "Il tuo file è stato duplicato con successo" msgid "dashboard.success-duplicate-project" msgstr "Il tuo progetto è stato duplicato con successo" -#: src/app/main/ui/dashboard/grid.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/grid.cljs, src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.success-move-file" msgstr "Il tuo file è stato spostato con successo" @@ -656,11 +661,13 @@ msgstr "Risultati della ricerca" msgid "dashboard.type-something" msgstr "Scrivi per cercare" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.unpublish-shared" msgstr "Spubblicare la libreria" -#: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs +#: src/app/main/ui/settings/password.cljs, +#: src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "Aggiorna le impostazioni" @@ -676,7 +683,11 @@ msgstr "E-mail" msgid "dashboard.your-name" msgstr "Il tuo nome" -#: src/app/main/ui/dashboard/search.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/libraries.cljs, src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/search.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/libraries.cljs, +#: src/app/main/ui/dashboard/projects.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.your-penpot" msgstr "Il tuo Penpot" @@ -719,7 +730,8 @@ msgstr "Sembra che tu non ti sia autenticato o che la sessione sia scaduta." msgid "errors.clipboard-not-implemented" msgstr "Il tuo browser non può effettuare questa operazione" -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +#: src/app/main/ui/auth/verify_token.cljs, +#: src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" msgstr "Indirizzo e-mail già utilizzato" @@ -735,9 +747,11 @@ msgid "errors.email-invalid-confirmation" msgstr "L'indirizzo e-mail di conferma deve corrispondere" msgid "errors.email-spam-or-permanent-bounces" -msgstr "L'e-mail \"%s\" è stata riportata come spam o respinta in modo permanente." +msgstr "" +"L'e-mail \"%s\" è stata riportata come spam o respinta in modo permanente." -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/auth/verify_token.cljs, +#: src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.generic" msgstr "Si è verificato un problema." @@ -773,7 +787,7 @@ msgstr "" "Sembra che il contenuto dell'immagine non corrisponda all'estensione del " "file." -#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-not-allowed" msgstr "L'immagine non sembra valida." @@ -806,9 +820,12 @@ msgstr "" "proprietario." msgid "errors.terms-privacy-agreement-invalid" -msgstr "È necessario accettare i termini di servizio e l'informativa sulla privacy." +msgstr "" +"È necessario accettare i termini di servizio e l'informativa sulla privacy." -#: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +#: src/app/main/data/media.cljs, +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs msgid "errors.unexpected-error" msgstr "Si è verificato un errore inaspettato." @@ -857,9 +874,9 @@ msgstr "Soggetto" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"Descrivi per favore il motivo della tua e-mail, specificando se si tratta " -"di un problema, di un'idea oppure di un dubbio. Un membro del nostro team " -"ti risponderà il prima possibile." +"Descrivi per favore il motivo della tua e-mail, specificando se si tratta di " +"un problema, di un'idea oppure di un dubbio. Un membro del nostro team ti " +"risponderà il prima possibile." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" @@ -929,7 +946,8 @@ msgstr "Altezza" msgid "handoff.attributes.layout.left" msgstr "Sinistra" -#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +#: src/app/main/ui/handoff/attributes/layout.cljs, +#: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.radius" msgstr "Raggio" @@ -969,15 +987,12 @@ msgstr "S" msgid "handoff.attributes.stroke" msgstr "Contorno" -#, permanent msgid "handoff.attributes.stroke.alignment.center" msgstr "Centro" -#, permanent msgid "handoff.attributes.stroke.alignment.inner" msgstr "Interno" -#, permanent msgid "handoff.attributes.stroke.alignment.outer" msgstr "Esterno" @@ -1102,7 +1117,7 @@ msgstr "Accettare" msgid "labels.add-custom-font" msgstr "Aggiungere un carattere personalizzato" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.admin" msgstr "Amministratore" @@ -1164,7 +1179,8 @@ msgstr "Puoi continuare con un account Penpot" msgid "labels.create" msgstr "Crea" -#: src/app/main/ui/dashboard/team_form.cljs, src/app/main/ui/dashboard/team_form.cljs +#: src/app/main/ui/dashboard/team_form.cljs, +#: src/app/main/ui/dashboard/team_form.cljs msgid "labels.create-team" msgstr "Crea un nuovo team" @@ -1182,7 +1198,8 @@ msgstr "Dashboard" msgid "labels.default" msgstr "predefinito" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete" msgstr "Eliminare" @@ -1202,7 +1219,10 @@ msgstr "Eliminare l'invito" msgid "labels.delete-multi-files" msgstr "Eliminare %s file" -#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/projects.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/files.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.drafts" msgstr "Bozze" @@ -1213,7 +1233,7 @@ msgstr "Modificare" msgid "labels.edit-file" msgstr "Modificare il file" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.editor" msgstr "Redattore" @@ -1252,7 +1272,9 @@ msgstr "Font" msgid "labels.github-repo" msgstr "Repository Github" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/settings/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.give-feedback" msgstr "Dai la tua opinione" @@ -1286,7 +1308,8 @@ msgstr "" msgid "labels.internal-error.main-message" msgstr "Errore interno" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.invitations" msgstr "Inviti" @@ -1311,11 +1334,11 @@ msgstr "Disconnetti" msgid "labels.manage-fonts" msgstr "Gestisci i font" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.member" msgstr "Membro" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.members" msgstr "Membri" @@ -1330,7 +1353,8 @@ msgstr "Nuova password" msgid "labels.next" msgstr "Prossimo" -#: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs +#: src/app/main/ui/workspace/comments.cljs, +#: src/app/main/ui/dashboard/comments.cljs msgid "labels.no-comments-available" msgstr "Non ci sono notifiche di commenti in attesa" @@ -1350,7 +1374,8 @@ msgstr "Sei connesso come" #: src/app/main/ui/static.cljs msgid "labels.not-found.desc-message" -msgstr "Questa pagina non esiste oppure non hai i permessi necessari per accedervi." +msgstr "" +"Questa pagina non esiste oppure non hai i permessi necessari per accedervi." #: src/app/main/ui/static.cljs msgid "labels.not-found.main-message" @@ -1383,7 +1408,8 @@ msgstr "oppure" msgid "labels.owner" msgstr "Proprietario" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/settings/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.password" msgstr "Password" @@ -1410,7 +1436,8 @@ msgstr "Recenti" msgid "labels.release-notes" msgstr "Note di versione" -#: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/workspace/libraries.cljs, +#: src/app/main/ui/dashboard/team.cljs msgid "labels.remove" msgstr "Rimuovere" @@ -1418,7 +1445,9 @@ msgstr "Rimuovere" msgid "labels.remove-member" msgstr "Rimuovi membro" -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.rename" msgstr "Rinominare" @@ -1430,7 +1459,7 @@ msgstr "Rinominare il team" msgid "labels.resend-invitation" msgstr "Invia di nuovo l'invito" -#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Riprova" @@ -1460,7 +1489,8 @@ msgstr "Stiamo effettuando la manutenzione programmata dei nostri sistemi." msgid "labels.service-unavailable.main-message" msgstr "Servizio non disponibile" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.settings" msgstr "Configurazione" @@ -1528,7 +1558,7 @@ msgstr "Spazio di lavoro" msgid "labels.write-new-comment" msgstr "Scrivere un nuovo commento" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.you" msgstr "(tu)" @@ -1536,21 +1566,24 @@ msgstr "(tu)" msgid "labels.your-account" msgstr "Il tuo account" -#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" msgstr "Caricamento dell'immagine…" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.accept" msgstr "Aggiungere come libreria condivisa" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.hint" msgstr "" "Una volta aggiunta come libreria condivisa, le risorse di questa libreria " "saranno disponibili per essere utilizzate nel resto dei tuoi file." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.add-shared-confirm.message" msgstr "Aggiungere \"%s\" come libreria condivisa" @@ -1565,8 +1598,8 @@ msgstr "Verificare il nuovo indirizzo e-mail" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.info" msgstr "" -"Ti invieremo un'e-mail al tuo attuale indirizzo e-mail \"%s\" per " -"verificare la tua identità." +"Ti invieremo un'e-mail al tuo attuale indirizzo e-mail \"%s\" per verificare " +"la tua identità." #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.new-email" @@ -1597,8 +1630,7 @@ msgstr "Sì, cancellare il mio account" #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.info" msgstr "" -"Cancellando il tuo account, perderai tutti i tuoi archivi e progetti " -"attuali." +"Cancellando il tuo account, perderai tutti i tuoi archivi e progetti attuali." #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.title" @@ -1676,31 +1708,36 @@ msgstr "Eliminare questo progetto?" msgid "modals.delete-project-confirm.title" msgstr "Elimina progetto" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.accept" msgid_plural "modals.delete-shared-confirm.accept" msgstr[0] "Elimina file" msgstr[1] "Elimina i file" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.message" msgid_plural "modals.delete-shared-confirm.message" msgstr[0] "Eliminare questo file?" msgstr[1] "Eliminare questi file?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message" msgid_plural "modals.delete-shared-confirm.scd-message" msgstr[0] "Questo file contiene librerie usate nel file:" msgstr[1] "Questo file contiene librerie usate nei file:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message-plural" msgid_plural "modals.delete-shared-confirm.scd-message-plural" msgstr[0] "Questi file contengono librerie usate nel file:" msgstr[1] "Questi file contengono librerie usate nei file:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" msgid_plural "modals.delete-shared-confirm.title" msgstr[0] "Eliminazione del file" @@ -1750,8 +1787,8 @@ msgstr "Invita membri al team" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" msgstr "" -"Poiché sei il solo membro di questo team, il team verrà eliminato insieme " -"ai sui file e progetti." +"Poiché sei il solo membro di questo team, il team verrà eliminato insieme ai " +"sui file e progetti." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.message" @@ -1812,18 +1849,21 @@ msgstr "" msgid "modals.promote-owner-confirm.title" msgstr "Nuovo proprietario del team" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.accept" msgstr "Elimina come Libreria Condivisa" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.hint" msgstr "" "Una volta eliminata come Libreria Condivisa, la Libreria dei File di questo " "file smetterà di essere a disposizione per essere usata con il resto dei " "tuoi file." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.message" msgstr "Elimina \"%s\" come Libreria Condivisa" @@ -1831,11 +1871,13 @@ msgstr "Elimina \"%s\" come Libreria Condivisa" msgid "modals.small-nudge" msgstr "Piccolo scatto" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.accept" msgstr "Annulla pubblicazione" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint" msgstr[0] "" @@ -1845,49 +1887,58 @@ msgstr[1] "" "Se annulli la pubblicazione, gli elementi diventeranno parte della libreria " "di questi file." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" msgid_plural "modals.unpublish-shared-confirm.message" msgstr[0] "Annullare la pubblicazione di questa libreria?" msgstr[1] "Annullare la pubblicazione di queste librerie?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" msgstr[0] "È utilizzata in questo file:" msgstr[1] "È utilizzata in questi file:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.title" msgid_plural "modals.unpublish-shared-confirm.title" msgstr[0] "Annulla pubblicazione libreria" msgstr[1] "Annulla pubblicazione librerie" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" msgstr "" "Stai per aggiornare i componenti in una libreria condivisa. Questo potrebbe " "causare modifiche nei file che la utilizzano." -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.message" msgstr "Aggiorna componenti in una libreria condivisa" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.accept" msgstr "Aggiorna" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.cancel" msgstr "Cancella" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" msgstr "" -"Stai per aggiornare un componente in una libreria condivisa. Questo " -"potrebbe causare modifiche nei file che la utilizzano." +"Stai per aggiornare un componente in una libreria condivisa. Questo potrebbe " +"causare modifiche nei file che la utilizzano." -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.message" msgstr "Aggiorna un componente in una libreria condivisa" @@ -1897,7 +1948,8 @@ msgstr "Invito inviato con successo" #: src/app/main/ui/settings/delete_account.cljs msgid "notifications.profile-deletion-not-allowed" -msgstr "Non puoi eliminare il tuo profilo. Riassegna i tuoi team prima di procedere." +msgstr "" +"Non puoi eliminare il tuo profilo. Riassegna i tuoi team prima di procedere." #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs msgid "notifications.profile-saved" @@ -1937,8 +1989,8 @@ msgstr "" msgid "onboarding-v2.welcome.desc2" msgstr "" -"Uno spazio pubblico dove imparare, condividere e discutere su Penpot, il " -"suo presente e futuro con l'intera Comunità e con il team di Penpot." +"Uno spazio pubblico dove imparare, condividere e discutere su Penpot, il suo " +"presente e futuro con l'intera Comunità e con il team di Penpot." msgid "onboarding-v2.welcome.desc2.title" msgstr "Partecipando nella Comunità" @@ -1961,7 +2013,8 @@ msgid "onboarding.choice.team-up.create-team" msgstr "Il nome del tuo team" msgid "onboarding.choice.team-up.create-team-desc" -msgstr "Dopo aver nominato il tuo team, potrai invitare persone ad unirsi ad esso." +msgstr "" +"Dopo aver nominato il tuo team, potrai invitare persone ad unirsi ad esso." msgid "onboarding.choice.team-up.create-team-placeholder" msgstr "Inserisci il nome del team" @@ -2061,4 +2114,31 @@ msgstr "Crea interazioni complete per imitare al meglio il prodotto finale." #: src/app/main/ui/dashboard/team.cljs msgid "title.team-invitations" -msgstr "Inviti - %s - Penpot" \ No newline at end of file +msgstr "Inviti - %s - Penpot" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "Cambia team" + +msgid "dashboard.export.options.detach.title" +msgstr "Considera le risorse delle librerie condivise come oggetti di base" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Prime lettere maiuscole" + +msgid "common.unpublish" +msgstr "Spubblica" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Fai squadra!" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Decorazioni testo" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Puoi aggiungere dei parametri di esportazione agli elementi accedendo alle " +"proprietà del design (in fondo alla barra laterale destra)." From 54239999139f8ac30ea29d835a4b52de8953c5f2 Mon Sep 17 00:00:00 2001 From: ascarida Date: Sun, 9 Oct 2022 22:41:11 +0000 Subject: [PATCH 094/682] :globe_with_meridians: Add translations for: Galician. Currently translated at 30.2% (366 of 1209 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/gl/ --- frontend/translations/gl.po | 907 +++++++++++++++++++++++++++++++++++- 1 file changed, 885 insertions(+), 22 deletions(-) diff --git a/frontend/translations/gl.po b/frontend/translations/gl.po index 809c583c86..8f99e12fa0 100644 --- a/frontend/translations/gl.po +++ b/frontend/translations/gl.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-06-11 09:14+0000\n" "Last-Translator: ascarida \n" -"Language-Team: Galician " -"\n" +"Language-Team: Galician \n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -36,10 +36,11 @@ msgstr "Queres probar?" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" msgstr "" -"Este é un servizo de DEMOSTRACIÓN. NON USAR para traballo real, os " -"proxectos borraranse." +"Este é un servizo de DEMOSTRACIÓN. NON O UTILICES para traballos reais, os " +"proxectos eliminanse periódicamente." -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +#: src/app/main/ui/auth/register.cljs, +#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs msgid "auth.email" msgstr "Correo electrónico" @@ -101,7 +102,8 @@ msgstr "Perfil sen verificar, valida o perfil antes de continuar." #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.recovery-token-sent" -msgstr "Enviouse ó teu correo electrónico un enlace co que recuperar o contrasinal." +msgstr "" +"Enviouse ó teu correo electrónico un enlace co que recuperar o contrasinal." #: src/app/main/ui/auth/verify_token.cljs msgid "auth.notifications.team-invitation-accepted" @@ -208,7 +210,8 @@ msgstr "Só esta páxina" msgid "common.share-link.view-selected-pages" msgstr "Páxinas seleccionadas" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" msgstr "Engadir como Biblioteca Compartida" @@ -235,7 +238,8 @@ msgstr "Eliminar equipo" msgid "dashboard.draft-title" msgstr "Borrador" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.duplicate" msgstr "Duplicar" @@ -248,7 +252,6 @@ msgid "dashboard.empty-files" msgstr "Aínda non tes ficheiros" #: src/app/main/ui/dashboard/grid.cljs -#, markdown msgid "dashboard.empty-placeholder-drafts" msgstr "" "Ai non! Ainda non tes ficheiros! Se queres facer a proba con algún modelo " @@ -299,8 +302,8 @@ msgstr "" msgid "dashboard.export.options.all.message" msgstr "" -"os ficheiros con bibliotecas compartidas incluiranse na exportación " -"mantendo os vínculos." +"os ficheiros con bibliotecas compartidas incluiranse na exportación mantendo " +"os vínculos." msgid "dashboard.export.options.all.title" msgstr "Exportar bibliotecas compartidas" @@ -319,7 +322,8 @@ msgstr "" "biblioteca do ficheiro." msgid "dashboard.export.options.merge.title" -msgstr "Incluir os recursos de bibliotecas compartidas na biblioteca do ficheiro" +msgstr "" +"Incluir os recursos de bibliotecas compartidas na biblioteca do ficheiro" msgid "dashboard.export.title" msgstr "Exportar ficheiros" @@ -340,7 +344,6 @@ msgid_plural "dashboard.fonts.fonts-added" msgstr[0] "Engadiuse 1 fonte" msgstr[1] "Engadíronse % fontes" -#, markdown msgid "dashboard.fonts.hero-text1" msgstr "" "Calquera fonte que cargues aquí engadirase na listaxe de familias de fontes " @@ -349,7 +352,6 @@ msgstr "" "Podes cargar fontes cos seguintes formatos: **TTF, OFT e WOFF** (só se " "precisa un)." -#, markdown msgid "dashboard.fonts.hero-text2" msgstr "" "Só debes cargar fontes da túa propiedade ou das que teñas licenza para usar " @@ -368,7 +370,8 @@ msgid "dashboard.import.analyze-error" msgstr "Vaia! Non se puido importar o ficheiro" msgid "dashboard.import.import-error" -msgstr "Houbo un problema ao importar o ficheiro. Non se puido importar o ficheiro." +msgstr "" +"Houbo un problema ao importar o ficheiro. Non se puido importar o ficheiro." msgid "dashboard.import.import-message" msgstr "% ficheiros importáronse correctamente." @@ -401,7 +404,8 @@ msgstr "Enviando ficheiro: %s" msgid "dashboard.invite-profile" msgstr "Invitar ao equipo" -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.leave-team" msgstr "Abandonar o equipo" @@ -416,7 +420,8 @@ msgstr "cargando os teus ficheiros …" msgid "dashboard.loading-fonts" msgstr "cargando as túas fontes …" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.move-to" msgstr "Mover a" @@ -428,7 +433,8 @@ msgstr "Mover % ficheiros a" msgid "dashboard.move-to-other-team" msgstr "Mover a outro equipo" -#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +#: src/app/main/ui/dashboard/projects.cljs, +#: src/app/main/ui/dashboard/files.cljs msgid "dashboard.new-file" msgstr "+ Novo ficheiro" @@ -446,7 +452,8 @@ msgstr "Novo proxecto" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-msg" -msgstr "Envíame noticias, actualizacións do produto e recomendacións sobre Penpot." +msgstr "" +"Envíame noticias, actualizacións do produto e recomendacións sobre Penpot." msgid "dashboard.options" msgstr "Opcións" @@ -527,7 +534,8 @@ msgstr "Altura" msgid "handoff.attributes.layout.left" msgstr "Esquerda" -#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +#: src/app/main/ui/handoff/attributes/layout.cljs, +#: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.radius" msgstr "Radio" @@ -563,6 +571,861 @@ msgstr "Y" msgid "handoff.attributes.shadow.shorthand.spread" msgstr "S" -#, permanent msgid "handoff.attributes.stroke.alignment.center" -msgstr "Centro" \ No newline at end of file +msgstr "Centro" + +msgid "shortcuts.ungroup" +msgstr "Dispersar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Editar" + +msgid "shortcuts.duplicate" +msgstr "Duplicar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Ficheiro" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.preferences" +msgstr "Preferencias" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Compoñentes" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Cores" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Agrupar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" +msgstr "Marxe" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" +msgstr "Dereita" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Enriba" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "Ningún" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.none" +msgstr "Nada" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Editar" + +msgid "common.share-link.all-users" +msgstr "Todas as persoas usuarias de Penpot" + +msgid "common.share-link.current-tag" +msgstr "(actual)" + +msgid "common.share-link.destroy-link" +msgstr "Eliminar ligazón" + +msgid "common.share-link.manage-ops" +msgstr "Xestionar permisos" + +msgid "common.share-link.permissions-can-comment" +msgstr "Poden comentar" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Poden ver o código" + +msgid "common.share-link.permissions-pages" +msgstr "Páxinas compartidas" + +msgid "common.share-link.view-all" +msgstr "Seleccionar todas" + +msgid "common.share-link.team-members" +msgstr "Só membros do equipo" + +msgid "dashboard.export-binary-multi" +msgstr "Descargar %s ficheiros Penpot (.penpot)" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Riscar" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Suliñar" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Ningunha" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Ningunha" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Curva" + +msgid "handoff.tabs.code.selected.component" +msgstr "Compoñente" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Código" + +msgid "handoff.tabs.code.selected.frame" +msgstr "taboleiro" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Círculo" + +msgid "handoff.tabs.code.selected.group" +msgstr "Grupo" + +msgid "handoff.tabs.code.selected.image" +msgstr "Imaxe" + +msgid "handoff.tabs.code.selected.path" +msgstr "Trazado" + +msgid "handoff.tabs.code.selected.mask" +msgstr "Máscara" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Rectángulo" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.text" +msgstr "Texto" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Información" + +#: src/app/main/ui/workspace/header.cljs +msgid "label.shortcuts" +msgstr "Atallos do teclado" + +msgid "labels.accept" +msgstr "Aceptar" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.admin" +msgstr "Administración" + +#: src/app/main/ui/workspace/comments.cljs +msgid "labels.all" +msgstr "Todo" + +msgid "labels.and" +msgstr "e" + +msgid "labels.back" +msgstr "Volver" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.cancel" +msgstr "Cancelar" + +msgid "labels.centered" +msgstr "Centrado" + +msgid "labels.close" +msgstr "Pechar" + +#: src/app/main/ui/dashboard/comments.cljs +msgid "labels.comments" +msgstr "Comentarios" + +msgid "labels.content" +msgstr "Contido" + +msgid "labels.continue" +msgstr "Continuar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "labels.create" +msgstr "Crear" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.dashboard" +msgstr "Panel" + +msgid "labels.default" +msgstr "por defecto" + +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.delete" +msgstr "Eliminar" + +#: src/app/main/ui/dashboard/projects.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/files.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.drafts" +msgstr "Borradores" + +#: src/app/main/ui/comments.cljs +msgid "labels.edit" +msgstr "Editar" + +msgid "labels.font-variants" +msgstr "Estilos" + +msgid "labels.fonts" +msgstr "Fontes" + +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +msgstr "Pechar sesión" + +#: src/app/main/ui/settings/options.cljs +msgid "labels.language" +msgstr "Lingua" + +msgid "labels.link" +msgstr "Ligazón" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.member" +msgstr "Integrante" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.members" +msgstr "Integrantes" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.name" +msgstr "Nome" + +msgid "labels.next" +msgstr "Seguinte" + +#: src/app/main/ui/static.cljs +msgid "labels.not-found.main-message" +msgstr "Vaia!" + +msgid "labels.or" +msgstr "ou" + +#: src/app/main/ui/settings/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.password" +msgstr "Contrasinal" + +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.profile" +msgstr "Perfil" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.pending-invitation" +msgstr "Pendente" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.permissions" +msgstr "Permisos" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.projects" +msgstr "Proxectos" + +msgid "labels.recent" +msgstr "Recente" + +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +msgid "labels.retry" +msgstr "Volver tentar" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.role" +msgstr "Rol" + +msgid "labels.save" +msgstr "Gardar" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.send" +msgstr "Enviar" + +#: src/app/main/ui/settings/feedback.cljs +msgid "labels.sending" +msgstr "Enviando…" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.settings" +msgstr "Configuración" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.shared-libraries" +msgstr "Bibliotecas" + +msgid "labels.start" +msgstr "Comezar" + +msgid "labels.skip" +msgstr "Pasar" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.status" +msgstr "Estado" + +msgid "labels.uploading" +msgstr "Cargando…" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.tutorials" +msgstr "Titoriales" + +#: src/app/main/ui/settings/profile.cljs +msgid "labels.update" +msgstr "Actualizar" + +msgid "labels.upload" +msgstr "Cargar" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Visor" + +msgid "labels.workspace" +msgstr "Espazo de traballo" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.you" +msgstr "(ti)" + +# SECTIONS +msgid "shortcut-section.basics" +msgstr "Básicos" + +msgid "shortcut-section.dashboard" +msgstr "Panel" + +msgid "shortcut-section.viewer" +msgstr "Visor" + +msgid "shortcut-section.workspace" +msgstr "Espazo de traballo" + +# SUBSECTIONS +msgid "shortcut-subsection.alignment" +msgstr "Aliñamento" + +msgid "shortcut-subsection.general-dashboard" +msgstr "Xenérico" + +msgid "shortcut-subsection.general-viewer" +msgstr "Xenérico" + +msgid "shortcut-subsection.zoom-workspace" +msgstr "Zoom" + +msgid "shortcuts.add-comment" +msgstr "Comentarios" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Zoom" + +msgid "shortcuts.copy" +msgstr "Copiar" + +msgid "shortcuts.delete" +msgstr "Eliminar" + +msgid "shortcuts.draw-curve" +msgstr "Curva" + +msgid "shortcuts.draw-ellipse" +msgstr "Elipse" + +msgid "shortcuts.escape" +msgstr "Cancelar" + +msgid "shortcuts.mask" +msgstr "Máscara" + +msgid "shortcuts.move" +msgstr "Mover" + +msgid "shortcuts.paste" +msgstr "Pegar" + +msgid "shortcuts.redo" +msgstr "Refacer" + +msgid "shortcuts.undo" +msgstr "Desfacer" + +msgid "viewer.breaking-change.message" +msgstr "Sentímolo!" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interaccións" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Mapa do sitio" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Duplicar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Gráficos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.edit" +msgstr "Editar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Bibliotecas" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "COMPARTIDA" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografías" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Fonte" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Variante" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs, +#: src/app/main/ui/handoff/attributes/text.cljs, +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "workspace.assets.typography.sample" +msgstr "Ag" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.ungroup" +msgstr "Dispersar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.reset-zoom" +msgstr "Restablecer" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Gardado" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Gardando" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Engadir" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.hsv" +msgstr "HSV" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "BIBLIOTECA" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "BIBLIOTECAS" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "Actualizacións" + +msgid "workspace.library.libraries" +msgstr "Bibliotecas" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Actualizar" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Desenfoque" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints" +msgstr "Restricións" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.bottom" +msgstr "Embaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.leftright" +msgstr "Esquerda e Dereita" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.right" +msgstr "Dereita" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.top" +msgstr "Enriba" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.topbottom" +msgstr "Enriba e Embaixo" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.design" +msgstr "Deseño" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export" +msgstr "Exportar" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-multiple" +msgstr "Exportar selección" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "Exportación completada" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +msgid "workspace.options.export.suffix" +msgstr "Sufixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object" +msgstr "Exportando…" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-object-error" +msgstr "Erro na exportación" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Recheo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.grid-title" +msgstr "Grade" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Embaixo" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Enriba" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Esquerda" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-animation-none" +msgstr "Ningún" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-pos-center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.bottom" +msgstr "Embaixo" + +msgid "common.publish" +msgstr "Publicar" + +msgid "common.unpublish" +msgstr "Cancelar publicación" + +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Exterior" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Punteado" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Mixto" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Ningún" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Sólido" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografía" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.editor" +msgstr "Editor" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "Correo electrónico" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Caducada" + +msgid "labels.export" +msgstr "Exportar" + +msgid "labels.icons" +msgstr "Iconas" + +msgid "labels.images" +msgstr "Imaxes" + +#: src/app/main/ui/workspace/libraries.cljs, +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove" +msgstr "Retirar" + +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.rename" +msgstr "Mudar o nome" + +msgid "shortcut-subsection.edit" +msgstr "Editar" + +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Navegación" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Navegación" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Navegación" + +msgid "shortcut-subsection.panels" +msgstr "Paneis" + +msgid "shortcut-subsection.path-editor" +msgstr "Ruta" + +msgid "shortcut-subsection.shape" +msgstr "Formas" + +msgid "shortcut-subsection.tools" +msgstr "Ferramentas" + +msgid "shortcuts.draw-frame" +msgstr "Taboleiro" + +msgid "shortcuts.draw-path" +msgstr "Ruta" + +msgid "shortcuts.draw-rect" +msgstr "Rectángulo" + +msgid "shortcuts.draw-text" +msgstr "Texto" + +msgid "shortcuts.go-to-search" +msgstr "Buscar" + +msgid "shortcuts.group" +msgstr "Agrupar" + +msgid "shortcuts.unmask" +msgstr "Quitar máscara" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Recursos" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Gráficos" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.delete" +msgstr "Eliminar" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename" +msgstr "Mudar o nome" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Tamaño" + +msgid "workspace.focus.selection" +msgstr "Selección" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Fondo" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Capa" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Compoñente" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Automático" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Columna" + +msgid "workspace.options.grid.params.color" +msgstr "Cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Columnas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Marxe" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 páxina compartida" +msgstr[1] "% páxinas compartidas" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Xestión do equipo" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Aprende os conceptos básicos de Penpot mentres te divirtes con este titorial " +"práctico." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Da unha volta por Penpot e coñece as súas características principais." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Comeza a visita" + +msgid "dashboard.download-binary-file" +msgstr "Descargar ficheiro Penpot (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Descargar ficheiro estándar (.svg + .json)" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgid_plural "workspace.options.export-object" +msgstr[0] "Exportar 1 elemento" +msgstr[1] "Exportar %s elementos" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Dereita" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Un pracer volverte ver!" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot está deseñado para equipos. Convida a persoas coas que trallar en " +"proxectos e ficheiros" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Titorial práctico" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Comeza o titorial" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Bordo" + +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Interior" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Ancho" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.invitations" +msgstr "Invitacións" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.accept" +msgstr "Actualizar" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.cancel" +msgstr "Cancelar" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + +msgid "shortcuts.cut" +msgstr "Cortar" From 5caaa2d593c820baaf1fee43d76197e1067b2d30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Mon, 10 Oct 2022 15:04:16 +0000 Subject: [PATCH 095/682] :globe_with_meridians: Add translations for: Turkish. Currently translated at 100.0% (1214 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/ --- frontend/translations/tr.po | 54 +++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 1ce32b6dad..7762b18d09 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-27 15:18+0000\n" +"PO-Revision-Date: 2022-10-10 17:34+0000\n" "Last-Translator: Oğuz Ersen \n" -"Language-Team: Turkish " -"\n" +"Language-Team: Turkish \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -2669,7 +2669,7 @@ msgid "shortcuts.toggle-lock-size" msgstr "Oranları kilitle" msgid "shortcuts.toggle-rules" -msgstr "Kuralları göster/gizle" +msgstr "Cetvelleri göster/gizle" msgid "shortcuts.toggle-scale-text" msgstr "Ölçek metnini değiştir" @@ -3104,7 +3104,7 @@ msgstr "Piksel ızgarasını gizle" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-rules" -msgstr "Cetveli gizle" +msgstr "Cetvelleri gizle" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-textpalette" @@ -3159,7 +3159,7 @@ msgstr "Piksel ızgarasını göster" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-rules" -msgstr "Cetveli göster" +msgstr "Cetvelleri göster" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-textpalette" @@ -3417,7 +3417,9 @@ msgstr "Seçimi dışa aktar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "1 ögeyi dışa aktar" +msgid_plural "" +msgstr[0] "1 ögeyi dışa aktar" +msgstr[1] "%s ögeyi dışa aktar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -4771,4 +4773,40 @@ msgid "workspace.updates.update" msgstr "Güncelle" msgid "workspace.viewport.click-to-close-path" -msgstr "Yolu kapatmak için tıklayın" \ No newline at end of file +msgstr "Yolu kapatmak için tıklayın" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-in-assets" +msgstr "Varlıklar panelinde göster" + +msgid "onboarding-v2.newsletter.privacy1" +msgstr "Gizliliğe önem veriyoruz, buradan okuyabilirsiniz. " + +msgid "onboarding-v2.newsletter.privacy2" +msgstr "" +"Size yalnızca ilgili e-postaları göndereceğiz. Bültenlerimizden herhangi " +"birindeki abonelikten çıkma bağlantısını kullanarak istediğiniz zaman " +"aboneliğinizi iptal edebilirsiniz." + +msgid "onboarding-v2.newsletter.updates" +msgstr "" +"Bana ürün güncellemeleri gönder (yeni özellikler, sürümler, düzeltmeler...)." + +msgid "workspace.sidebar.collapse" +msgstr "Kenar çubuğunu daralt" + +msgid "workspace.sidebar.expand" +msgstr "Kenar çubuğunu genişlet" + +msgid "errors.profile-blocked" +msgstr "Profil engellendi" + +msgid "onboarding-v2.newsletter.desc" +msgstr "" +"Ürün geliştirme sürecinden ve haberlerden haberdar olmak için Penpot " +"bültenine abone olun." + +msgid "onboarding-v2.newsletter.news" +msgstr "" +"Bana Penpot hakkında haberler gönder (blog gönderileri, video öğreticiler, " +"yayınlar...)." From fca26f40221f3371d7eb48575da056f402bf0b61 Mon Sep 17 00:00:00 2001 From: Stas Haas Date: Mon, 10 Oct 2022 10:04:01 +0000 Subject: [PATCH 096/682] :globe_with_meridians: Add translations for: German. Currently translated at 99.0% (1203 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index e9ca1adeeb..8233f79236 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-23 20:54+0000\n" +"PO-Revision-Date: 2022-10-10 17:35+0000\n" "Last-Translator: Stas Haas \n" -"Language-Team: German " -"\n" +"Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -4811,4 +4811,7 @@ msgid "workspace.updates.update" msgstr "Aktualisieren" msgid "workspace.viewport.click-to-close-path" -msgstr "Klicken Sie, um den Pfad zu schließen" \ No newline at end of file +msgstr "Klicken Sie, um den Pfad zu schließen" + +msgid "errors.profile-blocked" +msgstr "Das Profil ist gesperrt" From 796211c655d65dbf839726f204fab7607626fd6f Mon Sep 17 00:00:00 2001 From: Eva Date: Tue, 11 Oct 2022 10:05:42 +0200 Subject: [PATCH 097/682] :bug: Fix loading placeholder --- .../resources/styles/main/partials/dashboard-grid.scss | 8 ++++++++ frontend/src/app/main/ui/dashboard/libraries.cljs | 7 ++++--- frontend/src/app/main/ui/dashboard/placeholder.cljs | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/frontend/resources/styles/main/partials/dashboard-grid.scss b/frontend/resources/styles/main/partials/dashboard-grid.scss index 0b3e344176..64341c9bb3 100644 --- a/frontend/resources/styles/main/partials/dashboard-grid.scss +++ b/frontend/resources/styles/main/partials/dashboard-grid.scss @@ -420,6 +420,14 @@ background-color: rgba(227, 227, 227, 0.3); padding: 13px; margin-right: 13px; + &.loader { + justify-items: center; + } + .icon { + display: flex; + align-items: center; + justify-content: center; + } &.libs { background-image: url(/images/ph-left.svg), url(/images/ph-right.svg); background-position: 15% bottom, 85% top; diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs index a2590d53e4..73f33447e3 100644 --- a/frontend/src/app/main/ui/dashboard/libraries.cljs +++ b/frontend/src/app/main/ui/dashboard/libraries.cljs @@ -24,10 +24,11 @@ (let [files-map (mf/deref refs/dashboard-shared-files) projects (mf/deref refs/dashboard-projects) default-project (->> projects vals (d/seek :is-default)) - files (->> (vals files-map) + files (if (nil? files-map) + nil + (->> (vals files-map) (sort-by :modified-at) - (reverse) - (not-empty)) + (reverse))) components-v2 (features/use-feature :components-v2) diff --git a/frontend/src/app/main/ui/dashboard/placeholder.cljs b/frontend/src/app/main/ui/dashboard/placeholder.cljs index 6ea8f2a096..1f093d7d78 100644 --- a/frontend/src/app/main/ui/dashboard/placeholder.cljs +++ b/frontend/src/app/main/ui/dashboard/placeholder.cljs @@ -35,7 +35,7 @@ (mf/defc loading-placeholder [] - [:div.grid-empty-placeholder + [:div.grid-empty-placeholder.loader [:div.icon i/loader] [:div.text (tr "dashboard.loading-files")]]) From 7e70f0ce30ca19576105ca705f5f7cce94614367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 14 Oct 2022 17:15:20 +0200 Subject: [PATCH 098/682] :recycle: Move internal.xxx namespaces to separated files --- common/src/app/common/types/file.cljc | 20 ++--- .../app/common/types/file/media_object.cljc | 21 +++++ common/src/app/common/types/page.cljc | 77 ++----------------- common/src/app/common/types/page/flow.cljc | 24 ++++++ common/src/app/common/types/page/grid.cljc | 44 +++++++++++ common/src/app/common/types/page/guide.cljc | 27 +++++++ common/src/app/common/types/shape.cljc | 76 ++---------------- common/src/app/common/types/shape/path.cljc | 20 +++++ common/src/app/common/types/shape/text.cljc | 72 +++++++++++++++++ 9 files changed, 226 insertions(+), 155 deletions(-) create mode 100644 common/src/app/common/types/file/media_object.cljc create mode 100644 common/src/app/common/types/page/flow.cljc create mode 100644 common/src/app/common/types/page/grid.cljc create mode 100644 common/src/app/common/types/page/guide.cljc create mode 100644 common/src/app/common/types/shape/path.cljc create mode 100644 common/src/app/common/types/shape/text.cljc diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 74f11a20bb..ed02a19a3f 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -11,12 +11,12 @@ [app.common.geom.shapes :as gsh] [app.common.pages.common :refer [file-version]] [app.common.pages.helpers :as cph] - [app.common.spec :as us] [app.common.types.color :as ctc] [app.common.types.colors-list :as ctcl] [app.common.types.component :as ctk] [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] + [app.common.types.file.media-object :as ctfm] [app.common.types.page :as ctp] [app.common.types.pages-list :as ctpl] [app.common.types.shape-tree :as ctst] @@ -28,23 +28,13 @@ ;; Specs -(s/def :internal.media-object/name string?) -(s/def :internal.media-object/width ::us/safe-integer) -(s/def :internal.media-object/height ::us/safe-integer) -(s/def :internal.media-object/mtype string?) - -;; NOTE: This is marked as nilable for backward compatibility, but -;; right now is just exists or not exists. We can thin in a gradual -;; migration and then mark it as not nilable. -(s/def :internal.media-object/path (s/nilable string?)) - (s/def ::media-object (s/keys :req-un [::id ::name - :internal.media-object/width - :internal.media-object/height - :internal.media-object/mtype] - :opt-un [:internal.media-object/path])) + ::ctfm/width + ::ctfm/height + ::ctfm/mtype] + :opt-un [::ctfm/path])) (s/def ::colors (s/map-of uuid? ::ctc/color)) diff --git a/common/src/app/common/types/file/media_object.cljc b/common/src/app/common/types/file/media_object.cljc new file mode 100644 index 0000000000..8b3f6eec2e --- /dev/null +++ b/common/src/app/common/types/file/media_object.cljc @@ -0,0 +1,21 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.file.media-object + (:require + [app.common.spec :as us] + [clojure.spec.alpha :as s])) + +(s/def ::name string?) +(s/def ::width ::us/safe-integer) +(s/def ::height ::us/safe-integer) +(s/def ::mtype string?) + +;; NOTE: This is marked as nilable for backward compatibility, but +;; right now is just exists or not exists. We can thin in a gradual +;; migration and then mark it as not nilable. +(s/def ::path (s/nilable string?)) + diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 2aee3761a6..975915ceda 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -7,85 +7,24 @@ (ns app.common.types.page (:require [app.common.data :as d] - [app.common.spec :as us] + [app.common.types.page.flow :as ctpf] + [app.common.types.page.grid :as ctpg] + [app.common.types.page.guide :as ctpu] [app.common.types.shape :as cts] [app.common.uuid :as uuid] [clojure.spec.alpha :as s])) -;; --- Grid options - -(s/def :internal.grid.color/color string?) -(s/def :internal.grid.color/opacity ::us/safe-number) - -(s/def :internal.grid/size (s/nilable ::us/safe-integer)) -(s/def :internal.grid/item-length (s/nilable ::us/safe-number)) - -(s/def :internal.grid/color (s/keys :req-un [:internal.grid.color/color - :internal.grid.color/opacity])) -(s/def :internal.grid/type #{:stretch :left :center :right}) -(s/def :internal.grid/gutter (s/nilable ::us/safe-integer)) -(s/def :internal.grid/margin (s/nilable ::us/safe-integer)) - -(s/def :internal.grid/square - (s/keys :req-un [:internal.grid/size - :internal.grid/color])) - -(s/def :internal.grid/column - (s/keys :req-un [:internal.grid/color] - :opt-un [:internal.grid/size - :internal.grid/type - :internal.grid/item-length - :internal.grid/margin - :internal.grid/gutter])) - -(s/def :internal.grid/row :internal.grid/column) - -(s/def ::saved-grids - (s/keys :opt-un [:internal.grid/square - :internal.grid/row - :internal.grid/column])) - -;; --- Background options +;; --- Background color (s/def ::background string?) -;; --- Flow options - -(s/def :internal.flow/id uuid?) -(s/def :internal.flow/name string?) -(s/def :internal.flow/starting-frame uuid?) - -(s/def ::flow - (s/keys :req-un [:internal.flow/id - :internal.flow/name - :internal.flow/starting-frame])) - -(s/def ::flows - (s/coll-of ::flow :kind vector?)) - -;; --- Guides - -(s/def :internal.guides/id uuid?) -(s/def :internal.guides/axis #{:x :y}) -(s/def :internal.guides/position ::us/safe-number) -(s/def :internal.guides/frame-id (s/nilable uuid?)) - -(s/def ::guide - (s/keys :req-un [:internal.guides/id - :internal.guides/axis - :internal.guides/position] - :opt-un [:internal.guides/frame-id])) - -(s/def ::guides - (s/map-of uuid? ::guide)) - -;; --- Page Options +;; --- Page options (s/def ::options (s/keys :opt-un [::background - ::saved-grids - ::flows - ::guides])) + ::ctpg/saved-grids + ::ctpf/flows + ::ctpu/guides])) ;; --- Page diff --git a/common/src/app/common/types/page/flow.cljc b/common/src/app/common/types/page/flow.cljc new file mode 100644 index 0000000000..20aea3019c --- /dev/null +++ b/common/src/app/common/types/page/flow.cljc @@ -0,0 +1,24 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.page.flow + (:require + [clojure.spec.alpha :as s])) + +;; --- Interaction Flows + +(s/def ::id uuid?) +(s/def ::name string?) +(s/def ::starting-frame uuid?) + +(s/def ::flow + (s/keys :req-un [::id + ::name + ::starting-frame])) + +(s/def ::flows + (s/coll-of ::flow :kind vector?)) + diff --git a/common/src/app/common/types/page/grid.cljc b/common/src/app/common/types/page/grid.cljc new file mode 100644 index 0000000000..b5d609d66e --- /dev/null +++ b/common/src/app/common/types/page/grid.cljc @@ -0,0 +1,44 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.page.grid + (:require + [app.common.spec :as us] + [clojure.spec.alpha :as s])) + +;; --- Board grids + +(s/def :grid/color string?) +(s/def :grid/opacity ::us/safe-number) + +(s/def ::size (s/nilable ::us/safe-integer)) +(s/def ::item-length (s/nilable ::us/safe-number)) + +(s/def ::color (s/keys :req-un [:grid/color + :grid/opacity])) +(s/def ::type #{:stretch :left :center :right}) +(s/def ::gutter (s/nilable ::us/safe-integer)) +(s/def ::margin (s/nilable ::us/safe-integer)) + +(s/def ::square + (s/keys :req-un [::size + ::color])) + +(s/def ::column + (s/keys :req-un [::color] + :opt-un [::size + ::type + ::item-length + ::margin + ::gutter])) + +(s/def ::row ::column) + +(s/def ::saved-grids + (s/keys :opt-un [::square + ::row + ::column])) + diff --git a/common/src/app/common/types/page/guide.cljc b/common/src/app/common/types/page/guide.cljc new file mode 100644 index 0000000000..cfff18c319 --- /dev/null +++ b/common/src/app/common/types/page/guide.cljc @@ -0,0 +1,27 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.page.guide + (:require + [app.common.spec :as us] + [clojure.spec.alpha :as s])) + +;; --- Page guides + +(s/def ::id uuid?) +(s/def ::axis #{:x :y}) +(s/def ::position ::us/safe-number) +(s/def ::frame-id (s/nilable uuid?)) + +(s/def ::guide + (s/keys :req-un [::id + ::axis + ::position] + :opt-un [::frame-id])) + +(s/def ::guides + (s/map-of uuid? ::guide)) + diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 249c589ced..21b488e4a6 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -19,8 +19,10 @@ [app.common.types.shape.export :as ctse] [app.common.types.shape.interactions :as ctsi] [app.common.types.shape.layout :as ctsl] + [app.common.types.shape.path :as ctsp] [app.common.types.shape.radius :as ctsr] [app.common.types.shape.shadow :as ctss] + [app.common.types.shape.text :as ctsx] [app.common.uuid :as uuid] [clojure.set :as set] [clojure.spec.alpha :as s])) @@ -229,74 +231,6 @@ ::opacity ::blend-mode]))) -(s/def :internal.shape.text/type #{"root" "paragraph-set" "paragraph"}) -(s/def :internal.shape.text/children - (s/coll-of :internal.shape.text/content - :kind vector? - :min-count 1)) - -(s/def :internal.shape.text/text string?) -(s/def :internal.shape.text/key string?) - -(s/def :internal.shape.text/content - (s/nilable - (s/or :text-container - (s/keys :req-un [:internal.shape.text/type] - :opt-un [:internal.shape.text/key - :internal.shape.text/children]) - :text-content - (s/keys :req-un [:internal.shape.text/text])))) - -(s/def :internal.shape.text/position-data - (s/coll-of :internal.shape.text/position-data-element - :kind vector? - :min-count 1)) - -(s/def :internal.shape.text/position-data-element - (s/keys :req-un [:internal.shape.text.position-data/x - :internal.shape.text.position-data/y - :internal.shape.text.position-data/width - :internal.shape.text.position-data/height] - :opt-un [:internal.shape.text.position-data/fill-color - :internal.shape.text.position-data/fill-opacity - :internal.shape.text.position-data/font-family - :internal.shape.text.position-data/font-size - :internal.shape.text.position-data/font-style - :internal.shape.text.position-data/font-weight - :internal.shape.text.position-data/rtl - :internal.shape.text.position-data/text - :internal.shape.text.position-data/text-decoration - :internal.shape.text.position-data/text-transform])) - -(s/def :internal.shape.text.position-data/x ::us/safe-number) -(s/def :internal.shape.text.position-data/y ::us/safe-number) -(s/def :internal.shape.text.position-data/width ::us/safe-number) -(s/def :internal.shape.text.position-data/height ::us/safe-number) - -(s/def :internal.shape.text.position-data/fill-color ::fill-color) -(s/def :internal.shape.text.position-data/fill-opacity ::fill-opacity) -(s/def :internal.shape.text.position-data/fill-color-gradient ::fill-color-gradient) - -(s/def :internal.shape.text.position-data/font-family string?) -(s/def :internal.shape.text.position-data/font-size string?) -(s/def :internal.shape.text.position-data/font-style string?) -(s/def :internal.shape.text.position-data/font-weight string?) -(s/def :internal.shape.text.position-data/rtl boolean?) -(s/def :internal.shape.text.position-data/text string?) -(s/def :internal.shape.text.position-data/text-decoration string?) -(s/def :internal.shape.text.position-data/text-transform string?) - -(s/def :internal.shape.path/command keyword?) -(s/def :internal.shape.path/params - (s/nilable (s/map-of keyword? any?))) - -(s/def :internal.shape.path/command-item - (s/keys :req-un [:internal.shape.path/command] - :opt-un [:internal.shape.path/params])) - -(s/def :internal.shape.path/content - (s/coll-of :internal.shape.path/command-item :kind vector?)) - (defmulti shape-spec :type) (defmethod shape-spec :default [_] @@ -304,12 +238,12 @@ (defmethod shape-spec :text [_] (s/and ::shape-attrs - (s/keys :opt-un [:internal.shape.text/content - :internal.shape.text/position-data]))) + (s/keys :opt-un [::ctsx/content + ::ctsx/position-data]))) (defmethod shape-spec :path [_] (s/and ::shape-attrs - (s/keys :opt-un [:internal.shape.path/content]))) + (s/keys :opt-un [::ctsp/content]))) (defmethod shape-spec :frame [_] (s/and ::shape-attrs diff --git a/common/src/app/common/types/shape/path.cljc b/common/src/app/common/types/shape/path.cljc new file mode 100644 index 0000000000..85adef0aee --- /dev/null +++ b/common/src/app/common/types/shape/path.cljc @@ -0,0 +1,20 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.shape.path + (:require + [clojure.spec.alpha :as s])) + +(s/def ::command keyword?) +(s/def ::params (s/nilable (s/map-of keyword? any?))) + +(s/def ::command-item + (s/keys :req-un [::command] + :opt-un [::params])) + +(s/def ::content + (s/coll-of ::command-item :kind vector?)) + diff --git a/common/src/app/common/types/shape/text.cljc b/common/src/app/common/types/shape/text.cljc new file mode 100644 index 0000000000..d67e1a86dd --- /dev/null +++ b/common/src/app/common/types/shape/text.cljc @@ -0,0 +1,72 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.shape.text + (:require + [app.common.spec :as us] + [app.common.types.color :as ctc] + [clojure.spec.alpha :as s])) + +(s/def ::type #{"root" "paragraph-set" "paragraph"}) +(s/def ::text string?) +(s/def ::key string?) +(s/def ::fill-color string?) +(s/def ::fill-opacity ::us/safe-number) +(s/def ::fill-color-gradient (s/nilable ::ctc/gradient)) + +(s/def ::content + (s/nilable + (s/or :text-container + (s/keys :req-un [::type] + :opt-un [::key + ::children]) + :text-content + (s/keys :req-un [::text])))) + +(s/def ::children + (s/coll-of ::content + :kind vector? + :min-count 1)) + +(s/def ::position-data + (s/coll-of ::position-data-element + :kind vector? + :min-count 1)) + +(s/def ::position-data-element + (s/keys :req-un [:position-data/x + :position-data/y + :position-data/width + :position-data/height] + :opt-un [:position-data/fill-color + :position-data/fill-opacity + :position-data/font-family + :position-data/font-size + :position-data/font-style + :position-data/font-weight + :position-data/rtl + :position-data/text + :position-data/text-decoration + :position-data/text-transform])) + +(s/def :position-data/x ::us/safe-number) +(s/def :position-data/y ::us/safe-number) +(s/def :position-data/width ::us/safe-number) +(s/def :position-data/height ::us/safe-number) + +(s/def :position-data/fill-color ::fill-color) +(s/def :position-data/fill-opacity ::fill-opacity) +(s/def :position-data/fill-color-gradient ::fill-color-gradient) + +(s/def :position-data/font-family string?) +(s/def :position-data/font-size string?) +(s/def :position-data/font-style string?) +(s/def :position-data/font-weight string?) +(s/def :position-data/rtl boolean?) +(s/def :position-data/text string?) +(s/def :position-data/text-decoration string?) +(s/def :position-data/text-transform string?) + From 160e0d218bfce88be5aa45baa7ebe01b30c9e1d7 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Wed, 12 Oct 2022 11:53:55 +0000 Subject: [PATCH 099/682] :globe_with_meridians: Add translations for: Hebrew. Currently translated at 100.0% (1214 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/ --- frontend/translations/he.po | 52 +++++++++++++++++++++++++++++++------ 1 file changed, 44 insertions(+), 8 deletions(-) diff --git a/frontend/translations/he.po b/frontend/translations/he.po index df71db2e68..2d4897b79f 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -1,16 +1,16 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-10 17:20+0000\n" +"PO-Revision-Date: 2022-10-16 13:49+0000\n" "Last-Translator: Yaron Shahrabani \n" -"Language-Team: Hebrew " -"\n" +"Language-Team: Hebrew \n" "Language: he\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=4; plural=(n == 1) ? 0 : ((n == 2) ? 1 : ((n > 10 && " "n % 10 == 0) ? 2 : 3));\n" -"X-Generator: Weblate 4.14.1-dev\n" +"X-Generator: Weblate 4.15-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -3062,7 +3062,7 @@ msgstr "הסתרת רשת פיקסלים" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-rules" -msgstr "הסתרת כללים" +msgstr "הסתרת סרגלים" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-textpalette" @@ -3117,7 +3117,7 @@ msgstr "הצגת רשת פיקסלים" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-rules" -msgstr "הצגת כללים" +msgstr "הצגת סרגלים" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-textpalette" @@ -3374,7 +3374,11 @@ msgstr "ייצוא הבחירה" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgstr "ייצוא רכיב" +msgid_plural "" +msgstr[0] "ייצוא רכיב" +msgstr[1] "ייצוא %s רכיבים" +msgstr[2] "ייצוא %s רכיבים" +msgstr[3] "ייצוא %s רכיבים" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -4725,4 +4729,36 @@ msgid "workspace.updates.update" msgstr "עדכון" msgid "workspace.viewport.click-to-close-path" -msgstr "לחיצה תסגור את הנתיב" \ No newline at end of file +msgstr "לחיצה תסגור את הנתיב" + +msgid "onboarding-v2.newsletter.news" +msgstr "נא לשלוח לי חדשות על Penpot (בלוגים, מדריכים מצולמים, שידורים…)." + +msgid "onboarding-v2.newsletter.privacy1" +msgstr "אכפת לנו מפרטיות, כאן ניתן לקרוא את " + +msgid "workspace.sidebar.collapse" +msgstr "צמצום סרגל צד" + +msgid "onboarding-v2.newsletter.privacy2" +msgstr "" +"אנו נשלח לך בדוא״ל רק הודעות שרלוונטיות לך. אפשר לבטל את המינוי דרך כפתור " +"ביטול המינוי בכל אחת מהודעות הדיוור שלנו." + +msgid "errors.profile-blocked" +msgstr "הפרופיל חסום" + +msgid "onboarding-v2.newsletter.desc" +msgstr "" +"ניתן להירשם לרשימת הדיוור של Penpot כדי להתעדכן בתהליך פיתוח המוצר ובחדשות " +"נוספות." + +msgid "onboarding-v2.newsletter.updates" +msgstr "נא לשלוח לי עדכונים על המוצר (יכולות חדשות, מהדורות, תיקונים…)." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-in-assets" +msgstr "הצגה בלוח משאבים" + +msgid "workspace.sidebar.expand" +msgstr "הרחבת סרגל צד" From ea10ec22c2f20566d8a88ffafca5e8b4b5caaba7 Mon Sep 17 00:00:00 2001 From: ascarida Date: Tue, 11 Oct 2022 14:28:17 +0000 Subject: [PATCH 100/682] :globe_with_meridians: Add translations for: Galician. Currently translated at 30.6% (372 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/gl/ --- frontend/translations/gl.po | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/frontend/translations/gl.po b/frontend/translations/gl.po index 8f99e12fa0..1ecf802883 100644 --- a/frontend/translations/gl.po +++ b/frontend/translations/gl.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-06-11 09:14+0000\n" +"PO-Revision-Date: 2022-10-16 13:49+0000\n" "Last-Translator: ascarida \n" "Language-Team: Galician \n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.13-dev\n" +"X-Generator: Weblate 4.15-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -1429,3 +1429,23 @@ msgstr "Penpot" msgid "shortcuts.cut" msgstr "Cortar" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Non hai elementos con configuración de exportación." + +msgid "dashboard.export-standard-multi" +msgstr "Descargar %s ficheiros estándar (.svg + .json)" + +msgid "dashboard.libraries-and-templates" +msgstr "Bibliotecas e Modelos" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Explora máis e descubre como contribuír" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Ocorreu un problema ao importar o modelo. Non se importou o modelo." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Subscrición ao boletín" From 8baaae1770122e491b28c14a3981e3cdfdf7b4be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tummas=20J=C3=B3han=20Sigvardsen?= Date: Wed, 12 Oct 2022 09:52:34 +0000 Subject: [PATCH 101/682] :globe_with_meridians: Add translations for: Faroese. Currently translated at 1.0% (13 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/ --- frontend/translations/fo.po | 50 +++++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index b1fc23594e..89afa14f58 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -1,6 +1,52 @@ msgid "" msgstr "" -"X-Generator: Weblate\n" +"PO-Revision-Date: 2022-10-16 13:49+0000\n" +"Last-Translator: Tummas Jóhan Sigvardsen \n" +"Language-Team: Faroese \n" +"Language: fo\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" -"Content-Transfer-Encoding: 8bit\n" \ No newline at end of file +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.15-dev\n" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Google" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "GitLab" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Hevur tú longu ein brúkara?" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Rita inn" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Innrita her" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "OpenID" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Gott at síggja teg aftur!" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "GitHub" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.email" +msgstr "Teldupostur" From 948bda7cc89b932cb273462563b28b05f9f2cd19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogi=20Napoleon=20Wennerstr=C3=B8m?= Date: Sun, 16 Oct 2022 13:49:03 +0000 Subject: [PATCH 102/682] :globe_with_meridians: Add translations for: Faroese. Currently translated at 1.0% (13 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/ --- frontend/translations/fo.po | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index 89afa14f58..9d4add4b99 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-16 13:49+0000\n" -"Last-Translator: Tummas Jóhan Sigvardsen \n" +"Last-Translator: Bogi Napoleon Wennerstrøm \n" "Language-Team: Faroese \n" "Language: fo\n" @@ -50,3 +50,15 @@ msgstr "GitHub" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs msgid "auth.email" msgstr "Teldupostur" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Vátta loyniorðið" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Vilt tú royna tað?" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Gloymt loyniorðið?" From 04f8bbb1f2068fb6a1c1022fc3be118788c1557d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 17 Oct 2022 16:32:00 +0200 Subject: [PATCH 103/682] :books: Fix copyright --- common/src/app/common/types/file/media_object.cljc | 2 +- common/src/app/common/types/page/flow.cljc | 2 +- common/src/app/common/types/page/grid.cljc | 2 +- common/src/app/common/types/page/guide.cljc | 2 +- common/src/app/common/types/shape/blur.cljc | 2 +- common/src/app/common/types/shape/export.cljc | 2 +- common/src/app/common/types/shape/interactions.cljc | 2 +- common/src/app/common/types/shape/layout.cljc | 2 +- common/src/app/common/types/shape/path.cljc | 2 +- common/src/app/common/types/shape/radius.cljc | 2 +- common/src/app/common/types/shape/shadow.cljc | 2 +- common/src/app/common/types/shape/text.cljc | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/common/src/app/common/types/file/media_object.cljc b/common/src/app/common/types/file/media_object.cljc index 8b3f6eec2e..615a4e9448 100644 --- a/common/src/app/common/types/file/media_object.cljc +++ b/common/src/app/common/types/file/media_object.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.file.media-object (:require diff --git a/common/src/app/common/types/page/flow.cljc b/common/src/app/common/types/page/flow.cljc index 20aea3019c..62f2b15048 100644 --- a/common/src/app/common/types/page/flow.cljc +++ b/common/src/app/common/types/page/flow.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.page.flow (:require diff --git a/common/src/app/common/types/page/grid.cljc b/common/src/app/common/types/page/grid.cljc index b5d609d66e..fae6e7de14 100644 --- a/common/src/app/common/types/page/grid.cljc +++ b/common/src/app/common/types/page/grid.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.page.grid (:require diff --git a/common/src/app/common/types/page/guide.cljc b/common/src/app/common/types/page/guide.cljc index cfff18c319..d7a5eca815 100644 --- a/common/src/app/common/types/page/guide.cljc +++ b/common/src/app/common/types/page/guide.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.page.guide (:require diff --git a/common/src/app/common/types/shape/blur.cljc b/common/src/app/common/types/shape/blur.cljc index 03257d8f9c..f10a935037 100644 --- a/common/src/app/common/types/shape/blur.cljc +++ b/common/src/app/common/types/shape/blur.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape.blur (:require diff --git a/common/src/app/common/types/shape/export.cljc b/common/src/app/common/types/shape/export.cljc index 057d575e09..e63430b285 100644 --- a/common/src/app/common/types/shape/export.cljc +++ b/common/src/app/common/types/shape/export.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape.export (:require diff --git a/common/src/app/common/types/shape/interactions.cljc b/common/src/app/common/types/shape/interactions.cljc index db55d26dbb..103c32a837 100644 --- a/common/src/app/common/types/shape/interactions.cljc +++ b/common/src/app/common/types/shape/interactions.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape.interactions (:require diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 4e7e7059b5..81ed9f108d 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape.layout (:require diff --git a/common/src/app/common/types/shape/path.cljc b/common/src/app/common/types/shape/path.cljc index 85adef0aee..ef0163711d 100644 --- a/common/src/app/common/types/shape/path.cljc +++ b/common/src/app/common/types/shape/path.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape.path (:require diff --git a/common/src/app/common/types/shape/radius.cljc b/common/src/app/common/types/shape/radius.cljc index a93a8a927e..94651847fc 100644 --- a/common/src/app/common/types/shape/radius.cljc +++ b/common/src/app/common/types/shape/radius.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape.radius (:require diff --git a/common/src/app/common/types/shape/shadow.cljc b/common/src/app/common/types/shape/shadow.cljc index e2e92e5868..6e00859390 100644 --- a/common/src/app/common/types/shape/shadow.cljc +++ b/common/src/app/common/types/shape/shadow.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape.shadow (:require diff --git a/common/src/app/common/types/shape/text.cljc b/common/src/app/common/types/shape/text.cljc index d67e1a86dd..bdb6768e55 100644 --- a/common/src/app/common/types/shape/text.cljc +++ b/common/src/app/common/types/shape/text.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape.text (:require From 369dc8ffb58d02174ceca10da8cf16153c525e23 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Mon, 3 Oct 2022 11:39:58 +0200 Subject: [PATCH 104/682] :tada: Basic changes to use penpot as a library --- common/src/app/common/file_builder.cljc | 14 +++- frontend/deps.edn | 7 +- frontend/shadow-cljs.edn | 2 +- frontend/src/app/libs/file_builder.cljs | 66 ++++++++++++++++++- frontend/src/app/main/broadcast.cljs | 6 +- frontend/src/app/main/ui/hooks.cljs | 28 +------- frontend/src/app/main/ui/shapes/embed.cljs | 2 +- frontend/src/app/main/ui/shapes/fills.cljs | 2 +- frontend/src/app/main/ui/shapes/shape.cljs | 3 +- .../app/main/ui/shapes/text/fontfaces.cljs | 3 +- frontend/src/app/util/storage.cljs | 6 +- 11 files changed, 96 insertions(+), 43 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 7194fdf10b..3c0bea7323 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -21,6 +21,7 @@ [app.common.types.pages-list :as ctpl] [app.common.types.shape :as cts] [app.common.uuid :as uuid] + [clojure.spec.alpha :as spec] [cuerdas.core :as str])) (def root-frame uuid/zero) @@ -52,9 +53,18 @@ (when fail-on-spec? (us/verify ::pcs/change change)) - (let [valid? (us/valid? ::pcs/change change)] + (let [valid? (us/valid? ::pcs/change change) + explain (spec/explain-str ::pcs/change change)] #?(:cljs - (when-not valid? (.warn js/console "Invalid shape" (clj->js change)))) + (when-not valid? + (do + (.warn js/console "Invalid shape" (clj->js change)) + (.warn js/console explain))) + :clj + (when-not valid? + (do + (prn "Invalid shape" change) + (prn explain)))) (cond-> file valid? diff --git a/frontend/deps.edn b/frontend/deps.edn index 0464f4daaf..b5944ea6e9 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -13,9 +13,10 @@ funcool/tubax {:mvn/version "2021.05.20-0"} funcool/rumext - {:git/tag "v2.0" - :git/sha "fc617a8" - :git/url "https://github.com/funcool/rumext.git"} + {:git/tag "v2.1" + :git/sha "6343102" + :git/url "https://github.com/funcool/rumext.git" + } instaparse/instaparse {:mvn/version "1.4.12"} garden/garden {:git/url "https://github.com/noprompt/garden" diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index b88e95a358..b532672e9b 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -53,7 +53,7 @@ :createFile app.libs.file-builder/create-file-export}}} :compiler-options - {:output-feature-set :es8 + {:output-feature-set :es2020 :output-wrapper false :warnings {:fn-deprecated false}} diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index caa7607400..1a4af9fb56 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -8,6 +8,11 @@ (:require [app.common.data :as d] [app.common.file-builder :as fb] + [app.common.uuid :as uuid] + [app.util.dom :as dom] + [app.util.zip :as uz] + [app.worker.export :as e] + [beicon.core :as rx] [cuerdas.core :as str])) (defn parse-data [data] @@ -22,6 +27,50 @@ key (-> key d/name str/kebab keyword)] [key value])) $))) + +(defn export-file + [file] + (let [file (assoc file + :name (:name file) + :file-name (:name file) + :is-shared false) + + files-stream (->> (rx/of {(:id file) file}) + (rx/share)) + + manifest-stream + (->> files-stream + (rx/map #(e/create-manifest (uuid/next) (:id file) :all %)) + (rx/map (fn [a] + (vector "manifest.json" a)))) + + render-stream + (->> files-stream + (rx/flat-map vals) + (rx/flat-map e/process-pages) + (rx/observe-on :async) + (rx/flat-map e/get-page-data) + (rx/share)) + pages-stream + (->> render-stream + (rx/map e/collect-page))] + + (rx/merge + (->> render-stream + (rx/map #(hash-map + :type :progress + :file (:id file) + :data (str "Render " (:file-name %) " - " (:name %))))) + + (->> (rx/merge + manifest-stream + pages-stream) + (rx/reduce conj []) + (rx/with-latest-from files-stream) + (rx/flat-map (fn [[data _]] + (->> (uz/compress-files data) + (rx/map #(vector file %))))))))) + (deftype File [^:mutable file] Object @@ -50,6 +99,13 @@ (closeGroup [_] (set! file (fb/close-group file))) + (addBool [_ data] + (set! file (fb/add-bool file (parse-data data))) + (str (:last-id file))) + + (closeBool [_] + (set! file (fb/close-bool file))) + (createRect [_ data] (set! file (fb/create-rect file (parse-data data))) (str (:last-id file))) @@ -78,7 +134,15 @@ (set! file (fb/close-svg-raw file))) (asMap [_] - (clj->js file))) + (clj->js file)) + + (export [_] + (->> (export-file file) + (rx/subs + (fn [value] + (when (not (contains? value :type)) + (let [[file export-blob] value] + (dom/trigger-download (:name file) export-blob)))))))) (defn create-file-export [^string name] (File. (fb/create-file name))) diff --git a/frontend/src/app/main/broadcast.cljs b/frontend/src/app/main/broadcast.cljs index 15c5da74d4..b83f2fa91e 100644 --- a/frontend/src/app/main/broadcast.cljs +++ b/frontend/src/app/main/broadcast.cljs @@ -7,6 +7,7 @@ (ns app.main.broadcast "BroadcastChannel API." (:require + [app.common.exceptions :as ex] [app.common.transit :as t] [beicon.core :as rx] [potok.core :as ptk])) @@ -18,9 +19,12 @@ (def ^:const default-topic "penpot") ;; The main broadcast channel instance, used for emit data +;; If used as a library may be we can't access js/BroadcastChannel. +;; and even if it exists we can receive an exception like: +;; Failed to construct 'BroadcastChannel': Can't create BroadcastChannel in an opaque origin (defonce default-channel (when (exists? js/BroadcastChannel) - (js/BroadcastChannel. default-topic))) + (ex/ignoring (js/BroadcastChannel. default-topic)))) (defonce stream (if (exists? js/BroadcastChannel) diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index 9a49f806ee..0d1238e409 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -7,9 +7,7 @@ (ns app.main.ui.hooks "A collection of general purpose react hooks." (:require - [app.common.data.macros :as dm] [app.common.pages :as cp] - [app.common.uuid :as uuid] [app.main.broadcast :as mbc] [app.main.data.shortcuts :as dsc] [app.main.refs :as refs] @@ -22,11 +20,6 @@ [goog.functions :as f] [rumext.v2 :as mf])) -(defn use-id - "Get a stable id value across rerenders." - [] - (mf/use-memo #(dm/str (uuid/next)))) - (defn use-rxsub [ob] (let [[state reset-state!] (mf/useState #(if (satisfies? IDeref ob) @ob nil))] @@ -253,24 +246,6 @@ (mf/set-ref-val! ref val)) (mf/ref-val ref))) -(defn- ssr? - "Checks if the current environment is run under a SSR context" - [] - (try - (not js/window) - (catch :default _e - ;; When exception accessing window we're in ssr - true))) - -(defn use-effect-ssr - "Use effect that handles SSR" - [deps effect-fn] - - (if (ssr?) - (let [ret (effect-fn)] - (when (fn? ret) (ret))) - (mf/use-effect deps effect-fn))) - (defn with-focus-objects ([objects] (let [focus (mf/deref refs/workspace-focus-selected)] @@ -298,7 +273,7 @@ localStorage. And it will keep watching events with type equals to `key` for new values." [key default] - (let [id (use-id) + (let [id (mf/use-id) state (mf/use-state (get @storage key default)) stream (mf/with-memo [id] (->> mbc/stream @@ -311,7 +286,6 @@ (swap! storage assoc key @state)) (use-stream stream (partial reset! state)) - state)) (defonce ^:private intersection-subject (rx/subject)) diff --git a/frontend/src/app/main/ui/shapes/embed.cljs b/frontend/src/app/main/ui/shapes/embed.cljs index 2105d15d2a..8d0f04b399 100644 --- a/frontend/src/app/main/ui/shapes/embed.cljs +++ b/frontend/src/app/main/ui/shapes/embed.cljs @@ -19,7 +19,7 @@ uri-data (mf/use-ref {}) state (mf/use-state 0)] - (hooks/use-effect-ssr + (mf/use-ssr-effect (mf/deps embed? urls) (fn [] (let [;; When not active the embedding we return the URI diff --git a/frontend/src/app/main/ui/shapes/fills.cljs b/frontend/src/app/main/ui/shapes/fills.cljs index 556a8020db..668ba8c399 100644 --- a/frontend/src/app/main/ui/shapes/fills.cljs +++ b/frontend/src/app/main/ui/shapes/fills.cljs @@ -80,7 +80,7 @@ (obj/set! "height" height))]) (when has-image? - [:image {:href (get embed uri uri) + [:image {:href (or (:data-uri shape) (get embed uri uri)) :preserveAspectRatio "none" :width width :height height}])]])]))))) diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 8b4a8fc2ab..c3b4c70d31 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -10,7 +10,6 @@ [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] [app.main.ui.context :as muc] - [app.main.ui.hooks :as h] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.export :as ed] [app.main.ui.shapes.fills :as fills] @@ -56,7 +55,7 @@ disable-shadows? (unchecked-get props "disable-shadows?") type (:type shape) - render-id (h/use-id) + render-id (mf/use-id) filter-id (dm/str "filter_" render-id) styles (-> (obj/create) (obj/set! "pointerEvents" pointer-events) diff --git a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs index d3da397915..6bfcdf675c 100644 --- a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs +++ b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs @@ -9,7 +9,6 @@ [app.common.data :as d] [app.common.pages.helpers :as cph] [app.main.fonts :as fonts] - [app.main.ui.hooks :as hooks] [app.main.ui.shapes.embed :as embed] [app.util.object :as obj] [beicon.core :as rx] @@ -32,7 +31,7 @@ (let [fonts-css-ref (mf/use-ref "") redraw (mf/use-state 0)] - (hooks/use-effect-ssr + (mf/use-ssr-effect (mf/deps fonts) (fn [] (let [sub diff --git a/frontend/src/app/util/storage.cljs b/frontend/src/app/util/storage.cljs index 2d920f6dc6..a290497404 100644 --- a/frontend/src/app/util/storage.cljs +++ b/frontend/src/app/util/storage.cljs @@ -6,6 +6,7 @@ (ns app.util.storage (:require + [app.common.exceptions :as ex] [app.common.transit :as t] [app.util.globals :as g] [app.util.timers :as tm])) @@ -38,7 +39,8 @@ {} (range len))))) - -(defonce storage (atom (load (unchecked-get g/global "localStorage")))) +;; Using ex/ignoring because can receive a DOMException like this when importing the code as a library: +;; Failed to read the 'localStorage' property from 'Window': Storage is disabled inside 'data:' URLs. +(defonce storage (atom (load (ex/ignoring (unchecked-get g/global "localStorage"))))) (add-watch storage :persistence #(persist js/localStorage %3 %4)) From 8463d501cdc170cb27f8390ad418666c5781c61b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 17 Oct 2022 17:06:07 +0200 Subject: [PATCH 105/682] :recycle: Remove some undeclared namespaces --- common/src/app/common/pages/changes_spec.cljc | 4 +- common/src/app/common/types/color.cljc | 94 +++++++++---------- common/src/app/common/types/file.cljc | 10 +- .../app/common/types/file/media_object.cljc | 9 ++ common/src/app/common/types/page/grid.cljc | 10 +- common/src/app/common/types/shape/shadow.cljc | 17 ++-- common/src/app/common/types/shape/text.cljc | 59 ++++++------ 7 files changed, 103 insertions(+), 100 deletions(-) diff --git a/common/src/app/common/pages/changes_spec.cljc b/common/src/app/common/pages/changes_spec.cljc index f8e1be45d1..4a6c69efa5 100644 --- a/common/src/app/common/pages/changes_spec.cljc +++ b/common/src/app/common/pages/changes_spec.cljc @@ -8,7 +8,7 @@ (:require [app.common.spec :as us] [app.common.types.color :as ctc] - [app.common.types.file :as ctf] + [app.common.types.file.media-object :as ctfm] [app.common.types.page :as ctp] [app.common.types.shape :as cts] [app.common.types.typography :as ctt] @@ -126,7 +126,7 @@ (s/keys :req-un [:internal.changes.add-recent-color/color])) -(s/def :internal.changes.add-media/object ::ctf/media-object) +(s/def :internal.changes.add-media/object ::ctfm/media-object) (defmethod change-spec :add-media [_] (s/keys :req-un [:internal.changes.add-media/object])) diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index 06c5c497ba..9ce0da1c6c 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -9,77 +9,77 @@ [app.common.data :as d] [app.common.spec :as us] [app.common.text :as txt] + [app.common.types.color.generic :as-alias color-generic] + [app.common.types.color.gradient :as-alias color-gradient] + [app.common.types.color.gradient.stop :as-alias color-gradient-stop] [clojure.spec.alpha :as s])) -;; TODO: waiting clojure 1.11 to rename this all :internal.stuff to a -;; more consistent name. - ;; TODO: maybe define ::color-hex-string with proper hex color spec? ;; --- GRADIENTS (s/def ::id uuid?) -(s/def :internal.gradient.stop/color string?) -(s/def :internal.gradient.stop/opacity ::us/safe-number) -(s/def :internal.gradient.stop/offset ::us/safe-number) +(s/def ::color-gradient/type #{:linear :radial}) +(s/def ::color-gradient/start-x ::us/safe-number) +(s/def ::color-gradient/start-y ::us/safe-number) +(s/def ::color-gradient/end-x ::us/safe-number) +(s/def ::color-gradient/end-y ::us/safe-number) +(s/def ::color-gradient/width ::us/safe-number) -(s/def :internal.gradient/type #{:linear :radial}) -(s/def :internal.gradient/start-x ::us/safe-number) -(s/def :internal.gradient/start-y ::us/safe-number) -(s/def :internal.gradient/end-x ::us/safe-number) -(s/def :internal.gradient/end-y ::us/safe-number) -(s/def :internal.gradient/width ::us/safe-number) +(s/def ::color-gradient-stop/color string?) +(s/def ::color-gradient-stop/opacity ::us/safe-number) +(s/def ::color-gradient-stop/offset ::us/safe-number) -(s/def :internal.gradient/stop - (s/keys :req-un [:internal.gradient.stop/color - :internal.gradient.stop/opacity - :internal.gradient.stop/offset])) +(s/def ::color-gradient/stop + (s/keys :req-un [::color-gradient-stop/color + ::color-gradient-stop/opacity + ::color-gradient-stop/offset])) -(s/def :internal.gradient/stops - (s/coll-of :internal.gradient/stop :kind vector?)) +(s/def ::color-gradient/stops + (s/coll-of ::color-gradient/stop :kind vector?)) (s/def ::gradient - (s/keys :req-un [:internal.gradient/type - :internal.gradient/start-x - :internal.gradient/start-y - :internal.gradient/end-x - :internal.gradient/end-y - :internal.gradient/width - :internal.gradient/stops])) + (s/keys :req-un [::color-gradient/type + ::color-gradient/start-x + ::color-gradient/start-y + ::color-gradient/end-x + ::color-gradient/end-y + ::color-gradient/width + ::color-gradient/stops])) ;; --- COLORS -(s/def :internal.color/name string?) -(s/def :internal.color/path (s/nilable string?)) -(s/def :internal.color/value (s/nilable string?)) -(s/def :internal.color/color (s/nilable string?)) -(s/def :internal.color/opacity (s/nilable ::us/safe-number)) -(s/def :internal.color/gradient (s/nilable ::gradient)) -(s/def :internal.color/ref-id uuid?) -(s/def :internal.color/ref-file uuid?) +(s/def ::color-generic/name string?) +(s/def ::color-generic/path (s/nilable string?)) +(s/def ::color-generic/value (s/nilable string?)) +(s/def ::color-generic/color (s/nilable string?)) +(s/def ::color-generic/opacity (s/nilable ::us/safe-number)) +(s/def ::color-generic/gradient (s/nilable ::gradient)) +(s/def ::color-generic/ref-id uuid?) +(s/def ::color-generic/ref-file uuid?) (s/def ::shape-color (s/keys :req-un [:us/color - :internal.color/opacity] - :opt-un [:internal.color/gradient - :internal.color/ref-id - :internal.color/ref-file])) + ::color-generic/opacity] + :opt-un [::color-generic/gradient + ::color-generic/ref-id + ::color-generic/ref-file])) (s/def ::color (s/keys :opt-un [::id - :internal.color/name - :internal.color/path - :internal.color/value - :internal.color/color - :internal.color/opacity - :internal.color/gradient])) + ::color-generic/name + ::color-generic/path + ::color-generic/value + ::color-generic/color + ::color-generic/opacity + ::color-generic/gradient])) (s/def ::recent-color - (s/keys :opt-un [:internal.color/value - :internal.color/color - :internal.color/opacity - :internal.color/gradient])) + (s/keys :opt-un [::color-generic/value + ::color-generic/color + ::color-generic/opacity + ::color-generic/gradient])) ;; --- Helpers for color in different parts of a shape diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index ed02a19a3f..1ff989e503 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -28,14 +28,6 @@ ;; Specs -(s/def ::media-object - (s/keys :req-un [::id - ::name - ::ctfm/width - ::ctfm/height - ::ctfm/mtype] - :opt-un [::ctfm/path])) - (s/def ::colors (s/map-of uuid? ::ctc/color)) @@ -49,7 +41,7 @@ (s/coll-of uuid? :kind vector?)) (s/def ::media - (s/map-of uuid? ::media-object)) + (s/map-of uuid? ::ctfm/media-object)) (s/def ::pages-index (s/map-of uuid? ::ctp/page)) diff --git a/common/src/app/common/types/file/media_object.cljc b/common/src/app/common/types/file/media_object.cljc index 615a4e9448..c5174c5051 100644 --- a/common/src/app/common/types/file/media_object.cljc +++ b/common/src/app/common/types/file/media_object.cljc @@ -9,6 +9,7 @@ [app.common.spec :as us] [clojure.spec.alpha :as s])) +(s/def ::id uuid?) (s/def ::name string?) (s/def ::width ::us/safe-integer) (s/def ::height ::us/safe-integer) @@ -19,3 +20,11 @@ ;; migration and then mark it as not nilable. (s/def ::path (s/nilable string?)) +(s/def ::media-object + (s/keys :req-un [::id + ::name + ::width + ::height + ::mtype] + :opt-un [::path])) + diff --git a/common/src/app/common/types/page/grid.cljc b/common/src/app/common/types/page/grid.cljc index fae6e7de14..ea2640be1f 100644 --- a/common/src/app/common/types/page/grid.cljc +++ b/common/src/app/common/types/page/grid.cljc @@ -7,18 +7,20 @@ (ns app.common.types.page.grid (:require [app.common.spec :as us] + [app.common.types.page.grid.color :as-alias grid-color] [clojure.spec.alpha :as s])) ;; --- Board grids -(s/def :grid/color string?) -(s/def :grid/opacity ::us/safe-number) + +(s/def ::grid-color/color string?) +(s/def ::grid-color/opacity ::us/safe-number) (s/def ::size (s/nilable ::us/safe-integer)) (s/def ::item-length (s/nilable ::us/safe-number)) -(s/def ::color (s/keys :req-un [:grid/color - :grid/opacity])) +(s/def ::color (s/keys :req-un [::grid-color/color + ::grid-color/opacity])) (s/def ::type #{:stretch :left :center :right}) (s/def ::gutter (s/nilable ::us/safe-integer)) (s/def ::margin (s/nilable ::us/safe-integer)) diff --git a/common/src/app/common/types/shape/shadow.cljc b/common/src/app/common/types/shape/shadow.cljc index 6e00859390..839152d0f5 100644 --- a/common/src/app/common/types/shape/shadow.cljc +++ b/common/src/app/common/types/shape/shadow.cljc @@ -21,7 +21,6 @@ (s/def ::spread ::us/safe-number) (s/def ::hidden boolean?) - (s/def ::color string?) (s/def ::opacity ::us/safe-number) (s/def ::gradient (s/nilable ::ctc/gradient)) @@ -36,14 +35,14 @@ ::id])) (s/def ::shadow-props - (s/keys :req-un [:internal.shadow/id - :internal.shadow/style - :shadow/color - :internal.shadow/offset-x - :internal.shadow/offset-y - :internal.shadow/blur - :internal.shadow/spread - :internal.shadow/hidden])) + (s/keys :req-un [::id + ::style + ::color + ::offset-x + ::offset-y + ::blur + ::spread + ::hidden])) (s/def ::shadow (s/coll-of ::shadow-props :kind vector?)) diff --git a/common/src/app/common/types/shape/text.cljc b/common/src/app/common/types/shape/text.cljc index bdb6768e55..2d3ed43686 100644 --- a/common/src/app/common/types/shape/text.cljc +++ b/common/src/app/common/types/shape/text.cljc @@ -8,6 +8,7 @@ (:require [app.common.spec :as us] [app.common.types.color :as ctc] + [app.common.types.shape.text.position-data :as-alias position-data] [clojure.spec.alpha :as s])) (s/def ::type #{"root" "paragraph-set" "paragraph"}) @@ -37,36 +38,36 @@ :min-count 1)) (s/def ::position-data-element - (s/keys :req-un [:position-data/x - :position-data/y - :position-data/width - :position-data/height] - :opt-un [:position-data/fill-color - :position-data/fill-opacity - :position-data/font-family - :position-data/font-size - :position-data/font-style - :position-data/font-weight - :position-data/rtl - :position-data/text - :position-data/text-decoration - :position-data/text-transform])) + (s/keys :req-un [::position-data/x + ::position-data/y + ::position-data/width + ::position-data/height] + :opt-un [::position-data/fill-color + ::position-data/fill-opacity + ::position-data/font-family + ::position-data/font-size + ::position-data/font-style + ::position-data/font-weight + ::position-data/rtl + ::position-data/text + ::position-data/text-decoration + ::position-data/text-transform])) -(s/def :position-data/x ::us/safe-number) -(s/def :position-data/y ::us/safe-number) -(s/def :position-data/width ::us/safe-number) -(s/def :position-data/height ::us/safe-number) +(s/def ::position-data/x ::us/safe-number) +(s/def ::position-data/y ::us/safe-number) +(s/def ::position-data/width ::us/safe-number) +(s/def ::position-data/height ::us/safe-number) -(s/def :position-data/fill-color ::fill-color) -(s/def :position-data/fill-opacity ::fill-opacity) -(s/def :position-data/fill-color-gradient ::fill-color-gradient) +(s/def ::position-data/fill-color ::fill-color) +(s/def ::position-data/fill-opacity ::fill-opacity) +(s/def ::position-data/fill-color-gradient ::fill-color-gradient) -(s/def :position-data/font-family string?) -(s/def :position-data/font-size string?) -(s/def :position-data/font-style string?) -(s/def :position-data/font-weight string?) -(s/def :position-data/rtl boolean?) -(s/def :position-data/text string?) -(s/def :position-data/text-decoration string?) -(s/def :position-data/text-transform string?) +(s/def ::position-data/font-family string?) +(s/def ::position-data/font-size string?) +(s/def ::position-data/font-style string?) +(s/def ::position-data/font-weight string?) +(s/def ::position-data/rtl boolean?) +(s/def ::position-data/text string?) +(s/def ::position-data/text-decoration string?) +(s/def ::position-data/text-transform string?) From ef5bc687abdaeb5131fca7bcbc37d19942f0e997 Mon Sep 17 00:00:00 2001 From: Stas Haas Date: Mon, 17 Oct 2022 07:34:25 +0000 Subject: [PATCH 106/682] :globe_with_meridians: Add translations for: German. Currently translated at 100.0% (1214 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 8233f79236..efebfcbb01 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-10 17:35+0000\n" +"PO-Revision-Date: 2022-10-18 08:01+0000\n" "Last-Translator: Stas Haas \n" "Language-Team: German \n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.14.1\n" +"X-Generator: Weblate 4.15-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -4815,3 +4815,36 @@ msgstr "Klicken Sie, um den Pfad zu schließen" msgid "errors.profile-blocked" msgstr "Das Profil ist gesperrt" + +msgid "onboarding-v2.newsletter.news" +msgstr "" +"Senden Sie mir Neuigkeiten über Penpot (Blogbeiträge, Video-Tutorials, " +"Streamings, ...)." + +msgid "onboarding-v2.newsletter.desc" +msgstr "" +"Um über den Fortschritt der Produktentwicklung und Neuigkeiten auf dem " +"Laufenden zu bleiben, abonnieren Sie den Penpot-Newsletter." + +msgid "onboarding-v2.newsletter.privacy1" +msgstr "Datenschutz ist uns wichtig, hier können Sie das nachlesen " + +msgid "onboarding-v2.newsletter.updates" +msgstr "" +"Ich möchte Informationen über Produktaktualisierungen erhalten (neue " +"Funktionen, Veröffentlichung neuer Versionen, Verbesserungen...)." + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-in-assets" +msgstr "Im Assets-Panel anzeigen" + +msgid "workspace.sidebar.collapse" +msgstr "Seitenleiste ausblenden" + +msgid "onboarding-v2.newsletter.privacy2" +msgstr "" +"Wir werden nur relevante E-Mails an Sie senden. Sie können sich jederzeit " +"über den Abmeldelink in jedem unserer Newsletter abmelden." + +msgid "workspace.sidebar.expand" +msgstr "Seitenleiste einblenden" From 845144486188b49287989e0b643b6491705c646a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tummas=20J=C3=B3han=20Sigvardsen?= Date: Tue, 18 Oct 2022 07:59:44 +0000 Subject: [PATCH 107/682] :globe_with_meridians: Add translations for: Faroese. Currently translated at 4.4% (54 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/ --- frontend/translations/fo.po | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index 9d4add4b99..2d85675c3b 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-16 13:49+0000\n" -"Last-Translator: Bogi Napoleon Wennerstrøm \n" +"PO-Revision-Date: 2022-10-18 08:01+0000\n" +"Last-Translator: Tummas Jóhan Sigvardsen \n" "Language-Team: Faroese \n" "Language: fo\n" @@ -62,3 +62,28 @@ msgstr "Vilt tú royna tað?" #: src/app/main/ui/auth/login.cljs msgid "auth.forgot-password" msgstr "Gloymt loyniorðið?" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Toymisleiðsla" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Toyma upp!" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Stovna royndarkonto" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.fullname" +msgstr "Fullfíggja navn" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Sameinadan í toymið var væleydnað" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot er fyri toymum. Bjóða limir at arbeiða saman á verkætlanir og fílur" From 00e724ce09f38fdbefaf725c6ebc8f4d503df45d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogi=20Napoleon=20Wennerstr=C3=B8m?= Date: Sun, 16 Oct 2022 13:52:09 +0000 Subject: [PATCH 108/682] :globe_with_meridians: Add translations for: Faroese. Currently translated at 4.4% (54 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/ --- frontend/translations/fo.po | 135 +++++++++++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 1 deletion(-) diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index 2d85675c3b..a0fd601d6d 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -1,7 +1,7 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-18 08:01+0000\n" -"Last-Translator: Tummas Jóhan Sigvardsen \n" +"Last-Translator: Bogi Napoleon Wennerstrøm \n" "Language-Team: Faroese \n" "Language: fo\n" @@ -87,3 +87,136 @@ msgstr "Sameinadan í toymið var væleydnað" msgid "dasboard.team-hero.text" msgstr "" "Penpot er fyri toymum. Bjóða limir at arbeiða saman á verkætlanir og fílur" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Vit senda tær ein teldupost við vegleiðing" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Gloymt loyniorð?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Broyt títt loyniorð" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Bjóða við í toymi" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Far úr toymu" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Nýggj verkætlan" + +msgid "dashboard.options" +msgstr "Valmøguleikar" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Broyt loyniorð" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Skriva eitt nýtt loyniorð" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-successfully" +msgstr "Loyniorðið er broytt" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Loyniorð" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Minst 8 stavir" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Onga konto enn?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Stovna konto" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Stovna eina konto" + +msgid "common.share-link.destroy-link" +msgstr "Strika leinki" + +msgid "common.share-link.permissions-can-comment" +msgstr "Kann viðmerkja" + +msgid "common.share-link.permissions-hint" +msgstr "Ein og hvør við leinkjuni hevur atgongd" + +msgid "common.share-link.team-members" +msgstr "Einans limir í toymi" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Broyt teldupost" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(avrita)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "Stovna nýtt toymi" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Títt Penpot" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Strika toymi" + +msgid "dashboard.download-binary-file" +msgstr "Heinta Penpot fílu (.penpot)" + +msgid "dashboard.export-binary-multi" +msgstr "Heinta %s Penpot fílur (.penpot)" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Flyt til" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Flyt %s fílur til" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Flyt til eitt annað toymi" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ Nýggja fílu" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "Nýggja fílu" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ Nýggj verkætlan" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "%s limir" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "Lat fílu upp í nýggjum skiljiblaði" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "Verkætlanir" From 5a6b7800d76657df06199561cf8c4e3ff1adfbcf Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 18 Oct 2022 13:05:14 +0200 Subject: [PATCH 109/682] Update translation files --- frontend/translations/ar.po | 194 +++++---- frontend/translations/de.po | 78 ++-- frontend/translations/en.po | 17 +- frontend/translations/es.po | 17 +- frontend/translations/fo.po | 179 ++++---- frontend/translations/gl.po | 810 ++++++++++++++++++------------------ frontend/translations/he.po | 76 ++-- frontend/translations/it.po | 118 +++--- frontend/translations/tr.po | 81 ++-- 9 files changed, 771 insertions(+), 799 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index e62b746e3d..cd9daf93d3 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Youkho \n" -"Language-Team: Arabic \n" +"Language-Team: Arabic " +"\n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -18,8 +18,7 @@ msgstr "هل لديك حساب؟" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" -msgstr "" -"تحقق من بريدك الإلكتروني وانقر على الرابط للتحقق والبدء في استخدام Penpot." +msgstr "تحقق من بريدك الإلكتروني وانقر على الرابط للتحقق والبدء في استخدام Penpot." #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" @@ -35,8 +34,7 @@ msgstr "ترغب في التجربة فحسب؟" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" -msgstr "" -"هذه خدمة تجريبية ، لا تستخدمها للعمل الحقيقي ، سيتم مسح المشاريع بشكل دوري." +msgstr "هذه خدمة تجريبية ، لا تستخدمها للعمل الحقيقي ، سيتم مسح المشاريع بشكل دوري." #: src/app/main/ui/auth/register.cljs, #: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs @@ -163,8 +161,7 @@ msgstr "شروط الخدمة" #: src/app/main/ui/auth/register.cljs msgid "auth.terms-privacy-agreement" -msgstr "" -"عند إنشاء حساب جديد ، فإنك توافق على شروط الخدمة وسياسة الخصوصية الخاصة بنا." +msgstr "عند إنشاء حساب جديد ، فإنك توافق على شروط الخدمة وسياسة الخصوصية الخاصة بنا." #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" @@ -178,7 +175,8 @@ msgstr "جميع مستخدمي Penpot" msgid "common.share-link.confirm-deletion-link-description" msgstr "" -"هل أنت متأكد أنك تريد إزالة هذا الرابط؟ إذا قمت بذلك ، فلن يكون متاحًا لأي شخص" +"هل أنت متأكد أنك تريد إزالة هذا الرابط؟ إذا قمت بذلك ، فلن يكون متاحًا لأي " +"شخص" msgid "common.share-link.current-tag" msgstr "(الحالي)" @@ -248,8 +246,7 @@ msgstr "إعمل فريق!" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.info" -msgstr "" -"تعلم أساسيات برنامج Penpot بينما تستمع بتجربته في هذه الدورة التعليمية." +msgstr "تعلم أساسيات برنامج Penpot بينما تستمع بتجربته في هذه الدورة التعليمية." #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.start" @@ -378,15 +375,15 @@ msgstr "" "أصولهم*؟" msgid "dashboard.export.options.all.message" -msgstr "" -"سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم." +msgstr "سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم." msgid "dashboard.export.options.all.title" msgstr "صدر المكتبات المشتركة" msgid "dashboard.export.options.detach.message" msgstr "" -"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى المكتبة. " +"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى " +"المكتبة. " msgid "dashboard.export.options.detach.title" msgstr "عامل أصول المكتبة المشتركة كعناصر بسيطة" @@ -430,8 +427,9 @@ msgstr "" msgid "dashboard.fonts.hero-text2" msgstr "" "يجب عليك فقط تحميل الخطوط التي تمتلكها أو لديك ترخيص لاستخدامها في Penpot. " -"اكتشف المزيد في قسم حقوق المحتوى في [شروط خدمة Penpot] (https://penpot.app/" -"terms.html). قد ترغب أيضًا في القراءة عن [ترخيص الخطوط] (2)." +"اكتشف المزيد في قسم حقوق المحتوى في [شروط خدمة Penpot] " +"(https://penpot.app/terms.html). قد ترغب أيضًا في القراءة عن [ترخيص الخطوط] " +"(2)." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -649,6 +647,10 @@ msgstr "تم نقل الملفات بنجاح" msgid "dashboard.success-move-project" msgstr "تم نقل مشروعك بنجاح" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "تبديل الفريق" + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.team-info" msgstr "معلومات الفريق" @@ -902,8 +904,8 @@ msgstr "موضوع" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"يرجى وصف سبب بريدك الإلكتروني ، وتحديد ما إذا كانت مشكلة أم فكرة أم شك. سيرد " -"أحد أعضاء فريقنا في أسرع وقت ممكن." +"يرجى وصف سبب بريدك الإلكتروني ، وتحديد ما إذا كانت مشكلة أم فكرة أم شك. " +"سيرد أحد أعضاء فريقنا في أسرع وقت ممكن." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" @@ -1340,8 +1342,7 @@ msgstr "الخطوط المتوفرة" #: src/app/main/ui/static.cljs msgid "labels.internal-error.desc-message" -msgstr "" -"شيء سيء حدث الرجاء إعادة محاولة العملية وإذا استمرت المشكلة، اتصل بالدعم." +msgstr "شيء سيء حدث الرجاء إعادة محاولة العملية وإذا استمرت المشكلة، اتصل بالدعم." #: src/app/main/ui/static.cljs msgid "labels.internal-error.main-message" @@ -1403,8 +1404,7 @@ msgstr "لا توجد دعوات." #: src/app/main/ui/dashboard/team.cljs msgid "labels.no-invitations-hint" -msgstr "" -"اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." +msgstr "اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" @@ -1668,8 +1668,7 @@ msgstr "تغيير بريدك الإلكتروني" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.change-owner-and-leave-confirm.message" -msgstr "" -"أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." +msgstr "أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" @@ -1727,14 +1726,14 @@ msgstr "حذف %s الملفات" msgid "modals.delete-font-variant.message" msgstr "" -"هل أنت متأكد أنك تريد حذف نمط هذا الخط؟ لن يتم تحميله إذا تم استخدامه في ملف." +"هل أنت متأكد أنك تريد حذف نمط هذا الخط؟ لن يتم تحميله إذا تم استخدامه في " +"ملف." msgid "modals.delete-font-variant.title" msgstr "حذف نمط الخط" msgid "modals.delete-font.message" -msgstr "" -"هل أنت متأكد أنك تريد حذف هذا الخط؟ لن يتم تحميله إذا تم استخدامه في ملف." +msgstr "هل أنت متأكد أنك تريد حذف هذا الخط؟ لن يتم تحميله إذا تم استخدامه في ملف." msgid "modals.delete-font.title" msgstr "حذف الخط" @@ -1802,8 +1801,7 @@ msgstr "ادعُ الأعضاء إلى الفريق" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" -msgstr "" -"نظرًا لأنك العضو الوحيد في هذا الفريق ، فسيتم حذف الفريق مع مشاريعه وملفاته." +msgstr "نظرًا لأنك العضو الوحيد في هذا الفريق ، فسيتم حذف الفريق مع مشاريعه وملفاته." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.message" @@ -1814,6 +1812,11 @@ msgstr "" "لا يمكنك مغادرة الفريق إذا لم يكن هناك عضو آخر للترقية إلى المالك. قد ترغب " "في حذف الفريق." +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"لا يمكنك مغادرة الفريق إذا لم يكن هناك عضو آخر للترقية إلى المالك. قد ترغب " +"في حذف الفريق." + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint1" msgstr "أنت %s المالك." @@ -1877,8 +1880,8 @@ msgstr "إزالة كمكتبة مشتركة" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.hint" msgstr "" -"بمجرد إزالتها كمكتبة مشتركة ، ستتوقف مكتبة الملفات لهذا الملف عن كونها متاحة " -"للاستخدام بين بقية ملفاتك." +"بمجرد إزالتها كمكتبة مشتركة ، ستتوقف مكتبة الملفات لهذا الملف عن كونها " +"متاحة للاستخدام بين بقية ملفاتك." #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs @@ -1981,8 +1984,8 @@ msgstr "المشاركة في المجتمع" msgid "onboarding-v2.welcome.desc3" msgstr "" -"حيث ستجد كيفية التعاون في الترجمات وطلبات الميزات والمساهمات الأساسية والبحث " -"عن الأخطاء …" +"حيث ستجد كيفية التعاون في الترجمات وطلبات الميزات والمساهمات الأساسية " +"والبحث عن الأخطاء …" msgid "onboarding-v2.welcome.desc3.title" msgstr "دليل المساهمة" @@ -2127,6 +2130,9 @@ msgstr "" msgid "onboarding.team-modal.create-team-feature-1" msgstr "ملفات ومشاريع غير محدودة" +msgid "onboarding.team-modal.create-team-feature-2" +msgstr "إصدار متعدد اللاعبين" + msgid "onboarding.team-modal.create-team-feature-3" msgstr "إدارة الأدوار" @@ -2247,6 +2253,18 @@ msgstr "محاذاة المركز عموديًا" msgid "shortcuts.artboard-selection" msgstr "إنشاء لوحة من الاختيار" +msgid "shortcuts.bool-difference" +msgstr "فرق منطقي" + +msgid "shortcuts.bool-exclude" +msgstr "استبعاد منطقي" + +msgid "shortcuts.bool-intersection" +msgstr "تقاطع منطقي" + +msgid "shortcuts.bool-union" +msgstr "الاتحاد المنطقي" + msgid "shortcuts.bring-back" msgstr "أرسل إلى الخلف" @@ -2268,6 +2286,9 @@ msgstr "إنسخ" msgid "shortcuts.create-component" msgstr "تكوين المكون" +msgid "shortcuts.create-new-project" +msgstr "أضف جديد" + msgid "shortcuts.cut" msgstr "إقطع" @@ -2394,6 +2415,45 @@ msgstr "تحرك للأعلى" msgid "shortcuts.next-frame" msgstr "اللوحة التالية" +msgid "shortcuts.not-found" +msgstr "لا يوجد إختصارات" + +msgid "shortcuts.opacity-0" +msgstr "ضبط التعتيم على 100٪" + +msgid "shortcuts.opacity-1" +msgstr "اضبط التعتيم على 10٪" + +msgid "shortcuts.opacity-2" +msgstr "اضبط التعتيم على 20٪" + +msgid "shortcuts.opacity-3" +msgstr "اضبط التعتيم على 30%" + +msgid "shortcuts.opacity-4" +msgstr "اضبط التعتيم على 40%" + +msgid "shortcuts.opacity-5" +msgstr "اضبط التعتيم على 50%" + +msgid "shortcuts.opacity-6" +msgstr "اضبط التعتيم على 60%" + +msgid "shortcuts.opacity-7" +msgstr "اضبط التعتيم على 70%" + +msgid "shortcuts.opacity-8" +msgstr "اضبط التعتيم على 80%" + +msgid "shortcuts.opacity-9" +msgstr "اضبط التعتيم على 90%" + +msgid "shortcuts.open-color-picker" +msgstr "أداة انتقاء اللون" + +msgid "shortcuts.open-dashboard" +msgstr "إذهب إلى لوحة المعلومات" + #: src/app/main/ui/dashboard/files.cljs msgid "title.dashboard.files" msgstr "%s - Penpot" @@ -3081,70 +3141,4 @@ msgid "workspace.updates.update" msgstr "تحديث" msgid "workspace.viewport.click-to-close-path" -msgstr "انقر لإغلاق المسار" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.switch-team" -msgstr "تبديل الفريق" - -msgid "modals.leave-and-reassign.forbiden" -msgstr "" -"لا يمكنك مغادرة الفريق إذا لم يكن هناك عضو آخر للترقية إلى المالك. قد ترغب " -"في حذف الفريق." - -msgid "shortcuts.bool-union" -msgstr "الاتحاد المنطقي" - -msgid "shortcuts.create-new-project" -msgstr "أضف جديد" - -msgid "shortcuts.not-found" -msgstr "لا يوجد إختصارات" - -msgid "shortcuts.opacity-4" -msgstr "اضبط التعتيم على 40%" - -msgid "shortcuts.opacity-5" -msgstr "اضبط التعتيم على 50%" - -msgid "shortcuts.bool-intersection" -msgstr "تقاطع منطقي" - -msgid "shortcuts.opacity-9" -msgstr "اضبط التعتيم على 90%" - -msgid "shortcuts.open-color-picker" -msgstr "أداة انتقاء اللون" - -msgid "onboarding.team-modal.create-team-feature-2" -msgstr "إصدار متعدد اللاعبين" - -msgid "shortcuts.bool-difference" -msgstr "فرق منطقي" - -msgid "shortcuts.opacity-1" -msgstr "اضبط التعتيم على 10٪" - -msgid "shortcuts.opacity-0" -msgstr "ضبط التعتيم على 100٪" - -msgid "shortcuts.open-dashboard" -msgstr "إذهب إلى لوحة المعلومات" - -msgid "shortcuts.bool-exclude" -msgstr "استبعاد منطقي" - -msgid "shortcuts.opacity-2" -msgstr "اضبط التعتيم على 20٪" - -msgid "shortcuts.opacity-6" -msgstr "اضبط التعتيم على 60%" - -msgid "shortcuts.opacity-3" -msgstr "اضبط التعتيم على 30%" - -msgid "shortcuts.opacity-8" -msgstr "اضبط التعتيم على 80%" - -msgid "shortcuts.opacity-7" -msgstr "اضبط التعتيم على 70%" +msgstr "انقر لإغلاق المسار" \ No newline at end of file diff --git a/frontend/translations/de.po b/frontend/translations/de.po index efebfcbb01..4019da1d85 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-18 08:01+0000\n" "Last-Translator: Stas Haas \n" -"Language-Team: German \n" +"Language-Team: German " +"\n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -846,6 +846,9 @@ msgstr "Bestätigungspasswort muss übereinstimmen" msgid "errors.password-too-short" msgstr "Das Passwort sollte mindestens 8 Zeichen lang sein" +msgid "errors.profile-blocked" +msgstr "Das Profil ist gesperrt" + #: src/app/main/ui/auth/recovery_request.cljs, #: src/app/main/ui/settings/change_email.cljs, #: src/app/main/ui/dashboard/team.cljs @@ -2062,6 +2065,29 @@ msgstr "Video-Tutorials" msgid "onboarding-v2.before-start.title" msgstr "Bevor Sie beginnen" +msgid "onboarding-v2.newsletter.desc" +msgstr "" +"Um über den Fortschritt der Produktentwicklung und Neuigkeiten auf dem " +"Laufenden zu bleiben, abonnieren Sie den Penpot-Newsletter." + +msgid "onboarding-v2.newsletter.news" +msgstr "" +"Senden Sie mir Neuigkeiten über Penpot (Blogbeiträge, Video-Tutorials, " +"Streamings, ...)." + +msgid "onboarding-v2.newsletter.privacy1" +msgstr "Datenschutz ist uns wichtig, hier können Sie das nachlesen " + +msgid "onboarding-v2.newsletter.privacy2" +msgstr "" +"Wir werden nur relevante E-Mails an Sie senden. Sie können sich jederzeit " +"über den Abmeldelink in jedem unserer Newsletter abmelden." + +msgid "onboarding-v2.newsletter.updates" +msgstr "" +"Ich möchte Informationen über Produktaktualisierungen erhalten (neue " +"Funktionen, Veröffentlichung neuer Versionen, Verbesserungen...)." + msgid "onboarding-v2.welcome.desc1" msgstr "" "Penpot ist Open Source und wird sowohl von Kaleidos als auch von der " @@ -4545,6 +4571,10 @@ msgstr "Ebene auswählen" msgid "workspace.shape.menu.show" msgstr "Anzeigen" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-in-assets" +msgstr "Im Assets-Panel anzeigen" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.show-main" @@ -4586,6 +4616,12 @@ msgstr "Hauptkomponenten aktualisieren" msgid "workspace.shape.menu.update-main" msgstr "Hauptkomponente aktualisieren" +msgid "workspace.sidebar.collapse" +msgstr "Seitenleiste ausblenden" + +msgid "workspace.sidebar.expand" +msgstr "Seitenleiste einblenden" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.sidebar.history" msgstr "Verlauf (%s)" @@ -4811,40 +4847,4 @@ msgid "workspace.updates.update" msgstr "Aktualisieren" msgid "workspace.viewport.click-to-close-path" -msgstr "Klicken Sie, um den Pfad zu schließen" - -msgid "errors.profile-blocked" -msgstr "Das Profil ist gesperrt" - -msgid "onboarding-v2.newsletter.news" -msgstr "" -"Senden Sie mir Neuigkeiten über Penpot (Blogbeiträge, Video-Tutorials, " -"Streamings, ...)." - -msgid "onboarding-v2.newsletter.desc" -msgstr "" -"Um über den Fortschritt der Produktentwicklung und Neuigkeiten auf dem " -"Laufenden zu bleiben, abonnieren Sie den Penpot-Newsletter." - -msgid "onboarding-v2.newsletter.privacy1" -msgstr "Datenschutz ist uns wichtig, hier können Sie das nachlesen " - -msgid "onboarding-v2.newsletter.updates" -msgstr "" -"Ich möchte Informationen über Produktaktualisierungen erhalten (neue " -"Funktionen, Veröffentlichung neuer Versionen, Verbesserungen...)." - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show-in-assets" -msgstr "Im Assets-Panel anzeigen" - -msgid "workspace.sidebar.collapse" -msgstr "Seitenleiste ausblenden" - -msgid "onboarding-v2.newsletter.privacy2" -msgstr "" -"Wir werden nur relevante E-Mails an Sie senden. Sie können sich jederzeit " -"über den Abmeldelink in jedem unserer Newsletter abmelden." - -msgid "workspace.sidebar.expand" -msgstr "Seitenleiste einblenden" +msgstr "Klicken Sie, um den Pfad zu schließen" \ No newline at end of file diff --git a/frontend/translations/en.po b/frontend/translations/en.po index c60ec067c3..89b266192e 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -729,6 +729,12 @@ msgstr "Authentication provider not configured." msgid "errors.auth.unable-to-login" msgstr "Looks like you are not authenticated or session expired." +msgid "errors.bad-font" +msgstr "The font %s could not be loaded" + +msgid "errors.bad-font-plural" +msgstr "The fonts %s could not be loaded" + #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" msgstr "Your browser cannot do this operation" @@ -4563,13 +4569,4 @@ msgid "workspace.updates.update" msgstr "Update" msgid "workspace.viewport.click-to-close-path" -msgstr "Click to close the path" - -msgid "errors.profile-blocked" -msgstr "The profile is blocked" - -msgid "errors.bad-font" -msgstr "The font %s could not be loaded" - -msgid "errors.bad-font-plural" -msgstr "The fonts %s could not be loaded" +msgstr "Click to close the path" \ No newline at end of file diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 9c0c2ea1fa..9081e2c186 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -749,6 +749,12 @@ msgstr "Proveedor de autenticación no configurado." msgid "errors.auth.unable-to-login" msgstr "Parece que no has iniciado sesión, o la sesión ha expirado." +msgid "errors.bad-font" +msgstr "No se ha podido cargar la fuente %s" + +msgid "errors.bad-font-plural" +msgstr "No se han podido cargar las fuentes %s" + #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" msgstr "Tu navegador no puede realizar esta operación" @@ -4778,13 +4784,4 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" - -msgid "errors.profile-blocked" -msgstr "El perfil esta blockeado" - -msgid "errors.bad-font" -msgstr "No se ha podido cargar la fuente %s" - -msgid "errors.bad-font-plural" -msgstr "No se han podido cargar las fuentes %s" +msgstr "Pulsar para cerrar la ruta" \ No newline at end of file diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index a0fd601d6d..729109203a 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-18 08:01+0000\n" "Last-Translator: Bogi Napoleon Wennerstrøm \n" -"Language-Team: Faroese \n" +"Language-Team: Faroese " +"\n" "Language: fo\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -11,82 +11,85 @@ msgstr "" "Plural-Forms: nplurals=2; plural=n != 1;\n" "X-Generator: Weblate 4.15-dev\n" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-google-submit" -msgstr "Google" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-gitlab-submit" -msgstr "GitLab" - #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" msgstr "Hevur tú longu ein brúkara?" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-submit" -msgstr "Rita inn" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.login-here" -msgstr "Innrita her" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-oidc-submit" -msgstr "OpenID" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-title" -msgstr "Gott at síggja teg aftur!" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-ldap-submit" -msgstr "LDAP" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-github-submit" -msgstr "GitHub" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.email" -msgstr "Teldupostur" - #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" msgstr "Vátta loyniorðið" +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Stovna royndarkonto" + #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.create-demo-profile" msgstr "Vilt tú royna tað?" +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.email" +msgstr "Teldupostur" + #: src/app/main/ui/auth/login.cljs msgid "auth.forgot-password" msgstr "Gloymt loyniorðið?" -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Toymisleiðsla" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Toyma upp!" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.create-demo-account" -msgstr "Stovna royndarkonto" - #: src/app/main/ui/auth/register.cljs msgid "auth.fullname" msgstr "Fullfíggja navn" +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Innrita her" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Rita inn" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Gott at síggja teg aftur!" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "GitHub" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "GitLab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Google" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "OpenID" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Skriva eitt nýtt loyniorð" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-successfully" +msgstr "Loyniorðið er broytt" + #: src/app/main/ui/auth/verify_token.cljs msgid "auth.notifications.team-invitation-accepted" msgstr "Sameinadan í toymið var væleydnað" -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot er fyri toymum. Bjóða limir at arbeiða saman á verkætlanir og fílur" +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Loyniorð" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Minst 8 stavir" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.recovery-request-subtitle" @@ -100,41 +103,6 @@ msgstr "Gloymt loyniorð?" msgid "auth.recovery-submit" msgstr "Broyt títt loyniorð" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.invite-profile" -msgstr "Bjóða við í toymi" - -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.leave-team" -msgstr "Far úr toymu" - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-project-prefix" -msgstr "Nýggj verkætlan" - -msgid "dashboard.options" -msgstr "Valmøguleikar" - -#: src/app/main/ui/settings/password.cljs -msgid "dashboard.password-change" -msgstr "Broyt loyniorð" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.new-password" -msgstr "Skriva eitt nýtt loyniorð" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.password-changed-successfully" -msgstr "Loyniorðið er broytt" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.password" -msgstr "Loyniorð" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.password-length-hint" -msgstr "Minst 8 stavir" - #: src/app/main/ui/auth/login.cljs msgid "auth.register" msgstr "Onga konto enn?" @@ -159,6 +127,18 @@ msgstr "Ein og hvør við leinkjuni hevur atgongd" msgid "common.share-link.team-members" msgstr "Einans limir í toymi" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Toymisleiðsla" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "Penpot er fyri toymum. Bjóða limir at arbeiða saman á verkætlanir og fílur" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Toyma upp!" + #: src/app/main/ui/settings/profile.cljs msgid "dashboard.change-email" msgstr "Broyt teldupost" @@ -185,6 +165,14 @@ msgstr "Heinta Penpot fílu (.penpot)" msgid "dashboard.export-binary-multi" msgstr "Heinta %s Penpot fílur (.penpot)" +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "Bjóða við í toymi" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "Far úr toymu" + #: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.move-to" msgstr "Flyt til" @@ -209,6 +197,10 @@ msgstr "Nýggja fílu" msgid "dashboard.new-project" msgstr "+ Nýggj verkætlan" +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Nýggj verkætlan" + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.num-of-members" msgstr "%s limir" @@ -217,6 +209,13 @@ msgstr "%s limir" msgid "dashboard.open-in-new-tab" msgstr "Lat fílu upp í nýggjum skiljiblaði" +msgid "dashboard.options" +msgstr "Valmøguleikar" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "Broyt loyniorð" + #: src/app/main/ui/dashboard/projects.cljs msgid "dashboard.projects-title" -msgstr "Verkætlanir" +msgstr "Verkætlanir" \ No newline at end of file diff --git a/frontend/translations/gl.po b/frontend/translations/gl.po index 1ecf802883..953f7f8b52 100644 --- a/frontend/translations/gl.po +++ b/frontend/translations/gl.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-16 13:49+0000\n" "Last-Translator: ascarida \n" -"Language-Team: Galician \n" +"Language-Team: Galician " +"\n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -60,6 +60,10 @@ msgstr "Entra aquí" msgid "auth.login-submit" msgstr "Entrar" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Un pracer volverte ver!" + #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" msgstr "GitHub" @@ -102,8 +106,7 @@ msgstr "Perfil sen verificar, valida o perfil antes de continuar." #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.recovery-token-sent" -msgstr "" -"Enviouse ó teu correo electrónico un enlace co que recuperar o contrasinal." +msgstr "Enviouse ó teu correo electrónico un enlace co que recuperar o contrasinal." #: src/app/main/ui/auth/verify_token.cljs msgid "auth.notifications.team-invitation-accepted" @@ -169,11 +172,23 @@ msgstr "" msgid "auth.verification-email-sent" msgstr "Enviamos un correo electrónico de verificación a" +msgid "common.publish" +msgstr "Publicar" + +msgid "common.share-link.all-users" +msgstr "Todas as persoas usuarias de Penpot" + msgid "common.share-link.confirm-deletion-link-description" msgstr "" "Seguro que queres eliminar esta ligazón? Se o fas, non estará dispoñible " "para ninguén" +msgid "common.share-link.current-tag" +msgstr "(actual)" + +msgid "common.share-link.destroy-link" +msgstr "Eliminar ligazón" + msgid "common.share-link.get-link" msgstr "Obter ligazón" @@ -183,24 +198,47 @@ msgstr "A ligazón copiouse correctamente" msgid "common.share-link.link-deleted-success" msgstr "A ligazón eliminouse correctamente" +msgid "common.share-link.manage-ops" +msgstr "Xestionar permisos" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 páxina compartida" +msgstr[1] "% páxinas compartidas" + msgid "common.share-link.permissions-can-access" msgstr "Pode acceder a" +msgid "common.share-link.permissions-can-comment" +msgstr "Poden comentar" + +msgid "common.share-link.permissions-can-inspect" +msgstr "Poden ver o código" + msgid "common.share-link.permissions-can-view" msgstr "Pode ver" msgid "common.share-link.permissions-hint" msgstr "Calquera persoa ca ligazón terá acceso" +msgid "common.share-link.permissions-pages" +msgstr "Páxinas compartidas" + msgid "common.share-link.placeholder" msgstr "A ligazón para compartir aparecerá aquí" msgid "common.share-link.remove-link" msgstr "Eliminar ligazón" +msgid "common.share-link.team-members" +msgstr "Só membros do equipo" + msgid "common.share-link.title" msgstr "Compartir prototipos" +msgid "common.share-link.view-all" +msgstr "Seleccionar todas" + msgid "common.share-link.view-all-pages" msgstr "Todas as páxinas" @@ -210,6 +248,41 @@ msgstr "Só esta páxina" msgid "common.share-link.view-selected-pages" msgstr "Páxinas seleccionadas" +msgid "common.unpublish" +msgstr "Cancelar publicación" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.management" +msgstr "Xestión do equipo" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.text" +msgstr "" +"Penpot está deseñado para equipos. Convida a persoas coas que trallar en " +"proxectos e ficheiros" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.info" +msgstr "" +"Aprende os conceptos básicos de Penpot mentres te divirtes con este " +"titorial práctico." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Comeza o titorial" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.title" +msgstr "Titorial práctico" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.info" +msgstr "Da unha volta por Penpot e coñece as súas características principais." + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Comeza a visita" + #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" @@ -235,6 +308,12 @@ msgstr "O teu Penpot" msgid "dashboard.delete-team" msgstr "Eliminar equipo" +msgid "dashboard.download-binary-file" +msgstr "Descargar ficheiro Penpot (.penpot)" + +msgid "dashboard.download-standard-file" +msgstr "Descargar ficheiro estándar (.svg + .json)" + msgid "dashboard.draft-title" msgstr "Borrador" @@ -257,6 +336,9 @@ msgstr "" "Ai non! Ainda non tes ficheiros! Se queres facer a proba con algún modelo " "vai a [Bibliotecas e modelos] (https://penpot.app/libraries-templates.html)" +msgid "dashboard.export-binary-multi" +msgstr "Descargar %s ficheiros Penpot (.penpot)" + msgid "dashboard.export-frames" msgstr "Exportar marcos a PDF" @@ -285,6 +367,10 @@ msgstr "" msgid "dashboard.export-shapes.how-to-link" msgstr "Información sobre como configurar as exportacións en Penpot." +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Non hai elementos con configuración de exportación." + #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.title" msgstr "Exportar selección" @@ -292,6 +378,9 @@ msgstr "Exportar selección" msgid "dashboard.export-single" msgstr "Exportar arquivo Penpot" +msgid "dashboard.export-standard-multi" +msgstr "Descargar %s ficheiros estándar (.svg + .json)" + msgid "dashboard.export.detail" msgstr "* Pode incluir compoñentes, gráficos, cores e/ou fontes." @@ -302,8 +391,8 @@ msgstr "" msgid "dashboard.export.options.all.message" msgstr "" -"os ficheiros con bibliotecas compartidas incluiranse na exportación mantendo " -"os vínculos." +"os ficheiros con bibliotecas compartidas incluiranse na exportación " +"mantendo os vínculos." msgid "dashboard.export.options.all.title" msgstr "Exportar bibliotecas compartidas" @@ -322,8 +411,7 @@ msgstr "" "biblioteca do ficheiro." msgid "dashboard.export.options.merge.title" -msgstr "" -"Incluir os recursos de bibliotecas compartidas na biblioteca do ficheiro" +msgstr "Incluir os recursos de bibliotecas compartidas na biblioteca do ficheiro" msgid "dashboard.export.title" msgstr "Exportar ficheiros" @@ -370,8 +458,7 @@ msgid "dashboard.import.analyze-error" msgstr "Vaia! Non se puido importar o ficheiro" msgid "dashboard.import.import-error" -msgstr "" -"Houbo un problema ao importar o ficheiro. Non se puido importar o ficheiro." +msgstr "Houbo un problema ao importar o ficheiro. Non se puido importar o ficheiro." msgid "dashboard.import.import-message" msgstr "% ficheiros importáronse correctamente." @@ -409,6 +496,15 @@ msgstr "Invitar ao equipo" msgid "dashboard.leave-team" msgstr "Abandonar o equipo" +msgid "dashboard.libraries-and-templates" +msgstr "Bibliotecas e Modelos" + +msgid "dashboard.libraries-and-templates.explore" +msgstr "Explora máis e descubre como contribuír" + +msgid "dashboard.libraries-and-templates.import-error" +msgstr "Ocorreu un problema ao importar o modelo. Non se importou o modelo." + #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" msgstr "Bibliotecas compartidas" @@ -452,8 +548,11 @@ msgstr "Novo proxecto" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-msg" -msgstr "" -"Envíame noticias, actualizacións do produto e recomendacións sobre Penpot." +msgstr "Envíame noticias, actualizacións do produto e recomendacións sobre Penpot." + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.newsletter-title" +msgstr "Subscrición ao boletín" msgid "dashboard.options" msgstr "Opcións" @@ -571,103 +670,41 @@ msgstr "Y" msgid "handoff.attributes.shadow.shorthand.spread" msgstr "S" +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Bordo" + msgid "handoff.attributes.stroke.alignment.center" msgstr "Centro" -msgid "shortcuts.ungroup" -msgstr "Dispersar" +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Interior" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.edit" -msgstr "Editar" +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Exterior" -msgid "shortcuts.duplicate" -msgstr "Duplicar" +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Punteado" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.file" -msgstr "Ficheiro" +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Mixto" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.option.preferences" -msgstr "Preferencias" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.components" -msgstr "Compoñentes" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.colors" -msgstr "Cores" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.group" -msgstr "Agrupar" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.left" -msgstr "Esquerda" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout.margin" -msgstr "Marxe" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.right" -msgstr "Dereita" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.top" -msgstr "Enriba" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke-cap.none" +msgid "handoff.attributes.stroke.style.none" msgstr "Ningún" -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.center" -msgstr "Centro" +msgid "handoff.attributes.stroke.style.solid" +msgstr "Sólido" -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.none" -msgstr "Nada" +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Ancho" -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.edit" -msgstr "Editar" +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografía" -msgid "common.share-link.all-users" -msgstr "Todas as persoas usuarias de Penpot" - -msgid "common.share-link.current-tag" -msgstr "(actual)" - -msgid "common.share-link.destroy-link" -msgstr "Eliminar ligazón" - -msgid "common.share-link.manage-ops" -msgstr "Xestionar permisos" - -msgid "common.share-link.permissions-can-comment" -msgstr "Poden comentar" - -msgid "common.share-link.permissions-can-inspect" -msgstr "Poden ver o código" - -msgid "common.share-link.permissions-pages" -msgstr "Páxinas compartidas" - -msgid "common.share-link.view-all" -msgstr "Seleccionar todas" - -msgid "common.share-link.team-members" -msgstr "Só membros do equipo" - -msgid "dashboard.export-binary-multi" -msgstr "Descargar %s ficheiros Penpot (.penpot)" +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Ningunha" msgid "handoff.attributes.typography.text-decoration.strikethrough" msgstr "Riscar" @@ -675,40 +712,37 @@ msgstr "Riscar" msgid "handoff.attributes.typography.text-decoration.underline" msgstr "Suliñar" -msgid "handoff.attributes.typography.text-decoration.none" -msgstr "Ningunha" - msgid "handoff.attributes.typography.text-transform.none" msgstr "Ningunha" -msgid "handoff.tabs.code.selected.curve" -msgstr "Curva" - -msgid "handoff.tabs.code.selected.component" -msgstr "Compoñente" - #: src/app/main/ui/handoff/right_sidebar.cljs msgid "handoff.tabs.code" msgstr "Código" -msgid "handoff.tabs.code.selected.frame" -msgstr "taboleiro" - msgid "handoff.tabs.code.selected.circle" msgstr "Círculo" +msgid "handoff.tabs.code.selected.component" +msgstr "Compoñente" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Curva" + +msgid "handoff.tabs.code.selected.frame" +msgstr "taboleiro" + msgid "handoff.tabs.code.selected.group" msgstr "Grupo" msgid "handoff.tabs.code.selected.image" msgstr "Imaxe" -msgid "handoff.tabs.code.selected.path" -msgstr "Trazado" - msgid "handoff.tabs.code.selected.mask" msgstr "Máscara" +msgid "handoff.tabs.code.selected.path" +msgstr "Trazado" + msgid "handoff.tabs.code.selected.rect" msgstr "Rectángulo" @@ -790,15 +824,37 @@ msgstr "Borradores" msgid "labels.edit" msgstr "Editar" +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.editor" +msgstr "Editor" + +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs +msgid "labels.email" +msgstr "Correo electrónico" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.expired-invitation" +msgstr "Caducada" + +msgid "labels.export" +msgstr "Exportar" + msgid "labels.font-variants" msgstr "Estilos" msgid "labels.fonts" msgstr "Fontes" -#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.logout" -msgstr "Pechar sesión" +msgid "labels.icons" +msgstr "Iconas" + +msgid "labels.images" +msgstr "Imaxes" + +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.invitations" +msgstr "Invitacións" #: src/app/main/ui/settings/options.cljs msgid "labels.language" @@ -807,6 +863,10 @@ msgstr "Lingua" msgid "labels.link" msgstr "Ligazón" +#: src/app/main/ui/settings.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "labels.logout" +msgstr "Pechar sesión" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.member" msgstr "Integrante" @@ -834,10 +894,6 @@ msgstr "ou" msgid "labels.password" msgstr "Contrasinal" -#: src/app/main/ui/settings/sidebar.cljs -msgid "labels.profile" -msgstr "Perfil" - #: src/app/main/ui/dashboard/team.cljs msgid "labels.pending-invitation" msgstr "Pendente" @@ -846,6 +902,10 @@ msgstr "Pendente" msgid "labels.permissions" msgstr "Permisos" +#: src/app/main/ui/settings/sidebar.cljs +msgid "labels.profile" +msgstr "Perfil" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.projects" msgstr "Proxectos" @@ -853,6 +913,17 @@ msgstr "Proxectos" msgid "labels.recent" msgstr "Recente" +#: src/app/main/ui/workspace/libraries.cljs, +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.remove" +msgstr "Retirar" + +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.rename" +msgstr "Mudar o nome" + #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Volver tentar" @@ -881,19 +952,16 @@ msgstr "Configuración" msgid "labels.shared-libraries" msgstr "Bibliotecas" -msgid "labels.start" -msgstr "Comezar" - msgid "labels.skip" msgstr "Pasar" +msgid "labels.start" +msgstr "Comezar" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.status" msgstr "Estado" -msgid "labels.uploading" -msgstr "Cargando…" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.tutorials" msgstr "Titoriales" @@ -905,6 +973,9 @@ msgstr "Actualizar" msgid "labels.upload" msgstr "Cargar" +msgid "labels.uploading" +msgstr "Cargando…" + #: src/app/main/ui/dashboard/team.cljs msgid "labels.viewer" msgstr "Visor" @@ -916,6 +987,19 @@ msgstr "Espazo de traballo" msgid "labels.you" msgstr "(ti)" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.accept" +msgstr "Actualizar" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs +msgid "modals.update-remote-component.cancel" +msgstr "Cancelar" + +msgid "onboarding.welcome.alt" +msgstr "Penpot" + # SECTIONS msgid "shortcut-section.basics" msgstr "Básicos" @@ -933,24 +1017,51 @@ msgstr "Espazo de traballo" msgid "shortcut-subsection.alignment" msgstr "Aliñamento" +msgid "shortcut-subsection.edit" +msgstr "Editar" + msgid "shortcut-subsection.general-dashboard" msgstr "Xenérico" msgid "shortcut-subsection.general-viewer" msgstr "Xenérico" +msgid "shortcut-subsection.navigation-dashboard" +msgstr "Navegación" + +msgid "shortcut-subsection.navigation-viewer" +msgstr "Navegación" + +msgid "shortcut-subsection.navigation-workspace" +msgstr "Navegación" + +msgid "shortcut-subsection.panels" +msgstr "Paneis" + +msgid "shortcut-subsection.path-editor" +msgstr "Ruta" + +msgid "shortcut-subsection.shape" +msgstr "Formas" + +msgid "shortcut-subsection.tools" +msgstr "Ferramentas" + +msgid "shortcut-subsection.zoom-viewer" +msgstr "Zoom" + msgid "shortcut-subsection.zoom-workspace" msgstr "Zoom" msgid "shortcuts.add-comment" msgstr "Comentarios" -msgid "shortcut-subsection.zoom-viewer" -msgstr "Zoom" - msgid "shortcuts.copy" msgstr "Copiar" +msgid "shortcuts.cut" +msgstr "Cortar" + msgid "shortcuts.delete" msgstr "Eliminar" @@ -960,9 +1071,30 @@ msgstr "Curva" msgid "shortcuts.draw-ellipse" msgstr "Elipse" +msgid "shortcuts.draw-frame" +msgstr "Taboleiro" + +msgid "shortcuts.draw-path" +msgstr "Ruta" + +msgid "shortcuts.draw-rect" +msgstr "Rectángulo" + +msgid "shortcuts.draw-text" +msgstr "Texto" + +msgid "shortcuts.duplicate" +msgstr "Duplicar" + msgid "shortcuts.escape" msgstr "Cancelar" +msgid "shortcuts.go-to-search" +msgstr "Buscar" + +msgid "shortcuts.group" +msgstr "Agrupar" + msgid "shortcuts.mask" msgstr "Máscara" @@ -978,6 +1110,12 @@ msgstr "Refacer" msgid "shortcuts.undo" msgstr "Desfacer" +msgid "shortcuts.ungroup" +msgstr "Dispersar" + +msgid "shortcuts.unmask" +msgstr "Quitar máscara" + msgid "viewer.breaking-change.message" msgstr "Sentímolo!" @@ -989,6 +1127,29 @@ msgstr "Interaccións" msgid "viewer.header.sitemap" msgstr "Mapa do sitio" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Recursos" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Gráficos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Cores" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Compoñentes" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.delete" +msgstr "Eliminar" + #: src/app/main/ui/workspace/sidebar/sitemap.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.duplicate" @@ -996,18 +1157,28 @@ msgstr "Duplicar" #: src/app/main/ui/workspace/sidebar/assets.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.graphics" -msgstr "Gráficos" +msgid "workspace.assets.edit" +msgstr "Editar" #: src/app/main/ui/workspace/sidebar/assets.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.edit" -msgstr "Editar" +msgid "workspace.assets.graphics" +msgstr "Gráficos" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Agrupar" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.libraries" msgstr "Bibliotecas" +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs, +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename" +msgstr "Mudar o nome" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.shared" msgstr "COMPARTIDA" @@ -1021,6 +1192,10 @@ msgstr "Tipografías" msgid "workspace.assets.typography.font-id" msgstr "Fonte" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Tamaño" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.assets.typography.font-variant-id" msgstr "Variante" @@ -1035,6 +1210,21 @@ msgstr "Ag" msgid "workspace.assets.ungroup" msgstr "Dispersar" +msgid "workspace.focus.selection" +msgstr "Selección" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.edit" +msgstr "Editar" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.file" +msgstr "Ficheiro" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.option.preferences" +msgstr "Preferencias" + #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.reset-zoom" msgstr "Restablecer" @@ -1055,13 +1245,21 @@ msgstr "Engadir" msgid "workspace.libraries.colors.hsv" msgstr "HSV" +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.rgba" +msgstr "RGBA" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "BIBLIOTECAS" + #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.library" msgstr "BIBLIOTECA" #: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.libraries" -msgstr "BIBLIOTECAS" +msgid "workspace.libraries.update" +msgstr "Actualizar" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.updates" @@ -1070,17 +1268,19 @@ msgstr "Actualizacións" msgid "workspace.library.libraries" msgstr "Bibliotecas" -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.update" -msgstr "Actualizar" +msgid "workspace.options.blur-options.background-blur" +msgstr "Fondo" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Capa" #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs msgid "workspace.options.blur-options.title" msgstr "Desenfoque" -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.center" -msgstr "Centro" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Compoñente" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints" @@ -1090,6 +1290,10 @@ msgstr "Restricións" msgid "workspace.options.constraints.bottom" msgstr "Embaixo" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.center" +msgstr "Centro" + #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.left" msgstr "Esquerda" @@ -1125,14 +1329,21 @@ msgid "workspace.options.export-multiple" msgstr "Exportar selección" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, -#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs -msgid "workspace.options.exporting-complete" -msgstr "Exportación completada" +#: src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgid_plural "workspace.options.export-object" +msgstr[0] "Exportar 1 elemento" +msgstr[1] "Exportar %s elementos" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" msgstr "Sufixo" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +msgid "workspace.options.exporting-complete" +msgstr "Exportación completada" + #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.exporting-object" @@ -1147,10 +1358,29 @@ msgstr "Erro na exportación" msgid "workspace.options.fill" msgstr "Recheo" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Automático" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Columna" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.grid-title" msgstr "Grade" +msgid "workspace.options.grid.params.color" +msgstr "Cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Columnas" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Marxe" + #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.type.bottom" msgstr "Embaixo" @@ -1159,14 +1389,18 @@ msgstr "Embaixo" msgid "workspace.options.grid.params.type.center" msgstr "Centro" -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.top" -msgstr "Enriba" - #: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs msgid "workspace.options.grid.params.type.left" msgstr "Esquerda" +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Dereita" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Enriba" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-animation-none" msgstr "Ningún" @@ -1179,273 +1413,35 @@ msgstr "Centro" msgid "workspace.options.layout.bottom" msgstr "Embaixo" -msgid "common.publish" -msgstr "Publicar" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.left" +msgstr "Esquerda" -msgid "common.unpublish" -msgstr "Cancelar publicación" - -msgid "handoff.attributes.stroke.alignment.outer" -msgstr "Exterior" - -msgid "handoff.attributes.stroke.style.dotted" -msgstr "Punteado" - -msgid "handoff.attributes.stroke.style.mixed" -msgstr "Mixto" - -msgid "handoff.attributes.stroke.style.none" -msgstr "Ningún" - -msgid "handoff.attributes.stroke.style.solid" -msgstr "Sólido" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography" -msgstr "Tipografía" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs -msgid "labels.editor" -msgstr "Editor" - -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs -msgid "labels.email" -msgstr "Correo electrónico" - -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.expired-invitation" -msgstr "Caducada" - -msgid "labels.export" -msgstr "Exportar" - -msgid "labels.icons" -msgstr "Iconas" - -msgid "labels.images" -msgstr "Imaxes" - -#: src/app/main/ui/workspace/libraries.cljs, -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.remove" -msgstr "Retirar" - -#: src/app/main/ui/dashboard/sidebar.cljs, -#: src/app/main/ui/dashboard/project_menu.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "labels.rename" -msgstr "Mudar o nome" - -msgid "shortcut-subsection.edit" -msgstr "Editar" - -msgid "shortcut-subsection.navigation-dashboard" -msgstr "Navegación" - -msgid "shortcut-subsection.navigation-viewer" -msgstr "Navegación" - -msgid "shortcut-subsection.navigation-workspace" -msgstr "Navegación" - -msgid "shortcut-subsection.panels" -msgstr "Paneis" - -msgid "shortcut-subsection.path-editor" -msgstr "Ruta" - -msgid "shortcut-subsection.shape" -msgstr "Formas" - -msgid "shortcut-subsection.tools" -msgstr "Ferramentas" - -msgid "shortcuts.draw-frame" -msgstr "Taboleiro" - -msgid "shortcuts.draw-path" -msgstr "Ruta" - -msgid "shortcuts.draw-rect" -msgstr "Rectángulo" - -msgid "shortcuts.draw-text" -msgstr "Texto" - -msgid "shortcuts.go-to-search" -msgstr "Buscar" - -msgid "shortcuts.group" -msgstr "Agrupar" - -msgid "shortcuts.unmask" -msgstr "Quitar máscara" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.assets" -msgstr "Recursos" - -msgid "workspace.assets.box-filter-graphics" -msgstr "Gráficos" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, -#: src/app/main/ui/workspace/sidebar/assets.cljs, -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.delete" -msgstr "Eliminar" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, -#: src/app/main/ui/workspace/sidebar/assets.cljs, -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.rename" -msgstr "Mudar o nome" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-size" -msgstr "Tamaño" - -msgid "workspace.focus.selection" -msgstr "Selección" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.rgba" -msgstr "RGBA" - -msgid "workspace.options.blur-options.background-blur" -msgstr "Fondo" - -msgid "workspace.options.blur-options.layer-blur" -msgstr "Capa" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs -msgid "workspace.options.component" -msgstr "Compoñente" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.auto" -msgstr "Automático" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.column" -msgstr "Columna" - -msgid "workspace.options.grid.params.color" -msgstr "Cor" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.columns" -msgstr "Columnas" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.margin" +#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +msgid "workspace.options.layout.margin" msgstr "Marxe" -msgid "common.share-link.page-shared" -msgid_plural "common.share-link.page-shared" -msgstr[0] "1 páxina compartida" -msgstr[1] "% páxinas compartidas" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.management" -msgstr "Xestión do equipo" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.info" -msgstr "" -"Aprende os conceptos básicos de Penpot mentres te divirtes con este titorial " -"práctico." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.info" -msgstr "Da unha volta por Penpot e coñece as súas características principais." - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.walkthrough-hero.start" -msgstr "Comeza a visita" - -msgid "dashboard.download-binary-file" -msgstr "Descargar ficheiro Penpot (.penpot)" - -msgid "dashboard.download-standard-file" -msgstr "Descargar ficheiro estándar (.svg + .json)" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, -#: src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.export-object" -msgid_plural "workspace.options.export-object" -msgstr[0] "Exportar 1 elemento" -msgstr[1] "Exportar %s elementos" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.right" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.right" msgstr "Dereita" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-title" -msgstr "Un pracer volverte ver!" +#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs +msgid "workspace.options.layout.top" +msgstr "Enriba" -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.text" -msgstr "" -"Penpot está deseñado para equipos. Convida a persoas coas que trallar en " -"proxectos e ficheiros" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke-cap.none" +msgstr "Ningún" -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.title" -msgstr "Titorial práctico" +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Centro" -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.tutorial-hero.start" -msgstr "Comeza o titorial" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs, +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.none" +msgstr "Nada" -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke" -msgstr "Bordo" - -msgid "handoff.attributes.stroke.alignment.inner" -msgstr "Interior" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke.width" -msgstr "Ancho" - -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.invitations" -msgstr "Invitacións" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.accept" -msgstr "Actualizar" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, -#: src/app/main/ui/workspace/context_menu.cljs -msgid "modals.update-remote-component.cancel" -msgstr "Cancelar" - -msgid "onboarding.welcome.alt" -msgstr "Penpot" - -msgid "shortcuts.cut" -msgstr "Cortar" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.no-elements" -msgstr "Non hai elementos con configuración de exportación." - -msgid "dashboard.export-standard-multi" -msgstr "Descargar %s ficheiros estándar (.svg + .json)" - -msgid "dashboard.libraries-and-templates" -msgstr "Bibliotecas e Modelos" - -msgid "dashboard.libraries-and-templates.explore" -msgstr "Explora máis e descubre como contribuír" - -msgid "dashboard.libraries-and-templates.import-error" -msgstr "Ocorreu un problema ao importar o modelo. Non se importou o modelo." - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-title" -msgstr "Subscrición ao boletín" +msgid "workspace.shape.menu.edit" +msgstr "Editar" \ No newline at end of file diff --git a/frontend/translations/he.po b/frontend/translations/he.po index 2d4897b79f..2c2cafb5ba 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-16 13:49+0000\n" "Last-Translator: Yaron Shahrabani \n" -"Language-Team: Hebrew \n" +"Language-Team: Hebrew " +"\n" "Language: he\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -814,6 +814,9 @@ msgstr "סיסמת האימות חייבת להיות תואמת" msgid "errors.password-too-short" msgstr "הסיסמה חייבת להיות באורך 8 תווים לפחות" +msgid "errors.profile-blocked" +msgstr "הפרופיל חסום" + #: src/app/main/ui/auth/recovery_request.cljs, #: src/app/main/ui/settings/change_email.cljs, #: src/app/main/ui/dashboard/team.cljs @@ -2006,6 +2009,25 @@ msgstr "מדריכים מצולמים" msgid "onboarding-v2.before-start.title" msgstr "לפני שמתחילים" +msgid "onboarding-v2.newsletter.desc" +msgstr "" +"ניתן להירשם לרשימת הדיוור של Penpot כדי להתעדכן בתהליך פיתוח המוצר ובחדשות " +"נוספות." + +msgid "onboarding-v2.newsletter.news" +msgstr "נא לשלוח לי חדשות על Penpot (בלוגים, מדריכים מצולמים, שידורים…)." + +msgid "onboarding-v2.newsletter.privacy1" +msgstr "אכפת לנו מפרטיות, כאן ניתן לקרוא את " + +msgid "onboarding-v2.newsletter.privacy2" +msgstr "" +"אנו נשלח לך בדוא״ל רק הודעות שרלוונטיות לך. אפשר לבטל את המינוי דרך כפתור " +"ביטול המינוי בכל אחת מהודעות הדיוור שלנו." + +msgid "onboarding-v2.newsletter.updates" +msgstr "נא לשלוח לי עדכונים על המוצר (יכולות חדשות, מהדורות, תיקונים…)." + msgid "onboarding-v2.welcome.desc1" msgstr "" "Penpot הוא בקוד פתוח והוא נוצר על ידי Kaleidos וגם על ידי הקהילה בה מגוון " @@ -3374,11 +3396,7 @@ msgstr "ייצוא הבחירה" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "ייצוא רכיב" -msgstr[1] "ייצוא %s רכיבים" -msgstr[2] "ייצוא %s רכיבים" -msgstr[3] "ייצוא %s רכיבים" +msgstr "ייצוא רכיב" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -4464,6 +4482,10 @@ msgstr "בחירת שכבה" msgid "workspace.shape.menu.show" msgstr "הצגה" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-in-assets" +msgstr "הצגה בלוח משאבים" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.show-main" @@ -4505,6 +4527,12 @@ msgstr "עדכון הרכיבים הראשיים" msgid "workspace.shape.menu.update-main" msgstr "עדכון הרכיב הראשי" +msgid "workspace.sidebar.collapse" +msgstr "צמצום סרגל צד" + +msgid "workspace.sidebar.expand" +msgstr "הרחבת סרגל צד" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.sidebar.history" msgstr "היסטוריה (%s)" @@ -4729,36 +4757,4 @@ msgid "workspace.updates.update" msgstr "עדכון" msgid "workspace.viewport.click-to-close-path" -msgstr "לחיצה תסגור את הנתיב" - -msgid "onboarding-v2.newsletter.news" -msgstr "נא לשלוח לי חדשות על Penpot (בלוגים, מדריכים מצולמים, שידורים…)." - -msgid "onboarding-v2.newsletter.privacy1" -msgstr "אכפת לנו מפרטיות, כאן ניתן לקרוא את " - -msgid "workspace.sidebar.collapse" -msgstr "צמצום סרגל צד" - -msgid "onboarding-v2.newsletter.privacy2" -msgstr "" -"אנו נשלח לך בדוא״ל רק הודעות שרלוונטיות לך. אפשר לבטל את המינוי דרך כפתור " -"ביטול המינוי בכל אחת מהודעות הדיוור שלנו." - -msgid "errors.profile-blocked" -msgstr "הפרופיל חסום" - -msgid "onboarding-v2.newsletter.desc" -msgstr "" -"ניתן להירשם לרשימת הדיוור של Penpot כדי להתעדכן בתהליך פיתוח המוצר ובחדשות " -"נוספות." - -msgid "onboarding-v2.newsletter.updates" -msgstr "נא לשלוח לי עדכונים על המוצר (יכולות חדשות, מהדורות, תיקונים…)." - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show-in-assets" -msgstr "הצגה בלוח משאבים" - -msgid "workspace.sidebar.expand" -msgstr "הרחבת סרגל צד" +msgstr "לחיצה תסגור את הנתיב" \ No newline at end of file diff --git a/frontend/translations/it.po b/frontend/translations/it.po index 3f664865dd..c9185a27ba 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-01 14:17+0000\n" "Last-Translator: Jacopo Lodovico Trabia \n" -"Language-Team: Italian \n" +"Language-Team: Italian " +"\n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -226,6 +226,9 @@ msgstr "Condividi i prototipi" msgid "common.share-link.view-all" msgstr "Seleziona tutto" +msgid "common.unpublish" +msgstr "Spubblica" + #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.management" msgstr "Gestisci team" @@ -236,6 +239,10 @@ msgstr "" "Penpot è studiato per i team. Invita membri per lavorare insieme a file e " "progetti" +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.team-hero.title" +msgstr "Fai squadra!" + #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.info" msgstr "Impara le basi di Penpot divertendoti con questo tutorial pratico." @@ -334,6 +341,12 @@ msgstr "%s di %s elementi selezionati" msgid "dashboard.export-shapes" msgstr "Esporta" +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.how-to" +msgstr "" +"Puoi aggiungere dei parametri di esportazione agli elementi accedendo alle " +"proprietà del design (in fondo alla barra laterale destra)." + #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.how-to-link" msgstr "Informazioni utili su come configurare l'esportazione in Penpot." @@ -364,8 +377,8 @@ msgstr "" msgid "dashboard.export.options.all.message" msgstr "" -"I file con librerie condivise verranno inclusi nell'esportazione, mantenendo " -"il loro collegamento." +"I file con librerie condivise verranno inclusi nell'esportazione, " +"mantenendo il loro collegamento." msgid "dashboard.export.options.all.title" msgstr "Esporta le librerie condivise" @@ -375,6 +388,9 @@ msgstr "" "Le librerie condivise non saranno incluse nell'esportazione e nessuna " "risorsa verrà aggiunta alla libreria. " +msgid "dashboard.export.options.detach.title" +msgstr "Considera le risorse delle librerie condivise come oggetti di base" + msgid "dashboard.export.options.merge.message" msgstr "" "Il tuo file verrà esportato con tutte le risorse esterne riunite nella " @@ -404,16 +420,16 @@ msgstr "" "Qualsiasi font web caricato qui verrà aggiunto alla lista dei font family " "disponibile nelle impostazioni testo dei file di questo team. I font che " "arrecano lo stesso nome di font family verranno raggruppati come un " -"**singolo font family**. È possibile caricare font con i seguenti formati:" -"**TTF, OTF e WOFF**(uno solo di questi è necessario)." +"**singolo font family**. È possibile caricare font con i seguenti " +"formati:**TTF, OTF e WOFF**(uno solo di questi è necessario)." msgid "dashboard.fonts.hero-text2" msgstr "" -"È consigliabile caricare unicamente font di cui si è proprietari o dei quali " -"si possiede la licenza d'uso in Penpot. Ulteriori informazioni sui diritti " -"dei contenuti sono disponibili nella sezione [Termini di Servizio di Penpot]" -"(https://penpot.app/terms.html). Potresti anche voler approfondire le " -"[licenze per i font](https://www.typography.com/faq)." +"È consigliabile caricare unicamente font di cui si è proprietari o dei " +"quali si possiede la licenza d'uso in Penpot. Ulteriori informazioni sui " +"diritti dei contenuti sono disponibili nella sezione [Termini di Servizio " +"di Penpot](https://penpot.app/terms.html). Potresti anche voler " +"approfondire le [licenze per i font](https://www.typography.com/faq)." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -637,6 +653,10 @@ msgstr "I tuoi file sono stati spostati con successo" msgid "dashboard.success-move-project" msgstr "Il tuo progetto è stato spostato con successo" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "Cambia team" + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.team-info" msgstr "Informazioni sul team" @@ -747,8 +767,7 @@ msgid "errors.email-invalid-confirmation" msgstr "L'indirizzo e-mail di conferma deve corrispondere" msgid "errors.email-spam-or-permanent-bounces" -msgstr "" -"L'e-mail \"%s\" è stata riportata come spam o respinta in modo permanente." +msgstr "L'e-mail \"%s\" è stata riportata come spam o respinta in modo permanente." #: src/app/main/ui/auth/verify_token.cljs, #: src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs @@ -820,8 +839,7 @@ msgstr "" "proprietario." msgid "errors.terms-privacy-agreement-invalid" -msgstr "" -"È necessario accettare i termini di servizio e l'informativa sulla privacy." +msgstr "È necessario accettare i termini di servizio e l'informativa sulla privacy." #: src/app/main/data/media.cljs, #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, @@ -874,9 +892,9 @@ msgstr "Soggetto" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"Descrivi per favore il motivo della tua e-mail, specificando se si tratta di " -"un problema, di un'idea oppure di un dubbio. Un membro del nostro team ti " -"risponderà il prima possibile." +"Descrivi per favore il motivo della tua e-mail, specificando se si tratta " +"di un problema, di un'idea oppure di un dubbio. Un membro del nostro team " +"ti risponderà il prima possibile." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" @@ -1036,6 +1054,10 @@ msgstr "Spaziatura delle lettere" msgid "handoff.attributes.typography.line-height" msgstr "Altezza Linea" +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Decorazioni testo" + msgid "handoff.attributes.typography.text-decoration.none" msgstr "Nessuno" @@ -1055,6 +1077,9 @@ msgstr "Minuscolo" msgid "handoff.attributes.typography.text-transform.none" msgstr "Nessuno" +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Prime lettere maiuscole" + msgid "handoff.attributes.typography.text-transform.uppercase" msgstr "Maiuscolo" @@ -1374,8 +1399,7 @@ msgstr "Sei connesso come" #: src/app/main/ui/static.cljs msgid "labels.not-found.desc-message" -msgstr "" -"Questa pagina non esiste oppure non hai i permessi necessari per accedervi." +msgstr "Questa pagina non esiste oppure non hai i permessi necessari per accedervi." #: src/app/main/ui/static.cljs msgid "labels.not-found.main-message" @@ -1598,8 +1622,8 @@ msgstr "Verificare il nuovo indirizzo e-mail" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.info" msgstr "" -"Ti invieremo un'e-mail al tuo attuale indirizzo e-mail \"%s\" per verificare " -"la tua identità." +"Ti invieremo un'e-mail al tuo attuale indirizzo e-mail \"%s\" per " +"verificare la tua identità." #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.new-email" @@ -1630,7 +1654,8 @@ msgstr "Sì, cancellare il mio account" #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.info" msgstr "" -"Cancellando il tuo account, perderai tutti i tuoi archivi e progetti attuali." +"Cancellando il tuo account, perderai tutti i tuoi archivi e progetti " +"attuali." #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.title" @@ -1787,8 +1812,8 @@ msgstr "Invita membri al team" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" msgstr "" -"Poiché sei il solo membro di questo team, il team verrà eliminato insieme ai " -"sui file e progetti." +"Poiché sei il solo membro di questo team, il team verrà eliminato insieme " +"ai sui file e progetti." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.message" @@ -1934,8 +1959,8 @@ msgstr "Cancella" #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" msgstr "" -"Stai per aggiornare un componente in una libreria condivisa. Questo potrebbe " -"causare modifiche nei file che la utilizzano." +"Stai per aggiornare un componente in una libreria condivisa. Questo " +"potrebbe causare modifiche nei file che la utilizzano." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs @@ -1948,8 +1973,7 @@ msgstr "Invito inviato con successo" #: src/app/main/ui/settings/delete_account.cljs msgid "notifications.profile-deletion-not-allowed" -msgstr "" -"Non puoi eliminare il tuo profilo. Riassegna i tuoi team prima di procedere." +msgstr "Non puoi eliminare il tuo profilo. Riassegna i tuoi team prima di procedere." #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs msgid "notifications.profile-saved" @@ -1989,8 +2013,8 @@ msgstr "" msgid "onboarding-v2.welcome.desc2" msgstr "" -"Uno spazio pubblico dove imparare, condividere e discutere su Penpot, il suo " -"presente e futuro con l'intera Comunità e con il team di Penpot." +"Uno spazio pubblico dove imparare, condividere e discutere su Penpot, il " +"suo presente e futuro con l'intera Comunità e con il team di Penpot." msgid "onboarding-v2.welcome.desc2.title" msgstr "Partecipando nella Comunità" @@ -2013,8 +2037,7 @@ msgid "onboarding.choice.team-up.create-team" msgstr "Il nome del tuo team" msgid "onboarding.choice.team-up.create-team-desc" -msgstr "" -"Dopo aver nominato il tuo team, potrai invitare persone ad unirsi ad esso." +msgstr "Dopo aver nominato il tuo team, potrai invitare persone ad unirsi ad esso." msgid "onboarding.choice.team-up.create-team-placeholder" msgstr "Inserisci il nome del team" @@ -2114,31 +2137,4 @@ msgstr "Crea interazioni complete per imitare al meglio il prodotto finale." #: src/app/main/ui/dashboard/team.cljs msgid "title.team-invitations" -msgstr "Inviti - %s - Penpot" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.switch-team" -msgstr "Cambia team" - -msgid "dashboard.export.options.detach.title" -msgstr "Considera le risorse delle librerie condivise come oggetti di base" - -msgid "handoff.attributes.typography.text-transform.titlecase" -msgstr "Prime lettere maiuscole" - -msgid "common.unpublish" -msgstr "Spubblica" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dasboard.team-hero.title" -msgstr "Fai squadra!" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-decoration" -msgstr "Decorazioni testo" - -#: src/app/main/ui/export.cljs -msgid "dashboard.export-shapes.how-to" -msgstr "" -"Puoi aggiungere dei parametri di esportazione agli elementi accedendo alle " -"proprietà del design (in fondo alla barra laterale destra)." +msgstr "Inviti - %s - Penpot" \ No newline at end of file diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 7762b18d09..3a2b2f79a6 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-10 17:34+0000\n" "Last-Translator: Oğuz Ersen \n" -"Language-Team: Turkish \n" +"Language-Team: Turkish " +"\n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -835,6 +835,9 @@ msgstr "Parolalar eşleşmedi" msgid "errors.password-too-short" msgstr "Parola en az 8 karakterden oluşmalı" +msgid "errors.profile-blocked" +msgstr "Profil engellendi" + #: src/app/main/ui/auth/recovery_request.cljs, #: src/app/main/ui/settings/change_email.cljs, #: src/app/main/ui/dashboard/team.cljs @@ -2033,6 +2036,28 @@ msgstr "Video öğreticiler" msgid "onboarding-v2.before-start.title" msgstr "Başlamadan önce" +msgid "onboarding-v2.newsletter.desc" +msgstr "" +"Ürün geliştirme sürecinden ve haberlerden haberdar olmak için Penpot " +"bültenine abone olun." + +msgid "onboarding-v2.newsletter.news" +msgstr "" +"Bana Penpot hakkında haberler gönder (blog gönderileri, video öğreticiler, " +"yayınlar...)." + +msgid "onboarding-v2.newsletter.privacy1" +msgstr "Gizliliğe önem veriyoruz, buradan okuyabilirsiniz. " + +msgid "onboarding-v2.newsletter.privacy2" +msgstr "" +"Size yalnızca ilgili e-postaları göndereceğiz. Bültenlerimizden herhangi " +"birindeki abonelikten çıkma bağlantısını kullanarak istediğiniz zaman " +"aboneliğinizi iptal edebilirsiniz." + +msgid "onboarding-v2.newsletter.updates" +msgstr "Bana ürün güncellemeleri gönder (yeni özellikler, sürümler, düzeltmeler...)." + msgid "onboarding-v2.welcome.desc1" msgstr "" "Penpot açık kaynaklıdır ve Kaleidos'un yanı sıra birçok insanın birbirine " @@ -3417,9 +3442,7 @@ msgstr "Seçimi dışa aktar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-object" -msgid_plural "" -msgstr[0] "1 ögeyi dışa aktar" -msgstr[1] "%s ögeyi dışa aktar" +msgstr "1 ögeyi dışa aktar" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" @@ -4508,6 +4531,10 @@ msgstr "Katman seç" msgid "workspace.shape.menu.show" msgstr "Göster" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show-in-assets" +msgstr "Varlıklar panelinde göster" + #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.show-main" @@ -4549,6 +4576,12 @@ msgstr "Ana bileşenleri güncelle" msgid "workspace.shape.menu.update-main" msgstr "Ana bileşeni güncelle" +msgid "workspace.sidebar.collapse" +msgstr "Kenar çubuğunu daralt" + +msgid "workspace.sidebar.expand" +msgstr "Kenar çubuğunu genişlet" + #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.sidebar.history" msgstr "Geçmiş (%s)" @@ -4773,40 +4806,4 @@ msgid "workspace.updates.update" msgstr "Güncelle" msgid "workspace.viewport.click-to-close-path" -msgstr "Yolu kapatmak için tıklayın" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show-in-assets" -msgstr "Varlıklar panelinde göster" - -msgid "onboarding-v2.newsletter.privacy1" -msgstr "Gizliliğe önem veriyoruz, buradan okuyabilirsiniz. " - -msgid "onboarding-v2.newsletter.privacy2" -msgstr "" -"Size yalnızca ilgili e-postaları göndereceğiz. Bültenlerimizden herhangi " -"birindeki abonelikten çıkma bağlantısını kullanarak istediğiniz zaman " -"aboneliğinizi iptal edebilirsiniz." - -msgid "onboarding-v2.newsletter.updates" -msgstr "" -"Bana ürün güncellemeleri gönder (yeni özellikler, sürümler, düzeltmeler...)." - -msgid "workspace.sidebar.collapse" -msgstr "Kenar çubuğunu daralt" - -msgid "workspace.sidebar.expand" -msgstr "Kenar çubuğunu genişlet" - -msgid "errors.profile-blocked" -msgstr "Profil engellendi" - -msgid "onboarding-v2.newsletter.desc" -msgstr "" -"Ürün geliştirme sürecinden ve haberlerden haberdar olmak için Penpot " -"bültenine abone olun." - -msgid "onboarding-v2.newsletter.news" -msgstr "" -"Bana Penpot hakkında haberler gönder (blog gönderileri, video öğreticiler, " -"yayınlar...)." +msgstr "Yolu kapatmak için tıklayın" \ No newline at end of file From 8ad4dfe4540059dc77c442fcc5ffadfb0d5f0a1e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Oct 2022 17:03:36 +0200 Subject: [PATCH 110/682] :paperclip: Minor changes on user namespace --- backend/dev/user.clj | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/backend/dev/user.clj b/backend/dev/user.clj index 0ef74e9db4..fb6aa9a726 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -13,8 +13,11 @@ [app.common.perf :as perf] [app.common.pprint :as pp] [app.common.transit :as t] + [app.common.uuid :as uuid] [app.config :as cfg] [app.main :as main] + [app.srepl.helpers] + [app.srepl.main :as srepl] [app.srepl.main :as srepl] [app.util.blob :as blob] [app.util.fressian :as fres] @@ -30,6 +33,7 @@ [clojure.test :as test] [clojure.tools.namespace.repl :as repl] [clojure.walk :refer [macroexpand-all]] + [criterium.core :as crit] [datoteka.core] [integrant.core :as ig])) @@ -42,19 +46,19 @@ (defmacro run-quick-bench [& exprs] - `(with-progress-reporting (quick-bench (do ~@exprs) :verbose))) + `(crit/with-progress-reporting (crit/quick-bench (do ~@exprs) :verbose))) (defmacro run-quick-bench' [& exprs] - `(quick-bench (do ~@exprs))) + `(crit/quick-bench (do ~@exprs))) (defmacro run-bench [& exprs] - `(with-progress-reporting (bench (do ~@exprs) :verbose))) + `(crit/with-progress-reporting (crit/bench (do ~@exprs) :verbose))) (defmacro run-bench' [& exprs] - `(bench (do ~@exprs))) + `(crit/bench (do ~@exprs))) ;; --- Development Stuff @@ -100,12 +104,20 @@ (defn compression-bench [data] - (let [humanize (fn [v] (hum/filesize v :binary true :format " %.4f "))] + (let [humanize (fn [v] (hum/filesize v :binary true :format " %.4f ")) + v1 (time (humanize (alength (blob/encode data {:version 1})))) + v3 (time (humanize (alength (blob/encode data {:version 3})))) + v4 (time (humanize (alength (blob/encode data {:version 4})))) + v5 (time (humanize (alength (blob/encode data {:version 5})))) + v6 (time (humanize (alength (blob/encode data {:version 6})))) + ] (print-table - [{:v1 (humanize (alength (blob/encode data {:version 1}))) - :v2 (humanize (alength (blob/encode data {:version 2}))) - :v3 (humanize (alength (blob/encode data {:version 3}))) - :v4 (humanize (alength (blob/encode data {:version 4}))) + [{ + :v1 v1 + :v3 v3 + :v4 v4 + :v5 v5 + :v6 v6 }]))) (defonce debug-tap From d71c5e41057e7fe0a49c37e7464ee5bc10a23c9e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Oct 2022 17:04:02 +0200 Subject: [PATCH 111/682] :paperclip: Add another print preference method --- backend/src/app/config.clj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index a2eb5f1eee..fdfcfc98b6 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -27,6 +27,10 @@ clojure.lang.IRecord clojure.lang.IDeref) +(prefer-method print-method + clojure.lang.IPersistentMap + clojure.lang.IDeref) + (prefer-method pprint/simple-dispatch clojure.lang.IPersistentMap clojure.lang.IDeref) From 5fe3842d1e9db8ad69c36e27613be06622bba65e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Oct 2022 17:04:24 +0200 Subject: [PATCH 112/682] :tada: Add v5 blob format (lz4 framed, less gc) --- backend/src/app/config.clj | 2 +- backend/src/app/util/blob.clj | 48 ++++++++++++++++++++++++------- backend/src/app/util/fressian.clj | 37 ++++++++++++++++++++---- 3 files changed, 71 insertions(+), 16 deletions(-) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index fdfcfc98b6..9e65cd3d38 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -50,7 +50,7 @@ :database-username "penpot" :database-password "penpot" - :default-blob-version 4 + :default-blob-version 5 :loggers-zmq-uri "tcp://localhost:45556" :rpc-rlimit-config (fs/path "resources/rlimit.edn") diff --git a/backend/src/app/util/blob.clj b/backend/src/app/util/blob.clj index bd24ca3d2b..9fe030e748 100644 --- a/backend/src/app/util/blob.clj +++ b/backend/src/app/util/blob.clj @@ -12,14 +12,18 @@ [app.config :as cf] [app.util.fressian :as fres]) (:import + com.github.luben.zstd.Zstd java.io.ByteArrayInputStream java.io.ByteArrayOutputStream java.io.DataInputStream java.io.DataOutputStream - com.github.luben.zstd.Zstd + java.io.InputStream + java.io.OutputStream + net.jpountz.lz4.LZ4Compressor net.jpountz.lz4.LZ4Factory net.jpountz.lz4.LZ4FastDecompressor - net.jpountz.lz4.LZ4Compressor)) + net.jpountz.lz4.LZ4FrameInputStream + net.jpountz.lz4.LZ4FrameOutputStream)) (set! *warn-on-reflection* true) @@ -28,18 +32,21 @@ (declare decode-v1) (declare decode-v3) (declare decode-v4) +(declare decode-v5) (declare encode-v1) (declare encode-v3) (declare encode-v4) +(declare encode-v5) (defn encode ([data] (encode data nil)) ([data {:keys [version]}] - (let [version (or version (cf/get :default-blob-version 4))] + (let [version (or version (cf/get :default-blob-version 5))] (case (long version) 1 (encode-v1 data) 3 (encode-v3 data) 4 (encode-v4 data) + 5 (encode-v5 data) (throw (ex-info "unsupported version" {:version version})))))) (defn decode @@ -53,6 +60,7 @@ 1 (decode-v1 data ulen) 3 (decode-v3 data ulen) 4 (decode-v4 data ulen) + 5 (decode-v5 data) (throw (ex-info "unsupported version" {:version version})))))) ;; --- IMPL @@ -108,15 +116,17 @@ dlen (alength ^bytes data) mlen (Zstd/compressBound dlen) cdata (byte-array mlen) - clen (Zstd/compressByteArray ^bytes cdata 0 mlen + cdlen (alength ^bytes cdata) + cmlen (Zstd/compressByteArray ^bytes cdata 0 mlen ^bytes data 0 dlen 0)] - (with-open [^ByteArrayOutputStream baos (ByteArrayOutputStream. (+ (alength cdata) 2 4)) - ^DataOutputStream dos (DataOutputStream. baos)] - (.writeShort dos (short 4)) ;; version number - (.writeInt dos (int dlen)) - (.write dos ^bytes cdata (int 0) clen) - (.toByteArray baos)))) + + (with-open [^ByteArrayOutputStream output (ByteArrayOutputStream. (+ cdlen 2 4))] + (with-open [^DataOutputStream output (DataOutputStream. output)] + (.writeShort output (short 4)) ;; version number + (.writeInt output (int dlen)) + (.write output ^bytes cdata (int 0) (int cmlen))) + (.toByteArray output)))) (defn- decode-v4 [^bytes cdata ^long ulen] @@ -124,3 +134,21 @@ (Zstd/decompressByteArray ^bytes udata 0 ulen ^bytes cdata 6 (- (alength cdata) 6)) (fres/decode udata))) + +(defn- encode-v5 + [data] + (with-open [^ByteArrayOutputStream output (ByteArrayOutputStream.)] + (with-open [^DataOutputStream output (DataOutputStream. output)] + (.writeShort output (short 5)) ;; version number + (.writeInt output (int -1)) + (with-open [^OutputStream output (LZ4FrameOutputStream. output)] + (-> (fres/writer output) + (fres/write! data)))) + (.toByteArray output))) + +(defn- decode-v5 + [^bytes cdata] + (with-open [^InputStream input (ByteArrayInputStream. cdata)] + (.skip input 6) + (with-open [^InputStream input (LZ4FrameInputStream. input)] + (-> input fres/reader fres/read!)))) diff --git a/backend/src/app/util/fressian.clj b/backend/src/app/util/fressian.clj index 1b4367889d..66c23fe439 100644 --- a/backend/src/app/util/fressian.clj +++ b/backend/src/app/util/fressian.clj @@ -271,11 +271,38 @@ (defn encode [data] - (with-open [out (ByteArrayOutputStream.)] - (write! (writer out) data) - (.toByteArray out))) + (with-open [^ByteArrayOutputStream output (ByteArrayOutputStream.)] + (-> (writer output) + (write! data)) + (.toByteArray output))) (defn decode [data] - (with-open [input (ByteArrayInputStream. ^bytes data)] - (read! (reader input)))) + (with-open [^ByteArrayInputStream input (ByteArrayInputStream. ^bytes data)] + (-> input reader read!))) + +;; --- ADDITIONAL + +(add-handlers! + {:name "penpot/point" + :class app.common.geom.point.Point + :wfn (fn [n w ^Point o] + (write-tag! w n 1) + (write-list! w (List/of (.-x o) (.-y o)))) + :rfn (fn [^Reader rdr] + (let [^List x (read-object! rdr)] + (Point. (.get x 0) (.get x 1))))} + + {:name "penpot/matrix" + :class app.common.geom.matrix.Matrix + :wfn (fn [^String n ^Writer w o] + (write-tag! w n 1) + (write-list! w (List/of (.-a ^Matrix o) + (.-b ^Matrix o) + (.-c ^Matrix o) + (.-d ^Matrix o) + (.-e ^Matrix o) + (.-f ^Matrix o)))) + :rfn (fn [^Reader rdr] + (let [^List x (read-object! rdr)] + (Matrix. (.get x 0) (.get x 1) (.get x 2) (.get x 3) (.get x 4) (.get x 5))))}) From b1296ef7657b2b8fdc0078207c95952798f4233e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Oct 2022 17:04:55 +0200 Subject: [PATCH 113/682] :sparkles: Make fressian module extensible --- backend/src/app/util/fressian.clj | 357 +++++++++++++++--------------- 1 file changed, 183 insertions(+), 174 deletions(-) diff --git a/backend/src/app/util/fressian.clj b/backend/src/app/util/fressian.clj index 66c23fe439..7d54e32075 100644 --- a/backend/src/app/util/fressian.clj +++ b/backend/src/app/util/fressian.clj @@ -6,6 +6,7 @@ (ns app.util.fressian (:require + [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [clojure.data.fressian :as fres]) @@ -17,14 +18,13 @@ java.io.ByteArrayOutputStream java.time.Instant java.time.OffsetDateTime + java.util.List org.fressian.Reader org.fressian.StreamingWriter org.fressian.Writer org.fressian.handlers.ReadHandler org.fressian.handlers.WriteHandler)) -;; --- MISC - (set! *warn-on-reflection* true) (defn str->bytes @@ -33,231 +33,242 @@ ([^String s, ^String encoding] (.getBytes s encoding))) +;; --- LOW LEVEL FRESSIAN API + +(defn write-object! + ([^Writer w ^Object o] + (.writeObject w o)) + ([^Writer w ^Object o ^Boolean cache?] + (.writeObject w o cache?))) + +(defn read-object! + [^Reader r] + (.readObject r)) + +(defn write-tag! + ([^Writer w ^String n] + (.writeTag w n 1)) + ([^Writer w ^String n ^long ni] + (.writeTag w n ni))) + +(defn write-bytes! + [^Writer w ^bytes data] + (.writeBytes w data)) + +(defn write-int! + [^Writer w ^long val] + (.writeInt w val)) + +(defn write-list! + [^Writer w ^List val] + (.writeList w val)) + +;; --- READ AND WRITE HANDLERS + +(defn read-symbol + [r] + (symbol (read-object! r) + (read-object! r))) + +(defn read-keyword + [r] + (keyword (read-object! r) + (read-object! r))) + (defn write-named [tag ^Writer w s] - (.writeTag w tag 2) - (.writeObject w (namespace s) true) - (.writeObject w (name s) true)) + (write-tag! w tag 2) + (write-object! w (namespace s) true) + (write-object! w (name s) true)) (defn write-list-like - ([^Writer w tag o] - (.writeTag w tag 1) - (.writeList w o))) - -(defn read-list-like - [^Reader rdr build-fn] - (build-fn (.readObject rdr))) + [tag ^Writer w o] + (write-tag! w tag 1) + (write-list! w o)) (defn write-map-like "Writes a map as Fressian with the tag 'map' and all keys cached." - [^Writer w tag m] - (.writeTag w tag 1) + [tag ^Writer w m] + (write-tag! w tag 1) (.beginClosedList ^StreamingWriter w) (loop [items (seq m)] (when-let [^clojure.lang.MapEntry item (first items)] - (.writeObject w (.key item) true) - (.writeObject w (.val item)) + (write-object! w (.key item) true) + (write-object! w (.val item)) (recur (rest items)))) (.endList ^StreamingWriter w)) (defn read-map-like [^Reader rdr] - (let [kvs ^java.util.List (.readObject rdr)] + (let [kvs ^java.util.List (read-object! rdr)] (if (< (.size kvs) 16) (clojure.lang.PersistentArrayMap. (.toArray kvs)) (clojure.lang.PersistentHashMap/create (seq kvs))))) -(def write-handlers - { Character - {"char" - (reify WriteHandler - (write [_ w ch] - (.writeTag w "char" 1) - (.writeInt w (int ch))))} +(def ^:dynamic *write-handler-lookup* nil) +(def ^:dynamic *read-handler-lookup* nil) - app.common.geom.point.Point - {"penpot/point" - (reify WriteHandler - (write [_ w o] - (.writeTag ^Writer w "penpot/point" 1) - (.writeList ^Writer w (java.util.List/of (.-x ^Point o) (.-y ^Point o)))))} +(def write-handlers (atom {})) +(def read-handlers (atom {})) - app.common.geom.matrix.Matrix - {"penpot/matrix" - (reify WriteHandler - (write [_ w o] - (.writeTag ^Writer w "penpot/matrix" 1) - (.writeList ^Writer w (java.util.List/of (.-a ^Matrix o) - (.-b ^Matrix o) - (.-c ^Matrix o) - (.-d ^Matrix o) - (.-e ^Matrix o) - (.-f ^Matrix o)))))} +(defn add-handlers! + [& handlers] + (letfn [(adapt-write-handler [{:keys [name class wfn]}] + [class {name (reify WriteHandler + (write [_ w o] + (wfn name w o)))}]) - Instant - {"java/instant" - (reify WriteHandler - (write [_ w ch] - (.writeTag w "java/instant" 1) - (.writeInt w (.toEpochMilli ^Instant ch))))} + (adapt-read-handler [{:keys [name rfn]}] + [name (reify ReadHandler + (read [_ rdr _ _] + (rfn rdr)))]) - OffsetDateTime - {"java/instant" - (reify WriteHandler - (write [_ w ch] - (.writeTag w "java/instant" 1) - (.writeInt w (.toEpochMilli ^Instant (.toInstant ^OffsetDateTime ch)))))} + (merge-and-clean [m1 m2] + (-> (merge m1 m2) + (d/without-nils)))] - Ratio - {"ratio" - (reify WriteHandler - (write [_ w n] - (.writeTag w "ratio" 2) - (.writeObject w (.numerator ^Ratio n)) - (.writeObject w (.denominator ^Ratio n))))} + (let [whs (into {} + (comp + (filter :wfn) + (map adapt-write-handler)) + handlers) + rhs (into {} + (comp + (filter :rfn) + (map adapt-read-handler)) + handlers) + cwh (swap! write-handlers merge-and-clean whs) + crh (swap! read-handlers merge-and-clean rhs)] - clojure.lang.IPersistentMap - {"clj/map" - (reify WriteHandler - (write [_ w d] - (write-map-like w "clj/map" d)))} + (alter-var-root #'*write-handler-lookup* (constantly (-> cwh fres/associative-lookup fres/inheritance-lookup))) + (alter-var-root #'*read-handler-lookup* (constantly (-> crh fres/associative-lookup))) + nil))) - clojure.lang.Keyword - {"clj/keyword" - (reify WriteHandler - (write [_ w s] - (write-named "clj/keyword" w s)))} +(defn write-char + [n w o] + (write-tag! w n 1) + (write-int! w o)) - clojure.lang.BigInt - {"bigint" - (reify WriteHandler - (write [_ w d] - (let [^BigInteger bi (if (instance? clojure.lang.BigInt d) - (.toBigInteger ^clojure.lang.BigInt d) - d)] - (.writeTag w "bigint" 1) - (.writeBytes w (.toByteArray bi)))))} +(defn read-char + [rdr] + (char (read-object! rdr))) - ;; Persistent set - clojure.lang.IPersistentSet - {"clj/set" - (reify WriteHandler - (write [_ w o] - (write-list-like w "clj/set" o)))} +(defn write-instant + [n w o] + (write-tag! w n 1) + (write-int! w (.toEpochMilli ^Instant o))) - ;; Persistent vector - clojure.lang.IPersistentVector - {"clj/vector" - (reify WriteHandler - (write [_ w o] - (write-list-like w "clj/vector" o)))} +(defn write-offset-date-time + [n w o] + (write-tag! w n 1) + (write-int! w (.toEpochMilli ^Instant (.toInstant ^OffsetDateTime o)))) - ;; Persistent list - clojure.lang.IPersistentList - {"clj/list" - (reify WriteHandler - (write [_ w o] - (write-list-like w "clj/list" o)))} +(defn read-instant + [rdr] + (Instant/ofEpochMilli (.readInt ^Reader rdr))) - ;; Persistent seq & lazy seqs - clojure.lang.ISeq - {"clj/seq" - (reify WriteHandler - (write [_ w o] - (write-list-like w "clj/seq" o)))} - }) +(defn write-ratio + [n w o] + (write-tag! w n 2) + (write-object! w (.numerator ^Ratio o)) + (write-object! w (.denominator ^Ratio o))) +(defn read-ratio + [rdr] + (Ratio. (biginteger (read-object! rdr)) + (biginteger (read-object! rdr)))) -(def read-handlers - {"bigint" - (reify ReadHandler - (read [_ rdr _ _] - (let [^bytes bibytes (.readObject rdr)] - (bigint (BigInteger. bibytes))))) +(defn write-bigint + [n w o] + (let [^BigInteger bi (if (instance? clojure.lang.BigInt o) + (.toBigInteger ^clojure.lang.BigInt o) + o)] + (write-tag! w n 1) + (write-bytes! w (.toByteArray bi)))) - "byte" - (reify ReadHandler - (read [_ rdr _ _] - (byte (.readObject rdr)))) +(defn read-bigint + [rdr] + (let [^bytes bibytes (read-object! rdr)] + (bigint (BigInteger. bibytes)))) - "penpot/matrix" - (reify ReadHandler - (read [_ rdr _ _] - (let [^java.util.List x (.readObject rdr)] - (Matrix. (.get x 0) (.get x 1) (.get x 2) (.get x 3) (.get x 4) (.get x 5))))) +(add-handlers! + {:name "char" + :class Character + :wfn write-char + :rfn read-char} - "penpot/point" - (reify ReadHandler - (read [_ rdr _ _] - (let [^java.util.List x (.readObject rdr)] - (Point. (.get x 0) (.get x 1))))) + {:name "java/instant" + :class Instant + :wfn write-instant + :rfn read-instant} - "char" - (reify ReadHandler - (read [_ rdr _ _] - (char (.readObject rdr)))) + {:name "java/instant" + :class OffsetDateTime + :wfn write-offset-date-time + :rfn read-instant} - "java/instant" - (reify ReadHandler - (read [_ rdr _ _] - (Instant/ofEpochMilli (.readInt rdr)))) + ;; LEGACY + {:name "ratio" + :rfn read-ratio} + {:name "clj/ratio" + :class Ratio + :wfn write-ratio + :rfn read-ratio} - "ratio" - (reify ReadHandler - (read [_ rdr _ _] - (Ratio. (biginteger (.readObject rdr)) - (biginteger (.readObject rdr))))) + {:name "clj/map" + :class clojure.lang.IPersistentMap + :wfn write-map-like + :rfn read-map-like} - "clj/keyword" - (reify ReadHandler - (read [_ rdr _ _] - (keyword (.readObject rdr) (.readObject rdr)))) + {:name "clj/keyword" + :class clojure.lang.Keyword + :wfn write-named + :rfn read-keyword} - "clj/map" - (reify ReadHandler - (read [_ rdr _ _] - (read-map-like rdr))) + {:name "clj/symbol" + :class clojure.lang.Symbol + :wfn write-named + :rfn read-symbol} - "clj/set" - (reify ReadHandler - (read [_ rdr _ _] - (read-list-like rdr set))) + ;; LEGACY + {:name "bigint" + :rfn read-bigint} - "clj/vector" - (reify ReadHandler - (read [_ rdr _ _] - (read-list-like rdr vec))) + {:name "clj/bigint" + :class clojure.lang.BigInt + :wfn write-bigint + :rfn read-bigint} - "clj/list" - (reify ReadHandler - (read [_ rdr _ _] - (read-list-like rdr #(apply list %)))) + {:name "clj/set" + :class clojure.lang.IPersistentSet + :wfn write-list-like + :rfn (comp set read-object!)} - "clj/seq" - (reify ReadHandler - (read [_ rdr _ _] - (read-list-like rdr sequence))) - }) + {:name "clj/vector" + :class clojure.lang.IPersistentVector + :wfn write-list-like + :rfn (comp vec read-object!)} -(def write-handler-lookup - (-> write-handlers - fres/associative-lookup - fres/inheritance-lookup)) + {:name "clj/list" + :class clojure.lang.IPersistentList + :wfn write-list-like + :rfn #(apply list (read-object! %))} -(def read-handler-lookup - (-> read-handlers - (fres/associative-lookup))) + {:name "clj/seq" + :class clojure.lang.ISeq + :wfn write-list-like + :rfn (comp sequence read-object!)}) -;; --- Low-Level Api +;; --- PUBLIC API (defn reader [istream] - (fres/create-reader istream :handlers read-handler-lookup)) + (fres/create-reader istream :handlers *read-handler-lookup*)) (defn writer [ostream] - (fres/create-writer ostream :handlers write-handler-lookup)) + (fres/create-writer ostream :handlers *write-handler-lookup*)) (defn read! [reader] @@ -267,8 +278,6 @@ [writer data] (fres/write-object writer data)) -;; --- High-Level Api - (defn encode [data] (with-open [^ByteArrayOutputStream output (ByteArrayOutputStream.)] From 4ece0cdeda131ad018b5a7deefafda84054b3080 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 14:57:49 +0200 Subject: [PATCH 114/682] :sparkles: Make transit module extensible --- common/src/app/common/transit.cljc | 224 +++++++++++++---------------- 1 file changed, 96 insertions(+), 128 deletions(-) diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc index 1f17cd2892..fd770904cf 100644 --- a/common/src/app/common/transit.cljc +++ b/common/src/app/common/transit.cljc @@ -6,6 +6,7 @@ (ns app.common.transit (:require + [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.uri :as uri] @@ -13,7 +14,6 @@ [lambdaisland.uri :as luri] [linked.core :as lk] [linked.set :as lks] - #?(:cljs ["luxon" :as lxn])) #?(:clj (:import @@ -28,7 +28,12 @@ lambdaisland.uri.URI linked.set.LinkedSet))) -;; --- MISC +(def write-handlers (atom nil)) +(def read-handlers (atom nil)) +(def write-handler-map (atom nil)) +(def read-handler-map (atom nil)) + +;; --- HELPERS #?(:clj (defn str->bytes @@ -44,151 +49,115 @@ ([^bytes data, ^String encoding] (String. data encoding)))) -#?(:clj - (def ^:private file-write-handler - (t/write-handler - (constantly "file") - (fn [v] (str v))))) +(defn add-handlers! + [& handlers] + (letfn [(adapt-write-handler [{:keys [id class wfn]}] + [class (t/write-handler (constantly id) wfn)]) -#?(:cljs - (def bigint-read-handler - (t/read-handler - (fn [value] - (js/parseInt value 10))))) + (adapt-read-handler [{:keys [id rfn]}] + [id (t/read-handler rfn)]) -#?(:cljs - (def uuid-read-handler - (t/read-handler uuid))) + (merge-and-clean [m1 m2] + (-> (merge m1 m2) + (d/without-nils)))] -;; --- GEOM + (let [rhs (into {} + (comp + (filter :rfn) + (map adapt-read-handler)) + handlers) + whs (into {} + (comp + (filter :wfn) + (map adapt-write-handler)) + handlers) + cwh (swap! write-handlers merge-and-clean whs) + crh (swap! read-handlers merge-and-clean rhs)] -(def point-write-handler - (t/write-handler - (constantly "point") - (fn [v] (into {} v)))) - -(def point-read-handler - (t/read-handler gpt/map->Point)) - -(def matrix-write-handler - (t/write-handler - (constantly "matrix") - (fn [v] (into {} v)))) - -(def matrix-read-handler - (t/read-handler (fn [data] - #?(:cljs (gmt/map->Matrix data) - :clj (let [{:keys [a b c d e f]} data] - (gmt/matrix (double a) - (double b) - (double c) - (double d) - (double e) - (double f))))))) - -;; --- ORDERED SET - -(def ordered-set-write-handler - (t/write-handler - (constantly "ordered-set") - (fn [v] (vec v)))) - -(def ordered-set-read-handler - (t/read-handler #(into (lk/set) %))) - -;; --- DURATION - -(def duration-read-handler - #?(:cljs (t/read-handler #(.fromMillis ^js lxn/Duration %)) - :clj (t/read-handler #(Duration/ofMillis %)))) - -(def duration-write-handler - (t/write-handler - (constantly "duration") - (fn [v] (inst-ms v)))) - -;; --- TIME - -(def ^:private instant-read-handler - #?(:clj - (t/read-handler - (fn [v] (-> (Long/parseLong v) - (Instant/ofEpochMilli)))) - :cljs - (t/read-handler - (fn [v] - (let [ms (js/parseInt v 10)] - (.fromMillis ^js lxn/DateTime ms)))))) - -(def ^:private instant-write-handler - (t/write-handler - (constantly "m") - (fn [v] (str (inst-ms v))))) - -;; --- URI - -(def uri-read-handler - (t/read-handler uri/uri)) - -(def uri-write-handler - (t/write-handler - (constantly "uri") - (fn [v] (str v)))) + (reset! write-handler-map #?(:clj (t/write-handler-map cwh) :cljs cwh)) + (reset! read-handler-map #?(:clj (t/read-handler-map crh) :cljs crh)) + nil))) ;; --- HANDLERS -(def +read-handlers+ - {"matrix" matrix-read-handler - "ordered-set" ordered-set-read-handler - "point" point-read-handler - "duration" duration-read-handler - "m" instant-read-handler - "uri" uri-read-handler - #?@(:cljs ["n" bigint-read-handler - "u" uuid-read-handler]) - }) +(add-handlers! + #?(:clj + {:id "file" + :class File + :wfn str + :rfn identity}) -(def +write-handlers+ - #?(:clj - {Matrix matrix-write-handler - Point point-write-handler - Instant instant-write-handler - LinkedSet ordered-set-write-handler - URI uri-write-handler - File file-write-handler - OffsetDateTime instant-write-handler} - :cljs - {gmt/Matrix matrix-write-handler - gpt/Point point-write-handler - lxn/DateTime instant-write-handler - lxn/Duration duration-write-handler - lks/LinkedSet ordered-set-write-handler - luri/URI uri-write-handler} - )) + #?(:cljs + {:id "n" + :rfn (fn [value] + (js/parseInt value 10))}) + #?(:cljs + {:id "u" + :rfn parse-uuid}) + + {:id "point" + :class #?(:clj Point :cljs gpt/Point) + :wfn #(into {} %) + :rfn gpt/map->Point} + + {:id "matrix" + :class #?(:clj Matrix :cljs gmt/Matrix) + :wfn #(into {} %) + :rfn #?(:cljs gmt/map->Matrix + :clj (fn [{:keys [a b c d e f]}] + (gmt/matrix (double a) + (double b) + (double c) + (double d) + (double e) + (double f))))} + + {:id "ordered-set" + :class #?(:clj LinkedSet :cljs lks/LinkedSet) + :wfn vec + :rfn #(into (lk/set) %)} + + {:id "duration" + :class #?(:clj Duration :cljs lxn/Duration) + :rfn (fn [v] + #?(:clj (Duration/ofMillis v) + :cljs (.fromMillis ^js lxn/Duration v))) + :wfn inst-ms} + + {:id "m" + :class #?(:clj Instant :cljs lxn/DateTime) + :rfn (fn [v] + #?(:clj (-> (Long/parseLong v) + (Instant/ofEpochMilli)) + :cljs (let [ms (js/parseInt v 10)] + (.fromMillis ^js lxn/DateTime ms)))) + :wfn (comp str inst-ms)} + + #?(:clj + {:id "m" + :class OffsetDateTime + :wfn (comp str inst-ms)}) + + {:id "uri" + :class #?(:clj URI :cljs luri/URI) + :rfn uri/uri + :wfn str}) ;; --- Low-Level Api -#?(:clj - (def read-handlers - (t/read-handler-map +read-handlers+))) - -#?(:clj - (def write-handlers - (t/write-handler-map +write-handlers+))) - #?(:clj (defn reader ([istream] (reader istream nil)) ([istream {:keys [type] :or {type :json}}] - (t/reader istream type {:handlers read-handlers})))) + (t/reader istream type {:handlers @read-handler-map})))) #?(:clj (defn writer ([ostream] (writer ostream nil)) ([ostream {:keys [type] :or {type :json}}] - (t/writer ostream type {:handlers write-handlers})))) + (t/writer ostream type {:handlers @write-handler-map})))) #?(:clj (defn read! @@ -200,7 +169,6 @@ [writer data] (t/write writer data))) - ;; --- High-Level Api #?(:clj @@ -223,7 +191,7 @@ ([data opts] #?(:cljs (let [t (:type opts :json) - w (t/writer t {:handlers +write-handlers+})] + w (t/writer t {:handlers @write-handler-map})] (t/write w data)) :clj (->> (encode data opts) @@ -234,7 +202,7 @@ ([data opts] #?(:cljs (let [t (:type opts :json) - r (t/reader t {:handlers +read-handlers+})] + r (t/reader t {:handlers @read-handler-map})] (t/read r data)) :clj (-> (str->bytes data) From c4104c816b0e26883a99223a7c5767a6363ecf88 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Oct 2022 17:05:21 +0200 Subject: [PATCH 115/682] :tada: Add serialization optimized ObjectsMap data type --- backend/src/app/util/objects_map.clj | 391 +++++++++++++++++++++++++++ 1 file changed, 391 insertions(+) create mode 100644 backend/src/app/util/objects_map.clj diff --git a/backend/src/app/util/objects_map.clj b/backend/src/app/util/objects_map.clj new file mode 100644 index 0000000000..31889d158e --- /dev/null +++ b/backend/src/app/util/objects_map.clj @@ -0,0 +1,391 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.util.objects-map + (:require + ;; [app.common.logging :as l] + [app.common.transit :as t] + [app.common.uuid :as uuid] + [app.util.fressian :as fres] + [clojure.core :as c]) + (:import + clojure.lang.Counted + clojure.lang.IHashEq + clojure.lang.IMapEntry + clojure.lang.IObj + clojure.lang.IPersistentCollection + clojure.lang.IPersistentMap + clojure.lang.Murmur3 + clojure.lang.RT + clojure.lang.Seqable + java.nio.ByteBuffer + java.util.Iterator + java.util.UUID)) + +(set! *warn-on-reflection* true) + +(def ^:dynamic *lazy* true) + +(def RECORD-SIZE (+ 16 8)) + +(declare create) + +(defprotocol IObjectsMap + (-initialize! [_]) + (-compact! [_]) + (-get-byte-array [_]) + (-get-key-hash [_ key]) + (-force-modified! [_])) + +(deftype ObjectsMapEntry [^UUID key cmap] + IMapEntry + (key [_] key) + (getKey [_] key) + + (val [_] + (get cmap key)) + (getValue [_] + (get cmap key)) + + IHashEq + (hasheq [_] + (-get-key-hash cmap key))) + +(deftype ObjectsMapIterator [^Iterator iterator cmap] + Iterator + (hasNext [_] + (.hasNext iterator)) + + (next [_] + (let [entry (.next iterator)] + (ObjectsMapEntry. (key entry) cmap)))) + +(deftype ObjectsMap [^:unsynchronized-mutable metadata + ^:unsynchronized-mutable hash + ^:unsynchronized-mutable positions + ^:unsynchronized-mutable cache + ^:unsynchronized-mutable blob + ^:unsynchronized-mutable header + ^:unsynchronized-mutable content + ^:unsynchronized-mutable initialized? + ^:unsynchronized-mutable modified?] + + IHashEq + (hasheq [this] + (when-not hash + (set! hash (Murmur3/hashUnordered this))) + hash) + + Object + (hashCode [this] + (.hasheq ^IHashEq this)) + + IObjectsMap + (-initialize! [_] + (when-not initialized? + ;; (l/trace :fn "-initialize!" :blob blob ::l/async false) + (let [hsize (.getInt ^ByteBuffer blob 0) + header' (.slice ^ByteBuffer blob 4 hsize) + content' (.slice ^ByteBuffer blob + (int (+ 4 hsize)) + (int (- (.remaining ^ByteBuffer blob) + (+ 4 hsize)))) + + nitems (long (/ (.remaining ^ByteBuffer header') RECORD-SIZE)) + positions' (reduce (fn [positions i] + (let [hb (.slice ^ByteBuffer header' + (int (* i RECORD-SIZE)) + (int RECORD-SIZE)) + msb (.getLong ^ByteBuffer hb) + lsb (.getLong ^ByteBuffer hb) + size (.getInt ^ByteBuffer hb) + pos (.getInt ^ByteBuffer hb) + key (uuid/custom msb lsb) + val [size pos]] + (assoc! positions key val))) + (transient {}) + (range nitems))] + (set! positions (persistent! positions')) + (if *lazy* + (set! cache {}) + (loop [cache' (transient {}) + entries (seq positions)] + (if-let [[key [size pos]] (first entries)] + (let [tmp (byte-array (- size 4))] + (.get ^ByteBuffer content' (int (+ pos 4)) ^bytes tmp (int 0) (int (- size 4))) + ;; (l/trace :fn "-initialize!" :step "preload" :key key :size size :pos pos ::l/async false) + (recur (assoc! cache' key (fres/decode tmp)) + (rest entries))) + + (set! cache (persistent! cache'))))) + + (set! header header') + (set! content content') + (set! initialized? true)))) + + (-force-modified! [this] + (set! modified? true) + (doseq [key (keys positions)] + (let [val (get this key)] + (set! positions (assoc positions key nil)) + (set! cache (assoc cache key val))))) + + (-compact! [_] + (when modified? + (let [[total-items total-size new-items new-hashes] + (loop [entries (seq positions) + total-size 0 + total-items 0 + new-items {} + new-hashes {}] + (if-let [[key [size _ :as entry]] (first entries)] + (if (nil? entry) + (let [oval (get cache key) + bval (fres/encode oval) + size (+ (alength ^bytes bval) 4)] + (recur (rest entries) + (+ total-size size) + (inc total-items) + (assoc new-items key bval) + (assoc new-hashes key (c/hash oval)))) + (recur (rest entries) + (long (+ total-size size)) + (inc total-items) + new-items + new-hashes)) + [total-items total-size new-items new-hashes])) + + hsize (* total-items RECORD-SIZE) + blob' (doto (ByteBuffer/allocate (+ hsize total-size 4)) + (.putInt 0 (int hsize))) + header' (.slice ^ByteBuffer blob' 4 (int hsize)) + content' (.slice ^ByteBuffer blob' (int (+ 4 hsize)) (int total-size)) + rbuf (ByteBuffer/allocate RECORD-SIZE) + + positions' + (loop [position 0 + entries (seq positions) + positions {}] + (if-let [[key [size prev-pos :as entry]] (first entries)] + (do + (doto ^ByteBuffer rbuf + (.clear) + (.putLong ^long (uuid/get-word-high key)) + (.putLong ^long (uuid/get-word-low key))) + + (if (nil? entry) + (let [bval (get new-items key) + hval (get new-hashes key) + size (+ (alength ^bytes bval) 4)] + + ;; (l/trace :fn "-compact!" :cache "miss" :key key :size size :pos position ::l/async false) + + (.putInt ^ByteBuffer rbuf (int size)) + (.putInt ^ByteBuffer rbuf (int position)) + (.rewind ^ByteBuffer rbuf) + + (.put ^ByteBuffer header' ^ByteBuffer rbuf) + (.putInt ^ByteBuffer content' (int hval)) + (.put ^ByteBuffer content' ^bytes bval) + (recur (+ position size) + (rest entries) + (assoc positions key [size position]))) + + (let [cbuf (.slice ^ByteBuffer content (int prev-pos) (int size))] + (.putInt ^ByteBuffer rbuf (int size)) + (.putInt ^ByteBuffer rbuf (int position)) + (.rewind ^ByteBuffer rbuf) + + ;; (l/trace :fn "-compact!" :cache "hit" :key key :size size :pos position ::l/async false) + (.put ^ByteBuffer header' ^ByteBuffer rbuf) + (.put ^ByteBuffer content' ^ByteBuffer cbuf) + (recur (long (+ position size)) + (rest entries) + (assoc positions key [size position]))))) + + positions))] + + (.rewind ^ByteBuffer header') + (.rewind ^ByteBuffer content') + (.rewind ^ByteBuffer blob') + + ;; (l/trace :fn "-compact!" :step "end" ::l/async false) + + (set! positions positions') + (set! modified? false) + (set! blob blob') + (set! header header') + (set! content content')))) + + (-get-byte-array [this] + ;; (l/trace :fn "-get-byte-array" :this (.getHashCode this) :blob blob ::l/async false) + (-compact! this) + (.array ^ByteBuffer blob)) + + (-get-key-hash [this key] + (-initialize! this) + (if (contains? cache key) + (c/hash (get cache key)) + (let [[_ pos] (get positions key)] + (.getInt ^ByteBuffer content (int pos))))) + + clojure.lang.IDeref + (deref [_] + {:positions positions + :cache cache + :blob blob + :header header + :content content + :initialized? initialized? + :modified? modified?}) + + Cloneable + (clone [_] + (if initialized? + (ObjectsMap. metadata hash positions cache blob header content initialized? modified?) + (ObjectsMap. metadata nil nil nil blob nil nil false false))) + + IObj + (meta [_] metadata) + (withMeta [this meta] + (set! metadata meta) + this) + + Seqable + (seq [this] + (-initialize! this) + (RT/chunkIteratorSeq (.iterator ^Iterable this))) + + IPersistentCollection + (equiv [_ _] + (throw (UnsupportedOperationException. "not implemented"))) + + IPersistentMap + (cons [this o] + (-initialize! this) + (if (map-entry? o) + (do + ;; (l/trace :fn "cons" :key (key o)) + (assoc this (key o) (val o))) + (if (vector? o) + (do + ;; (l/trace :fn "cons" :key (nth o 0)) + (assoc this (nth o 0) (nth o 1))) + (throw (UnsupportedOperationException. "invalid arguments to cons"))))) + + (empty [_] + (create)) + + (containsKey [this key] + (-initialize! this) + (contains? positions key)) + + (entryAt [this key] + (-initialize! this) + (ObjectsMapEntry. this key)) + + (valAt [this key] + (-initialize! this) + ;; (strace/print-stack-trace (ex-info "" {})) + (if (contains? cache key) + (do + ;; (l/trace :fn "valAt" :key key :cache "hit") + (get cache key)) + (do + (if (contains? positions key) + (let [[size pos] (get positions key) + tmp (byte-array (- size 4))] + (.get ^ByteBuffer content (int (+ pos 4)) ^bytes tmp (int 0) (int (- size 4))) + ;; (l/trace :fn "valAt" :key key :cache "miss" :size size :pos pos) + + (let [val (fres/decode tmp)] + (set! cache (assoc cache key val)) + val)) + (do + ;; (l/trace :fn "valAt" :key key :cache "miss" :val nil) + (set! cache (assoc cache key nil)) + nil))))) + + (valAt [this key not-found] + (-initialize! this) + (if (.containsKey ^IPersistentMap positions key) + (.valAt this key) + not-found)) + + (assoc [this key val] + (-initialize! this) + ;; (l/trace :fn "assoc" :key key ::l/async false) + (ObjectsMap. metadata + nil + (assoc positions key nil) + (assoc cache key val) + blob + header + content + initialized? + true)) + + (assocEx [_ _ _] + (throw (UnsupportedOperationException. "method not implemented"))) + + (without [this key] + (-initialize! this) + ;; (l/trace :fn "without" :key key ::l/async false) + (ObjectsMap. metadata + nil + (dissoc positions key) + (dissoc cache key) + blob + header + content + initialized? + true)) + + Counted + (count [_] + (count positions)) + + Iterable + (iterator [this] + (-initialize! this) + (ObjectsMapIterator. (.iterator ^Iterable positions) this)) + ) + +(defn create + ([] + (let [buf (ByteBuffer/allocate 4)] + (.putInt ^ByteBuffer buf 0 0) + (create buf))) + ([buf] + (cond + (bytes? buf) + (create (ByteBuffer/wrap ^bytes buf)) + + (instance? ByteBuffer buf) + (ObjectsMap. {} nil {} {} buf nil nil false false) + + :else + (throw (UnsupportedOperationException. "invalid arguments"))))) + +(defn wrap + [objects] + (if (instance? ObjectsMap objects) + objects + (into (create) objects))) + +(fres/add-handlers! + {:name "penpot/experimental/objects-map" + :class ObjectsMap + :wfn (fn [n w o] + (fres/write-tag! w n) + (fres/write-bytes! w (-get-byte-array o))) + :rfn (fn [r] + (-> r fres/read-object! create))}) + +(t/add-handlers! + {:id "map" + :class ObjectsMap + :wfn #(into {} %)}) From 69f084e1dfe1679220f79506cc213b956be29ab8 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Oct 2022 18:26:11 +0200 Subject: [PATCH 116/682] :sparkles: Add deleted at index to file table --- backend/src/app/migrations.clj | 7 +++++++ .../src/app/migrations/sql/0080-mod-index-names.sql | 11 +++++++++++ .../sql/0081-add-deleted-at-index-to-file-table.sql | 3 +++ 3 files changed, 21 insertions(+) create mode 100644 backend/src/app/migrations/sql/0080-mod-index-names.sql create mode 100644 backend/src/app/migrations/sql/0081-add-deleted-at-index-to-file-table.sql diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index 009593e395..b78583c58f 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -247,6 +247,13 @@ {:name "0079-mod-profile-table" :fn (mg/resource "app/migrations/sql/0079-mod-profile-table.sql")} + + {:name "0080-mod-index-names" + :fn (mg/resource "app/migrations/sql/0080-mod-index-names.sql")} + + {:name "0081-add-deleted-at-index-to-file-table" + :fn (mg/resource "app/migrations/sql/0081-add-deleted-at-index-to-file-table.sql")} + ]) diff --git a/backend/src/app/migrations/sql/0080-mod-index-names.sql b/backend/src/app/migrations/sql/0080-mod-index-names.sql new file mode 100644 index 0000000000..1d6bfaa1e4 --- /dev/null +++ b/backend/src/app/migrations/sql/0080-mod-index-names.sql @@ -0,0 +1,11 @@ +ALTER INDEX team_font_variant_deleted_at_idx +RENAME TO team_font_variant__deleted_at__idx; + +ALTER INDEX team_deleted_at_idx +RENAME TO team__deleted_at__idx; + +ALTER INDEX profile_deleted_at_idx +RENAME TO profile__deleted_at__idx; + +ALTER INDEX project_deleted_at_idx +RENAME TO project__deleted_at__idx; diff --git a/backend/src/app/migrations/sql/0081-add-deleted-at-index-to-file-table.sql b/backend/src/app/migrations/sql/0081-add-deleted-at-index-to-file-table.sql new file mode 100644 index 0000000000..3a7a9cfb07 --- /dev/null +++ b/backend/src/app/migrations/sql/0081-add-deleted-at-index-to-file-table.sql @@ -0,0 +1,3 @@ +CREATE INDEX file__deleted_at__idx + ON file (deleted_at, id) + WHERE deleted_at IS NOT NULL; From 951b3eb4fe93b8c7f2931b6423b9b902c53712e5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 6 Oct 2022 18:47:16 +0200 Subject: [PATCH 117/682] :sparkles: Integrate objects-map and introduce file feature flags --- backend/src/app/db.clj | 9 +- backend/src/app/migrations.clj | 3 + ...0082-add-features-column-to-file-table.sql | 2 + backend/src/app/rpc/mutations/files.clj | 109 +++++++++---- backend/src/app/rpc/queries/files.clj | 51 +++--- backend/src/app/rpc/queries/viewer.clj | 57 ++++--- backend/test/app/test_helpers.clj | 151 ++++++++++-------- common/src/app/common/files/features.cljc | 10 ++ common/src/app/common/pages/changes.cljc | 23 +-- .../src/app/common/pages/changes_builder.cljc | 15 +- common/src/app/common/types/component.cljc | 2 +- .../src/app/common/types/components_list.cljc | 36 ++++- common/src/app/common/types/container.cljc | 5 +- common/src/app/common/types/file.cljc | 38 ++--- common/src/app/common/types/page.cljc | 9 +- common/src/app/common/types/pages_list.cljc | 32 ++-- common/test/app/common/pages_test.cljc | 26 +-- .../test/app/common/test_helpers/files.cljc | 40 +++-- frontend/src/app/main/data/dashboard.cljs | 11 +- .../app/main/data/workspace/persistence.cljs | 40 +++-- frontend/src/app/main/features.cljs | 1 - 21 files changed, 406 insertions(+), 264 deletions(-) create mode 100644 backend/src/app/migrations/sql/0082-add-features-column-to-file-table.sql create mode 100644 common/src/app/common/files/features.cljc diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 28d8a3c501..7739580390 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -352,10 +352,13 @@ [v] (and (pgarray? v) (= "uuid" (.getBaseTypeName ^PgArray v)))) +;; TODO rename to decode-pgarray-into (defn decode-pgarray - ([v] (some->> ^PgArray v .getArray vec)) - ([v in] (some->> ^PgArray v .getArray (into in))) - ([v in xf] (some->> ^PgArray v .getArray (into in xf)))) + ([v] (decode-pgarray v [])) + ([v in] + (into in (some-> ^PgArray v .getArray))) + ([v in xf] + (into in xf (some-> ^PgArray v .getArray)))) (defn pgarray->set [v] diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index b78583c58f..77171d3455 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -254,6 +254,9 @@ {:name "0081-add-deleted-at-index-to-file-table" :fn (mg/resource "app/migrations/sql/0081-add-deleted-at-index-to-file-table.sql")} + {:name "0082-add-features-column-to-file-table" + :fn (mg/resource "app/migrations/sql/0082-add-features-column-to-file-table.sql")} + ]) diff --git a/backend/src/app/migrations/sql/0082-add-features-column-to-file-table.sql b/backend/src/app/migrations/sql/0082-add-features-column-to-file-table.sql new file mode 100644 index 0000000000..3649daa4a9 --- /dev/null +++ b/backend/src/app/migrations/sql/0082-add-features-column-to-file-table.sql @@ -0,0 +1,2 @@ +ALTER TABLE file + ADD COLUMN features text[] DEFAULT NULL; diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 605df391ec..3e87ef0cb0 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -8,6 +8,8 @@ (:require [app.common.data :as d] [app.common.exceptions :as ex] + [app.common.files.features :as ffeat] + [app.common.logging :as l] [app.common.pages :as cp] [app.common.pages.migrations :as pmg] [app.common.spec :as us] @@ -24,6 +26,7 @@ [app.rpc.semaphore :as rsem] [app.storage.impl :as simpl] [app.util.blob :as blob] + [app.util.objects-map :as omap] [app.util.services :as sv] [app.util.time :as dt] [clojure.spec.alpha :as s] @@ -44,10 +47,11 @@ ;; --- Mutation: Create File +(s/def ::features ::us/set-of-strings) (s/def ::is-shared ::us/boolean) (s/def ::create-file (s/keys :req-un [::profile-id ::name ::project-id] - :opt-un [::id ::is-shared ::components-v2])) + :opt-un [::id ::is-shared ::features ::components-v2])) (sv/defmethod ::create-file [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] @@ -68,27 +72,37 @@ (defn create-file [conn {:keys [id name project-id is-shared data revn modified-at deleted-at ignore-sync-until - components-v2] + components-v2 features] :or {is-shared false revn 0} :as params}] - (let [id (or id (:id data) (uuid/next)) - data (or data (ctf/make-file-data id components-v2)) - file (db/insert! conn :file - (d/without-nils - {:id id - :project-id project-id - :name name - :revn revn - :is-shared is-shared - :data (blob/encode data) - :ignore-sync-until ignore-sync-until - :modified-at modified-at - :deleted-at deleted-at}))] + (let [id (or id (:id data) (uuid/next)) + + ;; BACKWARD COMPATIBILITY with the components-v2 param + features (cond-> (or features #{}) + components-v2 (conj "components/v2")) + + data (or data + (binding [ffeat/*current* features] + (ctf/make-file-data id))) + + features (db/create-array conn "text" features) + file (db/insert! conn :file + (d/without-nils + {:id id + :project-id project-id + :name name + :revn revn + :is-shared is-shared + :data (blob/encode data) + :features features + :ignore-sync-until ignore-sync-until + :modified-at modified-at + :deleted-at deleted-at}))] (->> (assoc params :file-id id :role :owner) (create-file-role conn)) - (assoc file :data data))) + (-> file files/decode-row))) ;; --- Mutation: Rename File @@ -309,24 +323,59 @@ (s/def ::update-file (s/and (s/keys :req-un [::id ::session-id ::profile-id ::revn] - :opt-un [::changes ::changes-with-metadata ::components-v2]) + :opt-un [::changes ::changes-with-metadata ::components-v2 ::features]) (fn [o] (or (contains? o :changes) (contains? o :changes-with-metadata))))) +(def ^:private sql:retrieve-file + "SELECT f.*, p.team_id + FROM file AS f + JOIN project AS p ON (p.id = f.project_id) + WHERE f.id = ? + AND (f.deleted_at IS NULL OR + f.deleted_at > now()) + FOR KEY SHARE") + (sv/defmethod ::update-file {::rsem/queue :update-file} - [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + [{:keys [pool] :as cfg} {:keys [id profile-id components-v2] :as params}] (db/with-atomic [conn pool] (db/xact-lock! conn id) - (let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true}) - team-id (retrieve-team-id conn (:project-id file))] - (files/check-edition-permissions! conn profile-id id) - (with-meta - (update-file (assoc cfg :conn conn) - (assoc params :file file)) - {::audit/props {:project-id (:project-id file) - :team-id team-id}})))) + (let [file (db/exec-one! conn [sql:retrieve-file id]) + features' (:features params #{}) + features (db/decode-pgarray (:features file) features') + + ;; BACKWARD COMPATIBILITY with the components-v2 parameter + features (cond-> features + components-v2 (conj "components/v2")) + + file (assoc file :features features)] + + (when-not file + (ex/raise :type :not-found + :code :object-not-found + :hint (format "file with id '%s' does not exists" id))) + + ;; If features are specified from params and the final feature + ;; set is different than the persisted one, update it on the + ;; database. + (when (not= features features') + (let [features (db/create-array conn "text" features)] + (db/update! conn :file + {:features features} + {:id id}))) + + (binding [ffeat/*current* features + ffeat/*wrap-objects-fn* (if (features "storate/objects-map") + omap/wrap + identity)] + (files/check-edition-permissions! conn profile-id (:id file)) + (with-meta + (update-file (assoc cfg :conn conn) + (assoc params :file file)) + {::audit/props {:project-id (:project-id file) + :team-id (:team-id file)}}))))) (defn- take-snapshot? "Defines the rule when file `data` snapshot should be saved." @@ -347,7 +396,7 @@ (defn- update-file [{:keys [conn metrics] :as cfg} - {:keys [file changes changes-with-metadata session-id profile-id components-v2] :as params}] + {:keys [file changes changes-with-metadata session-id profile-id] :as params}] (when (> (:revn params) (:revn file)) @@ -378,7 +427,8 @@ (assoc :id (:id file)) (pmg/migrate-data)) - components-v2 + + (contains? ffeat/*current* "components/v2") (ctf/migrate-to-components-v2) :always @@ -455,7 +505,8 @@ :changes changes}) (when (and (:is-shared file) (seq lchanges)) - (let [team-id (retrieve-team-id conn (:project-id file))] + (let [team-id (or (:team-id file) + (retrieve-team-id conn (:project-id file)))] ;; Asynchronously publish message to the msgbus (mbus/pub! msgbus :topic team-id diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 8ed02d1c80..57748559d6 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -227,29 +227,34 @@ (d/index-by :object-id :data)))))) (defn retrieve-file - [{:keys [pool] :as cfg} id components-v2] + [{:keys [pool] :as cfg} id features] (let [file (->> (db/get-by-id pool :file id) (decode-row) (pmg/migrate-file))] - (if components-v2 + (if (contains? features "components/v2") (update file :data ctf/migrate-to-components-v2) - (if (get-in file [:data :options :components-v2]) + (if (dm/get-in file [:data :options :components-v2]) (ex/raise :type :restriction :code :feature-disabled - :hint "tried to open a components-v2 file with feature disabled") + :hint "tried to open a components/v2 file with feature disabled") file)))) +(s/def ::features ::us/set-of-strings) (s/def ::file (s/keys :req-un [::profile-id ::id] - :opt-un [::components-v2])) + :opt-un [::features ::components-v2])) (sv/defmethod ::file "Retrieve a file by its ID. Only authenticated users." - [{:keys [pool] :as cfg} {:keys [profile-id id components-v2] :as params}] - (let [perms (get-permissions pool profile-id id)] + [{:keys [pool] :as cfg} {:keys [profile-id id features components-v2] :as params}] + (let [perms (get-permissions pool profile-id id) + + ;; BACKWARD COMPATIBILTY with the components-v2 parameter + features (cond-> (or features #{}) + components-v2 (conj features "components/v2"))] (check-read-permissions! perms) - (let [file (retrieve-file cfg id components-v2) + (let [file (retrieve-file cfg id features) thumbs (retrieve-object-thumbnails cfg id)] (-> file (assoc :thumbnails thumbs) @@ -278,7 +283,7 @@ (s/def ::page (s/and (s/keys :req-un [::profile-id ::file-id] - :opt-un [::page-id ::object-id ::components-v2]) + :opt-un [::page-id ::object-id ::features ::components-v2]) (fn [obj] (if (contains? obj :object-id) (contains? obj :page-id) @@ -294,11 +299,15 @@ mandatory. Mainly used for rendering purposes." - [{:keys [pool] :as cfg} {:keys [profile-id file-id page-id object-id components-v2] :as props}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id page-id object-id features components-v2] :as props}] (check-read-permissions! pool profile-id file-id) - (let [file (retrieve-file cfg file-id components-v2) - page-id (or page-id (-> file :data :pages first)) - page (get-in file [:data :pages-index page-id])] + (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter + features (cond-> (or features #{}) + components-v2 (conj features "components/v2")) + + file (retrieve-file cfg file-id features) + page-id (or page-id (-> file :data :pages first)) + page (dm/get-in file [:data :pages-index page-id])] (cond-> (prune-thumbnails page) (uuid? object-id) @@ -384,14 +393,17 @@ (s/def ::file-data-for-thumbnail (s/keys :req-un [::profile-id ::file-id] - :opt-un [::components-v2])) + :opt-un [::components-v2 ::features])) (sv/defmethod ::file-data-for-thumbnail "Retrieves the data for generate the thumbnail of the file. Used mainly for render thumbnails on dashboard." - [{:keys [pool] :as cfg} {:keys [profile-id file-id components-v2] :as props}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id features components-v2] :as props}] (check-read-permissions! pool profile-id file-id) - (let [file (retrieve-file cfg file-id components-v2)] + (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter + features (cond-> (or features #{}) + components-v2 (conj features "components/v2")) + file (retrieve-file cfg file-id features)] {:file-id file-id :revn (:revn file) :page (get-file-thumbnail-data cfg file)})) @@ -567,8 +579,9 @@ ;; --- Helpers (defn decode-row - [{:keys [data changes] :as row}] + [{:keys [data changes features] :as row}] (when row (cond-> row - changes (assoc :changes (blob/decode changes)) - data (assoc :data (blob/decode data))))) + features (assoc :features (db/decode-pgarray features)) + changes (assoc :changes (blob/decode changes)) + data (assoc :data (blob/decode data))))) diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj index 1eebabb9bd..2e64986f48 100644 --- a/backend/src/app/rpc/queries/viewer.clj +++ b/backend/src/app/rpc/queries/viewer.clj @@ -23,8 +23,8 @@ (db/get-by-id pool :project id {:columns [:id :name :team-id]})) (defn- retrieve-bundle - [{:keys [pool] :as cfg} file-id profile-id components-v2] - (p/let [file (files/retrieve-file cfg file-id components-v2) + [{:keys [pool] :as cfg} file-id profile-id features] + (p/let [file (files/retrieve-file cfg file-id features) project (retrieve-project pool (:project-id file)) libs (files/retrieve-file-libraries cfg false file-id) users (comments/get-file-comments-users pool file-id profile-id) @@ -45,40 +45,49 @@ (s/def ::file-id ::us/uuid) (s/def ::profile-id ::us/uuid) (s/def ::share-id ::us/uuid) +(s/def ::features ::us/set-of-strings) + +;; TODO: deprecated, should be removed when version >= 1.18 (s/def ::components-v2 ::us/boolean) (s/def ::view-only-bundle - (s/keys :req-un [::file-id] :opt-un [::profile-id ::share-id ::components-v2])) + (s/keys :req-un [::file-id] + :opt-un [::profile-id ::share-id ::features ::components-v2])) (sv/defmethod ::view-only-bundle {:auth false} - [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id components-v2] :as params}] - (p/let [slink (slnk/retrieve-share-link pool file-id share-id) + [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id features components-v2] :as params}] + (p/let [;; BACKWARD COMPATIBILTY with the components-v2 parameter + features (cond-> (or features #{}) + components-v2 (conj features "components/v2")) + + slink (slnk/retrieve-share-link pool file-id share-id) perms (files/get-permissions pool profile-id file-id share-id) thumbs (files/retrieve-object-thumbnails cfg file-id) - bundle (p/-> (retrieve-bundle cfg file-id profile-id components-v2) + bundle (p/-> (retrieve-bundle cfg file-id profile-id features) (assoc :permissions perms) (assoc-in [:file :thumbnails] thumbs))] - ;; When we have neither profile nor share, we just return a not ;; found response to the user. - (when (and (not profile-id) - (not slink)) - (ex/raise :type :not-found - :code :object-not-found)) + (do + (when (and (not profile-id) + (not slink)) + (ex/raise :type :not-found + :code :object-not-found)) - ;; When we have only profile, we need to check read permissions - ;; on file. - (when (and profile-id (not slink)) - (files/check-read-permissions! pool profile-id file-id)) + ;; When we have only profile, we need to check read permissions + ;; on file. + (when (and profile-id (not slink)) + (files/check-read-permissions! pool profile-id file-id)) - (cond-> bundle - (some? slink) - (assoc :share slink) + (cond-> bundle + (some? slink) + (assoc :share slink) - (and (some? slink) + (and (some? slink) (not (contains? (:flags slink) "view-all-pages"))) - (update-in [:file :data] (fn [data] - (let [allowed-pages (:pages slink)] - (-> data - (update :pages (fn [pages] (filterv #(contains? allowed-pages %) pages))) - (update :pages-index (fn [index] (select-keys index allowed-pages)))))))))) + (update-in [:file :data] (fn [data] + (let [allowed-pages (:pages slink)] + (-> data + (update :pages (fn [pages] (filterv #(contains? allowed-pages %) pages))) + (update :pages-index (fn [index] (select-keys index allowed-pages))))))))))) + diff --git a/backend/test/app/test_helpers.clj b/backend/test/app/test_helpers.clj index 59ed7e40ac..fea2b70757 100644 --- a/backend/test/app/test_helpers.clj +++ b/backend/test/app/test_helpers.clj @@ -148,38 +148,41 @@ (defn create-profile* ([i] (create-profile* *pool* i {})) ([i params] (create-profile* *pool* i params)) - ([conn i params] + ([pool i params] (let [params (merge {:id (mk-uuid "profile" i) :fullname (str "Profile " i) :email (str "profile" i ".test@nodomain.com") :password "123123" :is-demo false} params)] - (->> params - (cmd.auth/create-profile conn) - (cmd.auth/create-profile-relations conn))))) + (with-open [conn (db/open pool)] + (->> params + (cmd.auth/create-profile conn) + (cmd.auth/create-profile-relations conn)))))) (defn create-project* ([i params] (create-project* *pool* i params)) - ([conn i {:keys [profile-id team-id] :as params}] + ([pool i {:keys [profile-id team-id] :as params}] (us/assert uuid? profile-id) (us/assert uuid? team-id) - (->> (merge {:id (mk-uuid "project" i) - :name (str "project" i)} - params) - (#'projects/create-project conn)))) + (with-open [conn (db/open pool)] + (->> (merge {:id (mk-uuid "project" i) + :name (str "project" i)} + params) + (#'projects/create-project conn))))) (defn create-file* ([i params] (create-file* *pool* i params)) - ([conn i {:keys [profile-id project-id] :as params}] + ([pool i {:keys [profile-id project-id] :as params}] (us/assert uuid? profile-id) (us/assert uuid? project-id) - (#'files/create-file conn - (merge {:id (mk-uuid "file" i) - :name (str "file" i) - :components-v2 true} - params)))) + (with-open [conn (db/open pool)] + (#'files/create-file conn + (merge {:id (mk-uuid "file" i) + :name (str "file" i) + :components-v2 true} + params))))) (defn mark-file-deleted* ([params] (mark-file-deleted* *pool* params)) @@ -188,85 +191,95 @@ (defn create-team* ([i params] (create-team* *pool* i params)) - ([conn i {:keys [profile-id] :as params}] + ([pool i {:keys [profile-id] :as params}] (us/assert uuid? profile-id) - (let [id (mk-uuid "team" i)] - (teams/create-team conn {:id id - :profile-id profile-id - :name (str "team" i)})))) + (with-open [conn (db/open pool)] + (let [id (mk-uuid "team" i)] + (teams/create-team conn {:id id + :profile-id profile-id + :name (str "team" i)}))))) (defn create-file-media-object* ([params] (create-file-media-object* *pool* params)) - ([conn {:keys [name width height mtype file-id is-local media-id] + ([pool {:keys [name width height mtype file-id is-local media-id] :or {name "sample" width 100 height 100 mtype "image/svg+xml" is-local true}}] - (db/insert! conn :file-media-object - {:id (uuid/next) - :file-id file-id - :is-local is-local - :name name - :media-id media-id - :width width - :height height - :mtype mtype}))) + + (with-open [conn (db/open pool)] + (db/insert! conn :file-media-object + {:id (uuid/next) + :file-id file-id + :is-local is-local + :name name + :media-id media-id + :width width + :height height + :mtype mtype})))) (defn link-file-to-library* ([params] (link-file-to-library* *pool* params)) - ([conn {:keys [file-id library-id] :as params}] - (#'files/link-file-to-library conn {:file-id file-id :library-id library-id}))) + ([pool {:keys [file-id library-id] :as params}] + (with-open [conn (db/open pool)] + (#'files/link-file-to-library conn {:file-id file-id :library-id library-id})))) (defn create-complaint-for - [conn {:keys [id created-at type]}] - (db/insert! conn :profile-complaint-report - {:profile-id id - :created-at (or created-at (dt/now)) - :type (name type) - :content (db/tjson {})})) + [pool {:keys [id created-at type]}] + (with-open [conn (db/open pool)] + (db/insert! conn :profile-complaint-report + {:profile-id id + :created-at (or created-at (dt/now)) + :type (name type) + :content (db/tjson {})}))) (defn create-global-complaint-for - [conn {:keys [email type created-at]}] - (db/insert! conn :global-complaint-report - {:email email - :type (name type) - :created-at (or created-at (dt/now)) - :content (db/tjson {})})) + [pool {:keys [email type created-at]}] + (with-open [conn (db/open pool)] + (db/insert! conn :global-complaint-report + {:email email + :type (name type) + :created-at (or created-at (dt/now)) + :content (db/tjson {})}))) (defn create-team-role* ([params] (create-team-role* *pool* params)) - ([conn {:keys [team-id profile-id role] :or {role :owner}}] - (#'teams/create-team-role conn {:team-id team-id - :profile-id profile-id - :role role}))) + ([pool {:keys [team-id profile-id role] :or {role :owner}}] + (with-open [conn (db/open pool)] + (#'teams/create-team-role conn {:team-id team-id + :profile-id profile-id + :role role})))) (defn create-project-role* ([params] (create-project-role* *pool* params)) - ([conn {:keys [project-id profile-id role] :or {role :owner}}] - (#'projects/create-project-role conn {:project-id project-id - :profile-id profile-id - :role role}))) + ([pool {:keys [project-id profile-id role] :or {role :owner}}] + (with-open [conn (db/open pool)] + (#'projects/create-project-role conn {:project-id project-id + :profile-id profile-id + :role role})))) (defn create-file-role* ([params] (create-file-role* *pool* params)) - ([conn {:keys [file-id profile-id role] :or {role :owner}}] - (#'files/create-file-role conn {:file-id file-id - :profile-id profile-id - :role role}))) + ([pool {:keys [file-id profile-id role] :or {role :owner}}] + (with-open [conn (db/open pool)] + (#'files/create-file-role conn {:file-id file-id + :profile-id profile-id + :role role})))) (defn update-file* ([params] (update-file* *pool* params)) - ([conn {:keys [file-id changes session-id profile-id revn] + ([pool {:keys [file-id changes session-id profile-id revn] :or {session-id (uuid/next) revn 0}}] - (let [file (db/get-by-id conn :file file-id) - msgbus (:app.msgbus/msgbus *system*) - metrics (:app.metrics/metrics *system*)] - (#'files/update-file {:conn conn - :msgbus msgbus - :metrics metrics} - {:file file - :revn revn - :components-v2 true - :changes changes - :session-id session-id - :profile-id profile-id})))) + (with-open [conn (db/open pool)] + (let [file (db/get-by-id conn :file file-id) + msgbus (:app.msgbus/msgbus *system*) + metrics (:app.metrics/metrics *system*)] + (#'files/update-file {:conn conn + :msgbus msgbus + :metrics metrics} + {:file file + :revn revn + :components-v2 true + :changes changes + :session-id session-id + :profile-id profile-id}))))) ;; --- RPC HELPERS diff --git a/common/src/app/common/files/features.cljc b/common/src/app/common/files/features.cljc new file mode 100644 index 0000000000..a51c2344fc --- /dev/null +++ b/common/src/app/common/files/features.cljc @@ -0,0 +1,10 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.files.features) + +(def ^:dynamic *current* #{}) +(def ^:dynamic *wrap-objects-fn* identity) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 1c7f580c56..227fdfa75d 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -347,27 +347,12 @@ ;; -- Components (defmethod process-change :add-component - [data {:keys [id name path main-instance-id main-instance-page shapes]}] - (ctkl/add-component data - id - name - path - main-instance-id - main-instance-page - shapes)) + [data params] + (ctkl/add-component data params)) (defmethod process-change :mod-component - [data {:keys [id name path objects]}] - (update-in data [:components id] - #(cond-> % - (some? name) - (assoc :name name) - - (some? path) - (assoc :path path) - - (some? objects) - (assoc :objects objects)))) + [data params] + (ctkl/mod-component data params)) (defmethod process-change :del-component [data {:keys [id skip-undelete?]}] diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index b1d1b91660..985b87ed56 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.files.features :as ffeat] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] @@ -50,10 +51,12 @@ (defn with-objects [changes objects] - (let [file-data (-> (ctf/make-file-data (uuid/next) uuid/zero true) - (assoc-in [:pages-index uuid/zero :objects] objects))] - (vary-meta changes assoc ::file-data file-data - ::applied-changes-count 0))) + (let [fdata (binding [ffeat/*current* #{"components/v2"}] + (ctf/make-file-data (uuid/next) uuid/zero)) + fdata (assoc-in fdata [:pages-index uuid/zero :objects] objects)] + (vary-meta changes assoc + ::file-data fdata + ::applied-changes-count 0))) (defn with-library-data [changes data] @@ -268,7 +271,7 @@ :page-id (::page-id (meta changes)) :parent-id (:parent-id shape) :shapes [(:id shape)] - :index idx})))] + :index idx})))] (-> changes (update :redo-changes conj set-parent-change) @@ -592,7 +595,7 @@ :main-instance-page main-instance-page :shapes new-shapes}) (into (map mk-change) updated-shapes)))) - (update :undo-changes + (update :undo-changes (fn [undo-changes] (-> undo-changes (d/preconj {:type :del-component diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 2614626a83..c352f981f6 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -9,7 +9,7 @@ (defn instance-root? [shape] (some? (:component-id shape))) - + (defn instance-of? [shape file-id component-id] (and (some? (:component-id shape)) diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc index 64edcab1fe..95abd7342e 100644 --- a/common/src/app/common/types/components_list.cljc +++ b/common/src/app/common/types/components_list.cljc @@ -2,30 +2,52 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KELEIDOS INC (ns app.common.types.components-list (:require - [app.common.data :as d])) + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.files.features :as feat])) (defn components-seq [file-data] (vals (:components file-data))) (defn add-component - [file-data id name path main-instance-id main-instance-page shapes] - (let [components-v2 (get-in file-data [:options :components-v2])] + [file-data {:keys [id name path main-instance-id main-instance-page shapes]}] + (let [components-v2 (dm/get-in file-data [:options :components-v2]) + wrap-object-fn feat/*wrap-objects-fn*] (cond-> file-data :always (assoc-in [:components id] {:id id :name name :path path - :objects (d/index-by :id shapes)}) + :objects (->> shapes + (d/index-by :id) + (wrap-object-fn))}) components-v2 - (update-in [:components id] assoc :main-instance-id main-instance-id - :main-instance-page main-instance-page)))) + (update-in [:components id] assoc + :main-instance-id main-instance-id + :main-instance-page main-instance-page)))) + +(defn mod-component + [file-data {:keys [id name path objects]}] + (let [wrap-objects-fn feat/*wrap-objects-fn*] + (update-in file-data [:components id] + (fn [component] + (let [objects (some-> objects wrap-objects-fn)] + (cond-> component + (some? name) + (assoc :name name) + + (some? path) + (assoc :path path) + + (some? objects) + (assoc :objects objects))))))) (defn get-component [file-data component-id] diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 104fd0f348..0915a082e5 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -6,6 +6,7 @@ (ns app.common.types.container (:require + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.spec :as us] @@ -41,8 +42,8 @@ (us/assert uuid? id) (-> (if (= type :page) - (get-in file [:pages-index id]) - (get-in file [:components id])) + (dm/get-in file [:pages-index id]) + (dm/get-in file [:components id])) (assoc :type type))) (defn get-shape diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 1ff989e503..a26333ffc2 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -7,6 +7,8 @@ (ns app.common.types.file (:require [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.files.features :as ffeat] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages.common :refer [file-version]] @@ -65,16 +67,16 @@ :pages-index {}}) (defn make-file-data - ([file-id components-v2] - (make-file-data file-id (uuid/next) components-v2)) + ([file-id] + (make-file-data file-id (uuid/next))) - ([file-id page-id components-v2] + ([file-id page-id] (let [page (ctp/make-empty-page page-id "Page-1")] (cond-> (-> empty-file-data (assoc :id file-id) (ctpl/add-page page)) - components-v2 + (contains? ffeat/*current* "components/v2") (assoc-in [:options :components-v2] true))))) ;; Helpers @@ -108,7 +110,7 @@ ([libraries component-id] (some #(ctkl/get-component (:data %) component-id) (vals libraries))) ([libraries library-id component-id] - (ctkl/get-component (get-in libraries [library-id :data]) component-id))) + (ctkl/get-component (dm/get-in libraries [library-id :data]) component-id))) (defn delete-component "Delete a component and store it to be able to be recovered later. @@ -118,7 +120,7 @@ (delete-component file-data component-id false)) ([file-data component-id skip-undelete?] - (let [components-v2 (get-in file-data [:options :components-v2]) + (let [components-v2 (dm/get-in file-data [:options :components-v2]) add-to-deleted-components (fn [file-data] @@ -144,12 +146,12 @@ (defn get-deleted-component "Retrieve a component that has been deleted but still is in the safe store." [file-data component-id] - (get-in file-data [:deleted-components component-id])) + (dm/get-in file-data [:deleted-components component-id])) (defn restore-component "Recover a deleted component and put it again in place." [file-data component-id] - (let [component (-> (get-in file-data [:deleted-components component-id]) + (let [component (-> (dm/get-in file-data [:deleted-components component-id]) (dissoc :main-instance-x :main-instance-y))] (cond-> file-data (some? component) @@ -242,7 +244,7 @@ [file-data] (let [components (ctkl/components-seq file-data)] (if (or (empty? components) - (get-in file-data [:options :components-v2])) + (dm/get-in file-data [:options :components-v2])) (assoc-in file-data [:options :components-v2] true) (let [grid-gap 50 @@ -342,12 +344,12 @@ copy-component (fn [file-data] (ctkl/add-component file-data - (:id component) - (:name component) - (:path component) - (:id main-instance-shape) - page-id - (vals (:objects component)))) + {:id (:id component) + :name (:name component) + :path (:path component) + :main-instance-id (:id main-instance-shape) + :main-instance-page page-id + :shapes (vals (:objects component))})) ; Change all existing instances to point to the local file remap-instances @@ -500,10 +502,10 @@ component-file (when component-file-id (get libraries component-file-id nil)) component (when component-id (if component-file - (get-in component-file [:data :components component-id]) + (dm/get-in component-file [:data :components component-id]) (get components component-id))) component-shape (when (and component (:shape-ref shape)) - (get-in component [:objects (:shape-ref shape)]))] + (dm/get-in component [:objects (:shape-ref shape)]))] (str/format " %s--> %s%s%s" (cond (:component-root? shape) "#" (:component-id shape) "@" @@ -518,7 +520,7 @@ component-file-id (:component-file shape) component-file (when component-file-id (get libraries component-file-id nil)) component (if component-file - (get-in component-file [:data :components component-id]) + (dm/get-in component-file [:data :components component-id]) (get components component-id))] (str/format " (%s%s)" (when component-file (str/format "<%s> " (:name component-file))) diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 975915ceda..9c5594c768 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -7,6 +7,7 @@ (ns app.common.types.page (:require [app.common.data :as d] + [app.common.files.features :as ffeat] [app.common.types.page.flow :as ctpf] [app.common.types.page.grid :as ctpg] [app.common.types.page.guide :as ctpu] @@ -48,9 +49,11 @@ (defn make-empty-page [id name] - (assoc empty-page-data - :id id - :name name)) + (let [wrap-fn ffeat/*wrap-objects-fn*] + (-> empty-page-data + (assoc :id id) + (assoc :name name) + (update :objects wrap-fn)))) ;; --- Helpers for flow diff --git a/common/src/app/common/types/pages_list.cljc b/common/src/app/common/types/pages_list.cljc index b9394c476a..777b6c0725 100644 --- a/common/src/app/common/types/pages_list.cljc +++ b/common/src/app/common/types/pages_list.cljc @@ -2,32 +2,29 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.pages-list (:require - [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.pages.helpers :as cph])) (defn get-page [file-data id] - (get-in file-data [:pages-index id])) + (dm/get-in file-data [:pages-index id])) (defn add-page - [file-data page] - (let [index (:index page) - page (dissoc page :index) - - ; It's legitimate to add a page that is already there, - ; for example in an idempotent changes operation. - add-if-not-exists (fn [pages id] - (cond - (d/seek #(= % id) pages) pages - (nil? index) (conj pages id) - :else (cph/insert-at-index pages index [id])))] - (-> file-data - (update :pages add-if-not-exists (:id page)) - (update :pages-index assoc (:id page) page)))) + [file-data {:keys [id index] :as page}] + (-> file-data + ;; It's legitimate to add a page that is already there, for + ;; example in an idempotent changes operation. + (update :pages (fn [pages] + (let [exists? (some (partial = id) pages)] + (cond + exists? pages + (nil? index) (conj pages id) + :else (cph/insert-at-index pages index [id]))))) + (update :pages-index assoc id (dissoc page :index)))) (defn pages-seq [file-data] @@ -42,4 +39,3 @@ (-> file-data (update :pages (fn [pages] (filterv #(not= % page-id) pages))) (update :pages-index dissoc page-id))) - diff --git a/common/test/app/common/pages_test.cljc b/common/test/app/common/pages_test.cljc index 89264a93dc..d9f5f2ee42 100644 --- a/common/test/app/common/pages_test.cljc +++ b/common/test/app/common/pages_test.cljc @@ -6,16 +6,22 @@ (ns app.common.pages-test (:require - [clojure.test :as t] - [clojure.pprint :refer [pprint]] + [app.common.files.features :as ffeat] [app.common.pages :as cp] [app.common.types.file :as ctf] - [app.common.uuid :as uuid])) + [app.common.uuid :as uuid] + [clojure.pprint :refer [pprint]] + [clojure.test :as t])) + +(defn- make-file-data + [file-id page-id] + (binding [ffeat/*current* #{"components/v2"}] + (ctf/make-file-data file-id page-id))) (t/deftest process-change-set-option (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (ctf/make-file-data file-id page-id true)] + data (make-file-data file-id page-id)] (t/testing "Sets option single" (let [chg {:type :set-option :page-id page-id @@ -81,7 +87,7 @@ (t/deftest process-change-add-obj (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (ctf/make-file-data file-id page-id true) + data (make-file-data file-id page-id) id-a (uuid/custom 2 1) id-b (uuid/custom 2 2) id-c (uuid/custom 2 3)] @@ -135,7 +141,7 @@ (t/deftest process-change-mod-obj (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (ctf/make-file-data file-id page-id true)] + data (make-file-data file-id page-id)] (t/testing "simple mod-obj" (let [chg {:type :mod-obj :page-id page-id @@ -162,7 +168,7 @@ (let [file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) id (uuid/custom 2 1) - data (ctf/make-file-data file-id page-id true) + data (make-file-data file-id page-id) data (-> data (assoc-in [:pages-index page-id :objects uuid/zero :shapes] [id]) (assoc-in [:pages-index page-id :objects id] @@ -206,7 +212,7 @@ file-id (uuid/custom 2 2) page-id (uuid/custom 1 1) - data (ctf/make-file-data file-id page-id true) + data (make-file-data file-id page-id) data (update-in data [:pages-index page-id :objects] #(-> % @@ -450,7 +456,7 @@ :obj {:type :rect :name "Shape 3"}} ] - data (ctf/make-file-data file-id page-id true) + data (make-file-data file-id page-id) data (cp/process-changes data changes)] (t/testing "preserve order on multiple shape mov 1" @@ -557,7 +563,7 @@ :parent-id group-1-id :shapes [shape-1-id shape-2-id]}] - data (ctf/make-file-data file-id page-id true) + data (make-file-data file-id page-id) data (cp/process-changes data changes)] (t/testing "case 1" diff --git a/common/test/app/common/test_helpers/files.cljc b/common/test/app/common/test_helpers/files.cljc index 3c3302cbae..1175907e89 100644 --- a/common/test/app/common/test_helpers/files.cljc +++ b/common/test/app/common/test_helpers/files.cljc @@ -6,16 +6,22 @@ (ns app.common.test-helpers.files (:require - [app.common.geom.point :as gpt] - [app.common.types.components-list :as ctkl] - [app.common.types.colors-list :as ctcl] - [app.common.types.container :as ctn] - [app.common.types.file :as ctf] - [app.common.types.pages-list :as ctpl] - [app.common.types.shape :as cts] - [app.common.types.shape-tree :as ctst] - [app.common.types.typographies-list :as ctyl] - [app.common.uuid :as uuid])) + [app.common.files.features :as ffeat] + [app.common.geom.point :as gpt] + [app.common.types.colors-list :as ctcl] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape :as cts] + [app.common.types.shape-tree :as ctst] + [app.common.types.typographies-list :as ctyl] + [app.common.uuid :as uuid])) + +(defn- make-file-data + [file-id page-id] + (binding [ffeat/*current* #{"components/v2"}] + (ctf/make-file-data file-id page-id))) (def ^:private idmap (atom {})) @@ -33,7 +39,7 @@ ([file-id page-id props] (merge {:id file-id :name (get props :name "File1") - :data (ctf/make-file-data file-id page-id true)} + :data (make-file-data file-id page-id)} props))) (defn sample-shape @@ -81,12 +87,12 @@ #(reduce (fn [page shape] (ctst/set-shape page shape)) % updated-shapes)) - (ctkl/add-component (:id component-shape) - (:name component-shape) - "" - shape-id - page-id - component-shapes)))))) + (ctkl/add-component {:id (:id component-shape) + :name (:name component-shape) + :path "" + :main-instance-id shape-id + :main-instance-page page-id + :shapes component-shapes})))))) (defn sample-instance [file label page-id library component-id] diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 35c1c6edd3..8b3b682de9 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -765,9 +765,14 @@ (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) - name (name (gensym (str (tr "dashboard.new-file-prefix") " "))) - components-v2 (features/active-feature? state :components-v2) - params (assoc params :name name :components-v2 components-v2)] + + name (name (gensym (str (tr "dashboard.new-file-prefix") " "))) + features (cond-> #{} + (features/active-feature? state :components-v2) + (conj "components/v2")) + params (-> params + (assoc :name name) + (assoc :features features))] (->> (rp/mutation! :create-file params) (rx/tap on-success) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index f5b2ec567f..2d4ad1d258 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -7,6 +7,7 @@ (ns app.main.data.workspace.persistence (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.logging :as log] [app.common.pages :as cp] [app.common.pages.changes-spec :as pcs] @@ -137,14 +138,16 @@ (ptk/reify ::persist-changes ptk/WatchEvent (watch [_ state _] - (let [components-v2 (features/active-feature? state :components-v2) - sid (:session-id state) - file (get state :workspace-file) - params {:id (:id file) - :revn (:revn file) - :session-id sid - :changes-with-metadata (into [] changes) - :components-v2 components-v2}] + (let [features (cond-> #{} + (features/active-feature? state :components-v2) + (conj "components/v2")) + sid (:session-id state) + file (get state :workspace-file) + params {:id (:id file) + :revn (:revn file) + :session-id sid + :changes-with-metadata (into [] changes) + :features features}] (when (= file-id (:id params)) (->> (rp/mutation :update-file params) @@ -180,15 +183,17 @@ (ptk/reify ::persist-synchronous-changes ptk/WatchEvent (watch [_ state _] - (let [components-v2 (features/active-feature? state :components-v2) - sid (:session-id state) - file (get-in state [:workspace-libraries file-id]) + (let [features (cond-> #{} + (features/active-feature? state :components-v2) + (conj "components/v2")) + sid (:session-id state) + file (dm/get-in state [:workspace-libraries file-id]) params {:id (:id file) :revn (:revn file) :session-id sid :changes changes - :components-v2 components-v2}] + :features features}] (when (:id params) (->> (rp/mutation :update-file params) @@ -220,6 +225,9 @@ (ptk/reify ::changes-persisted ptk/UpdateEvent (update [_ state] + ;; NOTE: we don't set the file features context here because + ;; there are no useful context for code that need to be executed + ;; on the frontend side (let [changes (group-by :page-id changes)] (if (= file-id (:current-file-id state)) (-> state @@ -238,7 +246,6 @@ (update-in [:workspace-libraries file-id :data] cp/process-changes changes))))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Fetching & Uploading ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -278,8 +285,11 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id) - components-v2 (features/active-feature? state :components-v2)] - (->> (rx/zip (rp/query! :file-raw {:id file-id :components-v2 components-v2}) + features (cond-> #{} + (features/active-feature? state :components-v2) + (conj "components/v2"))] + + (->> (rx/zip (rp/query! :file-raw {:id file-id :features features}) (rp/query! :team-users {:file-id file-id}) (rp/query! :project {:id project-id}) (rp/query! :file-libraries {:file-id file-id}) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index 3642edc946..c8126335e8 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -72,4 +72,3 @@ (doseq [f features-list] (when (not= f :components-v2) (toggle-feature! f))))) - From c0eab96253a734d3b13a9a01b3d717ff72a9a5f3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 14:53:08 +0200 Subject: [PATCH 118/682] :sparkles: Do not return the whole file on file rename --- backend/src/app/rpc/mutations/files.clj | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 3e87ef0cb0..b93047639a 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -119,10 +119,11 @@ (defn- rename-file [conn {:keys [id name] :as params}] - (db/update! conn :file - {:name name} - {:id id})) - + (-> (db/update! conn :file + {:name name + :modified-at (dt/now)} + {:id id}) + (select-keys [:id :name :created-at :modified-at]))) ;; --- Mutation: Set File shared From b2cbb1e60f99b98bbb3db9170a42150e7685fcf6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 14:54:15 +0200 Subject: [PATCH 119/682] :sparkles: Update srepl helpers --- backend/src/app/srepl/helpers.clj | 38 +++++++++++-------------------- backend/src/app/srepl/main.clj | 18 +++++++++++++++ 2 files changed, 31 insertions(+), 25 deletions(-) diff --git a/backend/src/app/srepl/helpers.clj b/backend/src/app/srepl/helpers.clj index 87dbd8b686..3e89feb8db 100644 --- a/backend/src/app/srepl/helpers.clj +++ b/backend/src/app/srepl/helpers.clj @@ -66,17 +66,20 @@ (db/with-atomic [conn (:app.db/pool system)] (let [file (db/get-by-id conn :file id {:for-update true}) file (-> file + (update :features db/decode-pgarray #{}) (update :data blob/decode) - (cond-> migrate? (update :data pmg/migrate-data)) - (update :data update-fn) - (update :data blob/encode) + (cond-> migrate? (update :data pmg/migrate-data))) + file (-> (update-fn file) (cond-> inc-revn? (update :revn inc)))] (when save? - (db/update! conn :file - {:data (:data file) - :revn (:revn file)} - {:id (:id file)})) - (update file :data blob/decode)))) + (let [features (db/create-array conn "text" (:features file)) + data (blob/encode (:data file))] + (db/update! conn :file + {:data data + :revn (:revn file) + :features features} + {:id id}))) + file))) (def ^:private sql:retrieve-files-chunk "SELECT id, name, created_at, data FROM file @@ -122,27 +125,12 @@ (on-end state) state)))))) - -(defn analyze-file-data - [system & {:keys [id on-form on-data]}] - (let [file (get-file system id)] - (cond - (fn? on-data) - (on-data (:data file)) - - (fn? on-form) - (walk/postwalk (fn [form] - (on-form form) - form) - (:data file))) - nil)) - (defn update-pages "Apply a function to all pages of one file. The function receives a page and returns an updated page." [data f] - (update data :pages-index d/update-vals f)) + (update data :pages-index update-vals f)) (defn update-shapes "Apply a function to all shapes of one page The function receives a shape and returns an updated shape" [page f] - (update page :objects d/update-vals f)) + (update page :objects update-vals f)) diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index 4c788d01cb..b113064882 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -16,6 +16,7 @@ [app.rpc.queries.profile :as profile] [app.srepl.fixes :as f] [app.srepl.helpers :as h] + [app.util.objects-map :as omap] [app.util.time :as dt] [clojure.pprint :refer [pprint]] [cuerdas.core :as str])) @@ -101,3 +102,20 @@ (db/update! conn :profile {:is-blocked true} {:id (:id profile)}) (db/delete! conn :http-session {:profile-id (:id profile)}) :blocked)))) + +(defn enable-objects-map-on-file + [system & {:keys [save? id]}] + (letfn [(update-file [{:keys [features] :as file}] + (if (contains? features "storage/objects-map") + file + (update file :data migrate-to-omap))) + + (migrate-to-omap [data] + (-> data + (update :pages-index update-vals #(update % :objects omap/wrap)) + (update :components update-vals #(update % :objects omap/wrap))))] + + (h/update-file! system + :id id + :update-fn update-file + :save? save?))) From 3dc2c52f64405d4faaffede168e0e20ac5381b35 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 14:54:34 +0200 Subject: [PATCH 120/682] :bug: Fix compatibility issues with jdk19 on util/async ns --- backend/src/app/util/async.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/app/util/async.clj b/backend/src/app/util/async.clj index cd89a34feb..217f54ac02 100644 --- a/backend/src/app/util/async.clj +++ b/backend/src/app/util/async.clj @@ -110,4 +110,4 @@ (defn thread-sleep [ms] - (Thread/sleep ms)) + (Thread/sleep (long ms))) From 96af4e26b0e2697438fe07de6a797126060adac0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 14:56:01 +0200 Subject: [PATCH 121/682] :zap: Improve performance issues on backend shape validation --- common/src/app/common/pages/changes.cljc | 32 +++++++++++++++++------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 227fdfa75d..8f786a5b27 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -8,6 +8,7 @@ #_:clj-kondo/ignore (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.bool :as gshb] @@ -39,7 +40,27 @@ ;; Page Transformation Changes ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; === Changes Processing Impl +;; Changes Processing Impl + +(defn validate-shapes! + [data objects items] + (letfn [(validate-shape! [[page-id {:keys [id] :as shape}]] + (when-not (= shape (dm/get-in data [:pages-index page-id :objects id])) + ;; If object has change verify is correct + (us/verify ::cts/shape shape)))] + (let [lookup (d/getf objects)] + (->> (into #{} (map :page-id) items) + (mapcat (fn [page-id] + (filter #(= page-id (:page-id %)) items))) + (mapcat (fn [{:keys [type id page-id] :as item}] + (sequence + (comp (keep lookup) + (map (partial vector page-id))) + (case type + (:add-obj :mod-obj :del-obj) (cons id nil) + (:mov-objects :reg-objects) (:shapes item) + nil)))) + (run! validate-shape!))))) (defmulti process-change (fn [_ change] (:type change))) (defmulti process-operation (fn [_ op] (:type op))) @@ -56,14 +77,7 @@ (let [result (reduce #(or (process-change %1 %2) %1) data items)] ;; Validate result shapes (only on the backend) - #?(:clj - (doseq [page-id (into #{} (map :page-id) items)] - (let [page (get-in result [:pages-index page-id])] - (doseq [[id shape] (:objects page)] - (when-not (= shape (get-in data [:pages-index page-id :objects id])) - ;; If object has change verify is correct - (us/verify ::cts/shape shape)))))) - + #?(:clj (validate-shapes! data result items)) result))) (defmethod process-change :set-option From 2befad433f0c5f4c5fd4668a83119314ef924dbd Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 14:56:45 +0200 Subject: [PATCH 122/682] :zap: Remove unnecesary index building on :mov-objects --- common/src/app/common/pages/changes.cljc | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 8f786a5b27..f7ccb2e6e9 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -225,8 +225,8 @@ (-> (update :touched cph/set-touched-group :shapes-group) (dissoc :remote-synced?))))) - (remove-from-old-parent [cpindex objects shape-id] - (let [prev-parent-id (get cpindex shape-id)] + (remove-from-old-parent [old-objects objects shape-id] + (let [prev-parent-id (dm/get-in old-objects [shape-id :parent-id])] ;; Do nothing if the parent id of the shape is the same as ;; the new destination target parent id. (if (= prev-parent-id parent-id) @@ -263,17 +263,6 @@ (move-objects [objects] (let [valid? (every? (partial is-valid-move? objects) shapes) - - ;; Create a index of shape ids pointing to the - ;; corresponding parents; used mainly for update old - ;; parents after move operation. - cpindex (reduce (fn [index id] - (let [obj (get objects id)] - (assoc! index id (:parent-id obj)))) - (transient {}) - (keys objects)) - cpindex (persistent! cpindex) - parent (get objects parent-id) frame-id (if (= :frame (:type parent)) (:id parent) @@ -290,7 +279,7 @@ ;; Analyze the old parents and clear the old links ;; only if the new parent is different form old ;; parent. - (reduce (partial remove-from-old-parent cpindex) $ shapes) + (reduce (partial remove-from-old-parent objects) $ shapes) ;; Ensure that all shapes of the new parent has a ;; correct link to the topside frame. From 8e6b93e2a7578dcae8555bf0fbc714b5f6b65f34 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 15:03:37 +0200 Subject: [PATCH 123/682] :paperclip: Set correct license holder on common module sources --- common/src/app/common/UUIDv8.java | 2 +- common/src/app/common/attrs.cljc | 2 +- common/src/app/common/colors.cljc | 2 +- common/src/app/common/data.cljc | 2 +- common/src/app/common/data/macros.cljc | 2 +- common/src/app/common/data/undo_stack.cljc | 2 +- common/src/app/common/exceptions.cljc | 2 +- common/src/app/common/file_builder.cljc | 2 +- common/src/app/common/flags.cljc | 2 +- common/src/app/common/geom/align.cljc | 2 +- common/src/app/common/geom/matrix.cljc | 2 +- common/src/app/common/geom/point.cljc | 2 +- common/src/app/common/geom/proportions.cljc | 2 +- common/src/app/common/geom/shapes.cljc | 2 +- common/src/app/common/geom/shapes/bool.cljc | 2 +- common/src/app/common/geom/shapes/bounds.cljc | 2 +- common/src/app/common/geom/shapes/common.cljc | 2 +- common/src/app/common/geom/shapes/constraints.cljc | 2 +- common/src/app/common/geom/shapes/corners.cljc | 2 +- common/src/app/common/geom/shapes/intersect.cljc | 2 +- common/src/app/common/geom/shapes/layout.cljc | 2 +- common/src/app/common/geom/shapes/modifiers.cljc | 2 +- common/src/app/common/geom/shapes/path.cljc | 2 +- common/src/app/common/geom/shapes/rect.cljc | 2 +- common/src/app/common/geom/shapes/text.cljc | 2 +- common/src/app/common/geom/shapes/transforms.cljc | 2 +- common/src/app/common/logging.cljc | 2 +- common/src/app/common/math.cljc | 2 +- common/src/app/common/media.cljc | 2 +- common/src/app/common/pages.cljc | 2 +- common/src/app/common/pages/changes.cljc | 2 +- common/src/app/common/pages/changes_builder.cljc | 2 +- common/src/app/common/pages/changes_spec.cljc | 2 +- common/src/app/common/pages/common.cljc | 2 +- common/src/app/common/pages/diff.cljc | 2 +- common/src/app/common/pages/focus.cljc | 2 +- common/src/app/common/pages/helpers.cljc | 2 +- common/src/app/common/pages/indices.cljc | 2 +- common/src/app/common/pages/migrations.cljc | 2 +- common/src/app/common/path/bool.cljc | 2 +- common/src/app/common/path/commands.cljc | 2 +- common/src/app/common/path/shapes_to_path.cljc | 2 +- common/src/app/common/path/subpaths.cljc | 2 +- common/src/app/common/perf.cljc | 2 +- common/src/app/common/pprint.cljc | 2 +- common/src/app/common/spec.cljc | 2 +- common/src/app/common/text.cljc | 2 +- common/src/app/common/transit.cljc | 2 +- common/src/app/common/types/color.cljc | 2 +- common/src/app/common/types/colors_list.cljc | 2 +- common/src/app/common/types/component.cljc | 2 +- common/src/app/common/types/container.cljc | 2 +- common/src/app/common/types/file.cljc | 2 +- common/src/app/common/types/page.cljc | 2 +- common/src/app/common/types/shape.cljc | 2 +- common/src/app/common/types/shape_tree.cljc | 2 +- common/src/app/common/types/typographies_list.cljc | 2 +- common/src/app/common/types/typography.cljc | 2 +- common/src/app/common/uri.cljc | 2 +- common/src/app/common/uuid.cljc | 2 +- common/src/app/common/uuid_impl.js | 2 +- common/src/app/common/version.cljc | 2 +- 62 files changed, 62 insertions(+), 62 deletions(-) diff --git a/common/src/app/common/UUIDv8.java b/common/src/app/common/UUIDv8.java index 261ee1a932..a4c99fa8c0 100644 --- a/common/src/app/common/UUIDv8.java +++ b/common/src/app/common/UUIDv8.java @@ -3,7 +3,7 @@ License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - Copyright (c) UXBOX Labs SL + Copyright (c) KALEIDOS INC This file contains a UUIDv8 with conformance with https://datatracker.ietf.org/doc/html/draft-peabody-dispatch-new-uuid-format diff --git a/common/src/app/common/attrs.cljc b/common/src/app/common/attrs.cljc index 83bdd27cc6..7409172feb 100644 --- a/common/src/app/common/attrs.cljc +++ b/common/src/app/common/attrs.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.attrs (:require diff --git a/common/src/app/common/colors.cljc b/common/src/app/common/colors.cljc index 95dbd63b6d..4e7a5dfd09 100644 --- a/common/src/app/common/colors.cljc +++ b/common/src/app/common/colors.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.colors (:refer-clojure :exclude [test])) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index f4b58f7f29..8dd84f3eee 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.data "Data manipulation and query helper functions." diff --git a/common/src/app/common/data/macros.cljc b/common/src/app/common/data/macros.cljc index a9573782f0..6c93dab858 100644 --- a/common/src/app/common/data/macros.cljc +++ b/common/src/app/common/data/macros.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC #_:clj-kondo/ignore (ns app.common.data.macros diff --git a/common/src/app/common/data/undo_stack.cljc b/common/src/app/common/data/undo_stack.cljc index 09694aa01d..61117cb2ee 100644 --- a/common/src/app/common/data/undo_stack.cljc +++ b/common/src/app/common/data/undo_stack.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.data.undo-stack (:refer-clojure :exclude [peek]) diff --git a/common/src/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc index 142b67fffe..e136285158 100644 --- a/common/src/app/common/exceptions.cljc +++ b/common/src/app/common/exceptions.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.exceptions "A helpers for work with exceptions." diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 3c0bea7323..a45d0d027b 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.file-builder "A version parsing helper." diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index 2bdaab8231..8a5d1ea6e3 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.flags "Flags parsing algorithm." diff --git a/common/src/app/common/geom/align.cljc b/common/src/app/common/geom/align.cljc index 19d91e7b27..82afa4232f 100644 --- a/common/src/app/common/geom/align.cljc +++ b/common/src/app/common/geom/align.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.align (:require diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index b9d8eca199..4d9e793265 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.matrix (:require diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index e60b97d7fd..8cbd5dabf0 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.point (:refer-clojure :exclude [divide min max]) diff --git a/common/src/app/common/geom/proportions.cljc b/common/src/app/common/geom/proportions.cljc index 0fd8dccc3f..8294e4301c 100644 --- a/common/src/app/common/geom/proportions.cljc +++ b/common/src/app/common/geom/proportions.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.proportions) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index a08fe8a21a..d0d05d47fd 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes (:require diff --git a/common/src/app/common/geom/shapes/bool.cljc b/common/src/app/common/geom/shapes/bool.cljc index a71fc52c9c..f404e680c8 100644 --- a/common/src/app/common/geom/shapes/bool.cljc +++ b/common/src/app/common/geom/shapes/bool.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.bool (:require diff --git a/common/src/app/common/geom/shapes/bounds.cljc b/common/src/app/common/geom/shapes/bounds.cljc index 33227cc369..065ff5e5d8 100644 --- a/common/src/app/common/geom/shapes/bounds.cljc +++ b/common/src/app/common/geom/shapes/bounds.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.bounds (:require diff --git a/common/src/app/common/geom/shapes/common.cljc b/common/src/app/common/geom/shapes/common.cljc index 4c85437ec3..8cb899f045 100644 --- a/common/src/app/common/geom/shapes/common.cljc +++ b/common/src/app/common/geom/shapes/common.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.common (:require diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index ee5a213d21..79821b364c 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.constraints (:require diff --git a/common/src/app/common/geom/shapes/corners.cljc b/common/src/app/common/geom/shapes/corners.cljc index 5fc7103c9d..ec7a825b71 100644 --- a/common/src/app/common/geom/shapes/corners.cljc +++ b/common/src/app/common/geom/shapes/corners.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.corners (:require diff --git a/common/src/app/common/geom/shapes/intersect.cljc b/common/src/app/common/geom/shapes/intersect.cljc index a61bd96f76..34b5ec7f64 100644 --- a/common/src/app/common/geom/shapes/intersect.cljc +++ b/common/src/app/common/geom/shapes/intersect.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.intersect (:require diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index 8020fbef1a..9d7300aca9 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.layout (:require diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index cd8a1ab021..c54ead388a 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.modifiers (:require diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc index b1922095a9..9178f2774a 100644 --- a/common/src/app/common/geom/shapes/path.cljc +++ b/common/src/app/common/geom/shapes/path.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.path (:require diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 692c1d2ba0..6637b7533a 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.rect (:require diff --git a/common/src/app/common/geom/shapes/text.cljc b/common/src/app/common/geom/shapes/text.cljc index 992f3c0926..c32cf84c4d 100644 --- a/common/src/app/common/geom/shapes/text.cljc +++ b/common/src/app/common/geom/shapes/text.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.text (:require diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index f9bda0dc2d..be27921094 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.transforms (:require diff --git a/common/src/app/common/logging.cljc b/common/src/app/common/logging.cljc index 9b93c1a108..33eb6e6342 100644 --- a/common/src/app/common/logging.cljc +++ b/common/src/app/common/logging.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.logging (:require diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index f5bb087745..d32531a26d 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.math "A collection of math utils." diff --git a/common/src/app/common/media.cljc b/common/src/app/common/media.cljc index 4c6e45e230..3709168c2e 100644 --- a/common/src/app/common/media.cljc +++ b/common/src/app/common/media.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.media (:require diff --git a/common/src/app/common/pages.cljc b/common/src/app/common/pages.cljc index b4c43458cb..0af3397227 100644 --- a/common/src/app/common/pages.cljc +++ b/common/src/app/common/pages.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages "A common (clj/cljs) functions and specs for pages." diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index f7ccb2e6e9..9e60acfd0d 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages.changes #_:clj-kondo/ignore diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 985b87ed56..52eae30f86 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages.changes-builder (:require diff --git a/common/src/app/common/pages/changes_spec.cljc b/common/src/app/common/pages/changes_spec.cljc index 4a6c69efa5..1e12a643ee 100644 --- a/common/src/app/common/pages/changes_spec.cljc +++ b/common/src/app/common/pages/changes_spec.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages.changes-spec (:require diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index 1b73deac24..181cf32cf4 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages.common (:require diff --git a/common/src/app/common/pages/diff.cljc b/common/src/app/common/pages/diff.cljc index 46ba2f46bd..dfcd4bf29d 100644 --- a/common/src/app/common/pages/diff.cljc +++ b/common/src/app/common/pages/diff.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages.diff "Given a page in its old version and the new will retrieve a map with diff --git a/common/src/app/common/pages/focus.cljc b/common/src/app/common/pages/focus.cljc index 7feab505a7..0463ae1eb2 100644 --- a/common/src/app/common/pages/focus.cljc +++ b/common/src/app/common/pages/focus.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages.focus (:require diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 02e21f8c2a..8d74c88cbe 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages.helpers (:require diff --git a/common/src/app/common/pages/indices.cljc b/common/src/app/common/pages/indices.cljc index f8b85bec01..9a7a94b6ac 100644 --- a/common/src/app/common/pages/indices.cljc +++ b/common/src/app/common/pages/indices.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages.indices (:require diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index 7807ea3ee0..79c9de1c81 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pages.migrations (:require diff --git a/common/src/app/common/path/bool.cljc b/common/src/app/common/path/bool.cljc index 543a60b13a..97333e7f02 100644 --- a/common/src/app/common/path/bool.cljc +++ b/common/src/app/common/path/bool.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.path.bool (:require diff --git a/common/src/app/common/path/commands.cljc b/common/src/app/common/path/commands.cljc index 5bae5862b2..7a02f6ec4d 100644 --- a/common/src/app/common/path/commands.cljc +++ b/common/src/app/common/path/commands.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.path.commands (:require diff --git a/common/src/app/common/path/shapes_to_path.cljc b/common/src/app/common/path/shapes_to_path.cljc index 6c3916ba5e..99caf6356a 100644 --- a/common/src/app/common/path/shapes_to_path.cljc +++ b/common/src/app/common/path/shapes_to_path.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.path.shapes-to-path (:require diff --git a/common/src/app/common/path/subpaths.cljc b/common/src/app/common/path/subpaths.cljc index 7501cba90b..ba1fb73252 100644 --- a/common/src/app/common/path/subpaths.cljc +++ b/common/src/app/common/path/subpaths.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.path.subpaths (:require diff --git a/common/src/app/common/perf.cljc b/common/src/app/common/perf.cljc index 4ec78df503..46ee65c877 100644 --- a/common/src/app/common/perf.cljc +++ b/common/src/app/common/perf.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.perf (:require diff --git a/common/src/app/common/pprint.cljc b/common/src/app/common/pprint.cljc index 78fc78b712..c17bc54736 100644 --- a/common/src/app/common/pprint.cljc +++ b/common/src/app/common/pprint.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.pprint (:refer-clojure :exclude [prn]) diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 6714415a35..0435116306 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.spec "Data validation & assertion helpers." diff --git a/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index df39fe7176..aee191e1e6 100644 --- a/common/src/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.text (:require diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc index fd770904cf..3f485ec7d2 100644 --- a/common/src/app/common/transit.cljc +++ b/common/src/app/common/transit.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.transit (:require diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index 9ce0da1c6c..ffe1da4109 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.color (:require diff --git a/common/src/app/common/types/colors_list.cljc b/common/src/app/common/types/colors_list.cljc index 6a1143471f..0739ae4a20 100644 --- a/common/src/app/common/types/colors_list.cljc +++ b/common/src/app/common/types/colors_list.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.colors-list) diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index c352f981f6..6d20c9ef3b 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.component) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 0915a082e5..8d08bb123a 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.container (:require diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index a26333ffc2..9ad0748f4a 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.file (:require diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 9c5594c768..8f3da96c85 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.page (:require diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 21b488e4a6..cc6df341b8 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape (:require diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 633e757a40..c732e87e04 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.shape-tree (:require diff --git a/common/src/app/common/types/typographies_list.cljc b/common/src/app/common/types/typographies_list.cljc index bda59c4a88..f388d0dd46 100644 --- a/common/src/app/common/types/typographies_list.cljc +++ b/common/src/app/common/types/typographies_list.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.typographies-list) diff --git a/common/src/app/common/types/typography.cljc b/common/src/app/common/types/typography.cljc index 0c1a15ef7d..426b00b7bc 100644 --- a/common/src/app/common/types/typography.cljc +++ b/common/src/app/common/types/typography.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.types.typography (:require diff --git a/common/src/app/common/uri.cljc b/common/src/app/common/uri.cljc index 627c0b5d38..4fe6e967a4 100644 --- a/common/src/app/common/uri.cljc +++ b/common/src/app/common/uri.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.uri (:refer-clojure :exclude [uri?]) diff --git a/common/src/app/common/uuid.cljc b/common/src/app/common/uuid.cljc index 85d3086a72..baef0d5f3b 100644 --- a/common/src/app/common/uuid.cljc +++ b/common/src/app/common/uuid.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.uuid (:refer-clojure :exclude [next uuid zero?]) diff --git a/common/src/app/common/uuid_impl.js b/common/src/app/common/uuid_impl.js index 06f0220ef5..5c184f808c 100644 --- a/common/src/app/common/uuid_impl.js +++ b/common/src/app/common/uuid_impl.js @@ -3,7 +3,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * - * Copyright (c) UXBOX Labs SL + * Copyright (c) KALEIDOS INC */ "use strict"; diff --git a/common/src/app/common/version.cljc b/common/src/app/common/version.cljc index 348052c40a..e73bd42690 100644 --- a/common/src/app/common/version.cljc +++ b/common/src/app/common/version.cljc @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns app.common.version "A version parsing helper." From e50137d186513f6b4d1b1dd28f946c916b63ae05 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 15:21:39 +0200 Subject: [PATCH 124/682] :sparkles: Minor improvement on internal RPC metadata api --- backend/src/app/rpc.clj | 4 ++-- backend/src/app/rpc/commands/auth.clj | 11 ++++++----- backend/src/app/rpc/commands/binfile.clj | 10 ++++++---- backend/src/app/rpc/commands/ldap.clj | 5 +++-- backend/src/app/rpc/commands/verify_token.clj | 5 +++-- backend/src/app/rpc/mutations/profile.clj | 5 +++-- backend/src/app/rpc/mutations/teams.clj | 3 ++- backend/src/app/rpc/queries/files.clj | 12 ++++++------ 8 files changed, 31 insertions(+), 24 deletions(-) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 12e16db883..2b4b204aa8 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -32,13 +32,13 @@ (defn- handle-response-transformation [response request mdata] (let [response (if (sv/wrapped? response) @response response)] - (if-let [transform-fn (:transform-response mdata)] + (if-let [transform-fn (::transform-response mdata)] (p/do (transform-fn request response)) (p/resolved response)))) (defn- handle-before-comple-hook [response mdata] - (when-let [hook-fn (:before-complete mdata)] + (when-let [hook-fn (::before-complete mdata)] (ex/ignoring (hook-fn))) response) diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index 26576194c7..6f7484e2b3 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -14,6 +14,7 @@ [app.db :as db] [app.emails :as eml] [app.loggers.audit :as audit] + [app.rpc :as-alias rpc] [app.rpc.doc :as-alias doc] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] @@ -134,7 +135,7 @@ profile)] (with-meta response - {:transform-response ((:create session) (:id profile)) + {::rpc/transform-response ((:create session) (:id profile)) ::audit/props (audit/profile->props profile) ::audit/profile-id (:id profile)}))))) @@ -161,7 +162,7 @@ ::doc/added "1.15"} [{:keys [session] :as cfg} _] (with-meta {} - {:transform-response (:delete session)})) + {::rpc/transform-response (:delete session)})) ;; ---- COMMAND: Recover Profile @@ -401,7 +402,7 @@ token (tokens/generate sprops claims) resp {:invitation-token token}] (with-meta resp - {:transform-response ((:create session) (:id profile)) + {::rpc/transform-response ((:create session) (:id profile)) ::audit/replace-props (audit/profile->props profile) ::audit/profile-id (:id profile)})) @@ -410,7 +411,7 @@ ;; we need to mark this session as logged. (not= "penpot" (:auth-backend profile)) (with-meta (profile/strip-private-attrs profile) - {:transform-response ((:create session) (:id profile)) + {::rpc/transform-response ((:create session) (:id profile)) ::audit/replace-props (audit/profile->props profile) ::audit/profile-id (:id profile)}) @@ -418,7 +419,7 @@ ;; to sign in the user directly, without email verification. (true? is-active) (with-meta (profile/strip-private-attrs profile) - {:transform-response ((:create session) (:id profile)) + {::rpc/transform-response ((:create session) (:id profile)) ::audit/replace-props (audit/profile->props profile) ::audit/profile-id (:id profile)}) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 8144b7464b..9c01debe0d 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -16,6 +16,7 @@ [app.config :as cf] [app.db :as db] [app.media :as media] + [app.rpc :as-alias rpc] [app.rpc.doc :as-alias doc] [app.rpc.queries.files :as files] [app.rpc.queries.projects :as projects] @@ -879,10 +880,11 @@ (export! output-stream))))] (with-meta (sv/wrap nil) - {:transform-response (fn [_ response] - (-> response - (assoc :body resp) - (assoc :headers {"content-type" "application/octet-stream"})))}))) + {::rpc/transform-response + (fn [_ response] + (-> response + (assoc :body resp) + (assoc :headers {"content-type" "application/octet-stream"})))}))) (s/def ::file ::media/upload) (s/def ::import-binfile diff --git a/backend/src/app/rpc/commands/ldap.clj b/backend/src/app/rpc/commands/ldap.clj index a0eab43e94..d7633336fd 100644 --- a/backend/src/app/rpc/commands/ldap.clj +++ b/backend/src/app/rpc/commands/ldap.clj @@ -11,6 +11,7 @@ [app.common.spec :as us] [app.db :as db] [app.loggers.audit :as-alias audit] + [app.rpc :as-alias rpc] [app.rpc.commands.auth :as cmd.auth] [app.rpc.doc :as-alias doc] [app.rpc.queries.profile :as profile] @@ -62,12 +63,12 @@ :member-email (:email profile)) token (tokens :generate claims)] (with-meta {:invitation-token token} - {:transform-response ((:create session) (:id profile)) + {::rpc/transform-response ((:create session) (:id profile)) ::audit/props (:props profile) ::audit/profile-id (:id profile)})) (with-meta profile - {:transform-response ((:create session) (:id profile)) + {::rpc/transform-response ((:create session) (:id profile)) ::audit/props (:props profile) ::audit/profile-id (:id profile)}))))) diff --git a/backend/src/app/rpc/commands/verify_token.clj b/backend/src/app/rpc/commands/verify_token.clj index 03e5e1d00f..f02db88399 100644 --- a/backend/src/app/rpc/commands/verify_token.clj +++ b/backend/src/app/rpc/commands/verify_token.clj @@ -10,6 +10,7 @@ [app.common.spec :as us] [app.db :as db] [app.loggers.audit :as audit] + [app.rpc :as-alias rpc] [app.rpc.doc :as-alias doc] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] @@ -67,7 +68,7 @@ {:id (:id profile)})) (with-meta claims - {:transform-response ((:create session) profile-id) + {::rpc/transform-response ((:create session) profile-id) ::audit/name "verify-profile-email" ::audit/props (audit/profile->props profile) ::audit/profile-id (:id profile)}))) @@ -171,7 +172,7 @@ (let [profile (accept-invitation cfg claims invitation member)] (with-meta (assoc claims :state :created) - {:transform-response ((:create session) (:id profile)) + {::rpc/transform-response ((:create session) (:id profile)) ::audit/name "accept-team-invitation" ::audit/props (merge (audit/profile->props profile) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 39e4c92e93..73555557a2 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -14,6 +14,7 @@ [app.emails :as eml] [app.loggers.audit :as audit] [app.media :as media] + [app.rpc :as-alias rpc] [app.rpc.commands.auth :as cmd.auth] [app.rpc.doc :as-alias doc] [app.rpc.mutations.teams :as teams] @@ -277,7 +278,7 @@ {:id profile-id}) (with-meta {} - {:transform-response (:delete session)})))) + {::rpc/transform-response (:delete session)})))) (def sql:owned-teams "with owner_teams as ( @@ -323,7 +324,7 @@ ::doc/deprecated "1.15"} [{:keys [session] :as cfg} _] (with-meta {} - {:transform-response (:delete session)})) + {::rpc/transform-response (:delete session)})) ;; --- MUTATION: Recover Profile diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index e7f6e88dfa..da04e4c65f 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -16,6 +16,7 @@ [app.emails :as eml] [app.loggers.audit :as audit] [app.media :as media] + [app.rpc :as-alias rpc] [app.rpc.mutations.projects :as projects] [app.rpc.permissions :as perms] [app.rpc.queries.profile :as profile] @@ -490,7 +491,7 @@ (with-meta team {::audit/props {:invitations (count emails)} - :before-complete + ::rpc/before-complete #(audit-fn :cmd :submit :type "mutation" :name "invite-team-member" diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 57748559d6..349c54bc27 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -17,6 +17,7 @@ [app.common.types.shape-tree :as ctt] [app.db :as db] [app.db.sql :as sql] + [app.rpc :as-alias rpc] [app.rpc.helpers :as rpch] [app.rpc.permissions :as perms] [app.rpc.queries.projects :as projects] @@ -569,12 +570,11 @@ (ex/raise :type :not-found :code :file-thumbnail-not-found)) - (with-meta - {:data (:data row) - :props (some-> (:props row) db/decode-transit-pgobject) - :revn (:revn row) - :file-id (:file-id row)} - {:transform-response (rpch/http-cache {:max-age (* 1000 60 60)})}))) + (with-meta {:data (:data row) + :props (some-> (:props row) db/decode-transit-pgobject) + :revn (:revn row) + :file-id (:file-id row)} + {::rpc/transform-response (rpch/http-cache {:max-age (* 1000 60 60)})}))) ;; --- Helpers From 4fe767c169d43b1391f4d40d36e547dd6e1cf8c0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 15:22:04 +0200 Subject: [PATCH 125/682] :zap: Add missing type hints on binfile ns --- backend/src/app/rpc/commands/binfile.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 9c01debe0d..d4275b281d 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -808,7 +808,7 @@ cs (volatile! nil)] (try (l/info :hint "start exportation" :export-id id) - (with-open [output (io/output-stream output)] + (with-open [^AutoCloseable output (io/output-stream output)] (binding [*position* (atom 0)] (write-export! (assoc cfg ::output output)))) @@ -831,7 +831,7 @@ (defn export-to-tmpfile! [cfg] (let [path (tmp/tempfile :prefix "penpot.export.")] - (with-open [output (io/output-stream path)] + (with-open [^AutoCloseable output (io/output-stream path)] (export! cfg output) path))) @@ -843,7 +843,7 @@ (try (l/info :hint "start importation" :import-id id) (binding [*position* (atom 0)] - (with-open [input (io/input-stream input)] + (with-open [^AutoCloseable input (io/input-stream input)] (read-import! (assoc cfg ::input input)))) (catch Throwable cause From 8de1ae04783be414a798276be03f4213ea4d24c3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 15:22:46 +0200 Subject: [PATCH 126/682] :paperclip: Add update-file process time log entry --- backend/resources/log4j2-devenv.xml | 1 + backend/src/app/rpc/mutations/files.clj | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/backend/resources/log4j2-devenv.xml b/backend/resources/log4j2-devenv.xml index fe69b81978..b653c60fa4 100644 --- a/backend/resources/log4j2-devenv.xml +++ b/backend/resources/log4j2-devenv.xml @@ -32,6 +32,7 @@ + diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index b93047639a..14d79347a9 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -20,6 +20,8 @@ [app.loggers.audit :as audit] [app.metrics :as mtx] [app.msgbus :as mbus] + [app.rpc :as-alias rpc] + [app.rpc.doc :as-alias doc] [app.rpc.permissions :as perms] [app.rpc.queries.files :as files] [app.rpc.queries.projects :as proj] @@ -54,6 +56,7 @@ :opt-un [::id ::is-shared ::features ::components-v2])) (sv/defmethod ::create-file + {::doc/added "1.0"} [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] (db/with-atomic [conn pool] (let [team-id (retrieve-team-id conn project-id)] @@ -112,6 +115,7 @@ (s/keys :req-un [::profile-id ::name ::id])) (sv/defmethod ::rename-file + {::doc/added "1.0"} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) @@ -135,6 +139,7 @@ (s/keys :req-un [::profile-id ::id ::is-shared])) (sv/defmethod ::set-file-shared + {::doc/added "1.2"} [{:keys [pool] :as cfg} {:keys [id profile-id is-shared] :as params}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) @@ -161,6 +166,7 @@ (s/keys :req-un [::id ::profile-id])) (sv/defmethod ::delete-file + {::doc/added "1.0"} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) @@ -339,7 +345,8 @@ FOR KEY SHARE") (sv/defmethod ::update-file - {::rsem/queue :update-file} + {::rsem/queue :update-file + ::doc/added "1.0"} [{:keys [pool] :as cfg} {:keys [id profile-id components-v2] :as params}] (db/with-atomic [conn pool] (db/xact-lock! conn id) @@ -351,7 +358,8 @@ features (cond-> features components-v2 (conj "components/v2")) - file (assoc file :features features)] + file (assoc file :features features) + tpoint (dt/tpoint)] (when-not file (ex/raise :type :not-found @@ -375,8 +383,15 @@ (with-meta (update-file (assoc cfg :conn conn) (assoc params :file file)) - {::audit/props {:project-id (:project-id file) - :team-id (:team-id file)}}))))) + {::audit/props + {:project-id (:project-id file) + :team-id (:team-id file)} + + ::rpc/before-complete + (fn [] + (let [elapsed (tpoint)] + (l/trace :hint "update-file" :time (dt/format-duration elapsed))))}))))) + (defn- take-snapshot? "Defines the rule when file `data` snapshot should be saved." @@ -539,6 +554,7 @@ (s/keys :req-un [::changes ::revn ::session-id ::id])) (sv/defmethod ::update-temp-file + {::doc/added "1.7"} [{:keys [pool] :as cfg} {:keys [profile-id session-id id revn changes] :as params}] (db/with-atomic [conn pool] (db/insert! conn :file-change @@ -556,6 +572,7 @@ (s/keys :req-un [::id ::profile-id])) (sv/defmethod ::persist-temp-file + {::doc/added "1.7"} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) From f28b62cd3d3a268ec2c4451b823c3d14c020df19 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 11 Oct 2022 17:17:22 +0200 Subject: [PATCH 127/682] :bug: Fix inconsistencies on common/types specs --- common/src/app/common/types/shape.cljc | 2 +- common/src/app/common/types/shape/layout.cljc | 5 +++++ common/src/app/common/types/shape/shadow.cljc | 6 +++--- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index cc6df341b8..29b4c2b976 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -212,7 +212,7 @@ ::strokes ::stroke-color ;; TODO: same thing ::stroke-color-ref-file ;; - ::stroke-color-ref-i ;; + ::stroke-color-ref-id ;; ::stroke-opacity ;; ::stroke-style ::stroke-width diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 81ed9f108d..8309afa9d2 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -39,6 +39,11 @@ ::layout-h-orientation ::layout-v-orientation])) +(s/def ::m1 ::us/safe-number) +(s/def ::m2 ::us/safe-number) +(s/def ::m3 ::us/safe-number) +(s/def ::m4 ::us/safe-number) + (s/def ::layout-margin (s/keys :req-un [::m1] :opt-un [::m2 ::m3 ::m4])) diff --git a/common/src/app/common/types/shape/shadow.cljc b/common/src/app/common/types/shape/shadow.cljc index 839152d0f5..e2ffe0c88f 100644 --- a/common/src/app/common/types/shape/shadow.cljc +++ b/common/src/app/common/types/shape/shadow.cljc @@ -8,9 +8,9 @@ (:require [app.common.spec :as us] [app.common.types.color :as ctc] + [app.common.types.shape.shadow.color :as-alias shadow-color] [clojure.spec.alpha :as s])) - ;;; SHADOW EFFECT (s/def ::id (s/nilable uuid?)) @@ -27,7 +27,7 @@ (s/def ::file-id (s/nilable uuid?)) (s/def ::ref-id (s/nilable uuid?)) -(s/def :shadow/color +(s/def ::shadow-color/color (s/keys :opt-un [::color ::opacity ::gradient @@ -37,7 +37,7 @@ (s/def ::shadow-props (s/keys :req-un [::id ::style - ::color + ::shadow-color/color ::offset-x ::offset-y ::blur From a6113df5523f0e50444d9eeefab26c38163ae323 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 18 Oct 2022 20:14:41 +0200 Subject: [PATCH 128/682] :bug: Remove unused translation --- frontend/translations/en.po | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 89b266192e..8d3b444bfd 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -582,10 +582,6 @@ msgstr "Pin/Unpin" msgid "dashboard.projects-title" msgstr "Projects" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promote to owner" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Want to remove your account?" From 5aeac28f36aa796beeb70dfb1b085fc5211bd8f0 Mon Sep 17 00:00:00 2001 From: Stas Haas Date: Tue, 18 Oct 2022 10:09:57 +0000 Subject: [PATCH 129/682] :globe_with_meridians: Add translations for: German. Currently translated at 100.0% (1214 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 180 ++++++++++++++++++++---------------- 1 file changed, 98 insertions(+), 82 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 4019da1d85..069eea79a3 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-18 08:01+0000\n" "Last-Translator: Stas Haas \n" -"Language-Team: German " -"\n" +"Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -18,8 +18,8 @@ msgstr "Sie haben schon ein Konto?" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" msgstr "" -"Überprüfen Sie Ihre E-Mail, klicken Sie auf den Link um sich zu " -"verifizieren und Penpot zu nutzen." +"Überprüfen Sie Ihre E-Mail, klicken Sie auf den Link um sich zu verifizieren " +"und Penpot zu nutzen." #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" @@ -36,8 +36,8 @@ msgstr "Einfach testen?" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" msgstr "" -"Das ist eine DEMO-VERSION, verwenden Sie es NICHT zum Arbeiten, die " -"Projekte werden regelmäßig gelöscht." +"Das ist eine DEMO-VERSION, verwenden Sie es NICHT zum Arbeiten, die Projekte " +"werden regelmäßig gelöscht." #: src/app/main/ui/auth/register.cljs, #: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs @@ -108,7 +108,8 @@ msgstr "" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.recovery-token-sent" -msgstr "Der Link zur Wiederherstellung des Passworts wurde an Ihre E-Mail gesendet." +msgstr "" +"Der Link zur Wiederherstellung des Passworts wurde an Ihre E-Mail gesendet." #: src/app/main/ui/auth/verify_token.cljs msgid "auth.notifications.team-invitation-accepted" @@ -265,7 +266,8 @@ msgstr "Praktisches Tutorial" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.walkthrough-hero.info" -msgstr "Erkunden Sie Penpot um mehr über die wichtigsten Funktionen zu erfahren." +msgstr "" +"Erkunden Sie Penpot um mehr über die wichtigsten Funktionen zu erfahren." #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.walkthrough-hero.start" @@ -326,8 +328,8 @@ msgstr "Sie haben hier noch keine Dateien" msgid "dashboard.empty-placeholder-drafts" msgstr "" "Hier gibt es noch keine Dateien. Wenn Sie einige Vorlagen ausprobieren " -"möchten, können Sie zum Abschnitt [Bibliotheken & " -"Vorlagen](https://penpot.app/libraries-templates.html) gehen" +"möchten, können Sie zum Abschnitt [Bibliotheken & Vorlagen](https://penpot." +"app/libraries-templates.html) gehen" msgid "dashboard.export-binary-multi" msgstr "%s Penpot-Dateien herunterladen (.penpot)" @@ -380,13 +382,12 @@ msgstr "* kann Komponenten, Grafiken, Farben und/oder Textstile enthalten." msgid "dashboard.export.explain" msgstr "" "Eine oder mehrere Dateien, die Sie exportieren möchten, verwenden geteilte " -"Bibliotheken. Was möchten Sie mit den Assets* aus diesen Bibliotheken " -"machen?" +"Bibliotheken. Was möchten Sie mit den Assets* aus diesen Bibliotheken machen?" msgid "dashboard.export.options.all.message" msgstr "" -"Dateien mit geteilten Bibliotheken werden exportiert, und ihre " -"Verknüpfungen bleiben erhalten." +"Dateien mit geteilten Bibliotheken werden exportiert, und ihre Verknüpfungen " +"bleiben erhalten." msgid "dashboard.export.options.all.title" msgstr "Geteilte Bibliotheken exportieren" @@ -429,18 +430,17 @@ msgstr[1] "%s Schriftarten hinzugefügt" msgid "dashboard.fonts.hero-text1" msgstr "" "Jede Webschriftart, die Sie hier hochladen, wird der Liste der Schriftarten " -"hinzugefügt, die in den Texteigenschaften der Dateien dieses Teams " -"verfügbar ist. Schriftarten mit dem gleichen Schriftfamilien-Namen werden " -"als **eine einzige Schriftfamilie** gruppiert. Sie können Schriftarten in " -"den folgenden Formaten hochladen: **TTF, OTF und WOFF** (nur eine wird " -"benötigt)." +"hinzugefügt, die in den Texteigenschaften der Dateien dieses Teams verfügbar " +"ist. Schriftarten mit dem gleichen Schriftfamilien-Namen werden als **eine " +"einzige Schriftfamilie** gruppiert. Sie können Schriftarten in den folgenden " +"Formaten hochladen: **TTF, OTF und WOFF** (nur eine wird benötigt)." msgid "dashboard.fonts.hero-text2" msgstr "" "Sie sollten nur Schriftarten hochladen, die Sie besitzen oder für die Sie " "eine Lizenz zur Verwendung in Penpot verfügen. Weitere Informationen finden " -"Sie im Abschnitt über Inhaltsrechte in den [Nutzungsbedingungen von " -"Penpot](https://penpot.app/terms.html). Mehr über die [Lizenzierung von " +"Sie im Abschnitt über Inhaltsrechte in den [Nutzungsbedingungen von Penpot]" +"(https://penpot.app/terms.html). Mehr über die [Lizenzierung von " "Schriftarten erfahren Sie hier](https://www.typography.com/faq)." #: src/app/main/ui/dashboard/fonts.cljs @@ -455,8 +455,8 @@ msgstr "Huch! Wir konnten diese Datei nicht importieren" msgid "dashboard.import.import-error" msgstr "" -"Beim Importieren der Datei ist ein Fehler aufgetreten. Die Datei wurde " -"nicht importiert." +"Beim Importieren der Datei ist ein Fehler aufgetreten. Die Datei wurde nicht " +"importiert." msgid "dashboard.import.import-message" msgstr "%s Dateien wurden erfolgreich importiert." @@ -689,7 +689,8 @@ msgstr "Suchergebnisse" msgid "dashboard.type-something" msgstr "Zum Suchen etwas eingeben" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.unpublish-shared" msgstr "Veröffentlichung der Bibliothek aufheben" @@ -752,7 +753,8 @@ msgid "errors.auth-provider-not-configured" msgstr "Authentifizierungsanbieter ist nicht konfiguriert." msgid "errors.auth.unable-to-login" -msgstr "Anscheinend sind Sie nicht authentifiziert oder die Sitzung ist abgelaufen." +msgstr "" +"Anscheinend sind Sie nicht authentifiziert oder die Sitzung ist abgelaufen." #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" @@ -779,7 +781,8 @@ msgstr "Sie können Ihre E-Mail-Adresse nicht als Passwort verwenden" #: src/app/main/ui/settings/change_email.cljs, #: src/app/main/ui/dashboard/team.cljs msgid "errors.email-has-permanent-bounces" -msgstr "Die E-Mail-Adresse «%s» hat viele permanente Unzustellbarkeitsberichte." +msgstr "" +"Die E-Mail-Adresse «%s» hat viele permanente Unzustellbarkeitsberichte." #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-invalid-confirmation" @@ -813,7 +816,8 @@ msgid "errors.ldap-disabled" msgstr "Die LDAP-Authentifizierung ist deaktiviert." msgid "errors.media-format-unsupported" -msgstr "Das Bildformat wird nicht unterstützt (es muss ein SVG, JPG oder PNG sein)." +msgstr "" +"Das Bildformat wird nicht unterstützt (es muss ein SVG, JPG oder PNG sein)." #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" @@ -822,8 +826,7 @@ msgstr "Das Bild ist zu groß, um eingefügt zu werden." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" msgstr "" -"Es scheint, dass der Bildinhalt nicht mit der Dateierweiterung " -"übereinstimmt." +"Es scheint, dass der Bildinhalt nicht mit der Dateierweiterung übereinstimmt." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-not-allowed" @@ -832,8 +835,8 @@ msgstr "Es scheint, dass dies kein gültiges Bild ist." #: src/app/main/ui/dashboard/team.cljs msgid "errors.member-is-muted" msgstr "" -"In dem von Ihnen eingeladenen Profil sind E-Mails stummgeschaltet " -"(Spam-Berichte oder hohe Unzustellbarkeitsberichte)." +"In dem von Ihnen eingeladenen Profil sind E-Mails stummgeschaltet (Spam-" +"Berichte oder hohe Unzustellbarkeitsberichte)." msgid "errors.network" msgstr "Es kann keine Verbindung zum Server hergestellt werden." @@ -871,13 +874,12 @@ msgstr "Das Mitglied, das Sie zuzuordnen möchten, existiert nicht." msgid "errors.team-leave.owner-cant-leave" msgstr "" -"Der Besitzer kann das Team nicht verlassen, Sie müssen die Besitzerrolle " -"neu zuweisen." +"Der Besitzer kann das Team nicht verlassen, Sie müssen die Besitzerrolle neu " +"zuweisen." msgid "errors.terms-privacy-agreement-invalid" msgstr "" -"Sie müssen unsere Nutzungsbedingungen und Datenschutzrichtlinien " -"akzeptieren." +"Sie müssen unsere Nutzungsbedingungen und Datenschutzrichtlinien akzeptieren." #: src/app/main/ui/auth/verify_token.cljs msgid "errors.token-expired" @@ -934,8 +936,8 @@ msgstr "Betreff" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"Bitte beschreiben Sie den Grund Ihrer E-Mail und geben Sie an, ob es sich " -"um ein Problem, eine Idee oder einem Bedenken handelt. Ein Mitglied unseres " +"Bitte beschreiben Sie den Grund Ihrer E-Mail und geben Sie an, ob es sich um " +"ein Problem, eine Idee oder einem Bedenken handelt. Ein Mitglied unseres " "Teams wird Ihnen so schnell wie möglich antworten." #: src/app/main/ui/settings/feedback.cljs @@ -1435,8 +1437,8 @@ msgstr "Es gibt keine Einladungen." #: src/app/main/ui/dashboard/team.cljs msgid "labels.no-invitations-hint" msgstr "" -"Drücken Sie die Schaltfläche \"Zum Team einladen\", um weitere Mitglieder " -"in dieses Team einzuladen." +"Drücken Sie die Schaltfläche \"Zum Team einladen\", um weitere Mitglieder in " +"dieses Team einzuladen." #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" @@ -1789,33 +1791,40 @@ msgstr "Sind Sie sicher, dass Sie dieses Projekt löschen möchten?" msgid "modals.delete-project-confirm.title" msgstr "Projekt löschen" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.accept" msgid_plural "modals.delete-shared-confirm.accept" msgstr[0] "Datei löschen" msgstr[1] "Dateien löschen" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.message" msgid_plural "modals.delete-shared-confirm.message" msgstr[0] "Möchten Sie diese Datei wirklich löschen?" msgstr[1] "Möchten Sie diese Dateien wirklich löschen?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message" msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Diese Datei enthält Bibliotheken, die in dieser Datei verwendet werden:" -msgstr[1] "Diese Datei enthält Bibliotheken, die in diesen Dateien verwendet werden:" +msgstr[0] "" +"Diese Datei enthält Bibliotheken, die in dieser Datei verwendet werden:" +msgstr[1] "" +"Diese Datei enthält Bibliotheken, die in diesen Dateien verwendet werden:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message-plural" msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "Diese Dateien enthalten Bibliotheken, die in dieser Datei verwendet werden:" +msgstr[0] "" +"Diese Dateien enthalten Bibliotheken, die in dieser Datei verwendet werden:" msgstr[1] "" -"Diese Dateien enthalten Bibliotheken, die in diesen Dateien verwendet " -"werden:" +"Diese Dateien enthalten Bibliotheken, die in diesen Dateien verwendet werden:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" msgid_plural "modals.delete-shared-confirm.title" msgstr[0] "Datei löschen" @@ -1845,7 +1854,8 @@ msgstr "Mitglied löschen" #: src/app/main/ui/dashboard/team.cljs msgid "modals.delete-team-member-confirm.message" -msgstr "Sind Sie sicher, dass Sie dieses Mitglied aus dem Team löschen möchten?" +msgstr "" +"Sind Sie sicher, dass Sie dieses Mitglied aus dem Team löschen möchten?" #: src/app/main/ui/dashboard/team.cljs msgid "modals.delete-team-member-confirm.title" @@ -1874,8 +1884,8 @@ msgstr "Sind Sie sicher, dass Sie das %s-Team verlassen wollen?" msgid "modals.leave-and-reassign.forbidden" msgstr "" -"Sie können das Team nicht verlassen, wenn es kein anderes Mitglied gibt, " -"das Sie zum Besitzer ernennen können. Sie können das Team jedoch löschen." +"Sie können das Team nicht verlassen, wenn es kein anderes Mitglied gibt, das " +"Sie zum Besitzer ernennen können. Sie können das Team jedoch löschen." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint1" @@ -1922,12 +1932,13 @@ msgstr "Befördern" #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.hint" msgstr "" -"Wenn Sie die Eigentümerschaft übertragen, ändern Sie Ihre Rolle in Admin " -"und verlieren einige Berechtigungen für dieses Team. " +"Wenn Sie die Eigentümerschaft übertragen, ändern Sie Ihre Rolle in Admin und " +"verlieren einige Berechtigungen für dieses Team. " #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.message" -msgstr "Sind Sie sicher, dass Sie diesen Benutzer zum Eigentümer befördern wollen?" +msgstr "" +"Sind Sie sicher, dass Sie diesen Benutzer zum Eigentümer befördern wollen?" #: src/app/main/ui/dashboard/team.cljs msgid "modals.promote-owner-confirm.title" @@ -1954,11 +1965,13 @@ msgstr "Entfernen Sie “%s” als gemeinsam genutzte Bibliothek" msgid "modals.small-nudge" msgstr "Minimal" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.accept" msgstr "Veröffentlichung aufheben" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint" msgstr[0] "" @@ -1968,19 +1981,24 @@ msgstr[1] "" "Wenn Sie die Veröffentlichung aufheben, werden die darin enthaltenen " "Elemente zu einer Bibliothek dieser Dateien." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" msgid_plural "modals.unpublish-shared-confirm.message" -msgstr[0] "Möchten Sie die Veröffentlichung dieser Bibliothek wirklich aufheben?" -msgstr[1] "Möchten Sie die Veröffentlichung dieser Bibliotheken wirklich aufheben?" +msgstr[0] "" +"Möchten Sie die Veröffentlichung dieser Bibliothek wirklich aufheben?" +msgstr[1] "" +"Möchten Sie die Veröffentlichung dieser Bibliotheken wirklich aufheben?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" msgstr[0] "Es wird in dieser Datei verwendet:" msgstr[1] "Es wird in diesen Dateien verwendet:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.title" msgid_plural "modals.unpublish-shared-confirm.title" msgstr[0] "Veröffentlichung der Bibliothek aufheben" @@ -2014,8 +2032,7 @@ msgstr "Abbrechen" msgid "modals.update-remote-component.hint" msgstr "" "Sie sind dabei, eine Komponente in einer gemeinsam genutzten Bibliothek zu " -"aktualisieren. Dies kann sich auf andere Dateien auswirken, die es " -"verwenden." +"aktualisieren. Dies kann sich auf andere Dateien auswirken, die es verwenden." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs @@ -2029,8 +2046,8 @@ msgstr "Einladung erfolgreich gesendet" #: src/app/main/ui/settings/delete_account.cljs msgid "notifications.profile-deletion-not-allowed" msgstr "" -"Sie können Ihr Profil nicht löschen. Weisen Sie Ihre Teams neu zu, bevor " -"Sie fortfahren." +"Sie können Ihr Profil nicht löschen. Weisen Sie Ihre Teams neu zu, bevor Sie " +"fortfahren." #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs msgid "notifications.profile-saved" @@ -2042,9 +2059,8 @@ msgstr "Verifizierungs-E-Mail an %s gesendet. Prüfen Sie Ihren Posteingang!" msgid "onboarding-v2.before-start.desc1" msgstr "" -"Sie sollten wissen, dass es viele Ressourcen gibt, die Ihnen den Einstieg " -"in Penpot erleichtern, wie z. B. das Benutzerhandbuch und unseren " -"Youtube-Kanal." +"Sie sollten wissen, dass es viele Ressourcen gibt, die Ihnen den Einstieg in " +"Penpot erleichtern, wie z. B. das Benutzerhandbuch und unseren Youtube-Kanal." msgid "onboarding-v2.before-start.desc2" msgstr "" @@ -2104,8 +2120,8 @@ msgstr "In der Community mitmachen" msgid "onboarding-v2.welcome.desc3" msgstr "" -"Hier erfahren Sie, wie Sie bei Übersetzungen, Feature Requests, " -"Core-Entwicklung und der Fehlersuche helfen können…" +"Hier erfahren Sie, wie Sie bei Übersetzungen, Feature Requests, Core-" +"Entwicklung und der Fehlersuche helfen können…" msgid "onboarding-v2.welcome.desc3.title" msgstr "Leitfaden für Mitwirkende" @@ -2155,9 +2171,8 @@ msgstr "Open Source" msgid "onboarding.contrib.desc1" msgstr "" -"Penpot ist Open Source Software, die von und für die Gemeinschaft " -"entwickelt wurde. Wenn Sie mitarbeiten möchten, sind Sie mehr als " -"willkommen!" +"Penpot ist Open Source Software, die von und für die Gemeinschaft entwickelt " +"wurde. Wenn Sie mitarbeiten möchten, sind Sie mehr als willkommen!" msgid "onboarding.contrib.desc2.1" msgstr "Sie können das Projekt" @@ -2269,8 +2284,8 @@ msgstr "Ein Team erstellen" msgid "onboarding.team-modal.create-team-desc" msgstr "" -"In einem Team können Sie mit anderen Penpot-Nutzern zusammenarbeiten, die " -"an denselben Dateien und Projekten arbeiten." +"In einem Team können Sie mit anderen Penpot-Nutzern zusammenarbeiten, die an " +"denselben Dateien und Projekten arbeiten." msgid "onboarding.team-modal.create-team-feature-1" msgstr "Unbegrenzte Anzahl von Dateien und Projekten" @@ -2971,8 +2986,8 @@ msgstr "Gruppe erstellen" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.create-group-hint" msgstr "" -"Ihre Elemente werden automatisch nach diesem Schema benannt: \"Gruppenname " -"/ Elementname\"" +"Ihre Elemente werden automatisch nach diesem Schema benannt: \"Gruppenname / " +"Elementname\"" #: src/app/main/ui/workspace/sidebar/sitemap.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs, @@ -3344,8 +3359,7 @@ msgstr "BIBLIOTHEK" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-libraries-need-sync" msgstr "" -"Es gibt keine gemeinsam genutzte Bibliotheken, die aktualisiert werden " -"müssen" +"Es gibt keine gemeinsam genutzte Bibliotheken, die aktualisiert werden müssen" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.no-matches-for" @@ -3484,6 +3498,7 @@ msgstr "Auswahl exportieren" #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs +#, fuzzy msgid "workspace.options.export-object" msgstr "1 Element exportieren" @@ -4571,7 +4586,8 @@ msgstr "Ebene auswählen" msgid "workspace.shape.menu.show" msgstr "Anzeigen" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.show-in-assets" msgstr "Im Assets-Panel anzeigen" @@ -4847,4 +4863,4 @@ msgid "workspace.updates.update" msgstr "Aktualisieren" msgid "workspace.viewport.click-to-close-path" -msgstr "Klicken Sie, um den Pfad zu schließen" \ No newline at end of file +msgstr "Klicken Sie, um den Pfad zu schließen" From ca8919dff0a41c8f842da6d4989bd81b873001e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tummas=20J=C3=B3han=20Sigvardsen?= Date: Tue, 18 Oct 2022 08:04:29 +0000 Subject: [PATCH 130/682] :globe_with_meridians: Add translations for: Faroese. Currently translated at 4.7% (58 of 1214 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/ --- frontend/translations/fo.po | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index 729109203a..c8e7e5acc4 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-18 08:01+0000\n" "Last-Translator: Bogi Napoleon Wennerstrøm \n" -"Language-Team: Faroese " -"\n" +"Language-Team: Faroese \n" "Language: fo\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -27,7 +27,8 @@ msgstr "Stovna royndarkonto" msgid "auth.create-demo-profile" msgstr "Vilt tú royna tað?" -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs +#: src/app/main/ui/auth/register.cljs, +#: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs msgid "auth.email" msgstr "Teldupostur" @@ -133,7 +134,8 @@ msgstr "Toymisleiðsla" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.text" -msgstr "Penpot er fyri toymum. Bjóða limir at arbeiða saman á verkætlanir og fílur" +msgstr "" +"Penpot er fyri toymum. Bjóða limir at arbeiða saman á verkætlanir og fílur" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.title" @@ -169,11 +171,13 @@ msgstr "Heinta %s Penpot fílur (.penpot)" msgid "dashboard.invite-profile" msgstr "Bjóða við í toymi" -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.leave-team" msgstr "Far úr toymu" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.move-to" msgstr "Flyt til" @@ -185,7 +189,8 @@ msgstr "Flyt %s fílur til" msgid "dashboard.move-to-other-team" msgstr "Flyt til eitt annað toymi" -#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +#: src/app/main/ui/dashboard/projects.cljs, +#: src/app/main/ui/dashboard/files.cljs msgid "dashboard.new-file" msgstr "+ Nýggja fílu" @@ -218,4 +223,16 @@ msgstr "Broyt loyniorð" #: src/app/main/ui/dashboard/projects.cljs msgid "dashboard.projects-title" -msgstr "Verkætlanir" \ No newline at end of file +msgstr "Verkætlanir" + +msgid "auth.privacy-policy" +msgstr "Privat politikkur" + +msgid "common.publish" +msgstr "Gev út" + +msgid "common.share-link.all-users" +msgstr "Allir Penpot brúkarar" + +msgid "common.share-link.view-all" +msgstr "Vel alt" From bc0f0064ed45b84de40623a4df4ddb9474b0259c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 18 Oct 2022 15:06:27 +0000 Subject: [PATCH 131/682] :globe_with_meridians: Add translations for: Turkish. Currently translated at 100.0% (1216 of 1216 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/ --- frontend/translations/tr.po | 154 +++++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 62 deletions(-) diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 3a2b2f79a6..3304790901 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-10 17:34+0000\n" "Last-Translator: Oğuz Ersen \n" -"Language-Team: Turkish " -"\n" +"Language-Team: Turkish \n" "Language: tr\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -321,12 +321,10 @@ msgid "dashboard.empty-files" msgstr "Burada hiç dosyan yok" #: src/app/main/ui/dashboard/grid.cljs -#, markdown msgid "dashboard.empty-placeholder-drafts" msgstr "" -"Olamaz! Henüz dosyanız yok! Bazı şablonları denemek isterseniz " -"[Kütüphaneler ve şablonlar](https://penpot.app/libraries-templates.html) " -"sayfasına gidin" +"Olamaz! Henüz dosyanız yok! Bazı şablonları denemek isterseniz [Kütüphaneler " +"ve şablonlar](https://penpot.app/libraries-templates.html) sayfasına gidin" msgid "dashboard.export-binary-multi" msgstr "%s Penpot dosyasını indir (.penpot)" @@ -427,19 +425,18 @@ msgstr[1] "%s yazı tipi eklendi" msgid "dashboard.fonts.hero-text1" msgstr "" "Buraya yüklediğiniz herhangi bir web yazı tipi, bu takımın dosyalarının " -"metin özelliklerinde bulunan yazı tipi ailesi listesine eklenecek. Aynı " -"yazı tipi ailesi adına sahip yazı tipleri, **tek yazı tipi ailesi** olarak " -"gruplandırılacak. Yazı tiplerini şu biçimlerde yükleyebilirsiniz: **TTF, " -"OTF ve WOFF** (yalnızca bir tane gerekli olacak)." +"metin özelliklerinde bulunan yazı tipi ailesi listesine eklenecek. Aynı yazı " +"tipi ailesi adına sahip yazı tipleri, **tek yazı tipi ailesi** olarak " +"gruplandırılacak. Yazı tiplerini şu biçimlerde yükleyebilirsiniz: **TTF, OTF " +"ve WOFF** (yalnızca bir tane gerekli olacak)." msgid "dashboard.fonts.hero-text2" msgstr "" "Sadece kendinize ait veya Penpot'ta kullanılabilecek bir lisansa sahip olan " -"yazi tiplerini yükleyebilirsiniz. [Penpot'un Kullanım " -"Şartları](https://penpot.app/terms.html) içindeki İçerik hakları bölümünden " -"ayrıntılı bilgi alabilirsiniz. Ayrıca [yazı tipi " -"lisanslama](https://www.typography.com/faq) hakkında daha fazla bilgi almak " -"isteyebilirsiniz." +"yazi tiplerini yükleyebilirsiniz. [Penpot'un Kullanım Şartları](https://" +"penpot.app/terms.html) içindeki İçerik hakları bölümünden ayrıntılı bilgi " +"alabilirsiniz. Ayrıca [yazı tipi lisanslama](https://www.typography.com/faq) " +"hakkında daha fazla bilgi almak isteyebilirsiniz." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -542,7 +539,8 @@ msgstr "Yeni Proje" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-msg" -msgstr "Bana Penpot ile ilgili haberler, ürün güncellemeleri ve tavsiyeler gönder." +msgstr "" +"Bana Penpot ile ilgili haberler, ürün güncellemeleri ve tavsiyeler gönder." #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-title" @@ -681,7 +679,8 @@ msgstr "Arama sonuçları" msgid "dashboard.type-something" msgstr "Aramak için yazın" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.unpublish-shared" msgstr "Kütüphaneyi Yayından Kaldır" @@ -780,7 +779,8 @@ msgstr "Doğrulama e-postası eşleşmiyor" msgid "errors.email-spam-or-permanent-bounces" msgstr "«%s» e-postasının spam veya kalıcı olarak geri döndüğü bildirildi." -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/auth/verify_token.cljs, +#: src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.generic" msgstr "Bir şeyler ters gitti." @@ -843,8 +843,8 @@ msgstr "Profil engellendi" #: src/app/main/ui/dashboard/team.cljs msgid "errors.profile-is-muted" msgstr "" -"Profilinizde sessize alınmış e-postalar var (spam raporları veya yüksek " -"geri dönüşler sebebiyle)." +"Profilinizde sessize alınmış e-postalar var (spam raporları veya yüksek geri " +"dönüşler sebebiyle)." #: src/app/main/ui/auth/register.cljs msgid "errors.registration-disabled" @@ -1363,7 +1363,8 @@ msgstr "" msgid "labels.internal-error.main-message" msgstr "İç Hata" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.invitations" msgstr "Davetler" @@ -1388,11 +1389,11 @@ msgstr "Oturumu kapat" msgid "labels.manage-fonts" msgstr "Yazı tiplerini yönet" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.member" msgstr "Üye" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.members" msgstr "Üyeler" @@ -1504,7 +1505,9 @@ msgstr "Kaldır" msgid "labels.remove-member" msgstr "Üyeyi kaldır" -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.rename" msgstr "Yeniden adlandır" @@ -1516,7 +1519,7 @@ msgstr "Takımı yeniden adlandır" msgid "labels.resend-invitation" msgstr "Daveti yeniden gönder" -#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Yeniden dene" @@ -1616,7 +1619,7 @@ msgstr "Çalışma alanı" msgid "labels.write-new-comment" msgstr "Yeni yorum yaz" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.you" msgstr "(siz)" @@ -1656,8 +1659,7 @@ msgstr "Yeni e-postayı doğrulayın" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.info" msgstr "" -"“%s” e-posta adresinize kimliğinizi doğrulamak için bir e-posta " -"göndereceğiz." +"“%s” e-posta adresinize kimliğinizi doğrulamak için bir e-posta göndereceğiz." #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.new-email" @@ -1700,8 +1702,7 @@ msgstr "Konuşmayı sil" #: src/app/main/ui/comments.cljs msgid "modals.delete-comment-thread.message" msgstr "" -"Bu konuşmayı silmek istediğinden emin misin? Konudaki tüm yorumlar " -"silinecek." +"Bu konuşmayı silmek istediğinden emin misin? Konudaki tüm yorumlar silinecek." #: src/app/main/ui/comments.cljs msgid "modals.delete-comment-thread.title" @@ -1767,31 +1768,36 @@ msgstr "Bu projeyi silmek istediğinden emin misin?" msgid "modals.delete-project-confirm.title" msgstr "Projeyi sil" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.accept" msgid_plural "modals.delete-shared-confirm.accept" msgstr[0] "Dosyayı sil" msgstr[1] "Dosyaları sil" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.message" msgid_plural "modals.delete-shared-confirm.message" msgstr[0] "Bu dosyayı silmek istediğinizden emin misiniz?" msgstr[1] "Bu dosyaları silmek istediğinizden emin misiniz?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message" msgid_plural "modals.delete-shared-confirm.scd-message" msgstr[0] "Bu dosyada, şu dosyada kullanılmakta olan kütüphaneler var:" msgstr[1] "Bu dosyada, şu dosyalarda kullanılmakta olan kütüphaneler var:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message-plural" msgid_plural "modals.delete-shared-confirm.scd-message-plural" msgstr[0] "Bu dosyalarda, şu dosyada kullanılmakta olan kütüphaneler var:" msgstr[1] "Bu dosyalarda, şu dosyalarda kullanılmakta olan kütüphaneler var:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" msgid_plural "modals.delete-shared-confirm.title" msgstr[0] "Dosya siliniyor" @@ -1932,11 +1938,13 @@ msgstr "“%s” Paylaşılan Kütüphanesini Kaldır" msgid "modals.small-nudge" msgstr "Küçük dürtme" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.accept" msgstr "Yayından kaldır" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint" msgstr[0] "" @@ -1946,19 +1954,22 @@ msgstr[1] "" "Yayından kaldırırsanız, içindeki varlıklar bu dosyaların bir kütüphanesi " "haline gelir." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" msgid_plural "modals.unpublish-shared-confirm.message" msgstr[0] "Bu kütüphaneyi yayından kaldırmak istediğinizden emin misiniz?" msgstr[1] "Bu kütüphaneleri yayından kaldırmak istediğinizden emin misiniz?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" msgstr[0] "Bu dosyada kullanılıyor:" msgstr[1] "Bu dosyalarda kullanılıyor:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.title" msgid_plural "modals.unpublish-shared-confirm.title" msgstr[0] "Kütüphaneyi yayından kaldır" @@ -2016,8 +2027,8 @@ msgstr "%s adresine doğrulama e-postası gönderildi. E-posta kutunuza bakın!" msgid "onboarding-v2.before-start.desc1" msgstr "" -"Kullanıcı Kılavuzu ve Youtube kanalımız gibi Penpot'u kullanmaya " -"başlamanıza yardımcı olacak birçok kaynak olduğunu bilmelisiniz." +"Kullanıcı Kılavuzu ve Youtube kanalımız gibi Penpot'u kullanmaya başlamanıza " +"yardımcı olacak birçok kaynak olduğunu bilmelisiniz." msgid "onboarding-v2.before-start.desc2" msgstr "" @@ -2028,7 +2039,8 @@ msgid "onboarding-v2.before-start.desc2.title" msgstr "Kullanıcı kılavuzu" msgid "onboarding-v2.before-start.desc3" -msgstr "Bizim ve topluluğumuz tarafından hazırlanan öğreticileri izleyebilirsiniz." +msgstr "" +"Bizim ve topluluğumuz tarafından hazırlanan öğreticileri izleyebilirsiniz." msgid "onboarding-v2.before-start.desc3.title" msgstr "Video öğreticiler" @@ -2056,7 +2068,8 @@ msgstr "" "aboneliğinizi iptal edebilirsiniz." msgid "onboarding-v2.newsletter.updates" -msgstr "Bana ürün güncellemeleri gönder (yeni özellikler, sürümler, düzeltmeler...)." +msgstr "" +"Bana ürün güncellemeleri gönder (yeni özellikler, sürümler, düzeltmeler...)." msgid "onboarding-v2.welcome.desc1" msgstr "" @@ -2090,7 +2103,8 @@ msgid "onboarding.choice.team-up.create-team" msgstr "Takımınızın adı" msgid "onboarding.choice.team-up.create-team-desc" -msgstr "Takımınızı adlandırdıktan sonra, insanları katılmaya davet edebileceksiniz." +msgstr "" +"Takımınızı adlandırdıktan sonra, insanları katılmaya davet edebileceksiniz." msgid "onboarding.choice.team-up.create-team-placeholder" msgstr "Takımın adını girin" @@ -2100,8 +2114,8 @@ msgstr "Üyeleri davet edin" msgid "onboarding.choice.team-up.invite-members-info" msgstr "" -"Herkesi dahil etmeyi unutmayın. Geliştiriciler, tasarımcılar, " -"yöneticiler... çeşitlilik iyidir :)" +"Herkesi dahil etmeyi unutmayın. Geliştiriciler, tasarımcılar, yöneticiler... " +"çeşitlilik iyidir :)" msgid "onboarding.choice.team-up.invite-members-skip" msgstr "Takım oluştur ve daha sonra davet et" @@ -2139,7 +2153,8 @@ msgid "onboarding.newsletter.accept" msgstr "Evet, abone ol" msgid "onboarding.newsletter.acceptance-message" -msgstr "Abonelik talebiniz iletildi, size onaylamak için bir e-posta göndereceğiz." +msgstr "" +"Abonelik talebiniz iletildi, size onaylamak için bir e-posta göndereceğiz." msgid "onboarding.newsletter.decline" msgstr "Hayır, teşekkürler" @@ -2168,7 +2183,8 @@ msgid "onboarding.slide.0.alt" msgstr "Tasarımlar oluşturun" msgid "onboarding.slide.0.desc1" -msgstr "Tüm takım üyeleriyle iş birliği içinde güzel kullanıcı arayüzleri oluşturun." +msgstr "" +"Tüm takım üyeleriyle iş birliği içinde güzel kullanıcı arayüzleri oluşturun." msgid "onboarding.slide.0.desc2" msgstr "" @@ -2798,8 +2814,8 @@ msgstr "%s - Penpot" msgid "viewer.breaking-change.description" msgstr "" -"Bu paylaşılabilir bağlantı artık geçerli değil. Yeni bir tane oluşturun " -"veya sahibinden yeni bir tane isteyin." +"Bu paylaşılabilir bağlantı artık geçerli değil. Yeni bir tane oluşturun veya " +"sahibinden yeni bir tane isteyin." msgid "viewer.breaking-change.message" msgstr "Üzgünüm!" @@ -2932,7 +2948,8 @@ msgstr "Grup oluştur" #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.create-group-hint" -msgstr "Ögeleriniz otomatik olarak \"grup adı / öge adı\" olarak adlandırılacak" +msgstr "" +"Ögeleriniz otomatik olarak \"grup adı / öge adı\" olarak adlandırılacak" #: src/app/main/ui/workspace/sidebar/sitemap.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs, @@ -3057,7 +3074,8 @@ msgstr "Odaklanma açık" msgid "workspace.focus.selection" msgstr "Seçim" -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +#: src/app/main/data/workspace/libraries.cljs, +#: src/app/main/ui/components/color_bullet.cljs msgid "workspace.gradients.linear" msgstr "Doğrusal degrade" @@ -3458,11 +3476,13 @@ msgstr "Dışa aktarma tamamlandı" msgid "workspace.options.exporting-object" msgstr "Dışa aktarılıyor…" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.exporting-object-error" msgstr "Dışa aktarılamadı" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.exporting-object-slow" msgstr "Dışa aktarma beklenmedik şekilde yavaş" @@ -4086,7 +4106,8 @@ msgstr "Tek köşe" msgid "workspace.options.recent-fonts" msgstr "Son kullanılanlar" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.retry" msgstr "Yeniden dene" @@ -4100,8 +4121,8 @@ msgstr "Yazı tipi ara" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.select-a-shape" msgstr "" -"Diğer çalışma yüzeyine bağlantı taşımak için bir şekil, çalışma yüzeyi ya " -"da grup seçin." +"Diğer çalışma yüzeyine bağlantı taşımak için bir şekil, çalışma yüzeyi ya da " +"grup seçin." #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.select-artboard" @@ -4356,7 +4377,8 @@ msgstr "Dikey hizalama" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.use-play-button" -msgstr "Prototip görünümünü çalıştırmak için başlıktaki oynatma düğmesini kullan." +msgstr "" +"Prototip görünümünü çalıştırmak için başlıktaki oynatma düğmesini kullan." msgid "workspace.options.width" msgstr "Genişlik" @@ -4531,7 +4553,8 @@ msgstr "Katman seç" msgid "workspace.shape.menu.show" msgstr "Göster" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.show-in-assets" msgstr "Varlıklar panelinde göster" @@ -4614,7 +4637,8 @@ msgstr "Şekiller" msgid "workspace.sidebar.layers.texts" msgstr "Metinler" -#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, +#: src/app/main/ui/handoff/attributes/svg.cljs msgid "workspace.sidebar.options.svg-attrs.title" msgstr "İçe Aktarılan SVG Öznitelikleri" @@ -4806,4 +4830,10 @@ msgid "workspace.updates.update" msgstr "Güncelle" msgid "workspace.viewport.click-to-close-path" -msgstr "Yolu kapatmak için tıklayın" \ No newline at end of file +msgstr "Yolu kapatmak için tıklayın" + +msgid "errors.bad-font-plural" +msgstr "%s yazı tipleri yüklenemedi" + +msgid "errors.bad-font" +msgstr "%s yazı tipi yüklenemedi" From 5e55dddd87df95cb9985c6d00e85bbe1e73c5e3c Mon Sep 17 00:00:00 2001 From: nautilusx Date: Tue, 18 Oct 2022 11:26:11 +0000 Subject: [PATCH 132/682] :globe_with_meridians: Add translations for: German. Currently translated at 100.0% (1216 of 1216 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 069eea79a3..6ac90b816d 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -4864,3 +4864,9 @@ msgstr "Aktualisieren" msgid "workspace.viewport.click-to-close-path" msgstr "Klicken Sie, um den Pfad zu schließen" + +msgid "errors.bad-font" +msgstr "Die Schriftart %s konnte nicht geladen werden" + +msgid "errors.bad-font-plural" +msgstr "Die Schriftarten %s konnten nicht geladen werden" From 66c086d4d3179c742bdf26dd2fc96165f5090911 Mon Sep 17 00:00:00 2001 From: Yaron Shahrabani Date: Tue, 18 Oct 2022 11:12:35 +0000 Subject: [PATCH 133/682] :globe_with_meridians: Add translations for: Hebrew. Currently translated at 100.0% (1216 of 1216 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/he/ --- frontend/translations/he.po | 147 ++++++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 56 deletions(-) diff --git a/frontend/translations/he.po b/frontend/translations/he.po index 2c2cafb5ba..ed158302d9 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-16 13:49+0000\n" "Last-Translator: Yaron Shahrabani \n" -"Language-Team: Hebrew " -"\n" +"Language-Team: Hebrew \n" "Language: he\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -18,7 +18,8 @@ msgstr "כבר יש לך חשבון?" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" -msgstr "נא לחפש בדוא״ל הנכנס שלך וללחוץ על הקישור כדי לאמת ולהתחיל להשתמש ב־Penpot." +msgstr "" +"נא לחפש בדוא״ל הנכנס שלך וללחוץ על הקישור כדי לאמת ולהתחיל להשתמש ב־Penpot." #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" @@ -34,7 +35,8 @@ msgstr "מעניין אותך רק להתנסות?" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" -msgstr "זה שירות ניסיוני, לא להשתמש בו לעבודה אמתית, המיזמים יימחקו מדי פעם בפעם." +msgstr "" +"זה שירות ניסיוני, לא להשתמש בו לעבודה אמתית, המיזמים יימחקו מדי פעם בפעם." #: src/app/main/ui/auth/register.cljs, #: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs @@ -234,7 +236,8 @@ msgstr "ניהול צוות" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.text" -msgstr "Penpot מיועד לצוותים. אפשר להזמין חברים כדי לעבוד ביחד על מיזמים וקבצים" +msgstr "" +"Penpot מיועד לצוותים. אפשר להזמין חברים כדי לעבוד ביחד על מיזמים וקבצים" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.title" @@ -312,7 +315,6 @@ msgid "dashboard.empty-files" msgstr "עדיין אין לך כאן קבצים" #: src/app/main/ui/dashboard/grid.cljs -#, markdown msgid "dashboard.empty-placeholder-drafts" msgstr "" "אבוי! עדיין אין לך קבצים! כדי להתנסות עם כמה מהתבניות ניתן לגשת אל [הספריות " @@ -341,7 +343,8 @@ msgstr "ייצוא" #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.how-to" -msgstr "אפשר להוסיף הגדרות ייצוא לרכיבים ממאפייני העיצוב (מתחתית הסרגל שמשמאל)." +msgstr "" +"אפשר להוסיף הגדרות ייצוא לרכיבים ממאפייני העיצוב (מתחתית הסרגל שמשמאל)." #: src/app/main/ui/export.cljs msgid "dashboard.export-shapes.how-to-link" @@ -410,17 +413,17 @@ msgstr[3] "נוספו %s גופנים" msgid "dashboard.fonts.hero-text1" msgstr "" -"כל גופן דפדפן שיועלה כאן יתווסף לרשימת משפחת הגופנים שזמין במאפייני הטקסט " -"של הקבצים של הצוות הזה. גופנים מאותו שם של משפחת גופנים יקובצו תחת **משפחת " +"כל גופן דפדפן שיועלה כאן יתווסף לרשימת משפחת הגופנים שזמין במאפייני הטקסט של " +"הקבצים של הצוות הזה. גופנים מאותו שם של משפחת גופנים יקובצו תחת **משפחת " "גופנים יחידה**. ניתן להעלות גופנים מהסוגים הבאים: **TTF,‏ OTF ו־WOFF** (אחד " "הסוגים יספיק)." msgid "dashboard.fonts.hero-text2" msgstr "" "עליך להעלות גופנים בבעלותך או שיש לך רישיון להשתמש בהם ב־Penpot. ניתן למצוא " -"על כך מידע נוסף בסעיף זכויות התוכן של [תנאי השירות של " -"Penpot](https://penpot.app/terms.html). אפשר גם לקרוא גם על [רישוי " -"גופנים](https://www.typography.com/faq)." +"על כך מידע נוסף בסעיף זכויות התוכן של [תנאי השירות של Penpot](https://penpot." +"app/terms.html). אפשר גם לקרוא גם על [רישוי גופנים](https://www.typography." +"com/faq)." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -662,7 +665,8 @@ msgstr "תוצאות חיפוש" msgid "dashboard.type-something" msgstr "נא להקליד כדי לחפש" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.unpublish-shared" msgstr "ביטול פרסום ספרייה" @@ -761,7 +765,8 @@ msgstr "כתובת הדוא״ל לאימות חייבת להיות תואמת" msgid "errors.email-spam-or-permanent-bounces" msgstr "כתובת הדוא״ל „%s” דווחה כספאם או שההודעות תוקפצנה לצמיתות." -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs +#: src/app/main/ui/auth/verify_token.cljs, +#: src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.generic" msgstr "קרה משהו לא טוב." @@ -801,7 +806,8 @@ msgstr "נראה כי זאת תמונה שגויה." #: src/app/main/ui/dashboard/team.cljs msgid "errors.member-is-muted" -msgstr "הודעות הדוא״ל לפרופיל שהזמנת מושתקות (דיווחים על דואר זבל או הרבה החזרות)." +msgstr "" +"הודעות הדוא״ל לפרופיל שהזמנת מושתקות (דיווחים על דואר זבל או הרבה החזרות)." msgid "errors.network" msgstr "לא ניתן ליצור קשר עם שרת המנגנון." @@ -1335,7 +1341,8 @@ msgstr "" msgid "labels.internal-error.main-message" msgstr "שגיאה פנימית" -#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/settings/sidebar.cljs, src/app/main/ui/dashboard/team.cljs, +#: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.invitations" msgstr "הזמנות" @@ -1360,11 +1367,11 @@ msgstr "יציאה" msgid "labels.manage-fonts" msgstr "ניהול גופנים" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.member" msgstr "חבר" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.members" msgstr "חברים" @@ -1480,7 +1487,9 @@ msgstr "הסרה" msgid "labels.remove-member" msgstr "הסרת חבר" -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/dashboard/sidebar.cljs, +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "labels.rename" msgstr "שינוי שם" @@ -1492,7 +1501,7 @@ msgstr "שינוי שם לצוות" msgid "labels.resend-invitation" msgstr "שליחת ההזמנה מחדש" -#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs +#: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "ניסיון חוזר" @@ -1591,7 +1600,7 @@ msgstr "סביבת עבודה" msgid "labels.write-new-comment" msgstr "כתיבת הערה חדשה" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs +#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.you" msgstr "(אני)" @@ -1647,8 +1656,8 @@ msgstr "החלפת כתובת הדוא״ל שלך" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.change-owner-and-leave-confirm.message" msgstr "" -"הבעלות של הצוות הזה בידיך. נא לבחור מישהו מהרשימה כדי לקדם אותו לבעלי " -"הקבוצה בטרם עזיבתך." +"הבעלות של הצוות הזה בידיך. נא לבחור מישהו מהרשימה כדי לקדם אותו לבעלי הקבוצה " +"בטרם עזיבתך." #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" @@ -1734,7 +1743,8 @@ msgstr "למחוק את המיזם הזה?" msgid "modals.delete-project-confirm.title" msgstr "מחיקת מיזם" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.accept" msgid_plural "modals.delete-shared-confirm.accept" msgstr[0] "מחיקת קובץ" @@ -1742,7 +1752,8 @@ msgstr[1] "מחיקת קבצים" msgstr[2] "מחיקת קבצים" msgstr[3] "מחיקת קבצים" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.message" msgid_plural "modals.delete-shared-confirm.message" msgstr[0] "למחוק את הקובץ?" @@ -1750,7 +1761,8 @@ msgstr[1] "למחוק את הקבצים?" msgstr[2] "למחוק את הקבצים?" msgstr[3] "למחוק את הקבצים?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message" msgid_plural "modals.delete-shared-confirm.scd-message" msgstr[0] "לקובץ זה יש ספריות שנעשה בהן שימוש בקובץ הזה:" @@ -1758,7 +1770,8 @@ msgstr[1] "לקובץ זה יש ספריות שנעשה בהן שימוש בקב msgstr[2] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" msgstr[3] "לקובץ זה יש ספריות שנעשה בהן שימוש בקבצים האלה:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message-plural" msgid_plural "modals.delete-shared-confirm.scd-message-plural" msgstr[0] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקובץ הזה:" @@ -1766,7 +1779,8 @@ msgstr[1] "לקבצים אלו יש ספריות שנעשה בהן שימוש ב msgstr[2] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" msgstr[3] "לקבצים אלו יש ספריות שנעשה בהן שימוש בקבצים אלה:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" msgid_plural "modals.delete-shared-confirm.title" msgstr[0] "מחיקת קובץ" @@ -1815,7 +1829,8 @@ msgstr "הזמנת חברים לצוות" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" -msgstr "כיוון שאין עוד חברים בצוות הזה מלבדך, הצוות יימחק על כל המיזמים והקבצים שלו." +msgstr "" +"כיוון שאין עוד חברים בצוות הזה מלבדך, הצוות יימחק על כל המיזמים והקבצים שלו." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.message" @@ -1828,7 +1843,8 @@ msgstr "" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint1" -msgstr "הבעלות על הצוות הזה בידיך. נא לבחור מישהו כדי לקידום לבעלות בטרם עזיבתך." +msgstr "" +"הבעלות על הצוות הזה בידיך. נא לבחור מישהו כדי לקידום לבעלות בטרם עזיבתך." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint2" @@ -1889,8 +1905,8 @@ msgstr "הסרה כספריה משותפת" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.hint" msgstr "" -"לאחר הסרה כספריה משותפת, ספריית הקבצים של הקובץ הזה לא תהיה זמינה עוד " -"לשימוש בקרב שאר הקבצים שלך." +"לאחר הסרה כספריה משותפת, ספריית הקבצים של הקובץ הזה לא תהיה זמינה עוד לשימוש " +"בקרב שאר הקבצים שלך." #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs @@ -1901,11 +1917,13 @@ msgstr "הסרת „%s” כספריה משותפת" msgid "modals.small-nudge" msgstr "הינד קטן" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.accept" msgstr "ביטול פרסום" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint" msgstr[0] "ביטול הפרסום הופך את המשאבים לספרייה של הקובץ הזה." @@ -1913,7 +1931,8 @@ msgstr[1] "ביטול הפרסום הופך את המשאבים לספרייה msgstr[2] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." msgstr[3] "ביטול הפרסום הופך את המשאבים לספרייה של הקבצים האלה." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" msgid_plural "modals.unpublish-shared-confirm.message" msgstr[0] "לבטל את פרסום הספרייה הזאת?" @@ -1921,7 +1940,8 @@ msgstr[1] "לבטל את פרסום הספריות האלו?" msgstr[2] "לבטל את פרסום הספריות האלו?" msgstr[3] "לבטל את פרסום הספריות האלו?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" msgstr[0] "בשימוש בקובץ הזה:" @@ -1929,7 +1949,8 @@ msgstr[1] "בשימוש בקבצים האלה:" msgstr[2] "בשימוש בקבצים האלה:" msgstr[3] "בשימוש בקבצים האלה:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.title" msgid_plural "modals.unpublish-shared-confirm.title" msgstr[0] "ביטול פרסום ספרייה" @@ -1941,8 +1962,7 @@ msgstr[3] "ביטול פרסום ספריות" #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" msgstr "" -"פעולה זו תעדכן רכיבים בספרייה משותפת. עשוי להשפיע על קבצים אחרים שמשתמשים " -"בה." +"פעולה זו תעדכן רכיבים בספרייה משותפת. עשוי להשפיע על קבצים אחרים שמשתמשים בה." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs @@ -1991,11 +2011,12 @@ msgstr "הודעת האימות נשלחה בדוא״ל אל %s. נא לבדוק msgid "onboarding-v2.before-start.desc1" msgstr "" -"רצוי לדעת שיש מגוון משאבים זמינים לך כדי לסייע לך להתחיל להשתמש ב־Penpot " -"כמו המדריך למשתמשים וערוץ ה־YouTube שלנו." +"רצוי לדעת שיש מגוון משאבים זמינים לך כדי לסייע לך להתחיל להשתמש ב־Penpot כמו " +"המדריך למשתמשים וערוץ ה־YouTube שלנו." msgid "onboarding-v2.before-start.desc2" -msgstr "מידע מפורט על אופן השימוש ב־Penpot. החל מתכנון אבטיפוס ועד שיתוף עיצובים." +msgstr "" +"מידע מפורט על אופן השימוש ב־Penpot. החל מתכנון אבטיפוס ועד שיתוף עיצובים." msgid "onboarding-v2.before-start.desc2.title" msgstr "מדריך למשתמשים" @@ -2113,8 +2134,8 @@ msgstr "לא, תודה" msgid "onboarding.newsletter.desc" msgstr "" -"ניתן להירשם לרשימת הדיוור שלנו כדי לשמור על קשר עם תהליך פיתוח המוצר " -"והחדשות העדכניות." +"ניתן להירשם לרשימת הדיוור שלנו כדי לשמור על קשר עם תהליך פיתוח המוצר והחדשות " +"העדכניות." msgid "onboarding.newsletter.policy" msgstr "מדיניות פרטיות." @@ -2171,7 +2192,8 @@ msgid "onboarding.slide.3.alt" msgstr "הגשה וקוד מקור" msgid "onboarding.slide.3.desc1" -msgstr "ניתן לסנכרן את העיצוב ואת הקוד של כל הרכיבים והעיצובים שלך ולקבל מקטעי קוד." +msgstr "" +"ניתן לסנכרן את העיצוב ואת הקוד של כל הרכיבים והעיצובים שלך ולקבל מקטעי קוד." msgid "onboarding.slide.3.desc2" msgstr "" @@ -2759,8 +2781,7 @@ msgstr "%s‏ - Penpot" msgid "viewer.breaking-change.description" msgstr "" -"קישור זה לשיתוף אינו תקף עוד. נא ליצור אחד חדש או לבקש מהבעלים ליצור אחד " -"חדש." +"קישור זה לשיתוף אינו תקף עוד. נא ליצור אחד חדש או לבקש מהבעלים ליצור אחד חדש." msgid "viewer.breaking-change.message" msgstr "מתנצלים!" @@ -3012,7 +3033,8 @@ msgstr "מיקוד פעיל" msgid "workspace.focus.selection" msgstr "בחירה" -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +#: src/app/main/data/workspace/libraries.cljs, +#: src/app/main/ui/components/color_bullet.cljs msgid "workspace.gradients.linear" msgstr "מדרג קווי" @@ -3389,7 +3411,8 @@ msgstr "עיצוב" msgid "workspace.options.export" msgstr "ייצוא" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.export-multiple" msgstr "ייצוא הבחירה" @@ -3407,15 +3430,18 @@ msgstr "סיומת" msgid "workspace.options.exporting-complete" msgstr "הייצוא הושלם" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs msgid "workspace.options.exporting-object" msgstr "מתבצע ייצוא…" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.exporting-object-error" msgstr "הייצוא נכשל" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.exporting-object-slow" msgstr "הייצוא אטי בהגזמה" @@ -4039,7 +4065,8 @@ msgstr "פינות בודדות" msgid "workspace.options.recent-fonts" msgstr "אחרונים" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.retry" msgstr "לנסות שוב" @@ -4482,7 +4509,8 @@ msgstr "בחירת שכבה" msgid "workspace.shape.menu.show" msgstr "הצגה" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.show-in-assets" msgstr "הצגה בלוח משאבים" @@ -4565,7 +4593,8 @@ msgstr "צורות" msgid "workspace.sidebar.layers.texts" msgstr "טקסטים" -#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, src/app/main/ui/handoff/attributes/svg.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs, +#: src/app/main/ui/handoff/attributes/svg.cljs msgid "workspace.sidebar.options.svg-attrs.title" msgstr "מאפייני SVG יובאו" @@ -4757,4 +4786,10 @@ msgid "workspace.updates.update" msgstr "עדכון" msgid "workspace.viewport.click-to-close-path" -msgstr "לחיצה תסגור את הנתיב" \ No newline at end of file +msgstr "לחיצה תסגור את הנתיב" + +msgid "errors.bad-font-plural" +msgstr "לא ניתן לטעון את הגופנים %s" + +msgid "errors.bad-font" +msgstr "לא ניתן לטעון את הגופן %s" From fb3d6b04af56ae12d328e257b6a2b91466841467 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tummas=20J=C3=B3han=20Sigvardsen?= Date: Tue, 18 Oct 2022 11:35:53 +0000 Subject: [PATCH 134/682] :globe_with_meridians: Add translations for: Faroese. Currently translated at 5.5% (67 of 1216 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/ --- frontend/translations/fo.po | 41 ++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index c8e7e5acc4..d4f56d1b05 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -135,7 +135,7 @@ msgstr "Toymisleiðsla" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.text" msgstr "" -"Penpot er fyri toymum. Bjóða limir at arbeiða saman á verkætlanir og fílur" +"Penpot er fyri toymum. Bjóða limum at arbeiða saman á verkætlanir og fílur" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.team-hero.title" @@ -236,3 +236,42 @@ msgstr "Allir Penpot brúkarar" msgid "common.share-link.view-all" msgstr "Vel alt" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Hetta er ein ROYNDAR tænasta, IKKI BRÚKA til veruligt arbeiði, " +"verkætlanirnar verða slettaðar regluliga." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "Eg góðtakið skrásetingina á tíðindalistanum hjá Penpot." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "" +"Vangamyndin er ikki váttað, vinarliga vátta vangamyndina áðrenn tú heldur " +"áfram." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Endurstovna loyniorð" + +msgid "auth.terms-of-service" +msgstr "Treytir" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.tutorial-hero.start" +msgstr "Byrja undirvísingina" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dasboard.walkthrough-hero.start" +msgstr "Byrja rundferð" + +msgid "dashboard.draft-title" +msgstr "Kladda" + +#: src/app/main/ui/dashboard/project_menu.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Tvítøka" From 9df8935d48dec3450c1481bc25f2a7d1e0e0d40a Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 18 Oct 2022 20:11:06 +0200 Subject: [PATCH 135/682] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ --- frontend/translations/ar.po | 62 +++++++++------- frontend/translations/ca.po | 68 +---------------- frontend/translations/da.po | 5 +- frontend/translations/el.po | 23 +----- frontend/translations/es.po | 122 ++++++++++++++++++------------- frontend/translations/eu.po | 58 +-------------- frontend/translations/fa.po | 26 +------ frontend/translations/fr.po | 67 +---------------- frontend/translations/gl.po | 26 ++++--- frontend/translations/hr.po | 19 +---- frontend/translations/id.po | 6 +- frontend/translations/it.po | 64 ++++++++-------- frontend/translations/ml.po | 6 +- frontend/translations/nb_NO.po | 13 +--- frontend/translations/pl.po | 55 +------------- frontend/translations/pt_BR.po | 40 +--------- frontend/translations/pt_PT.po | 19 +---- frontend/translations/ro.po | 17 +---- frontend/translations/ru.po | 20 +---- frontend/translations/zh_CN.po | 32 +------- frontend/translations/zh_Hant.po | 16 +--- 21 files changed, 172 insertions(+), 592 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index cd9daf93d3..d9fbab52fd 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-04 18:22+0000\n" "Last-Translator: Youkho \n" -"Language-Team: Arabic " -"\n" +"Language-Team: Arabic \n" "Language: ar\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -18,7 +18,8 @@ msgstr "هل لديك حساب؟" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" -msgstr "تحقق من بريدك الإلكتروني وانقر على الرابط للتحقق والبدء في استخدام Penpot." +msgstr "" +"تحقق من بريدك الإلكتروني وانقر على الرابط للتحقق والبدء في استخدام Penpot." #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" @@ -34,7 +35,8 @@ msgstr "ترغب في التجربة فحسب؟" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" -msgstr "هذه خدمة تجريبية ، لا تستخدمها للعمل الحقيقي ، سيتم مسح المشاريع بشكل دوري." +msgstr "" +"هذه خدمة تجريبية ، لا تستخدمها للعمل الحقيقي ، سيتم مسح المشاريع بشكل دوري." #: src/app/main/ui/auth/register.cljs, #: src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/auth/login.cljs @@ -161,7 +163,8 @@ msgstr "شروط الخدمة" #: src/app/main/ui/auth/register.cljs msgid "auth.terms-privacy-agreement" -msgstr "عند إنشاء حساب جديد ، فإنك توافق على شروط الخدمة وسياسة الخصوصية الخاصة بنا." +msgstr "" +"عند إنشاء حساب جديد ، فإنك توافق على شروط الخدمة وسياسة الخصوصية الخاصة بنا." #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" @@ -175,8 +178,7 @@ msgstr "جميع مستخدمي Penpot" msgid "common.share-link.confirm-deletion-link-description" msgstr "" -"هل أنت متأكد أنك تريد إزالة هذا الرابط؟ إذا قمت بذلك ، فلن يكون متاحًا لأي " -"شخص" +"هل أنت متأكد أنك تريد إزالة هذا الرابط؟ إذا قمت بذلك ، فلن يكون متاحًا لأي شخص" msgid "common.share-link.current-tag" msgstr "(الحالي)" @@ -246,7 +248,8 @@ msgstr "إعمل فريق!" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.info" -msgstr "تعلم أساسيات برنامج Penpot بينما تستمع بتجربته في هذه الدورة التعليمية." +msgstr "" +"تعلم أساسيات برنامج Penpot بينما تستمع بتجربته في هذه الدورة التعليمية." #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.start" @@ -375,15 +378,15 @@ msgstr "" "أصولهم*؟" msgid "dashboard.export.options.all.message" -msgstr "سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم." +msgstr "" +"سيتم ادراج الملفات التي لها مكتبات مشتركة في التصدير، مع الحفاظ على روابطهم." msgid "dashboard.export.options.all.title" msgstr "صدر المكتبات المشتركة" msgid "dashboard.export.options.detach.message" msgstr "" -"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى " -"المكتبة. " +"لن يتم تضمين المكتبات المشتركة في التصدير ولن يتم إضافة أي أصول إلى المكتبة. " msgid "dashboard.export.options.detach.title" msgstr "عامل أصول المكتبة المشتركة كعناصر بسيطة" @@ -427,9 +430,8 @@ msgstr "" msgid "dashboard.fonts.hero-text2" msgstr "" "يجب عليك فقط تحميل الخطوط التي تمتلكها أو لديك ترخيص لاستخدامها في Penpot. " -"اكتشف المزيد في قسم حقوق المحتوى في [شروط خدمة Penpot] " -"(https://penpot.app/terms.html). قد ترغب أيضًا في القراءة عن [ترخيص الخطوط] " -"(2)." +"اكتشف المزيد في قسم حقوق المحتوى في [شروط خدمة Penpot] (https://penpot.app/" +"terms.html). قد ترغب أيضًا في القراءة عن [ترخيص الخطوط] (2)." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -904,8 +906,8 @@ msgstr "موضوع" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"يرجى وصف سبب بريدك الإلكتروني ، وتحديد ما إذا كانت مشكلة أم فكرة أم شك. " -"سيرد أحد أعضاء فريقنا في أسرع وقت ممكن." +"يرجى وصف سبب بريدك الإلكتروني ، وتحديد ما إذا كانت مشكلة أم فكرة أم شك. سيرد " +"أحد أعضاء فريقنا في أسرع وقت ممكن." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" @@ -1342,7 +1344,8 @@ msgstr "الخطوط المتوفرة" #: src/app/main/ui/static.cljs msgid "labels.internal-error.desc-message" -msgstr "شيء سيء حدث الرجاء إعادة محاولة العملية وإذا استمرت المشكلة، اتصل بالدعم." +msgstr "" +"شيء سيء حدث الرجاء إعادة محاولة العملية وإذا استمرت المشكلة، اتصل بالدعم." #: src/app/main/ui/static.cljs msgid "labels.internal-error.main-message" @@ -1404,7 +1407,8 @@ msgstr "لا توجد دعوات." #: src/app/main/ui/dashboard/team.cljs msgid "labels.no-invitations-hint" -msgstr "اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." +msgstr "" +"اضغط على الزر \"دعوة إلى الفريق\" لدعوة المزيد من الأعضاء إلى هذا الفريق." #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" @@ -1668,7 +1672,8 @@ msgstr "تغيير بريدك الإلكتروني" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.change-owner-and-leave-confirm.message" -msgstr "أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." +msgstr "" +"أنت صاحب هذا الفريق. الرجاء تحديد عضو آخر للترقية إلى مالك قبل المغادرة." #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" @@ -1726,14 +1731,14 @@ msgstr "حذف %s الملفات" msgid "modals.delete-font-variant.message" msgstr "" -"هل أنت متأكد أنك تريد حذف نمط هذا الخط؟ لن يتم تحميله إذا تم استخدامه في " -"ملف." +"هل أنت متأكد أنك تريد حذف نمط هذا الخط؟ لن يتم تحميله إذا تم استخدامه في ملف." msgid "modals.delete-font-variant.title" msgstr "حذف نمط الخط" msgid "modals.delete-font.message" -msgstr "هل أنت متأكد أنك تريد حذف هذا الخط؟ لن يتم تحميله إذا تم استخدامه في ملف." +msgstr "" +"هل أنت متأكد أنك تريد حذف هذا الخط؟ لن يتم تحميله إذا تم استخدامه في ملف." msgid "modals.delete-font.title" msgstr "حذف الخط" @@ -1801,7 +1806,8 @@ msgstr "ادعُ الأعضاء إلى الفريق" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" -msgstr "نظرًا لأنك العضو الوحيد في هذا الفريق ، فسيتم حذف الفريق مع مشاريعه وملفاته." +msgstr "" +"نظرًا لأنك العضو الوحيد في هذا الفريق ، فسيتم حذف الفريق مع مشاريعه وملفاته." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.message" @@ -1880,8 +1886,8 @@ msgstr "إزالة كمكتبة مشتركة" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.remove-shared-confirm.hint" msgstr "" -"بمجرد إزالتها كمكتبة مشتركة ، ستتوقف مكتبة الملفات لهذا الملف عن كونها " -"متاحة للاستخدام بين بقية ملفاتك." +"بمجرد إزالتها كمكتبة مشتركة ، ستتوقف مكتبة الملفات لهذا الملف عن كونها متاحة " +"للاستخدام بين بقية ملفاتك." #: src/app/main/ui/workspace/header.cljs, #: src/app/main/ui/dashboard/file_menu.cljs @@ -1984,8 +1990,8 @@ msgstr "المشاركة في المجتمع" msgid "onboarding-v2.welcome.desc3" msgstr "" -"حيث ستجد كيفية التعاون في الترجمات وطلبات الميزات والمساهمات الأساسية " -"والبحث عن الأخطاء …" +"حيث ستجد كيفية التعاون في الترجمات وطلبات الميزات والمساهمات الأساسية والبحث " +"عن الأخطاء …" msgid "onboarding-v2.welcome.desc3.title" msgstr "دليل المساهمة" @@ -3141,4 +3147,4 @@ msgid "workspace.updates.update" msgstr "تحديث" msgid "workspace.viewport.click-to-close-path" -msgstr "انقر لإغلاق المسار" \ No newline at end of file +msgstr "انقر لإغلاق المسار" diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 373c8b8338..687ab285a7 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -1800,10 +1800,6 @@ msgstr "" msgid "modals.leave-and-reassign.hint1" msgstr "Sou el propietari de %s." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "Seleccioneu un altre membre per a ascendir-lo abans d'abandonar" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "Ascendeix i abandona" @@ -1928,11 +1924,6 @@ msgstr "Introduïu el nom de l'equip" msgid "onboarding.choice.team-up.invite-members" msgstr "Convida membres" -msgid "onboarding.choice.team-up.invite-members-desc" -msgstr "" -"També podreu convidar membres o canviar els rols des de la pàgina de " -"l'equip." - msgid "onboarding.choice.team-up.invite-members-skip" msgstr "Crea l'equip ara i convida membres en un altre moment" @@ -1973,17 +1964,9 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "No, gràcies" -msgid "onboarding.newsletter.desc" -msgstr "" -"Subscriviu-vos al nostre butlletí per estar al dia de les novetats i del " -"progrés del desenvolupament del producte." - msgid "onboarding.newsletter.policy" msgstr "Política de privacitat." -msgid "onboarding.newsletter.privacy1" -msgstr "Com que ens preocupa la privacitat, aquí hi ha la nostra " - msgid "onboarding.newsletter.privacy2" msgstr "" "Només us enviarem correus rellevants. Podeu anul·lar la subscripció sempre " @@ -2050,25 +2033,12 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "Una font compartida de veritat" -msgid "onboarding.team.create.desc1" -msgstr "" -"Esteu treballant amb algú? Creeu un equip per a treballar junts en " -"projectes i compartir recursos de disseny." - msgid "onboarding.team.create.input-placeholder" msgstr "Introduïu el nom de l'equip nou" -msgid "onboarding.team.create.title" -msgstr "Creeu un equip" - msgid "onboarding.team.start.button" msgstr "Comenceu de seguida" -msgid "onboarding.team.start.desc1" -msgstr "" -"Salteu ara al Penpot i comenceu a dissenyar pel vostre compte. Tindreu " -"l'oportunitat de crear més equips després." - msgid "onboarding.team.start.title" msgstr "Comenceu a dissenyar" @@ -2081,20 +2051,12 @@ msgstr "Comenceu a dissenyar" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "Visca! Ja sou usuari del Penpot :)" - msgid "onboarding.welcome.desc2" msgstr "" "Penpot es troba en la primera versió beta gràcies a la combinació de " "característiques bàsiques, maduresa, estabilitat i la increïble validació " "de la comunitat en el seu conjunt, a la qual sou més que benvinguts." -msgid "onboarding.welcome.desc3" -msgstr "" -"Mentre gaudiu del Penpot pel que és, seguirem millorant-lo, alliberant " -"iteracions dels nostres plans esperançadors." - msgid "onboarding.welcome.title" msgstr "Et donem la benvinguda a Penpot" @@ -2613,10 +2575,6 @@ msgstr "Comentaris (%s)" msgid "viewer.header.dont-show-interactions" msgstr "No mostris les interaccions" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-file" -msgstr "Edita el fitxer" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" msgstr "Pantalla completa" @@ -2651,10 +2609,6 @@ msgstr "Elimina l'enllaç" msgid "viewer.header.share.subtitle" msgstr "Qualsevol persona amb l'enllaç hi tindrà accés" -#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.title" -msgstr "Comparteix el prototip" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Mostra les interaccions" @@ -2740,10 +2694,6 @@ msgstr "Duplica" msgid "workspace.assets.edit" msgstr "Edita" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Biblioteca del fitxer" - #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" msgstr "Gràfics" @@ -3625,18 +3575,10 @@ msgstr "Capes seleccionades" msgid "workspace.options.layout-item.advanced-ops" msgstr "Opcions avançades" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.max-h" -msgstr "Alçada màx." - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.max-w" msgstr "Amplada màx." -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.min-h" -msgstr "Alçada mín." - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.min-w" msgstr "Amplada mín." @@ -3645,18 +3587,10 @@ msgstr "Amplada mín." msgid "workspace.options.layout-item.title" msgstr "Redimensió de l'element" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.max-h" -msgstr "Alçada màxima" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-w" msgstr "Amplada màxima" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.min-h" -msgstr "Alçada mínima" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.min-w" msgstr "Amplada mínima" @@ -4483,4 +4417,4 @@ msgid "workspace.updates.update" msgstr "Actualitza" msgid "workspace.viewport.click-to-close-path" -msgstr "Feu clic per a tancar el camí" \ No newline at end of file +msgstr "Feu clic per a tancar el camí" diff --git a/frontend/translations/da.po b/frontend/translations/da.po index 606f02a561..83075e81bd 100644 --- a/frontend/translations/da.po +++ b/frontend/translations/da.po @@ -434,9 +434,6 @@ msgstr "Skrifttypefamilie" msgid "labels.font-providers" msgstr "Skrifttype udbydere" -msgid "labels.font-variant" -msgstr "Stil" - msgid "labels.fonts" msgstr "Skrifttyper" @@ -472,4 +469,4 @@ msgstr "Skrifttype Udbydere - %s - Penpot" #: src/app/main/ui/dashboard/fonts.cljs msgid "title.dashboard.fonts" -msgstr "Skrifttyper - %s - Penpot" \ No newline at end of file +msgstr "Skrifttyper - %s - Penpot" diff --git a/frontend/translations/el.po b/frontend/translations/el.po index d1181aec0c..7feecf4257 100644 --- a/frontend/translations/el.po +++ b/frontend/translations/el.po @@ -1124,10 +1124,6 @@ msgstr "Αποστολή πρόσκλησης" msgid "modals.leave-and-reassign.hint1" msgstr "Είστε ο ιδιοκτήτης του %s" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "Επιλέξτε άλλο μέλος για προώθηση πριν από την αναχώρηση" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "Προώθηση και αφήστε" @@ -1234,10 +1230,6 @@ msgstr "Το πλαίσιο δεν βρέθηκε." msgid "viewer.header.dont-show-interactions" msgstr "Μην εμφανίζετε αλληλεπιδράσεις" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Επεξεργασία σελίδας" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" msgstr "Πλήρης οθόνη" @@ -1337,10 +1329,6 @@ msgstr "Αντιγραφή" msgid "workspace.assets.edit" msgstr "Επεξεργασία" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Βιβλιοθήκη αρχείων" - #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" msgstr "Γραφικά" @@ -2067,9 +2055,6 @@ msgstr "Φέρτε μπροστά" msgid "workspace.shape.menu.front" msgstr "Φέρτε μπροστά" -msgid "workspace.shape.menu.go-master" -msgstr "Μεταβείτε στο κύριο αρχείο συστατικών" - #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.group" msgstr "Ομάδα" @@ -2098,9 +2083,6 @@ msgstr "Επαναφορά παρακάμψεων" msgid "workspace.shape.menu.show" msgstr "προβολή" -msgid "workspace.shape.menu.show-master" -msgstr "Εμφάνιση κύριου στοιχείου" - #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.ungroup" msgstr "Κατάργηση ομάδας" @@ -2113,9 +2095,6 @@ msgstr "Ξεκλείδωμα" msgid "workspace.shape.menu.unmask" msgstr "Ανακάλυψη" -msgid "workspace.shape.menu.update-master" -msgstr "23 / 5000 Resultados de traducción Ενημέρωση κύριου στοιχείου" - #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.sidebar.history" msgstr "Ιστορικό (%s)" @@ -2308,4 +2287,4 @@ msgid "workspace.updates.update" msgstr "Ενημέρωση" msgid "workspace.viewport.click-to-close-path" -msgstr "Κάντε κλικ για να κλείσετε τη διαδρομή" \ No newline at end of file +msgstr "Κάντε κλικ για να κλείσετε τη διαδρομή" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 9081e2c186..bc115a2bd9 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-09-23 20:54+0000\n" "Last-Translator: Dário \n" -"Language-Team: Spanish " -"\n" +"Language-Team: Spanish \n" "Language: es\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -323,7 +323,6 @@ msgid "dashboard.empty-files" msgstr "Todavía no hay ningún archivo aquí" #: src/app/main/ui/dashboard/grid.cljs -#, markdown msgid "dashboard.empty-placeholder-drafts" msgstr "" "¡Oh, no! ¡Aún no tienes archivos! Si quieres probar con alguna plantilla ve " @@ -392,8 +391,8 @@ msgstr "Exportar librerias compartidas" msgid "dashboard.export.options.detach.message" msgstr "" -"Las librerias compartidas no se incluirán en la exportación y ningún " -"recurso será incluido en la librería. " +"Las librerias compartidas no se incluirán en la exportación y ningún recurso " +"será incluido en la librería. " msgid "dashboard.export.options.detach.title" msgstr "Usar los recursos como objetos básicos" @@ -425,7 +424,6 @@ msgid_plural "dashboard.fonts.fonts-added" msgstr[0] "1 fuente añadida" msgstr[1] "%s fuentes añadidas" -#, markdown msgid "dashboard.fonts.hero-text1" msgstr "" "Cualquier fuente personalizada añadida aquí aparecerá en la lista de " @@ -434,15 +432,13 @@ msgstr "" "como una **única familia de fuentes**. Se pueden cargar fuentes con los " "siguientes formatos: **TTF, OTF and WOFF** (con uno es suficiente)." -#, markdown msgid "dashboard.fonts.hero-text2" msgstr "" "Sólo deberías cargar fuentes que te pertenecen o de las que tienes una " "licencia que te permita usarlas en Penpot. Encuentra más información en la " -"sección de Derechos de Contenido: [Penpot's Terms of " -"Service](https://penpot.app/terms.html). También te puede interesar leer " -"más sobre licencias tipográficas: [font " -"licensing](https://www.typography.com/faq)." +"sección de Derechos de Contenido: [Penpot's Terms of Service](https://penpot." +"app/terms.html). También te puede interesar leer más sobre licencias " +"tipográficas: [font licensing](https://www.typography.com/faq)." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -461,7 +457,8 @@ msgid "dashboard.import.import-message" msgstr "%s files have been imported succesfully." msgid "dashboard.import.import-warning" -msgstr "Algunos ficheros contenían objetos erroneos que no han sido importados." +msgstr "" +"Algunos ficheros contenían objetos erroneos que no han sido importados." msgid "dashboard.import.progress.process-colors" msgstr "Procesando colores" @@ -546,8 +543,7 @@ msgstr "Nuevo Proyecto" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-msg" msgstr "" -"Envíame noticias, actualizaciones de producto y recomendaciones sobre " -"Penpot." +"Envíame noticias, actualizaciones de producto y recomendaciones sobre Penpot." #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-title" @@ -686,11 +682,13 @@ msgstr "Resultados de búsqueda" msgid "dashboard.type-something" msgstr "Escribe algo para buscar" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.unpublish-shared" msgstr "Despublicar Biblioteca" -#: src/app/main/ui/settings/password.cljs, src/app/main/ui/settings/options.cljs +#: src/app/main/ui/settings/password.cljs, +#: src/app/main/ui/settings/options.cljs msgid "dashboard.update-settings" msgstr "Actualizar opciones" @@ -919,8 +917,8 @@ msgstr "Ir al foro de Penpot" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discourse-subtitle1" msgstr "" -"Estamos encantados de tenerte por aquí. Si necesitas ayuda, busca, escribe " -"o pregunta lo que necesites." +"Estamos encantados de tenerte por aquí. Si necesitas ayuda, busca, escribe o " +"pregunta lo que necesites." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discourse-title" @@ -951,9 +949,9 @@ msgstr "Asunto" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"Por favor describe el motivo de tu mensaje, especificando si es un " -"problema, una idea o una duda. Alguien de nuestro equipo responderá tan " -"pronto como sea posible." +"Por favor describe el motivo de tu mensaje, especificando si es un problema, " +"una idea o una duda. Alguien de nuestro equipo responderá tan pronto como " +"sea posible." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" @@ -1451,7 +1449,8 @@ msgstr "No hay invitaciones." #: src/app/main/ui/dashboard/team.cljs msgid "labels.no-invitations-hint" -msgstr "Pulsa el botón 'Invitar al equipo' para añadir más integrantes al equipo." +msgstr "" +"Pulsa el botón 'Invitar al equipo' para añadir más integrantes al equipo." #: src/app/main/ui/static.cljs msgid "labels.not-found.auth-info" @@ -1572,7 +1571,8 @@ msgstr "Enviando…" #: src/app/main/ui/static.cljs msgid "labels.service-unavailable.desc-message" -msgstr "Estamos en una operación de mantenimiento programado de nuestros sistemas." +msgstr "" +"Estamos en una operación de mantenimiento programado de nuestros sistemas." #: src/app/main/ui/static.cljs msgid "labels.service-unavailable.main-message" @@ -1687,7 +1687,8 @@ msgstr "Verificar el nuevo correo" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.info" -msgstr "Enviaremos un mensaje a tu correo actual “%s” para verificar tu identidad." +msgstr "" +"Enviaremos un mensaje a tu correo actual “%s” para verificar tu identidad." #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.new-email" @@ -1797,29 +1798,33 @@ msgstr "¿Seguro que quieres eliminar este proyecto?" msgid "modals.delete-project-confirm.title" msgstr "Eliminar proyecto" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.accept" msgid_plural "modals.delete-shared-confirm.accept" msgstr[0] "Borrar archivo" msgstr[1] "Borrar archivos" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.message" msgid_plural "modals.delete-shared-confirm.message" msgstr[0] "¿Seguro que quieres borrar este archivo?" msgstr[1] "¿Seguro que quieres borrar estos archivos?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message" msgid_plural "modals.delete-shared-confirm.scd-message" msgstr[0] "" "El archivo que quieres borrar tiene una librería que se está usando en este " "archivo:" msgstr[1] "" -"El archivo que quieres borrar tiene una librería que se está usando en " -"estos archivos:" +"El archivo que quieres borrar tiene una librería que se está usando en estos " +"archivos:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message-plural" msgid_plural "modals.delete-shared-confirm.scd-message-plural" msgstr[0] "" @@ -1829,7 +1834,8 @@ msgstr[1] "" "Los archivos que quieres borrar tienen una librería que se está usando en " "estos archivos:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" msgid_plural "modals.delete-shared-confirm.title" msgstr[0] "Borrar archivo" @@ -1966,33 +1972,38 @@ msgstr "Añadir “%s” como Biblioteca Compartida" msgid "modals.small-nudge" msgstr "Mínimo" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.accept" msgstr "Despublicar" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint" msgstr[0] "" -"Si la despublicas, los elementos pasarán a formar parte de la biblioteca " -"del archivo." +"Si la despublicas, los elementos pasarán a formar parte de la biblioteca del " +"archivo." msgstr[1] "" "Si la despublicas, los elementos pasarán a formar parte de la biblioteca de " "los archivos." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" msgid_plural "modals.unpublish-shared-confirm.message" msgstr[0] "¿Seguro que quieres despublicar esta biblioteca?" msgstr[1] "¿Seguro que quieres despublicar estas bibliotecas?" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" msgstr[0] "Está siendo usada en este archivo:" msgstr[1] "Está siendo usada en estos archivos:" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, +#: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.title" msgid_plural "modals.unpublish-shared-confirm.title" msgstr[0] "Despublicar biblioteca" @@ -2055,8 +2066,8 @@ msgstr "" msgid "onboarding-v2.before-start.desc2" msgstr "" -"Información detallada sobre cómo usar Penpot. Desde prototipar a organizar " -"y compartir diseños." +"Información detallada sobre cómo usar Penpot. Desde prototipar a organizar y " +"compartir diseños." msgid "onboarding-v2.before-start.desc2.title" msgstr "Guía de usuario" @@ -2109,8 +2120,8 @@ msgstr "Participantdo en la Comunidad" msgid "onboarding-v2.welcome.desc3" msgstr "" -"Aquí encontrarás cómo colaborar con traducciones, solicitar " -"funcionalidades, contribuir en el código, cazar errores…" +"Aquí encontrarás cómo colaborar con traducciones, solicitar funcionalidades, " +"contribuir en el código, cazar errores…" msgid "onboarding-v2.welcome.desc3.title" msgstr "Guía de contribución" @@ -2191,7 +2202,8 @@ msgid "onboarding.slide.0.alt" msgstr "Crea diseños" msgid "onboarding.slide.0.desc1" -msgstr "Crea bellos interfaces en colaboración con todas las personas del equipo." +msgstr "" +"Crea bellos interfaces en colaboración con todas las personas del equipo." msgid "onboarding.slide.0.desc2" msgstr "" @@ -2205,7 +2217,8 @@ msgid "onboarding.slide.1.alt" msgstr "Prototipos interactivos" msgid "onboarding.slide.1.desc1" -msgstr "Crea interacciones completas para imitar el comportamiento del producto." +msgstr "" +"Crea interacciones completas para imitar el comportamiento del producto." msgid "onboarding.slide.1.desc2" msgstr "" @@ -2232,8 +2245,8 @@ msgstr "Especificaciones de código" msgid "onboarding.slide.3.desc1" msgstr "" -"Sincroniza diseño y código de todos tus estilos y componentes con a " -"snippets de código." +"Sincroniza diseño y código de todos tus estilos y componentes con a snippets " +"de código." msgid "onboarding.slide.3.desc2" msgstr "" @@ -3465,15 +3478,18 @@ msgstr[1] "Exportar %s elementos" msgid "workspace.options.export.suffix" msgstr "Sufijo" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.exporting-complete" msgstr "Exportación completa" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.exporting-object" msgstr "Exportando…" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, +#: src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.exporting-object-error" msgstr "Exportación fallida" @@ -4333,7 +4349,8 @@ msgstr "Alineación vertical" #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.use-play-button" -msgstr "Usa el botón de play de la cabecera para arrancar la vista de prototipo." +msgstr "" +"Usa el botón de play de la cabecera para arrancar la vista de prototipo." msgid "workspace.options.width" msgstr "Ancho" @@ -4508,7 +4525,8 @@ msgstr "Seleccionar capa" msgid "workspace.shape.menu.show" msgstr "Mostrar" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, +#: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.show-in-assets" msgstr "Ver en el panel de recursos" @@ -4784,4 +4802,4 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" \ No newline at end of file +msgstr "Pulsar para cerrar la ruta" diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index ea37c44014..353be3d028 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -1794,10 +1794,6 @@ msgstr "Bidali gonbidapena" msgid "modals.invite-member.emails" msgstr "Posta elektronikoak, komarekin banatuta" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-member.title" -msgstr "Gonbidatu taldera sartzeko" - #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-team-member.title" msgstr "Gonbidatu kideak taldera" @@ -2009,23 +2005,12 @@ msgstr "Laguntzeko gida" msgid "onboarding-v2.welcome.title" msgstr "Ongi etorri Penpotera!" -msgid "onboarding.choice.desc" -msgstr "Nola hasi nahi duzu?" - msgid "onboarding.choice.fly-solo" msgstr "Bakarrik hasi" -msgid "onboarding.choice.fly-solo-desc" -msgstr "Sartu Penpoten eta hasi zure kabuz diseinatzen." - msgid "onboarding.choice.team-up" msgstr "Osatu taldea" -msgid "onboarding.choice.team-up-desc" -msgstr "" -"Norbaitekin lan egiten duzu? Sortu talde bat eta gonbidatu elkarrekin lan " -"egin eta baliabideak partekatzeko." - msgid "onboarding.choice.team-up.create-later" msgstr "Sortu taldea beranduago" @@ -2041,11 +2026,6 @@ msgstr "Idatzi taldearen izena" msgid "onboarding.choice.team-up.invite-members" msgstr "Gonbidatu kideak" -msgid "onboarding.choice.team-up.invite-members-desc" -msgstr "" -"Geroago ere gonbidatu ditzakezu eta bakoitzaren baimenak ezarri taldearen " -"ezarpenetatik." - msgid "onboarding.choice.team-up.invite-members-info" msgstr "" "Ez ahaztu garapeneko, diseinuko, kudeaketako... pertsonak sartzea, " @@ -2094,17 +2074,9 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "Ez, eskerrik asko" -msgid "onboarding.newsletter.desc" -msgstr "" -"Harpidetu gure buletinera produktuaren aurrerapenari buruzko albisteak " -"jasotzeko." - msgid "onboarding.newsletter.policy" msgstr "Pribatutasun politika." -msgid "onboarding.newsletter.privacy1" -msgstr "Pribatutasunak axola digulako, hemen ikusi dezakezu gure " - msgid "onboarding.newsletter.privacy2" msgstr "" "Benetan baliozkoak diren mezuak bakarrik bidaliko dizkizugu. Edonoiz eten " @@ -2199,19 +2171,11 @@ msgstr "Hasi diseinatzen" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "Ondo! zure Penpot kontua ondo sortu da :)" - msgid "onboarding.welcome.desc2" msgstr "" "Penpot bere lehenengo beta bertsioan dago, hainbat funtzionalitate, " "egonkortasun eta komunitatearen onarpena jaso ostean, ongi etorri." -msgid "onboarding.welcome.desc3" -msgstr "" -"Penpot darabilzun artean, hobekuntzak egiten jarraituko dugu gure planekin " -"aurrera jarraituz." - msgid "onboarding.welcome.title" msgstr "Ongi etorri Penpotera" @@ -2849,10 +2813,6 @@ msgstr "Bikoiztu" msgid "workspace.assets.edit" msgstr "Editatu" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Fitxategiaren liburutegia" - #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" msgstr "Grafikoak" @@ -3755,18 +3715,10 @@ msgstr "Gutxieneko altuera" msgid "workspace.options.layout-item.layout-min-w" msgstr "Gutxieneko zabalera" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.max-h" -msgstr "Gehienezko altuera" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.max-w" msgstr "Gehienezko zabalera" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.min-h" -msgstr "Gutxieneko altuera" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.min-w" msgstr "Gutxieneko zabalera" @@ -3791,18 +3743,10 @@ msgstr "Gutxieneko altuera" msgid "workspace.options.layout-item.title.layout-min-w" msgstr "Gutxieneko zabalera" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.max-h" -msgstr "Gehienezko altuera" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-w" msgstr "Gehieneko zabalera" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.min-h" -msgstr "Gutxieneko altuera" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.min-w" msgstr "Gutxieneko zabalera" @@ -4642,4 +4586,4 @@ msgid "workspace.updates.update" msgstr "Eguneratu" msgid "workspace.viewport.click-to-close-path" -msgstr "Egin klik bidea ixteko" \ No newline at end of file +msgstr "Egin klik bidea ixteko" diff --git a/frontend/translations/fa.po b/frontend/translations/fa.po index 2cd42678da..19526fb919 100644 --- a/frontend/translations/fa.po +++ b/frontend/translations/fa.po @@ -59,10 +59,6 @@ msgstr "ورود از اینجا" msgid "auth.login-submit" msgstr "ورود" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "مشخصات خود را در زیر وارد کنید" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "خوشحالم که دوباره شما را می‌بینم!" @@ -875,10 +871,6 @@ msgstr "" msgid "feedback.discourse-title" msgstr "انجمن Penpot" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-go-to" -msgstr "برو سراغ بحث‌ها" - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-title" msgstr "بحث‌های تیمی" @@ -1668,19 +1660,9 @@ msgstr "" msgid "onboarding.choice.team-up.create-team-desc" msgstr "پس از نامگذاری تیم خود، می‌توانید افراد را برای پیوستن دعوت کنید." -msgid "onboarding.newsletter.privacy2" -msgstr "" -"ما فقط ایمیل‌های مربوطه را برای شما ارسال می‌کنیم. شما می‌توانید در هر زمان " -"در پروفایل کاربری خود یا از طریق پیوند لغو اشتراک در هر یک از خبرنامه‌های " -"ما، اشتراک خود را لغو کنید." - msgid "onboarding.welcome.alt" msgstr "Penpot" -#, fuzzy -msgid "onboarding.welcome.desc1" -msgstr "هورا! شما یک کاربر Penpot هستید :)" - #, fuzzy msgid "onboarding.welcome.desc2" msgstr "" @@ -1688,12 +1670,6 @@ msgstr "" "شگفت‌انگیز جامعه به‌عنوان یک کل، در اولین نسخه بتا خود قرار دارد، که از آن " "استقبال می‌کنید." -#, fuzzy -msgid "onboarding.welcome.desc3" -msgstr "" -"در حالی که از Penpot برای آنچه هست لذت می‌برید، ما به بهبود آن ادامه " -"می‌دهیم و برنامه‌های امیدوارکننده خود را تکرار می‌کنیم." - #, fuzzy msgid "onboarding.welcome.title" msgstr "به Penpot خوش آمدید" @@ -2928,4 +2904,4 @@ msgid "workspace.updates.update" msgstr "به‌روزرسانی" msgid "workspace.viewport.click-to-close-path" -msgstr "برای بستن مسیر کلیک کنید" \ No newline at end of file +msgstr "برای بستن مسیر کلیک کنید" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 675dbb0c24..3d8214dd7c 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -877,10 +877,6 @@ msgstr "" "Vous devez accepter nos conditions générales d'utilisation et notre " "politique de confidentialité." -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.token-expired" -msgstr "Jeton expiré" - #: src/app/main/data/media.cljs, #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs @@ -1877,10 +1873,6 @@ msgstr "" msgid "modals.leave-and-reassign.hint1" msgstr "Vous êtes le propriétaire de %s." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "Sélectionnez un autre membre à promouvoir avant de quitter l’équipe" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "Promouvoir et quitter" @@ -2031,24 +2023,12 @@ msgstr "Avant de démarrer" msgid "onboarding-v2.welcome.title" msgstr "Bienvenu sur Penpot !" -msgid "onboarding.choice.desc" -msgstr "Comment voulez-vous commencer ?" - msgid "onboarding.choice.fly-solo" msgstr "Commencer seul" -msgid "onboarding.choice.fly-solo-desc" -msgstr "Essayez Penpot et commencez à concevoir par vous-même." - msgid "onboarding.choice.team-up" msgstr "Faites équipe" -msgid "onboarding.choice.team-up-desc" -msgstr "" -"Vous travaillez avec quelqu'un ? Créez une équipe et invitez des personnes " -"pour travailler avec eux sur des projets et partager des ressources de " -"conception." - msgid "onboarding.choice.team-up.create-team" msgstr "Le nom de votre équipe" @@ -2063,11 +2043,6 @@ msgstr "Entrez le nom de l'équipe" msgid "onboarding.choice.team-up.invite-members" msgstr "Inviter des membres" -msgid "onboarding.choice.team-up.invite-members-desc" -msgstr "" -"Vous pourrez également inviter des membres et changer les rôles plus tard " -"dans la section équipe." - msgid "onboarding.choice.team-up.invite-members-skip" msgstr "Créer une équipe et inviter plus tard" @@ -2106,17 +2081,9 @@ msgstr "Demande d'abonnement envoyé, vous allez recevoir un e-mail de confirmat msgid "onboarding.newsletter.decline" msgstr "Non, merci" -msgid "onboarding.newsletter.desc" -msgstr "" -"Abonnez-vous à notre newsletter pour être tenu informé du développement et " -"de notre actualité." - msgid "onboarding.newsletter.policy" msgstr "Politique de confidentialité." -msgid "onboarding.newsletter.privacy1" -msgstr "Parce que nous nous soucions de la vie privée, voici notre " - msgid "onboarding.newsletter.privacy2" msgstr "" "Nous ne vous enverrons que des e-mails pertinents. Vous pouvez vous " @@ -2206,20 +2173,12 @@ msgstr "Commencer à concevoir" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "Hourra ! Vous êtes déjà un utilisateur de Penpot :)" - msgid "onboarding.welcome.desc2" msgstr "" "Penpot en est à sa première version bêta grâce à son ensemble de " "fonctionnalités de base, sa maturité, sa stabilité et aux retours très " "positifs de la communauté, à laquelle vous êtes plus que bienvenu." -msgid "onboarding.welcome.desc3" -msgstr "" -"Pendant que vous appréciez Penpot dans son état actuel, nous continuerons à " -"l'améliorer, en publiant fréquemment nos projets prometteurs." - msgid "onboarding.welcome.title" msgstr "Bienvenue chez Penpot" @@ -2689,10 +2648,6 @@ msgstr "Plan de travail introuvable." msgid "viewer.header.dont-show-interactions" msgstr "Ne pas afficher les interactions" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-file" -msgstr "Modifier le fichier" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.edit-page" msgstr "Modifier la page" @@ -2816,10 +2771,6 @@ msgstr "Dupliquer" msgid "workspace.assets.edit" msgstr "Modifier" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Bibliothèque du fichier" - #: src/app/main/ui/workspace/sidebar/assets.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" @@ -2942,18 +2893,10 @@ msgstr "Activer le redimensionnement du texte" msgid "workspace.header.menu.enable-snap-grid" msgstr "Aligner sur la grille" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-assets" -msgstr "Masquer les ressources" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-grid" msgstr "Masquer la grille" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-layers" -msgstr "Masquer les calques" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-palette" msgstr "Masquer la palette de couleurs" @@ -2974,18 +2917,10 @@ msgstr "Fichier" msgid "workspace.header.menu.select-all" msgstr "Tout sélectionner" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-assets" -msgstr "Montrer les ressources" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-grid" msgstr "Montrer la grille" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-layers" -msgstr "Montrer les calques" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-palette" msgstr "Montrer la palette de couleurs" @@ -4243,4 +4178,4 @@ msgid "workspace.updates.update" msgstr "Actualiser" msgid "workspace.viewport.click-to-close-path" -msgstr "Cliquez pour fermer le chemin" \ No newline at end of file +msgstr "Cliquez pour fermer le chemin" diff --git a/frontend/translations/gl.po b/frontend/translations/gl.po index 953f7f8b52..db2a25f6b8 100644 --- a/frontend/translations/gl.po +++ b/frontend/translations/gl.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-16 13:49+0000\n" "Last-Translator: ascarida \n" -"Language-Team: Galician " -"\n" +"Language-Team: Galician \n" "Language: gl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -106,7 +106,8 @@ msgstr "Perfil sen verificar, valida o perfil antes de continuar." #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.recovery-token-sent" -msgstr "Enviouse ó teu correo electrónico un enlace co que recuperar o contrasinal." +msgstr "" +"Enviouse ó teu correo electrónico un enlace co que recuperar o contrasinal." #: src/app/main/ui/auth/verify_token.cljs msgid "auth.notifications.team-invitation-accepted" @@ -264,8 +265,8 @@ msgstr "" #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.info" msgstr "" -"Aprende os conceptos básicos de Penpot mentres te divirtes con este " -"titorial práctico." +"Aprende os conceptos básicos de Penpot mentres te divirtes con este titorial " +"práctico." #: src/app/main/ui/dashboard/projects.cljs msgid "dasboard.tutorial-hero.start" @@ -391,8 +392,8 @@ msgstr "" msgid "dashboard.export.options.all.message" msgstr "" -"os ficheiros con bibliotecas compartidas incluiranse na exportación " -"mantendo os vínculos." +"os ficheiros con bibliotecas compartidas incluiranse na exportación mantendo " +"os vínculos." msgid "dashboard.export.options.all.title" msgstr "Exportar bibliotecas compartidas" @@ -411,7 +412,8 @@ msgstr "" "biblioteca do ficheiro." msgid "dashboard.export.options.merge.title" -msgstr "Incluir os recursos de bibliotecas compartidas na biblioteca do ficheiro" +msgstr "" +"Incluir os recursos de bibliotecas compartidas na biblioteca do ficheiro" msgid "dashboard.export.title" msgstr "Exportar ficheiros" @@ -458,7 +460,8 @@ msgid "dashboard.import.analyze-error" msgstr "Vaia! Non se puido importar o ficheiro" msgid "dashboard.import.import-error" -msgstr "Houbo un problema ao importar o ficheiro. Non se puido importar o ficheiro." +msgstr "" +"Houbo un problema ao importar o ficheiro. Non se puido importar o ficheiro." msgid "dashboard.import.import-message" msgstr "% ficheiros importáronse correctamente." @@ -548,7 +551,8 @@ msgstr "Novo proxecto" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-msg" -msgstr "Envíame noticias, actualizacións do produto e recomendacións sobre Penpot." +msgstr "" +"Envíame noticias, actualizacións do produto e recomendacións sobre Penpot." #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-title" @@ -1444,4 +1448,4 @@ msgstr "Nada" #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.edit" -msgstr "Editar" \ No newline at end of file +msgstr "Editar" diff --git a/frontend/translations/hr.po b/frontend/translations/hr.po index 4efa1c061e..d31cab9d8b 100644 --- a/frontend/translations/hr.po +++ b/frontend/translations/hr.po @@ -648,10 +648,6 @@ msgstr "Tvoje datoteke su uspješno premještene" msgid "dashboard.success-move-project" msgstr "Tvoj projekt je uspješno premješten" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.switch-team" -msgstr "Promijeni tim" - #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.team-info" msgstr "Informacije tima" @@ -1819,11 +1815,6 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "Jesi li siguran/na da želiš napustiti tim %s?" -msgid "modals.leave-and-reassign.forbiden" -msgstr "" -"Ne možeš napustiti tim ako nema drugog člana kojeg možeš promovirati u " -"vlasnika. Možda želiš izbrisati tim." - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint1" msgstr "" @@ -2084,17 +2075,9 @@ msgstr "Tvoj zahtjev za pretplatu je poslan, poslat ćemo ti e-mail da ga potvrd msgid "onboarding.newsletter.decline" msgstr "Ne, hvala" -msgid "onboarding.newsletter.desc" -msgstr "" -"Pretplati se na naš newsletter kako bi bio/la u toku s razvojem proizvoda i " -"novostima." - msgid "onboarding.newsletter.policy" msgstr "Politika privatnosti." -msgid "onboarding.newsletter.privacy1" -msgstr "Budući da brinemo o privatnosti, evo našeg " - msgid "onboarding.newsletter.privacy2" msgstr "" "Poslat ćemo ti samo relevantne e-mailove. Pretplatu možeš odjaviti u bilo " @@ -4653,4 +4636,4 @@ msgstr "Ažuriraj" #, fuzzy msgid "workspace.viewport.click-to-close-path" -msgstr "Pritisni da zatvoriš path" \ No newline at end of file +msgstr "Pritisni da zatvoriš path" diff --git a/frontend/translations/id.po b/frontend/translations/id.po index c18ae56de2..c849623eac 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -51,10 +51,6 @@ msgstr "Lupa kata sandi?" msgid "auth.fullname" msgstr "Nama Lengkap" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Kembali!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Masuk di sini" @@ -436,4 +432,4 @@ msgstr "Proyek Baru" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.newsletter-msg" -msgstr "Kirimkan saya berita, pembaruan produk dan rekomendasi tentang Penpot." \ No newline at end of file +msgstr "Kirimkan saya berita, pembaruan produk dan rekomendasi tentang Penpot." diff --git a/frontend/translations/it.po b/frontend/translations/it.po index c9185a27ba..060342d95c 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2022-10-01 14:17+0000\n" "Last-Translator: Jacopo Lodovico Trabia \n" -"Language-Team: Italian " -"\n" +"Language-Team: Italian \n" "Language: it\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -377,8 +377,8 @@ msgstr "" msgid "dashboard.export.options.all.message" msgstr "" -"I file con librerie condivise verranno inclusi nell'esportazione, " -"mantenendo il loro collegamento." +"I file con librerie condivise verranno inclusi nell'esportazione, mantenendo " +"il loro collegamento." msgid "dashboard.export.options.all.title" msgstr "Esporta le librerie condivise" @@ -420,16 +420,16 @@ msgstr "" "Qualsiasi font web caricato qui verrà aggiunto alla lista dei font family " "disponibile nelle impostazioni testo dei file di questo team. I font che " "arrecano lo stesso nome di font family verranno raggruppati come un " -"**singolo font family**. È possibile caricare font con i seguenti " -"formati:**TTF, OTF e WOFF**(uno solo di questi è necessario)." +"**singolo font family**. È possibile caricare font con i seguenti formati:" +"**TTF, OTF e WOFF**(uno solo di questi è necessario)." msgid "dashboard.fonts.hero-text2" msgstr "" -"È consigliabile caricare unicamente font di cui si è proprietari o dei " -"quali si possiede la licenza d'uso in Penpot. Ulteriori informazioni sui " -"diritti dei contenuti sono disponibili nella sezione [Termini di Servizio " -"di Penpot](https://penpot.app/terms.html). Potresti anche voler " -"approfondire le [licenze per i font](https://www.typography.com/faq)." +"È consigliabile caricare unicamente font di cui si è proprietari o dei quali " +"si possiede la licenza d'uso in Penpot. Ulteriori informazioni sui diritti " +"dei contenuti sono disponibili nella sezione [Termini di Servizio di Penpot]" +"(https://penpot.app/terms.html). Potresti anche voler approfondire le " +"[licenze per i font](https://www.typography.com/faq)." #: src/app/main/ui/dashboard/fonts.cljs msgid "dashboard.fonts.upload-all" @@ -767,7 +767,8 @@ msgid "errors.email-invalid-confirmation" msgstr "L'indirizzo e-mail di conferma deve corrispondere" msgid "errors.email-spam-or-permanent-bounces" -msgstr "L'e-mail \"%s\" è stata riportata come spam o respinta in modo permanente." +msgstr "" +"L'e-mail \"%s\" è stata riportata come spam o respinta in modo permanente." #: src/app/main/ui/auth/verify_token.cljs, #: src/app/main/ui/settings/feedback.cljs, src/app/main/ui/dashboard/team.cljs @@ -839,7 +840,8 @@ msgstr "" "proprietario." msgid "errors.terms-privacy-agreement-invalid" -msgstr "È necessario accettare i termini di servizio e l'informativa sulla privacy." +msgstr "" +"È necessario accettare i termini di servizio e l'informativa sulla privacy." #: src/app/main/data/media.cljs, #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, @@ -892,9 +894,9 @@ msgstr "Soggetto" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subtitle" msgstr "" -"Descrivi per favore il motivo della tua e-mail, specificando se si tratta " -"di un problema, di un'idea oppure di un dubbio. Un membro del nostro team " -"ti risponderà il prima possibile." +"Descrivi per favore il motivo della tua e-mail, specificando se si tratta di " +"un problema, di un'idea oppure di un dubbio. Un membro del nostro team ti " +"risponderà il prima possibile." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" @@ -1399,7 +1401,8 @@ msgstr "Sei connesso come" #: src/app/main/ui/static.cljs msgid "labels.not-found.desc-message" -msgstr "Questa pagina non esiste oppure non hai i permessi necessari per accedervi." +msgstr "" +"Questa pagina non esiste oppure non hai i permessi necessari per accedervi." #: src/app/main/ui/static.cljs msgid "labels.not-found.main-message" @@ -1622,8 +1625,8 @@ msgstr "Verificare il nuovo indirizzo e-mail" #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.info" msgstr "" -"Ti invieremo un'e-mail al tuo attuale indirizzo e-mail \"%s\" per " -"verificare la tua identità." +"Ti invieremo un'e-mail al tuo attuale indirizzo e-mail \"%s\" per verificare " +"la tua identità." #: src/app/main/ui/settings/change_email.cljs msgid "modals.change-email.new-email" @@ -1654,8 +1657,7 @@ msgstr "Sì, cancellare il mio account" #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.info" msgstr "" -"Cancellando il tuo account, perderai tutti i tuoi archivi e progetti " -"attuali." +"Cancellando il tuo account, perderai tutti i tuoi archivi e progetti attuali." #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.title" @@ -1812,8 +1814,8 @@ msgstr "Invita membri al team" #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.hint" msgstr "" -"Poiché sei il solo membro di questo team, il team verrà eliminato insieme " -"ai sui file e progetti." +"Poiché sei il solo membro di questo team, il team verrà eliminato insieme ai " +"sui file e progetti." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-close-confirm.message" @@ -1959,8 +1961,8 @@ msgstr "Cancella" #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" msgstr "" -"Stai per aggiornare un componente in una libreria condivisa. Questo " -"potrebbe causare modifiche nei file che la utilizzano." +"Stai per aggiornare un componente in una libreria condivisa. Questo potrebbe " +"causare modifiche nei file che la utilizzano." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs @@ -1973,7 +1975,8 @@ msgstr "Invito inviato con successo" #: src/app/main/ui/settings/delete_account.cljs msgid "notifications.profile-deletion-not-allowed" -msgstr "Non puoi eliminare il tuo profilo. Riassegna i tuoi team prima di procedere." +msgstr "" +"Non puoi eliminare il tuo profilo. Riassegna i tuoi team prima di procedere." #: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs msgid "notifications.profile-saved" @@ -2013,8 +2016,8 @@ msgstr "" msgid "onboarding-v2.welcome.desc2" msgstr "" -"Uno spazio pubblico dove imparare, condividere e discutere su Penpot, il " -"suo presente e futuro con l'intera Comunità e con il team di Penpot." +"Uno spazio pubblico dove imparare, condividere e discutere su Penpot, il suo " +"presente e futuro con l'intera Comunità e con il team di Penpot." msgid "onboarding-v2.welcome.desc2.title" msgstr "Partecipando nella Comunità" @@ -2037,7 +2040,8 @@ msgid "onboarding.choice.team-up.create-team" msgstr "Il nome del tuo team" msgid "onboarding.choice.team-up.create-team-desc" -msgstr "Dopo aver nominato il tuo team, potrai invitare persone ad unirsi ad esso." +msgstr "" +"Dopo aver nominato il tuo team, potrai invitare persone ad unirsi ad esso." msgid "onboarding.choice.team-up.create-team-placeholder" msgstr "Inserisci il nome del team" @@ -2137,4 +2141,4 @@ msgstr "Crea interazioni complete per imitare al meglio il prodotto finale." #: src/app/main/ui/dashboard/team.cljs msgid "title.team-invitations" -msgstr "Inviti - %s - Penpot" \ No newline at end of file +msgstr "Inviti - %s - Penpot" diff --git a/frontend/translations/ml.po b/frontend/translations/ml.po index eba23c6685..921705371d 100644 --- a/frontend/translations/ml.po +++ b/frontend/translations/ml.po @@ -59,10 +59,6 @@ msgstr "ഇവിടെ ലോഗിൻ ചെയ്യുക" msgid "auth.login-submit" msgstr "ലോഗിൻ" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "നിങ്ങളുടെ വിവരങ്ങൾ ഇവിടെ ചേർക്കുക" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" msgstr "നിങ്ങളെ വീണ്ടും കാണാൻ കഴിഞ്ഞതിൽ സന്തോഷം!" @@ -263,4 +259,4 @@ msgid "dashboard.export-single" msgstr "പെൻപോട്ട് ഫയൽ എക്സ്പോർട്ട് ചെയ്യുക" msgid "dashboard.export.detail" -msgstr "* ഘടകങ്ങൾ, ഗ്രാഫിക്സ്, നിറങ്ങൾ അല്ലെങ്കിൽ മുദ്രണകലകൾ എന്നിവ ഉൾപ്പെടാം." \ No newline at end of file +msgstr "* ഘടകങ്ങൾ, ഗ്രാഫിക്സ്, നിറങ്ങൾ അല്ലെങ്കിൽ മുദ്രണകലകൾ എന്നിവ ഉൾപ്പെടാം." diff --git a/frontend/translations/nb_NO.po b/frontend/translations/nb_NO.po index c54a6c6b28..82799790e0 100644 --- a/frontend/translations/nb_NO.po +++ b/frontend/translations/nb_NO.po @@ -376,9 +376,6 @@ msgstr "Skriftfamilie" msgid "labels.font-providers" msgstr "Skrifttilbydere" -msgid "labels.font-variant" -msgstr "Stil" - msgid "labels.fonts" msgstr "Skrifter" @@ -598,10 +595,6 @@ msgstr "Innstillinger - %s - Penpot" msgid "title.workspace" msgstr "%s - Penpot" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Rediger side" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.create-link" msgstr "Opprett lenke" @@ -633,10 +626,6 @@ msgstr "Slett" msgid "workspace.assets.edit" msgstr "Rediger" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Filbibliotek" - #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" msgstr "Grafikk" @@ -777,4 +766,4 @@ msgstr "Skjerm" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.radius.all-corners" -msgstr "Alle hjørner" \ No newline at end of file +msgstr "Alle hjørner" diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po index 805c6a6c84..b56ca130eb 100644 --- a/frontend/translations/pl.po +++ b/frontend/translations/pl.po @@ -838,10 +838,6 @@ msgstr "" "Musisz zaakceptować nasze warunki świadcznia usług oraz politykę " "prywatności." -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.token-expired" -msgstr "Token wygasł" - #: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "errors.unexpected-error" msgstr "Wystąpił nieoczekiwany błąd." @@ -884,20 +880,10 @@ msgstr "" msgid "feedback.discourse-title" msgstr "Społeczność Penpot" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-go-to" -msgstr "Przejdź do dyskusji" - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-subtitle1" msgstr "Dołącz do forum komunikacji zespołowej Penpot." -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-subtitle2" -msgstr "" -"Możesz zadawać pytania i odpowiadać na nie, prowadzić otwarte rozmowy i " -"śledzić decyzje mające wpływ na projekt." - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-title" msgstr "Dyskusje zespołowe" @@ -1987,17 +1973,9 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "Nie, dziękuję" -msgid "onboarding.newsletter.desc" -msgstr "" -"Zapisz się do naszego newslettera, aby być na bieżąco z postępami w rozwoju " -"produktów i nowościami." - msgid "onboarding.newsletter.policy" msgstr "Polityka prywatności." -msgid "onboarding.newsletter.privacy1" -msgstr "Ponieważ dbamy o prywatność, oto nasza " - msgid "onboarding.newsletter.privacy2" msgstr "" "Wyślemy Ci tylko zasadne emaile. Możesz zrezygnować z subskrypcji w " @@ -2066,9 +2044,6 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "Jedno wspólne źródło prawdy" -msgid "onboarding.team-input-placeholder" -msgstr "Wpisz nową nazwę zespołu" - msgid "onboarding.team.skip-and-invite-later" msgstr "Pomiń i zaproś później" @@ -2081,20 +2056,12 @@ msgstr "Zacznij projektować" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "Hurra! Jesteś już użytkownikiem Penpot :)" - msgid "onboarding.welcome.desc2" msgstr "" "Penpot jest w swojej pierwszej wersji beta dzięki połączeniu podstawowych " "funkcji, dojrzałości, stabilności i niesamowitej walidacji ze strony " "społeczności jako całości, w której jesteś mile widziany." -msgid "onboarding.welcome.desc3" -msgstr "" -"Podczas gdy Ty będziesz cieszyć się Penpot takim, jakim jest, będziemy go " -"ulepszać, wypuszczając iteracje naszych pełnych nadziei planów." - msgid "onboarding.welcome.title" msgstr "Witamy w Penpot" @@ -2726,10 +2693,6 @@ msgstr "Duplikuj" msgid "workspace.assets.edit" msgstr "Edytuj" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Biblioteka plików" - #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" msgstr "Grafika" @@ -2875,18 +2838,10 @@ msgstr "Włącz przyciąganie do piksela" msgid "workspace.header.menu.hide-artboard-names" msgstr "Ukryj nazwy obszarów kompozycji" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-assets" -msgstr "Ukryj zasoby" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-grid" msgstr "Ukryj siatki" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-layers" -msgstr "Ukryj warstwy" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-palette" msgstr "Ukryj paletę kolorów" @@ -2930,18 +2885,10 @@ msgstr "Zaznacz wszystko" msgid "workspace.header.menu.show-artboard-names" msgstr "Pokaz nazwy obszarów kompozycji" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-assets" -msgstr "Pokaż zasoby" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-grid" msgstr "Pokaż siatkę" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-layers" -msgstr "Pokaż warstwy" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-palette" msgstr "Pokaż paletę kolorów" @@ -4408,4 +4355,4 @@ msgid "workspace.updates.update" msgstr "Aktualizuj" msgid "workspace.viewport.click-to-close-path" -msgstr "Kliknij, aby zamknąć ścieżkę" \ No newline at end of file +msgstr "Kliknij, aby zamknąć ścieżkę" diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 33d291b7c8..b51470b01a 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -205,18 +205,12 @@ msgid_plural "common.share-link.page-shared" msgstr[0] "1 página compartilhada" msgstr[1] "%s páginas compartilhadas" -msgid "common.share-link.permissions-can-access" -msgstr "Pode acessar" - msgid "common.share-link.permissions-can-comment" msgstr "Pode comentar" msgid "common.share-link.permissions-can-inspect" msgstr "Pode inspecionar o código" -msgid "common.share-link.permissions-can-view" -msgstr "Pode visualizar" - msgid "common.share-link.permissions-hint" msgstr "Qualquer pessoa com o link terá acesso" @@ -226,9 +220,6 @@ msgstr "Páginas compartilhadas" msgid "common.share-link.placeholder" msgstr "O link compartilhável aparecerá aqui" -msgid "common.share-link.remove-link" -msgstr "Remover link" - msgid "common.share-link.team-members" msgstr "Apenas membros da equipe" @@ -238,15 +229,9 @@ msgstr "Compartilhar protótipos" msgid "common.share-link.view-all" msgstr "Selecionar todos" -msgid "common.share-link.view-all-pages" -msgstr "Todas as páginas" - msgid "common.share-link.view-current-page" msgstr "Somente esta página" -msgid "common.share-link.view-selected-pages" -msgstr "Páginas selecionadas" - msgid "common.unpublish" msgstr "Cancelar publicação" @@ -1301,9 +1286,6 @@ msgstr "Família da fonte" msgid "labels.font-providers" msgstr "Provedores de fonte" -msgid "labels.font-variant" -msgstr "Estilo" - msgid "labels.font-variants" msgstr "Estilos" @@ -1834,10 +1816,6 @@ msgstr "" msgid "modals.leave-and-reassign.hint1" msgstr "Você é o proprietário de %s." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "Selecione outro membro para promover antes de sair" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "Promover e sair" @@ -2092,17 +2070,9 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "Não, obrigado" -msgid "onboarding.newsletter.desc" -msgstr "" -"Subscreva a nossa newsletter para se manter atualizado com o progresso e as " -"novidades do desenvolvimento de produtos." - msgid "onboarding.newsletter.policy" msgstr "Politica de privacidade." -msgid "onboarding.newsletter.privacy1" -msgstr "Porque nos preocupamos com a privacidade, aqui está o nosso " - msgid "onboarding.newsletter.privacy2" msgstr "" "Enviaremos apenas e-mails relevantes para você. Você pode cancelar a " @@ -2720,10 +2690,6 @@ msgstr "Comentários (%s)" msgid "viewer.header.dont-show-interactions" msgstr "Não mostrar interações" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-file" -msgstr "Editar arquivo" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" msgstr "Tela cheia" @@ -2758,10 +2724,6 @@ msgstr "Remover link" msgid "viewer.header.share.subtitle" msgstr "Qualquer pessoa com o link terá acesso" -#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.title" -msgstr "Compartilhar protótipo" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Mostrar interações" @@ -4602,4 +4564,4 @@ msgid "workspace.updates.update" msgstr "Atualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Clique para fechar o caminho" \ No newline at end of file +msgstr "Clique para fechar o caminho" diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index 9e6f63396a..244b0c534f 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -649,10 +649,6 @@ msgstr "Os teus ficheiros foram movidos com sucesso" msgid "dashboard.success-move-project" msgstr "O teu projeto foi movido com sucesso" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.switch-team" -msgstr "Trocar de equipa" - #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.team-info" msgstr "Informação da equipa" @@ -1809,11 +1805,6 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "Tens a certeza de que pretendes sair da equipa %s?" -msgid "modals.leave-and-reassign.forbiden" -msgstr "" -"Não podes sair da equipa se não tiveres outros membros para promover para " -"proprietário. Poderás querer eliminar a equipa." - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint1" msgstr "" @@ -2074,17 +2065,9 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "Não, obrigado" -msgid "onboarding.newsletter.desc" -msgstr "" -"Subscreve a nossa newsletter para estares atualizado com o progresso e as " -"novidades do desenvolvimento de produto." - msgid "onboarding.newsletter.policy" msgstr "Política de Privacidade." -msgid "onboarding.newsletter.privacy1" -msgstr "Porque nos preocupamos com a privacidade, aqui está o nosso " - msgid "onboarding.newsletter.privacy2" msgstr "" "Enviaremos apenas e-mail relevantes para ti. Podes cancelar a subscrição a " @@ -4577,4 +4560,4 @@ msgid "workspace.updates.update" msgstr "Atualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Clica para fechar o caminho" \ No newline at end of file +msgstr "Clica para fechar o caminho" diff --git a/frontend/translations/ro.po b/frontend/translations/ro.po index c36ce7e082..bb00e1a952 100644 --- a/frontend/translations/ro.po +++ b/frontend/translations/ro.po @@ -864,9 +864,6 @@ msgstr "Familie de Fonturi" msgid "labels.font-providers" msgstr "Provideri de Fonturi" -msgid "labels.font-variant" -msgstr "Stil" - msgid "labels.fonts" msgstr "Fonturi" @@ -1235,10 +1232,6 @@ msgstr "" msgid "modals.leave-and-reassign.hint1" msgstr "Ești administratorul echipei %s." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "Selectează un membru pentru a-l promova, înainte de a părăsi echipa" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "Promovează şi părăseşte echipa" @@ -1403,10 +1396,6 @@ msgstr "Fereastra nu există." msgid "viewer.header.dont-show-interactions" msgstr "Nu afişa interacţiunile" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Editează pagina" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" msgstr "Ecran complet" @@ -1518,10 +1507,6 @@ msgstr "Duplică" msgid "workspace.assets.edit" msgstr "Editează" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Colecţii" - #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" msgstr "Obiecte grafice" @@ -2549,4 +2534,4 @@ msgid "workspace.updates.update" msgstr "Actualizează" msgid "workspace.viewport.click-to-close-path" -msgstr "Click pentru a închide calea" \ No newline at end of file +msgstr "Click pentru a închide calea" diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index b39ae2c3eb..63245d8a25 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -1874,12 +1874,6 @@ msgstr "" msgid "onboarding.contrib.link" msgstr "проект на Github" -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Мы будем отправлять только акутальные эл. письма. Вы можете отказаться от " -"подписки в любое время в своём профиле пользователя или по ссылке отказа от " -"подписки в любом из наших писем информационной рассылки." - msgid "onboarding.slide.1.alt" msgstr "Интерактивные прототипы" @@ -2124,10 +2118,6 @@ msgstr "На странице не найдено ни одного кадра." msgid "viewer.frame-not-found" msgstr "Кадр не найден." -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header-interactions" -msgstr "взаимодействия" - msgid "viewer.header.comments-section" msgstr "Комментарии (%s)" @@ -2135,10 +2125,6 @@ msgstr "Комментарии (%s)" msgid "viewer.header.dont-show-interactions" msgstr "Не показывать взаимодействия" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Редактировать страницу" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" msgstr "Полный экран" @@ -2249,10 +2235,6 @@ msgstr "Дублировать" msgid "workspace.assets.edit" msgstr "Редактировать" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Библиотека файлов" - #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" msgstr "Графика" @@ -3130,4 +3112,4 @@ msgid "workspace.updates.update" msgstr "Обновить" msgid "workspace.viewport.click-to-close-path" -msgstr "Нажмите для замыкания контура" \ No newline at end of file +msgstr "Нажмите для замыкания контура" diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 39922ac455..c568f9450b 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -197,18 +197,12 @@ msgid_plural "common.share-link.page-shared" msgstr[0] "1页已共享" msgstr[1] "%s页已共享" -msgid "common.share-link.permissions-can-access" -msgstr "可访问" - msgid "common.share-link.permissions-can-comment" msgstr "可评论" msgid "common.share-link.permissions-can-inspect" msgstr "可审查代码" -msgid "common.share-link.permissions-can-view" -msgstr "可浏览" - msgid "common.share-link.permissions-hint" msgstr "任何人通过此链接都可访问" @@ -1755,10 +1749,6 @@ msgstr "如果不能推选另一个成员作为团队所有者,你就无法离 msgid "modals.leave-and-reassign.hint1" msgstr "你是此团队的所有者。在你离开团队之前,请选择其他成员晋升为所有者。" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "请在退出前,从其他成员中选择一位晋升" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "晋升并退出" @@ -1980,15 +1970,9 @@ msgstr "您的订阅请求已发送,我们将向您发送一封电子邮件进 msgid "onboarding.newsletter.decline" msgstr "不,谢谢" -msgid "onboarding.newsletter.desc" -msgstr "订阅我们的时事通讯,随时了解产品开发进度和新闻。" - msgid "onboarding.newsletter.policy" msgstr "隐私策略。" -msgid "onboarding.newsletter.privacy1" -msgstr "我们关注个人隐私,这里是我们的 " - msgid "onboarding.newsletter.privacy2" msgstr "我们只会向您发送相关电子邮件。您可以随时在您的用户个人资料中取消订阅,也可以通过我们任何新闻通讯中的取消订阅链接取消订阅。" @@ -2070,15 +2054,9 @@ msgstr "开始设计" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "Hooray!你已经是Penpot的用户了 :)" - msgid "onboarding.welcome.desc2" msgstr "Penpot处于第一个测试版,这要归功于核心功能,成熟度,稳定性和整个社区的惊人验证的组合,我们非常欢迎您。" -msgid "onboarding.welcome.desc3" -msgstr "当你享受Penpot的时候,我们将不断改进它,发布我们希望的计划的迭代。" - msgid "onboarding.welcome.title" msgstr "欢迎来到Penpot" @@ -2595,10 +2573,6 @@ msgstr "注释 (%s)" msgid "viewer.header.dont-show-interactions" msgstr "不显示交互" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-file" -msgstr "编辑文件" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.edit-page" msgstr "编辑页面" @@ -2720,10 +2694,6 @@ msgstr "创建副本" msgid "workspace.assets.edit" msgstr "编辑" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "文档库" - #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" msgstr "图形" @@ -4473,4 +4443,4 @@ msgid "workspace.updates.update" msgstr "更新" msgid "workspace.viewport.click-to-close-path" -msgstr "单击以闭合路径" \ No newline at end of file +msgstr "单击以闭合路径" diff --git a/frontend/translations/zh_Hant.po b/frontend/translations/zh_Hant.po index a5488b9e7e..9da82de42e 100644 --- a/frontend/translations/zh_Hant.po +++ b/frontend/translations/zh_Hant.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-12-18 01:51+0000\n" "Last-Translator: Andy Li \n" -"Language-Team: Chinese (Traditional) " -"\n" +"Language-Team: Chinese (Traditional) \n" "Language: zh_Hant\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -91,10 +91,6 @@ msgstr "我同意訂閱 Penpot 的郵件討論群。" msgid "auth.notifications.invalid-token-error" msgstr "此 Recovery token 是無效的。" -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.password-changed-succesfully" -msgstr "成功修改密碼" - #: src/app/main/ui/auth/recovery.cljs msgid "auth.notifications.password-changed-successfully" msgstr "已成功更改密碼" @@ -896,18 +892,12 @@ msgstr "開放原始碼" msgid "onboarding.contrib.desc2.1" msgstr "您能夠存取" -msgid "onboarding.team.create.desc1" -msgstr "您正在與他人協作嗎?建立團隊以共同作業和分享設計資源。" - msgid "onboarding.team.create.input-placeholder" msgstr "輸入新團隊名稱" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "萬算!您已經是 Penpot 的使用者 :)" - msgid "onboarding.welcome.title" msgstr "歡迎使用 Penpot" @@ -1083,4 +1073,4 @@ msgstr "歷史" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.update" -msgstr "更新" \ No newline at end of file +msgstr "更新" From 89a27e298d363f43a58c56ec481728e0f1d65e00 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 18 Oct 2022 20:20:18 +0200 Subject: [PATCH 136/682] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ --- frontend/translations/ar.po | 40 -------------- frontend/translations/ca.po | 30 ----------- frontend/translations/da.po | 4 -- frontend/translations/de.po | 92 -------------------------------- frontend/translations/el.po | 4 -- frontend/translations/es.po | 59 -------------------- frontend/translations/eu.po | 26 --------- frontend/translations/fa.po | 16 ------ frontend/translations/fr.po | 23 -------- frontend/translations/gl.po | 15 ------ frontend/translations/he.po | 85 ----------------------------- frontend/translations/hr.po | 10 ---- frontend/translations/it.po | 21 -------- frontend/translations/jpn_JP.po | 6 +-- frontend/translations/nb_NO.po | 4 -- frontend/translations/pl.po | 23 -------- frontend/translations/pt_BR.po | 13 ----- frontend/translations/pt_PT.po | 10 ---- frontend/translations/ro.po | 4 -- frontend/translations/ru.po | 4 -- frontend/translations/tr.po | 92 -------------------------------- frontend/translations/zh_CN.po | 14 ----- frontend/translations/zh_Hant.po | 7 --- 23 files changed, 1 insertion(+), 601 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index d9fbab52fd..76937dcf10 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -583,10 +583,6 @@ msgstr "تثبيت / إلغاء التثبيت" msgid "dashboard.projects-title" msgstr "المشاريع" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "الترقية إلى مالك" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "هل تريد إزالة حسابك؟" @@ -649,10 +645,6 @@ msgstr "تم نقل الملفات بنجاح" msgid "dashboard.success-move-project" msgstr "تم نقل مشروعك بنجاح" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.switch-team" -msgstr "تبديل الفريق" - #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.team-info" msgstr "معلومات الفريق" @@ -1303,9 +1295,6 @@ msgstr "عائلة الخط" msgid "labels.font-providers" msgstr "موفري الخط" -msgid "labels.font-variant" -msgstr "نمط" - msgid "labels.font-variants" msgstr "الأنماط" @@ -1818,19 +1807,10 @@ msgstr "" "لا يمكنك مغادرة الفريق إذا لم يكن هناك عضو آخر للترقية إلى المالك. قد ترغب " "في حذف الفريق." -msgid "modals.leave-and-reassign.forbiden" -msgstr "" -"لا يمكنك مغادرة الفريق إذا لم يكن هناك عضو آخر للترقية إلى المالك. قد ترغب " -"في حذف الفريق." - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint1" msgstr "أنت %s المالك." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "حدد عضوًا آخر للترقية قبل المغادرة" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "قم بالترقية والمغادرة" @@ -2058,17 +2038,9 @@ msgstr "تم إرسال طلب الاشتراك الخاص بك ، وسوف نر msgid "onboarding.newsletter.decline" msgstr "لا شكرا" -msgid "onboarding.newsletter.desc" -msgstr "" -"اشترك في النشرة الإخبارية لدينا للبقاء على اطلاع دائم بتقدم تطوير المنتج " -"والأخبار." - msgid "onboarding.newsletter.policy" msgstr "سياسة الخصوصية." -msgid "onboarding.newsletter.privacy1" -msgstr "نظرًا لأننا نهتم بالخصوصية ، فإليك موقعنا " - msgid "onboarding.newsletter.privacy2" msgstr "" "سوف نرسل لك رسائل البريد الإلكتروني ذات الصلة فقط. يمكنك إلغاء الاشتراك في " @@ -2532,10 +2504,6 @@ msgstr "لوح الرسم غير موجود." msgid "viewer.header.dont-show-interactions" msgstr "لا تظهر التفاعلات" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-file" -msgstr "تعديل الملف" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" msgstr "ملء الشاشة" @@ -2564,10 +2532,6 @@ msgstr "إزالة الرابط" msgid "viewer.header.share.subtitle" msgstr "أي شخص لديه الرابط سيكون لديه حق الوصول" -#: src/app/main/ui/viewer/header.cljs, src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.title" -msgstr "مشاركة النموذج الأولي" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "إظهار التفاعلات" @@ -2657,10 +2621,6 @@ msgstr "تكرار" msgid "workspace.assets.edit" msgstr "تعديل" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "مكتبة الملف" - #: src/app/main/ui/workspace/sidebar/assets.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 687ab285a7..af1451d5de 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -590,10 +590,6 @@ msgstr "Fixa/Deixa de fixar" msgid "dashboard.projects-title" msgstr "Projectes" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promoure a propietari" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Voleu eliminar el vostre compte?" @@ -1967,12 +1963,6 @@ msgstr "No, gràcies" msgid "onboarding.newsletter.policy" msgstr "Política de privacitat." -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Només us enviarem correus rellevants. Podeu anul·lar la subscripció sempre " -"que vulgueu des del perfil d'usuari o amb l'enllaç que trobareu a qualsevol " -"dels nostres butlletins." - msgid "onboarding.newsletter.title" msgstr "Voleu rebre les novetats de Penpot?" @@ -2033,15 +2023,9 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "Una font compartida de veritat" -msgid "onboarding.team.create.input-placeholder" -msgstr "Introduïu el nom de l'equip nou" - msgid "onboarding.team.start.button" msgstr "Comenceu de seguida" -msgid "onboarding.team.start.title" -msgstr "Comenceu a dissenyar" - msgid "onboarding.templates.subtitle" msgstr "Aquí teniu algunes plantilles." @@ -2051,12 +2035,6 @@ msgstr "Comenceu a dissenyar" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc2" -msgstr "" -"Penpot es troba en la primera versió beta gràcies a la combinació de " -"característiques bàsiques, maduresa, estabilitat i la increïble validació " -"de la comunitat en el seu conjunt, a la qual sou més que benvinguts." - msgid "onboarding.welcome.title" msgstr "Et donem la benvinguda a Penpot" @@ -3575,10 +3553,6 @@ msgstr "Capes seleccionades" msgid "workspace.options.layout-item.advanced-ops" msgstr "Opcions avançades" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.max-w" -msgstr "Amplada màx." - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.min-w" msgstr "Amplada mín." @@ -3587,10 +3561,6 @@ msgstr "Amplada mín." msgid "workspace.options.layout-item.title" msgstr "Redimensió de l'element" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.max-w" -msgstr "Amplada màxima" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.min-w" msgstr "Amplada mínima" diff --git a/frontend/translations/da.po b/frontend/translations/da.po index 83075e81bd..18a949c619 100644 --- a/frontend/translations/da.po +++ b/frontend/translations/da.po @@ -301,10 +301,6 @@ msgstr "Fastgør/Løsne" msgid "dashboard.projects-title" msgstr "Projekter" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Forfrem til ejer" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Vil du slette din konto?" diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 6ac90b816d..6647cbf28d 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -599,10 +599,6 @@ msgstr "Anheften/Lösen" msgid "dashboard.projects-title" msgstr "Projekte" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Zum Eigentümer befördern" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Möchten Sie Ihr Konto entfernen?" @@ -881,10 +877,6 @@ msgid "errors.terms-privacy-agreement-invalid" msgstr "" "Sie müssen unsere Nutzungsbedingungen und Datenschutzrichtlinien akzeptieren." -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.token-expired" -msgstr "Token abgelaufen" - #: src/app/main/data/media.cljs, #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs @@ -1891,12 +1883,6 @@ msgstr "" msgid "modals.leave-and-reassign.hint1" msgstr "Sie sind der Eigentümer von %s." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "" -"Befördern Sie ein anderes Mitglied zum Eigentümer, bevor Sie das Team " -"verlassen" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "Befördern und verlassen" @@ -2144,11 +2130,6 @@ msgstr "Geben Sie den Namen des Teams ein" msgid "onboarding.choice.team-up.invite-members" msgstr "Mitglieder einladen" -msgid "onboarding.choice.team-up.invite-members-desc" -msgstr "" -"Im Teambereich können Sie auch später Mitglieder einladen und die Rollen " -"ändern." - msgid "onboarding.choice.team-up.invite-members-info" msgstr "" "Denken Sie daran, alle einzubeziehen. Entwickler, Designer, Manager... die " @@ -2197,17 +2178,9 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "Nein, danke" -msgid "onboarding.newsletter.desc" -msgstr "" -"Abonnieren Sie unseren Newsletter, um über Fortschritte in der " -"Produktentwicklung und Neuigkeiten auf dem Laufenden zu bleiben." - msgid "onboarding.newsletter.policy" msgstr "Datenschutzbestimmungen." -msgid "onboarding.newsletter.privacy1" -msgstr "Weil uns die Privatsphäre wichtig ist, hier ist unsere " - msgid "onboarding.newsletter.privacy2" msgstr "" "Wir werden Ihnen nur relevante E-Mails schicken. Sie können sich jederzeit " @@ -2276,9 +2249,6 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "Eine zentrale Anlaufstelle für alle" -msgid "onboarding.team-input-placeholder" -msgstr "Neuen Teamnamen eingeben" - msgid "onboarding.team-modal.create-team" msgstr "Ein Team erstellen" @@ -2302,31 +2272,17 @@ msgstr "Unbegrenzte Anzahl von Mitgliedern" msgid "onboarding.team-modal.create-team-feature-5" msgstr "100% kostenlos!" -msgid "onboarding.team.create.button" -msgstr "Team erstellen" - msgid "onboarding.team.create.desc1" msgstr "" "Arbeiten Sie mit jemandem zusammen? Erstellen Sie ein Team, um gemeinsam an " "Projekten zu arbeiten und Design-Assets zu teilen." -msgid "onboarding.team.create.input-placeholder" -msgstr "Neuen Teamnamen eingeben" - msgid "onboarding.team.create.title" msgstr "Team erstellen" -msgid "onboarding.team.skip-and-invite-later" -msgstr "Überspringen und später einladen" - msgid "onboarding.team.start.button" msgstr "Gleich loslegen" -msgid "onboarding.team.start.desc1" -msgstr "" -"Legen Sie gleich los und gestalten Sie erstmal alleine. Sie können später " -"immer noch Teams erstellen." - msgid "onboarding.team.start.title" msgstr "Mit der Gestaltung beginnen" @@ -2339,20 +2295,12 @@ msgstr "Beginnen Sie mit dem Entwerfen" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "Hurra! Sie sind bereits ein Penpot-Benutzer :)" - msgid "onboarding.welcome.desc2" msgstr "" "Penpot ist in seiner ersten Beta-Version dank der Kombination von " "Kernfunktionen, Reife, Stabilität und der erstaunlichen Validierung durch " "die gesamte Community, zu der Sie mehr als willkommen sind." -msgid "onboarding.welcome.desc3" -msgstr "" -"Während Sie Penpot als das genießen, was es ist, werden wir es weiter " -"verbessern und neue Versionen unserer hoffnungsvollen Pläne veröffentlichen." - msgid "onboarding.welcome.title" msgstr "Willkommen bei Penpot" @@ -2876,10 +2824,6 @@ msgstr "Kommentare (%s)" msgid "viewer.header.dont-show-interactions" msgstr "Interaktionen nicht anzeigen" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Seite bearbeiten" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" msgstr "Vollbildmodus" @@ -3005,10 +2949,6 @@ msgstr "Duplizieren" msgid "workspace.assets.edit" msgstr "Bearbeiten" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Dateibibliothek" - #: src/app/main/ui/workspace/sidebar/assets.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" @@ -3164,18 +3104,10 @@ msgstr "Ausrichten am Pixel aktivieren" msgid "workspace.header.menu.hide-artboard-names" msgstr "Namen von Zeichenflächen ausblenden" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-assets" -msgstr "Assets ausblenden" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-grid" msgstr "Raster ausblenden" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-layers" -msgstr "Ebenen ausblenden" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-palette" msgstr "Farbpalette ausblenden" @@ -3219,18 +3151,10 @@ msgstr "Alles auswählen" msgid "workspace.header.menu.show-artboard-names" msgstr "Namen der Zeichenflächen anzeigen" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-assets" -msgstr "Assets einblenden" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-grid" msgstr "Raster einblenden" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-layers" -msgstr "Ebenen einblenden" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-palette" msgstr "Farbpalette einblenden" @@ -3944,18 +3868,10 @@ msgstr "Min.Höhe" msgid "workspace.options.layout-item.layout-min-w" msgstr "Min.Breite" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.max-h" -msgstr "Max. Höhe" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.max-w" msgstr "Max. Breite" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.min-h" -msgstr "Min. Höhe" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.min-w" msgstr "Min. Breite" @@ -3980,18 +3896,10 @@ msgstr "Mindesthöhe" msgid "workspace.options.layout-item.title.layout-min-w" msgstr "Mindestbreite" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.max-h" -msgstr "Maximale Höhe" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-w" msgstr "Maximale Breite" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.min-h" -msgstr "Minimale Höhe" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.min-w" msgstr "Minimale Breite" diff --git a/frontend/translations/el.po b/frontend/translations/el.po index 7feecf4257..24d6c2dc49 100644 --- a/frontend/translations/el.po +++ b/frontend/translations/el.po @@ -266,10 +266,6 @@ msgstr "Καρφίτσωμα / ξεκαρφίτσωμα" msgid "dashboard.projects-title" msgstr "Εργα" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Προώθηση σε κάτοχο" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Θέλετε να καταργήσετε τον λογαριασμό σας;" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index bc115a2bd9..78f85997d3 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -592,10 +592,6 @@ msgstr "Anclar/Desanclar" msgid "dashboard.projects-title" msgstr "Proyectos" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promover a dueño" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "¿Quieres borrar tu cuenta?" @@ -876,10 +872,6 @@ msgstr "" msgid "errors.terms-privacy-agreement-invalid" msgstr "Debes aceptar nuestros términos de servicio y política de privacidad." -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.token-expired" -msgstr "Token expirado" - #: src/app/main/data/media.cljs, #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs @@ -924,20 +916,10 @@ msgstr "" msgid "feedback.discourse-title" msgstr "Comunidad de Penpot" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-go-to" -msgstr "Ir a las discusiones" - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-subtitle1" msgstr "Entra al foro colaborativo de Penpot." -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-subtitle2" -msgstr "" -"Puedes hacer preguntas y dar respuestas, participar en conversaciones " -"abiertas y hacer seguimiento de decisones que afectan al proyecto." - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-title" msgstr "Debates de equipo" @@ -2256,9 +2238,6 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "Una \"fuente de la verdad\" compartida" -msgid "onboarding.team-input-placeholder" -msgstr "Introduce el nuevo nombre del equipo" - msgid "onboarding.team-modal.create-team" msgstr "Crea un equipo" @@ -2282,31 +2261,17 @@ msgstr "Sin límite de integrantes" msgid "onboarding.team-modal.create-team-feature-5" msgstr "¡100% gratis!" -msgid "onboarding.team.create.button" -msgstr "Crea un equipo" - msgid "onboarding.team.create.desc1" msgstr "" "¿Trabajando con alguien más? Crea un equipo donde compartir proyectos y " "elementos de diseño." -msgid "onboarding.team.create.input-placeholder" -msgstr "Introduce el nombre del equipo" - msgid "onboarding.team.create.title" msgstr "Crear equipo" -msgid "onboarding.team.skip-and-invite-later" -msgstr "Saltar e invitar más tarde" - msgid "onboarding.team.start.button" msgstr "Comenzar directamente" -msgid "onboarding.team.start.desc1" -msgstr "" -"Entra directamente a Penpot y comienza a diseñar individualmente. Siempre " -"tendrás la opción de crear equipos más adelante." - msgid "onboarding.team.start.title" msgstr "Comienza a diseñar" @@ -2319,20 +2284,12 @@ msgstr "Empezar a diseñar" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "¡Hurra! Ya tienes tu cuenta en Penpot :)" - msgid "onboarding.welcome.desc2" msgstr "" "Penpot está en su primera versión beta gracias a una combinación de " "funcionalidades, madurez, estabilidad y la fantástica validación de su " "comunidad, a la que te damos la bienvenida." -msgid "onboarding.welcome.desc3" -msgstr "" -"Mientras disfrutas de Penpot seguiremos haciendo mejoras, lanzando " -"iteraciones de nuestros esperanzadores planes." - msgid "onboarding.welcome.title" msgstr "Te damos la bienvenida a Penpot" @@ -3136,18 +3093,10 @@ msgstr "Activar ajuste al pixel" msgid "workspace.header.menu.hide-artboard-names" msgstr "Ocultar nombres de tableros" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-assets" -msgstr "Ocultar recursos" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-grid" msgstr "Ocultar rejillas" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-layers" -msgstr "Ocultar capas" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-palette" msgstr "Ocultar paleta de colores" @@ -3191,18 +3140,10 @@ msgstr "Seleccionar todo" msgid "workspace.header.menu.show-artboard-names" msgstr "Mostrar nombres de tableros" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-assets" -msgstr "Mostrar recursos" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-grid" msgstr "Mostrar rejilla" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-layers" -msgstr "Mostrar capas" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-palette" msgstr "Mostrar paleta de colores" diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index 353be3d028..e3860875dc 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -583,10 +583,6 @@ msgstr "Ainguratu/Desainguratu" msgid "dashboard.projects-title" msgstr "Proiektuak" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Bihurtu jabe" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Kontua ezabatu nahi duzu?" @@ -2005,9 +2001,6 @@ msgstr "Laguntzeko gida" msgid "onboarding-v2.welcome.title" msgstr "Ongi etorri Penpotera!" -msgid "onboarding.choice.fly-solo" -msgstr "Bakarrik hasi" - msgid "onboarding.choice.team-up" msgstr "Osatu taldea" @@ -2077,12 +2070,6 @@ msgstr "Ez, eskerrik asko" msgid "onboarding.newsletter.policy" msgstr "Pribatutasun politika." -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Benetan baliozkoak diren mezuak bakarrik bidaliko dizkizugu. Edonoiz eten " -"dezakezu harpidetza zure profiletik edo buletinean bertan datorren esteka " -"erabiliz." - msgid "onboarding.newsletter.title" msgstr "Penpoti buruzko albisteak jaso nahi dituzu?" @@ -2171,11 +2158,6 @@ msgstr "Hasi diseinatzen" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc2" -msgstr "" -"Penpot bere lehenengo beta bertsioan dago, hainbat funtzionalitate, " -"egonkortasun eta komunitatearen onarpena jaso ostean, ongi etorri." - msgid "onboarding.welcome.title" msgstr "Ongi etorri Penpotera" @@ -3715,10 +3697,6 @@ msgstr "Gutxieneko altuera" msgid "workspace.options.layout-item.layout-min-w" msgstr "Gutxieneko zabalera" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.max-w" -msgstr "Gehienezko zabalera" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.min-w" msgstr "Gutxieneko zabalera" @@ -3743,10 +3721,6 @@ msgstr "Gutxieneko altuera" msgid "workspace.options.layout-item.title.layout-min-w" msgstr "Gutxieneko zabalera" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.max-w" -msgstr "Gehieneko zabalera" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.min-w" msgstr "Gutxieneko zabalera" diff --git a/frontend/translations/fa.po b/frontend/translations/fa.po index 19526fb919..e94ce27d09 100644 --- a/frontend/translations/fa.po +++ b/frontend/translations/fa.po @@ -581,11 +581,6 @@ msgstr "پین/برداشتن پین" msgid "dashboard.projects-title" msgstr "پروژه‌ها" -#: src/app/main/ui/dashboard/team.cljs -#, fuzzy -msgid "dashboard.promote-to-owner" -msgstr "ارتقا به مالک" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "آیا می‌خواهید حساب خود را حذف کنید؟" @@ -871,10 +866,6 @@ msgstr "" msgid "feedback.discourse-title" msgstr "انجمن Penpot" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-title" -msgstr "بحث‌های تیمی" - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" msgstr "موضوع" @@ -1663,13 +1654,6 @@ msgstr "پس از نامگذاری تیم خود، می‌توانید افرا msgid "onboarding.welcome.alt" msgstr "Penpot" -#, fuzzy -msgid "onboarding.welcome.desc2" -msgstr "" -"Penpot به لطف ترکیبی از ویژگی‌های اصلی، بلوغ، پایداری و اعتبارسنجی " -"شگفت‌انگیز جامعه به‌عنوان یک کل، در اولین نسخه بتا خود قرار دارد، که از آن " -"استقبال می‌کنید." - #, fuzzy msgid "onboarding.welcome.title" msgstr "به Penpot خوش آمدید" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 3d8214dd7c..49c3df2422 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -600,10 +600,6 @@ msgstr "Épingler/Désépingler" msgid "dashboard.projects-title" msgstr "Projets" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promouvoir propriétaire" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Vous souhaitez supprimer votre compte ?" @@ -2023,9 +2019,6 @@ msgstr "Avant de démarrer" msgid "onboarding-v2.welcome.title" msgstr "Bienvenu sur Penpot !" -msgid "onboarding.choice.fly-solo" -msgstr "Commencer seul" - msgid "onboarding.choice.team-up" msgstr "Faites équipe" @@ -2084,12 +2077,6 @@ msgstr "Non, merci" msgid "onboarding.newsletter.policy" msgstr "Politique de confidentialité." -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Nous ne vous enverrons que des e-mails pertinents. Vous pouvez vous " -"désabonner à tout moment via votre profil d’utilisateur ou via le lien de " -"désabonnement dans l’une de nos newsletters." - msgid "onboarding.newsletter.title" msgstr "Vous souhaitez recevoir les actualités de Penpot ?" @@ -2173,12 +2160,6 @@ msgstr "Commencer à concevoir" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc2" -msgstr "" -"Penpot en est à sa première version bêta grâce à son ensemble de " -"fonctionnalités de base, sa maturité, sa stabilité et aux retours très " -"positifs de la communauté, à laquelle vous êtes plus que bienvenu." - msgid "onboarding.welcome.title" msgstr "Bienvenue chez Penpot" @@ -2648,10 +2629,6 @@ msgstr "Plan de travail introuvable." msgid "viewer.header.dont-show-interactions" msgstr "Ne pas afficher les interactions" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Modifier la page" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" msgstr "Plein écran" diff --git a/frontend/translations/gl.po b/frontend/translations/gl.po index db2a25f6b8..1b9dd1e9fd 100644 --- a/frontend/translations/gl.po +++ b/frontend/translations/gl.po @@ -207,18 +207,12 @@ msgid_plural "common.share-link.page-shared" msgstr[0] "1 páxina compartida" msgstr[1] "% páxinas compartidas" -msgid "common.share-link.permissions-can-access" -msgstr "Pode acceder a" - msgid "common.share-link.permissions-can-comment" msgstr "Poden comentar" msgid "common.share-link.permissions-can-inspect" msgstr "Poden ver o código" -msgid "common.share-link.permissions-can-view" -msgstr "Pode ver" - msgid "common.share-link.permissions-hint" msgstr "Calquera persoa ca ligazón terá acceso" @@ -228,9 +222,6 @@ msgstr "Páxinas compartidas" msgid "common.share-link.placeholder" msgstr "A ligazón para compartir aparecerá aquí" -msgid "common.share-link.remove-link" -msgstr "Eliminar ligazón" - msgid "common.share-link.team-members" msgstr "Só membros do equipo" @@ -240,15 +231,9 @@ msgstr "Compartir prototipos" msgid "common.share-link.view-all" msgstr "Seleccionar todas" -msgid "common.share-link.view-all-pages" -msgstr "Todas as páxinas" - msgid "common.share-link.view-current-page" msgstr "Só esta páxina" -msgid "common.share-link.view-selected-pages" -msgstr "Páxinas seleccionadas" - msgid "common.unpublish" msgstr "Cancelar publicación" diff --git a/frontend/translations/he.po b/frontend/translations/he.po index ed158302d9..eb8486580e 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -575,10 +575,6 @@ msgstr "נעיצה/שחרור" msgid "dashboard.projects-title" msgstr "מיזמים" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "קידום לבעלות" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "להסיר את החשבון שלך?" @@ -845,10 +841,6 @@ msgstr "הבעלים לא יכולים לעזוב את הקבוצה, עליך ל msgid "errors.terms-privacy-agreement-invalid" msgstr "עליך לקבל את תנאי השירות ואת מדיניות הפרטיות." -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.token-expired" -msgstr "תוקף האסימון פג" - #: src/app/main/data/media.cljs, #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs @@ -1846,10 +1838,6 @@ msgid "modals.leave-and-reassign.hint1" msgstr "" "הבעלות על הצוות הזה בידיך. נא לבחור מישהו כדי לקידום לבעלות בטרם עזיבתך." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "נא לבחור חבר אחר לקידום לבעלות בטרם עזיבתך" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "קידום ועזיבה" @@ -2132,17 +2120,9 @@ msgstr "בקשת המינוי שלך נשלחה, נשלח לך הודעה בדו msgid "onboarding.newsletter.decline" msgstr "לא, תודה" -msgid "onboarding.newsletter.desc" -msgstr "" -"ניתן להירשם לרשימת הדיוור שלנו כדי לשמור על קשר עם תהליך פיתוח המוצר והחדשות " -"העדכניות." - msgid "onboarding.newsletter.policy" msgstr "מדיניות פרטיות." -msgid "onboarding.newsletter.privacy1" -msgstr "בגלל שאכפת לנו מפרטיות, הנה " - msgid "onboarding.newsletter.privacy2" msgstr "" "נשלח אליך הודעות שתואמות לבחירה שלך בלבד. אפשר לבטל את המינוי דרך פרופיל " @@ -2203,9 +2183,6 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "מקור משותף יחיד לאמת" -msgid "onboarding.team-input-placeholder" -msgstr "נא למלא שם חדש לצוות" - msgid "onboarding.team-modal.create-team" msgstr "יצירת צוות" @@ -2229,31 +2206,17 @@ msgstr "ללא הגבלת משתמשים" msgid "onboarding.team-modal.create-team-feature-5" msgstr "100% בחינם!" -msgid "onboarding.team.create.button" -msgstr "יצירת צוות" - msgid "onboarding.team.create.desc1" msgstr "" "יש לך עבודה בשיתוף עם גורם נוסף? ניתן ליצור צוות כדי לעבוד יחד על מיזמים " "ולשתף משאבי עיצוב." -msgid "onboarding.team.create.input-placeholder" -msgstr "נא למלא שם לצוות" - msgid "onboarding.team.create.title" msgstr "יצירת צוות" -msgid "onboarding.team.skip-and-invite-later" -msgstr "לדלג ולהזמין מאוחר יותר" - msgid "onboarding.team.start.button" msgstr "להתחיל כבר עכשיו" -msgid "onboarding.team.start.desc1" -msgstr "" -"אפשר לצלול עמוק ישירות אל Penpot ולהתחיל לעצב בעצמך. עדיין תהיה לך הזדמנות " -"ליצור צוותים בהמשך." - msgid "onboarding.team.start.title" msgstr "מתחילים לעצב" @@ -2266,19 +2229,11 @@ msgstr "להתחיל לעצב" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "יאי! כבר יש לך משתמש ב־Penpot :)" - msgid "onboarding.welcome.desc2" msgstr "" "Penpot נמצא בשלבי בטא ראשונית תודות לשילוב בין יכולות ליבה, בשלות, יציבות " "ואימות מדהים מכלל הקהילה, שתשמח לקבל אותך אליה." -msgid "onboarding.welcome.desc3" -msgstr "" -"בזמן השימוש המהנה ב־Penpot באשר הוא, אנו נמשיך לשפר אותו, נפיץ מחזורים של " -"התכניות השאפתניות שלנו." - msgid "onboarding.welcome.title" msgstr "ברוך בואך ל־Penpot" @@ -2924,10 +2879,6 @@ msgstr "שכפול" msgid "workspace.assets.edit" msgstr "עריכה" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "ספריית קבצים" - #: src/app/main/ui/workspace/sidebar/assets.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" @@ -3085,18 +3036,10 @@ msgstr "הפעלת הצמדה לפיקסל" msgid "workspace.header.menu.hide-artboard-names" msgstr "הסתרת שמות לוחות" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-assets" -msgstr "הסתרת משאבים" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-grid" msgstr "הסתרת רשתות" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-layers" -msgstr "הסתרת שכבות" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-palette" msgstr "הסתרת ערכת צבעים" @@ -3140,18 +3083,10 @@ msgstr "לבחור הכול" msgid "workspace.header.menu.show-artboard-names" msgstr "הצגת שמות לוחות" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-assets" -msgstr "הצגת משאבים" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-grid" msgstr "הצגת רשת" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-layers" -msgstr "הצגת שכבות" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-palette" msgstr "הצגת ערכת צבעים" @@ -3863,18 +3798,10 @@ msgstr "גובה מז.‏" msgid "workspace.options.layout-item.layout-min-w" msgstr "רוחב מז.‏" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.max-h" -msgstr "גובה מרבי" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.max-w" msgstr "רוחב מרבי" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.min-h" -msgstr "גובה מזערי" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.min-w" msgstr "רוחב מזערי" @@ -3899,18 +3826,10 @@ msgstr "גובה מזערי" msgid "workspace.options.layout-item.title.layout-min-w" msgstr "רוחב מזערי" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.max-h" -msgstr "גובה מרבי" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-w" msgstr "רוחב מרבי" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.min-h" -msgstr "גובה מזערי" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.min-w" msgstr "רוחב מזערי" @@ -4031,10 +3950,6 @@ msgstr "צבעים נוספים" msgid "workspace.options.more-lib-colors" msgstr "צבעי ספרייה נוספים" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.navigate-to" -msgstr "ניווט אל" - #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.none" msgstr "ללא" diff --git a/frontend/translations/hr.po b/frontend/translations/hr.po index d31cab9d8b..89c877feeb 100644 --- a/frontend/translations/hr.po +++ b/frontend/translations/hr.po @@ -584,10 +584,6 @@ msgstr "Prikvači/Otkvači" msgid "dashboard.projects-title" msgstr "Projekti" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promakni u vlasnika" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Želiš li ukloniti svoj račun?" @@ -2078,12 +2074,6 @@ msgstr "Ne, hvala" msgid "onboarding.newsletter.policy" msgstr "Politika privatnosti." -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Poslat ćemo ti samo relevantne e-mailove. Pretplatu možeš odjaviti u bilo " -"kojem trenutku u svom korisničkom profilu ili putem linka za odjavu u bilo " -"kojem od naših newslettera." - msgid "onboarding.newsletter.title" msgstr "Želiš primati Penpot novostii?" diff --git a/frontend/translations/it.po b/frontend/translations/it.po index 060342d95c..93ebabd1fe 100644 --- a/frontend/translations/it.po +++ b/frontend/translations/it.po @@ -587,10 +587,6 @@ msgstr "Appunta/Rimuovi" msgid "dashboard.projects-title" msgstr "Progetti" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promuovi a proprietario" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Desideri eliminare il tuo account?" @@ -653,10 +649,6 @@ msgstr "I tuoi file sono stati spostati con successo" msgid "dashboard.success-move-project" msgstr "Il tuo progetto è stato spostato con successo" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.switch-team" -msgstr "Cambia team" - #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.team-info" msgstr "Informazioni sul team" @@ -1821,11 +1813,6 @@ msgstr "" msgid "modals.leave-and-close-confirm.message" msgstr "Lasciare il team di %s?" -msgid "modals.leave-and-reassign.forbiden" -msgstr "" -"Non puoi lasciare il team se non c'è alcun membro da promuovere a " -"proprietario. Potresti voler cancellare il team." - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.hint1" msgstr "" @@ -2097,17 +2084,9 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "No, grazie" -msgid "onboarding.newsletter.desc" -msgstr "" -"Iscriviti alla nostra newsletter per rimanere aggiornato con le news e i " -"progressi di sviluppo del prodotto." - msgid "onboarding.newsletter.policy" msgstr "Condizioni sulla Privacy." -msgid "onboarding.newsletter.privacy1" -msgstr "In quanto ci teniamo alla privacy, qui puoi vedere la nostra " - msgid "onboarding.newsletter.privacy2" msgstr "" "Ti invieremo solamente email per te rilevanti. Puoi cancellare l'iscrizione " diff --git a/frontend/translations/jpn_JP.po b/frontend/translations/jpn_JP.po index d5cc7a9696..556f4e2343 100644 --- a/frontend/translations/jpn_JP.po +++ b/frontend/translations/jpn_JP.po @@ -398,10 +398,6 @@ msgstr "パスワードを変更" msgid "dashboard.projects-title" msgstr "プロジェクト" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "オーナーに昇格" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "アカウントを削除したいですか?" @@ -1000,4 +996,4 @@ msgstr "招待を再送" #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" -msgstr "リトライ" \ No newline at end of file +msgstr "リトライ" diff --git a/frontend/translations/nb_NO.po b/frontend/translations/nb_NO.po index 82799790e0..0bb518f0f1 100644 --- a/frontend/translations/nb_NO.po +++ b/frontend/translations/nb_NO.po @@ -135,10 +135,6 @@ msgstr "Fest/løsne" msgid "dashboard.projects-title" msgstr "Prosjekter" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promoter til eier" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Ønsker du å fjerne kontoen din?" diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po index b56ca130eb..afee0bbdf5 100644 --- a/frontend/translations/pl.po +++ b/frontend/translations/pl.po @@ -586,10 +586,6 @@ msgstr "Przypnij/Odepnij" msgid "dashboard.projects-title" msgstr "Projekty" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Awansuj na właściciela" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Czy chesz usunąć swoje konto?" @@ -880,10 +876,6 @@ msgstr "" msgid "feedback.discourse-title" msgstr "Społeczność Penpot" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-subtitle1" -msgstr "Dołącz do forum komunikacji zespołowej Penpot." - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-title" msgstr "Dyskusje zespołowe" @@ -1976,12 +1968,6 @@ msgstr "Nie, dziękuję" msgid "onboarding.newsletter.policy" msgstr "Polityka prywatności." -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Wyślemy Ci tylko zasadne emaile. Możesz zrezygnować z subskrypcji w " -"dowolnym momencie w swoim profilu użytkownika lub za pośrednictwem linku " -"rezygnacji z subskrypcji w dowolnym z naszych newsletterów." - msgid "onboarding.newsletter.title" msgstr "Chcesz otrzymywać informacje o Penpot?" @@ -2044,9 +2030,6 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "Jedno wspólne źródło prawdy" -msgid "onboarding.team.skip-and-invite-later" -msgstr "Pomiń i zaproś później" - msgid "onboarding.templates.subtitle" msgstr "Oto kilka szablonów." @@ -2056,12 +2039,6 @@ msgstr "Zacznij projektować" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc2" -msgstr "" -"Penpot jest w swojej pierwszej wersji beta dzięki połączeniu podstawowych " -"funkcji, dojrzałości, stabilności i niesamowitej walidacji ze strony " -"społeczności jako całości, w której jesteś mile widziany." - msgid "onboarding.welcome.title" msgstr "Witamy w Penpot" diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index b51470b01a..ba59bbe3f4 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -229,9 +229,6 @@ msgstr "Compartilhar protótipos" msgid "common.share-link.view-all" msgstr "Selecionar todos" -msgid "common.share-link.view-current-page" -msgstr "Somente esta página" - msgid "common.unpublish" msgstr "Cancelar publicação" @@ -586,10 +583,6 @@ msgstr "Fixar/Desafixar" msgid "dashboard.projects-title" msgstr "Projetos" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promover a proprietário" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Quer remover sua conta?" @@ -2073,12 +2066,6 @@ msgstr "Não, obrigado" msgid "onboarding.newsletter.policy" msgstr "Politica de privacidade." -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Enviaremos apenas e-mails relevantes para você. Você pode cancelar a " -"inscrição a qualquer momento em seu perfil de usuário ou por meio do link " -"de cancelamento de inscrição em qualquer um de nossos boletins informativos." - msgid "onboarding.newsletter.title" msgstr "Deseja receber novidades sobre o Penpot?" diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index 244b0c534f..8d3c9ca15d 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -585,10 +585,6 @@ msgstr "Fixar/Desafixar" msgid "dashboard.projects-title" msgstr "Projetos" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promover para proprietário" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Queres remover a tua conta?" @@ -2068,12 +2064,6 @@ msgstr "Não, obrigado" msgid "onboarding.newsletter.policy" msgstr "Política de Privacidade." -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Enviaremos apenas e-mail relevantes para ti. Podes cancelar a subscrição a " -"qualquer momento no teu perfil ou através do link de cancelamento em " -"qualquer uma das nossas newsletters." - msgid "onboarding.newsletter.title" msgstr "Queres receber as novidades do Penpot?" diff --git a/frontend/translations/ro.po b/frontend/translations/ro.po index bb00e1a952..b6fca73f3a 100644 --- a/frontend/translations/ro.po +++ b/frontend/translations/ro.po @@ -304,10 +304,6 @@ msgstr "Pin/Unpin" msgid "dashboard.projects-title" msgstr "Proiecte" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Promovează la administrator" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Doriți să vă ștergeți contul?" diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index 63245d8a25..86c2369206 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -581,10 +581,6 @@ msgstr "Закрепить/Открепить" msgid "dashboard.projects-title" msgstr "Проекты" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Сделать владельцем" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Хотите удалить свой аккаунт?" diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 3304790901..4f5f4e7a49 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -589,10 +589,6 @@ msgstr "Sabitle/Sabitleme" msgid "dashboard.projects-title" msgstr "Projeler" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "Sahibi olarak belirle" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "Hesabınızı kaldırmak mı istiyorsunuz?" @@ -862,10 +858,6 @@ msgstr "Sahip takımdan ayrılamaz, sahip rolünü yeniden atamanız gerekir." msgid "errors.terms-privacy-agreement-invalid" msgstr "Hizmet şartlarımızı ve gizlilik politikamızı kabul etmelisin." -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.token-expired" -msgstr "Jetonun süresi geçti" - #: src/app/main/data/media.cljs, #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, #: src/app/main/ui/handoff/exports.cljs @@ -1314,9 +1306,6 @@ msgstr "Yazı Tipi Ailesi" msgid "labels.font-providers" msgstr "Yazı tipi sağlayıcıları" -msgid "labels.font-variant" -msgstr "Stil" - msgid "labels.font-variants" msgstr "Biçimler" @@ -1865,10 +1854,6 @@ msgstr "" "Bu takımın sahibi sizsiniz. Lütfen ayrılmadan önce sahibi olarak belirlemek " "için başka bir üye seçin." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "Ayrılmadan önce terfi ettirmek için başka bir üye seçin" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.leave-and-reassign.promote-and-leave" msgstr "Terfi ettir ve ayrıl" @@ -2159,17 +2144,9 @@ msgstr "" msgid "onboarding.newsletter.decline" msgstr "Hayır, teşekkürler" -msgid "onboarding.newsletter.desc" -msgstr "" -"Ürün geliştirmedeki ilerleme ve haberlerden haberdar olmak için bültenimize " -"abone olun." - msgid "onboarding.newsletter.policy" msgstr "Gizlilik Politikası." -msgid "onboarding.newsletter.privacy1" -msgstr "Gizliliğe önem verdiğimiz için, işte bizim " - msgid "onboarding.newsletter.privacy2" msgstr "" "Size yalnızca ilgili e-postaları göndereceğiz. Aboneliğinizi istediğiniz " @@ -2235,9 +2212,6 @@ msgstr "" msgid "onboarding.slide.3.title" msgstr "Paylaşılan bir doğruluk kaynağı" -msgid "onboarding.team-input-placeholder" -msgstr "Yeni takım adı gir" - msgid "onboarding.team-modal.create-team" msgstr "Bir takım oluştur" @@ -2261,31 +2235,17 @@ msgstr "Sınırsız üye" msgid "onboarding.team-modal.create-team-feature-5" msgstr "%100 özgür!" -msgid "onboarding.team.create.button" -msgstr "Takım oluştur" - msgid "onboarding.team.create.desc1" msgstr "" "Biriyle mi çalışıyorsunuz? Projelerde birlikte çalışmak ve tasarım " "varlıklarını paylaşmak için bir takım oluşturun." -msgid "onboarding.team.create.input-placeholder" -msgstr "Yeni takım adı gir" - msgid "onboarding.team.create.title" msgstr "Takım oluştur" -msgid "onboarding.team.skip-and-invite-later" -msgstr "Atla ve daha sonra davet et" - msgid "onboarding.team.start.button" msgstr "Hemen başla" -msgid "onboarding.team.start.desc1" -msgstr "" -"Hemen Penpot'a atlayın ve kendi başınıza tasarlamaya başlayın. Daha sonra " -"takımlar oluşturma şansınız olacak." - msgid "onboarding.team.start.title" msgstr "Tasarlamaya başla" @@ -2298,20 +2258,12 @@ msgstr "Tasarlamaya başla" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc1" -msgstr "Yaşasın! Artık bir Penpot kullanıcısısınız :)" - msgid "onboarding.welcome.desc2" msgstr "" "Penpot temel özelliklerin, olgunluğun, kararlılığın ve bir bütün olarak " "topluluktan gelen muhteşem doğrulamanın birleşimi sayesinde ilk beta " "sürümündedir ve sizi memnuniyetle karşılıyoruz." -msgid "onboarding.welcome.desc3" -msgstr "" -"Siz Penpot'un tadını çıkarırken, biz onu geliştirmeye devam edeceğiz ve " -"umutlu planlarımızın tekrarlarını yayınlayacağız." - msgid "onboarding.welcome.title" msgstr "Penpot'a Hoş Geldiniz" @@ -2835,10 +2787,6 @@ msgstr "Yorumlar (%s)" msgid "viewer.header.dont-show-interactions" msgstr "Etkileşimleri gösterme" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-file" -msgstr "Dosyayı düzenle" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.edit-page" msgstr "Sayfayı düzenle" @@ -2967,10 +2915,6 @@ msgstr "Çoğalt" msgid "workspace.assets.edit" msgstr "Düzenle" -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.file-library" -msgstr "Dosya kütüphanesi" - #: src/app/main/ui/workspace/sidebar/assets.cljs, #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.graphics" @@ -3126,18 +3070,10 @@ msgstr "Piksele tutturmayı etkinleştir" msgid "workspace.header.menu.hide-artboard-names" msgstr "Çalışma yüzeyi adlarını gizle" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-assets" -msgstr "Varlıkları gizle" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-grid" msgstr "Izgaraları gizle" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-layers" -msgstr "Katmanları gizle" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.hide-palette" msgstr "Renk paletini gizle" @@ -3181,18 +3117,10 @@ msgstr "Tümünü seç" msgid "workspace.header.menu.show-artboard-names" msgstr "Çalışma yüzeylerinin adlarını göster" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-assets" -msgstr "Varlıkları göster" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-grid" msgstr "Izgarayı göster" -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-layers" -msgstr "Katmanları göster" - #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.show-palette" msgstr "Renk paletini göster" @@ -3904,18 +3832,10 @@ msgstr "Asgari Yükseklik" msgid "workspace.options.layout-item.layout-min-w" msgstr "Asgari Genişlik" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.max-h" -msgstr "Azami Yükseklik" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.max-w" msgstr "Azami Genişlik" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.min-h" -msgstr "Asgari Yükseklik" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.min-w" msgstr "Asgari Genişlik" @@ -3940,18 +3860,10 @@ msgstr "Asgari yükseklik" msgid "workspace.options.layout-item.title.layout-min-w" msgstr "Asgari genişlik" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.max-h" -msgstr "Azami yükseklik" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-w" msgstr "Azami genişlik" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.min-h" -msgstr "Asgari yükseklik" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.min-w" msgstr "Asgari genişlik" @@ -4072,10 +3984,6 @@ msgstr "Daha fazla renk" msgid "workspace.options.more-lib-colors" msgstr "Daha fazla kütüphane rengi" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.navigate-to" -msgstr "Git" - #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.none" msgstr "Hiçbiri" diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index c568f9450b..8dd42af0ee 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -555,10 +555,6 @@ msgstr "钉到侧边栏/取消钉住" msgid "dashboard.projects-title" msgstr "项目" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "晋级为所有者" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "希望注销您的账号?" @@ -1973,9 +1969,6 @@ msgstr "不,谢谢" msgid "onboarding.newsletter.policy" msgstr "隐私策略。" -msgid "onboarding.newsletter.privacy2" -msgstr "我们只会向您发送相关电子邮件。您可以随时在您的用户个人资料中取消订阅,也可以通过我们任何新闻通讯中的取消订阅链接取消订阅。" - msgid "onboarding.newsletter.title" msgstr "想要接收 Penpot 新闻?" @@ -2054,9 +2047,6 @@ msgstr "开始设计" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc2" -msgstr "Penpot处于第一个测试版,这要归功于核心功能,成熟度,稳定性和整个社区的惊人验证的组合,我们非常欢迎您。" - msgid "onboarding.welcome.title" msgstr "欢迎来到Penpot" @@ -2573,10 +2563,6 @@ msgstr "注释 (%s)" msgid "viewer.header.dont-show-interactions" msgstr "不显示交互" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "编辑页面" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" msgstr "全屏" diff --git a/frontend/translations/zh_Hant.po b/frontend/translations/zh_Hant.po index 9da82de42e..d62bdc3a17 100644 --- a/frontend/translations/zh_Hant.po +++ b/frontend/translations/zh_Hant.po @@ -380,10 +380,6 @@ msgstr "釘選/取消釘選" msgid "dashboard.projects-title" msgstr "專案" -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "提升為擁有者" - #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" msgstr "想要移除您的帳號嗎?" @@ -892,9 +888,6 @@ msgstr "開放原始碼" msgid "onboarding.contrib.desc2.1" msgstr "您能夠存取" -msgid "onboarding.team.create.input-placeholder" -msgstr "輸入新團隊名稱" - msgid "onboarding.welcome.alt" msgstr "Penpot" From 39c601a51ffbd077cd80a37a5f6429cc8c30b2ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 19 Oct 2022 11:14:07 +0200 Subject: [PATCH 137/682] :bug: Fix small import problem --- frontend/src/app/main/data/workspace/libraries.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index c5d01ebe0b..2572ab354e 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -19,6 +19,7 @@ [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] + [app.common.types.file.media-object :as ctfm] [app.common.types.pages-list :as ctpl] [app.common.types.shape-tree :as ctst] [app.common.types.typography :as ctt] @@ -192,7 +193,7 @@ (defn add-media [media] - (us/assert ::ctf/media-object media) + (us/assert ::ctfm/media-object media) (ptk/reify ::add-media ptk/WatchEvent (watch [it _ _] From 47be9a21f4cc9b6ce1786cfc8bd14d0146adc984 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 18 Oct 2022 22:46:41 +0200 Subject: [PATCH 138/682] :tada: New script to find unused translations --- frontend/package.json | 1 + frontend/scripts/find-unused-translations.js | 90 +++++++++++ .../src/app/main/ui/dashboard/export.cljs | 7 + .../ui/viewer/handoff/attributes/stroke.cljs | 9 ++ .../ui/viewer/handoff/attributes/text.cljs | 9 ++ .../main/ui/viewer/handoff/right_sidebar.cljs | 12 ++ .../main/ui/workspace/sidebar/history.cljs | 30 ++++ .../options/menus/layout_container.cljs | 14 +- .../sidebar/options/menus/layout_item.cljs | 9 ++ .../main/ui/workspace/sidebar/shortcuts.cljs | 140 +++++++++++++++++- 10 files changed, 316 insertions(+), 5 deletions(-) create mode 100644 frontend/scripts/find-unused-translations.js diff --git a/frontend/package.json b/frontend/package.json index 9fe5b314bf..fd2b8aa2cf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -21,6 +21,7 @@ "watch-main": "shadow-cljs watch main", "watch-test": "clojure -M:dev:shadow-cljs watch test", "validate-translations": "node ./scripts/validate-translations.js", + "find-unused-translations": "node ./scripts/find-unused-translations.js", "test-e2e": "cypress run", "test-e2e-gui": "cypress open" }, diff --git a/frontend/scripts/find-unused-translations.js b/frontend/scripts/find-unused-translations.js new file mode 100644 index 0000000000..3369e4dce9 --- /dev/null +++ b/frontend/scripts/find-unused-translations.js @@ -0,0 +1,90 @@ +const fs = require('fs').promises; +const gt = require("gettext-parser"); +const path = require('path'); +const util = require('node:util'); +const execFile = util.promisify(require('node:child_process').execFile); + + +async function processMsgId(msgId){ + return execFile('grep', ['-r', '-o', msgId, './src']) + .catch(()=> { return msgId}) +} + + +async function processFile(f) { + const content = await fs.readFile(f); + const data = gt.po.parse(content, "utf-8") + const translations = data.translations['']; + const badIds = []; + + for (const property in translations) { + const data = await processMsgId(translations[property].msgid); + if (data!=null && data.stdout === undefined){ + badIds.push(data) + } + } + + return badIds; +} + +async function cleanFile(f, badIds) { + console.log ("\n\nDoing automatic cleanup") + + const content = await fs.readFile(f); + const data = gt.po.parse(content, "utf-8"); + const translations = data.translations['']; + const keys = Object.keys(translations); + + for (const key of keys) { + property = translations[key]; + if (badIds.includes(property.msgid)){ + console.log ('----> deleting', property.msgid) + delete data.translations[''][key]; + } + } + + const buff = gt.po.compile(data, {sort: true}); + await fs.writeFile(f, buff); +} + + + +async function findExecutionTimeTranslations() { + const { stdout } = await execFile('grep', ['-r', '-h', '-F', '(tr (', './src']); + console.log(stdout); +} + +async function welcome() { + console.log ('####################################################################') + console.log ('# UNUSED TRANSLATIONS FINDER #') + console.log ('####################################################################') + console.log ('\n'); + console.log ('DISCLAIMER: Some translations are only available at execution time.') + console.log (' This finder can\'t process them, so there can be') + console.log (' false positives.\n') + console.log (' If you want to do an automatic clean anyway,') + console.log (' call the script with:') + console.log (' npm run find-unused-translations -- --clean') + console.log (' For example:'); + console.log ('--------------------------------------------------------------------'); + await findExecutionTimeTranslations(); + console.log ('--------------------------------------------------------------------'); +} + + +const doCleanup = process.argv.slice(2)[0] == "--clean"; + + +;(async () => { + await welcome(); + const target = path.normalize("./translations/en.po"); + const badIds = await processFile(target); + + if (doCleanup){ + cleanFile(target, badIds); + } else { + for (const badId of badIds){ + console.log(badId); + } + } +})() diff --git a/frontend/src/app/main/ui/dashboard/export.cljs b/frontend/src/app/main/ui/dashboard/export.cljs index 8b7a9d610c..509f6ae60e 100644 --- a/frontend/src/app/main/ui/dashboard/export.cljs +++ b/frontend/src/app/main/ui/dashboard/export.cljs @@ -122,6 +122,13 @@ (let [selected? (= @selected-option type)] [:div.export-option {:class (when selected? "selected")} [:label.option-container + ;; Execution time translation strings: + ;; dashboard.export.options.all.message + ;; dashboard.export.options.all.title + ;; dashboard.export.options.detach.message + ;; dashboard.export.options.detach.title + ;; dashboard.export.options.merge.message + ;; dashboard.export.options.merge.title [:h3 (tr (str "dashboard.export.options." (d/name type) ".title"))] [:p (tr (str "dashboard.export.options." (d/name type) ".message"))] [:input {:type "radio" diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs index 73ce49dbbe..f79e61a7f3 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/stroke.cljs @@ -67,7 +67,16 @@ [:div.attributes-stroke-row [:div.attributes-label (tr "handoff.attributes.stroke.width")] [:div.attributes-value (:stroke-width shape) "px"] + ;; Execution time translation strings: + ;; handoff.attributes.stroke.style.dotted + ;; handoff.attributes.stroke.style.mixed + ;; handoff.attributes.stroke.style.none + ;; handoff.attributes.stroke.style.solid [:div.attributes-value (->> stroke-style d/name (str "handoff.attributes.stroke.style.") (tr))] + ;; Execution time translation strings: + ;; handoff.attributes.stroke.alignment.center + ;; handoff.attributes.stroke.alignment.inner + ;; handoff.attributes.stroke.alignment.outer [:div.attributes-label (->> stroke-alignment d/name (str "handoff.attributes.stroke.alignment.") (tr))] [:& copy-button {:data (copy-stroke-data shape)}]])])) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs index d52c5b26d9..b536610deb 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs @@ -142,12 +142,21 @@ (when (:text-decoration style) [:div.attributes-unit-row [:div.attributes-label (tr "handoff.attributes.typography.text-decoration")] + ;; Execution time translation strings: + ;; handoff.attributes.typography.text-decoration.none + ;; handoff.attributes.typography.text-decoration.strikethrough + ;; handoff.attributes.typography.text-decoration.underline [:div.attributes-value (->> style :text-decoration (str "handoff.attributes.typography.text-decoration.") (tr))] [:& copy-button {:data (copy-style-data style :text-decoration)}]]) (when (:text-transform style) [:div.attributes-unit-row [:div.attributes-label (tr "handoff.attributes.typography.text-transform")] + ;; Execution time translation strings: + ;; handoff.attributes.typography.text-transform.lowercase + ;; handoff.attributes.typography.text-transform.none + ;; handoff.attributes.typography.text-transform.titlecase + ;; handoff.attributes.typography.text-transform.uppercase [:div.attributes-value (->> style :text-transform (str "handoff.attributes.typography.text-transform.") (tr))] [:& copy-button {:data (copy-style-data style :text-transform)}]])])) diff --git a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs index a6e5cb4516..d40599c1cd 100644 --- a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs @@ -45,6 +45,18 @@ [:* [:span.tool-window-bar-icon [:& si/element-icon {:shape first-shape}]] + ;; Execution time translation strings: + ;; handoff.tabs.code.selected.circle + ;; handoff.tabs.code.selected.component + ;; handoff.tabs.code.selected.curve + ;; handoff.tabs.code.selected.frame + ;; handoff.tabs.code.selected.group + ;; handoff.tabs.code.selected.image + ;; handoff.tabs.code.selected.mask + ;; handoff.tabs.code.selected.path + ;; handoff.tabs.code.selected.rect + ;; handoff.tabs.code.selected.svg-raw + ;; handoff.tabs.code.selected.text [:span.tool-window-bar-title (->> selected-type d/name (str "handoff.tabs.code.selected.") (tr))]])] [:div.tool-window-content [:& tab-container {:on-change-tab #(do diff --git a/frontend/src/app/main/ui/workspace/sidebar/history.cljs b/frontend/src/app/main/ui/workspace/sidebar/history.cljs index a22274c5d8..b90834ed34 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/history.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/history.cljs @@ -103,6 +103,36 @@ [locale type multiple?] (let [arity (if multiple? "multiple" "single") attribute (name (or type :multiple))] + ;; Execution time translation strings: + ;; workspace.undo.entry.multiple.circle + ;; workspace.undo.entry.multiple.color + ;; workspace.undo.entry.multiple.component + ;; workspace.undo.entry.multiple.curve + ;; workspace.undo.entry.multiple.frame + ;; workspace.undo.entry.multiple.group + ;; workspace.undo.entry.multiple.media + ;; workspace.undo.entry.multiple.multiple + ;; workspace.undo.entry.multiple.page + ;; workspace.undo.entry.multiple.path + ;; workspace.undo.entry.multiple.rect + ;; workspace.undo.entry.multiple.shape + ;; workspace.undo.entry.multiple.text + ;; workspace.undo.entry.multiple.typography + ;; workspace.undo.entry.single.circle + ;; workspace.undo.entry.single.color + ;; workspace.undo.entry.single.component + ;; workspace.undo.entry.single.curve + ;; workspace.undo.entry.single.frame + ;; workspace.undo.entry.single.group + ;; workspace.undo.entry.single.image + ;; workspace.undo.entry.single.media + ;; workspace.undo.entry.single.multiple + ;; workspace.undo.entry.single.page + ;; workspace.undo.entry.single.path + ;; workspace.undo.entry.single.rect + ;; workspace.undo.entry.single.shape + ;; workspace.undo.entry.single.text + ;; workspace.undo.entry.single.typography (t locale (str/format "workspace.undo.entry.%s.%s" arity attribute)))) (defn entry->message [locale entry] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index 3445970a76..2ed93ed90b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -77,6 +77,11 @@ :top (= :top dir) :bottom (= :bottom dir)) :key (dm/str "direction-" dir) + ;; Execution time translation strings: + ;; workspace.options.layout.direction.bottom + ;; workspace.options.layout.direction.left + ;; workspace.options.layout.direction.right + ;; workspace.options.layout.direction.top :alt (tr (dm/str "workspace.options.layout.direction." (d/name dir))) :on-click handle-on-click} i/auto-direction])) @@ -220,7 +225,7 @@ set-gap (fn [gap] (st/emit! (dwsl/update-layout ids {:layout-gap gap}))) - + change-padding-style (fn [type] (st/emit! (dwsl/update-layout ids {:layout-padding-type type}))) @@ -267,6 +272,13 @@ orientation (if (= type :packed) + ;; Execution time translation strings: + ;; workspace.options.layout.h.center + ;; workspace.options.layout.h.left + ;; workspace.options.layout.h.right + ;; workspace.options.layout.v.bottom + ;; workspace.options.layout.v.center + ;; workspace.options.layout.v.top (dm/str (tr (dm/str "workspace.options.layout.v." (d/name v))) ", " (tr (dm/str "workspace.options.layout.h." (d/name h))) ", ") diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index ce98a6761b..738ddb1dc1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -170,6 +170,15 @@ (for [item [:layout-max-h :layout-min-h :layout-max-w :layout-min-w]] [:div.input-element {:key (d/name item) + ;; Execution time translation strings: + ;; workspace.options.layout-item.layout-max-h + ;; workspace.options.layout-item.layout-max-w + ;; workspace.options.layout-item.layout-min-h + ;; workspace.options.layout-item.layout-min-w + ;; workspace.options.layout-item.title.layout-max-h + ;; workspace.options.layout-item.title.layout-max-w + ;; workspace.options.layout-item.title.layout-min-h + ;; workspace.options.layout-item.title.layout-min-w :alt (tr (dm/str "workspace.options.layout-item." (d/name item))) :title (tr (dm/str "workspace.options.layout-item." (d/name item))) :class (dom/classnames "maxH" (= item :layout-max-h) diff --git a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs index 5247f6696e..5492871b1f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/shortcuts.cljs @@ -49,10 +49,142 @@ (defn translation-keyname [type keyname] + ;; Execution time translation strings: + ;; shortcut-subsection.alignment + ;; shortcut-subsection.edit + ;; shortcut-subsection.general-dashboard + ;; shortcut-subsection.general-viewer + ;; shortcut-subsection.main-menu + ;; shortcut-subsection.modify-layers + ;; shortcut-subsection.navigation-dashboard + ;; shortcut-subsection.navigation-viewer + ;; shortcut-subsection.navigation-workspace + ;; shortcut-subsection.panels + ;; shortcut-subsection.path-editor + ;; shortcut-subsection.shape + ;; shortcut-subsection.tools + ;; shortcut-subsection.zoom-viewer + ;; shortcut-subsection.zoom-workspace + ;; shortcuts.add-comment + ;; shortcuts.add-node + ;; shortcuts.align-bottom + ;; shortcuts.align-hcenter + ;; shortcuts.align-left + ;; shortcuts.align-right + ;; shortcuts.align-top + ;; shortcuts.align-vcenter + ;; shortcuts.artboard-selection + ;; shortcuts.bool-difference + ;; shortcuts.bool-exclude + ;; shortcuts.bool-intersection + ;; shortcuts.bool-union + ;; shortcuts.bring-back + ;; shortcuts.bring-backward + ;; shortcuts.bring-forward + ;; shortcuts.bring-front + ;; shortcuts.clear-undo + ;; shortcuts.copy + ;; shortcuts.create-component + ;; shortcuts.create-new-project + ;; shortcuts.cut + ;; shortcuts.decrease-zoom + ;; shortcuts.delete + ;; shortcuts.delete-node + ;; shortcuts.detach-component + ;; shortcuts.draw-curve + ;; shortcuts.draw-ellipse + ;; shortcuts.draw-frame + ;; shortcuts.draw-nodes + ;; shortcuts.draw-path + ;; shortcuts.draw-rect + ;; shortcuts.draw-text + ;; shortcuts.duplicate + ;; shortcuts.escape + ;; shortcuts.export-shapes + ;; shortcuts.fit-all + ;; shortcuts.flip-horizontal + ;; shortcuts.flip-vertical + ;; shortcuts.go-to-drafts + ;; shortcuts.go-to-libs + ;; shortcuts.go-to-search + ;; shortcuts.group + ;; shortcuts.h-distribute + ;; shortcuts.hide-ui + ;; shortcuts.increase-zoom + ;; shortcuts.insert-image + ;; shortcuts.join-nodes + ;; shortcuts.make-corner + ;; shortcuts.make-curve + ;; shortcuts.mask + ;; shortcuts.merge-nodes + ;; shortcuts.move + ;; shortcuts.move-fast-down + ;; shortcuts.move-fast-left + ;; shortcuts.move-fast-right + ;; shortcuts.move-fast-up + ;; shortcuts.move-nodes + ;; shortcuts.move-unit-down + ;; shortcuts.move-unit-left + ;; shortcuts.move-unit-right + ;; shortcuts.move-unit-up + ;; shortcuts.next-frame + ;; shortcuts.opacity-0 + ;; shortcuts.opacity-1 + ;; shortcuts.opacity-2 + ;; shortcuts.opacity-3 + ;; shortcuts.opacity-4 + ;; shortcuts.opacity-5 + ;; shortcuts.opacity-6 + ;; shortcuts.opacity-7 + ;; shortcuts.opacity-8 + ;; shortcuts.opacity-9 + ;; shortcuts.open-color-picker + ;; shortcuts.open-comments + ;; shortcuts.open-dashboard + ;; shortcuts.open-handoff + ;; shortcuts.open-interactions + ;; shortcuts.open-viewer + ;; shortcuts.open-workspace + ;; shortcuts.paste + ;; shortcuts.prev-frame + ;; shortcuts.redo + ;; shortcuts.reset-zoom + ;; shortcuts.select-all + ;; shortcuts.separate-nodes + ;; shortcuts.show-pixel-grid + ;; shortcuts.show-shortcuts + ;; shortcuts.snap-nodes + ;; shortcuts.snap-pixel-grid + ;; shortcuts.start-editing + ;; shortcuts.start-measure + ;; shortcuts.stop-measure + ;; shortcuts.thumbnail-set + ;; shortcuts.toggle-alignment + ;; shortcuts.toggle-assets + ;; shortcuts.toggle-colorpalette + ;; shortcuts.toggle-focus-mode + ;; shortcuts.toggle-grid + ;; shortcuts.toggle-history + ;; shortcuts.toggle-layers + ;; shortcuts.toggle-lock + ;; shortcuts.toggle-lock-size + ;; shortcuts.toggle-rules + ;; shortcuts.toggle-scale-text + ;; shortcuts.toggle-snap-grid + ;; shortcuts.toggle-snap-guide + ;; shortcuts.toggle-textpalette + ;; shortcuts.toggle-visibility + ;; shortcuts.toggle-zoom-style + ;; shortcuts.toogle-fullscreen + ;; shortcuts.undo + ;; shortcuts.ungroup + ;; shortcuts.unmask + ;; shortcuts.v-distribute + ;; shortcuts.zoom-selected (let [translat-pre (case type - :sc "shortcuts." - :sec "shortcut-section." - :sub-sec "shortcut-subsection.")] + :sc "shortcuts." + :sec "shortcut-section." + :sub-sec "shortcut-subsection.")] (tr (str translat-pre (d/name keyname))))) (defn add-translation @@ -60,7 +192,7 @@ (map (fn [[k v]] [k (assoc v :translation (translation-keyname type k))]) item)) (defn shortcuts->subsections - "A function to obtain the list of subsections and their + "A function to obtain the list of subsections and their associated shortcus from the general map of shortcuts" [shortcuts] (let [subsections (into #{} (mapcat :subsections) (vals shortcuts)) From c24596b7f99b3a20e4c1079d05b081ed07a983ea Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Wed, 19 Oct 2022 16:05:14 +0200 Subject: [PATCH 139/682] :paperclip: Clean old translations --- frontend/translations/en.po | 292 +----------------------------------- 1 file changed, 1 insertion(+), 291 deletions(-) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 8d3b444bfd..965c62d854 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -85,10 +85,6 @@ msgstr "OpenID" msgid "auth.new-password" msgstr "Type a new password" -#: src/app/main/ui/auth/register.cljs -msgid "auth.newsletter-subscription" -msgstr "I agree to subscribe to the Penpot mailing list." - #: src/app/main/ui/auth/recovery.cljs msgid "auth.notifications.invalid-token-error" msgstr "The recovery token is invalid." @@ -192,9 +188,6 @@ msgstr "Get link" msgid "common.share-link.link-copied-success" msgstr "Link copied successfully" -msgid "common.share-link.link-deleted-success" -msgstr "Link deleted successfully" - msgid "common.share-link.manage-ops" msgstr "Manage permissions" @@ -300,9 +293,6 @@ msgstr "Download Penpot file (.penpot)" msgid "dashboard.download-standard-file" msgstr "Download standard file (.svg + .json)" -msgid "dashboard.draft-title" -msgstr "Draft" - #: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.duplicate" msgstr "Duplicate" @@ -311,10 +301,6 @@ msgstr "Duplicate" msgid "dashboard.duplicate-multi" msgstr "Duplicate %s files" -#: src/app/main/ui/dashboard/grid.cljs -msgid "dashboard.empty-files" -msgstr "You still have no files here" - #: src/app/main/ui/dashboard/grid.cljs #, markdown msgid "dashboard.empty-placeholder-drafts" @@ -361,9 +347,6 @@ msgstr "There are no elements with export settings." msgid "dashboard.export-shapes.title" msgstr "Export selection" -msgid "dashboard.export-single" -msgstr "Export Penpot file" - msgid "dashboard.export-standard-multi" msgstr "Download %s standard files (.svg + .json)" @@ -531,14 +514,6 @@ msgstr "+ New project" msgid "dashboard.new-project-prefix" msgstr "New Project" -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-msg" -msgstr "Send me news, product updates and recommendations about Penpot." - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.newsletter-title" -msgstr "Newsletter subscription" - #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" msgstr "No matches found for “%s“" @@ -714,10 +689,6 @@ msgstr "Ok" msgid "ds.confirm-title" msgstr "Are you sure?" -#: src/app/main/ui/dashboard/grid.cljs -msgid "ds.updated-at" -msgstr "Updated: %s" - #: src/app/main/ui/auth/login.cljs msgid "errors.auth-provider-not-configured" msgstr "Authentication provider not configured." @@ -765,10 +736,6 @@ msgstr "The email «%s» has been reported as spam or permanently bounce." msgid "errors.generic" msgstr "Something wrong has happened." -#: src/app/main/ui/auth/login.cljs -msgid "errors.google-auth-not-enabled" -msgstr "Authentication with google disabled on backend" - #: src/app/main/ui/components/color_input.cljs msgid "errors.invalid-color" msgstr "Invalid color" @@ -784,9 +751,6 @@ msgstr "This invite might be canceled or may be expired." msgid "errors.ldap-disabled" msgstr "LDAP authentication is disabled." -msgid "errors.media-format-unsupported" -msgstr "The image format is not supported (must be svg, jpg or png)." - #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" msgstr "The image is too large to be inserted." @@ -803,9 +767,6 @@ msgstr "Seems that this is not a valid image." msgid "errors.member-is-muted" msgstr "The profile you inviting has emails muted (spam reports or high bounces)." -msgid "errors.network" -msgstr "Unable to connect to backend server." - #: src/app/main/ui/settings/password.cljs msgid "errors.password-invalid-confirmation" msgstr "Confirmation password must match" @@ -834,9 +795,6 @@ msgstr "The member you try to assign does not exist." msgid "errors.team-leave.owner-cant-leave" msgstr "Owner can't leave team, you must reassign the owner role." -msgid "errors.terms-privacy-agreement-invalid" -msgstr "You must accept our terms of service and privacy policy." - #: src/app/main/data/media.cljs, src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs msgid "errors.unexpected-error" msgstr "An unexpected error occurred." @@ -853,14 +811,6 @@ msgstr "Username or password seems to be wrong." msgid "errors.wrong-old-password" msgstr "Old password is incorrect" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.chat-start" -msgstr "Join the chat" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.chat-subtitle" -msgstr "Feeling like talking? Chat with us at Gitter" - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.description" msgstr "Description" @@ -977,22 +927,6 @@ msgstr "Width" msgid "handoff.attributes.shadow" msgstr "Shadow" -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow.shorthand.blur" -msgstr "B" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow.shorthand.offset-x" -msgstr "X" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow.shorthand.offset-y" -msgstr "Y" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow.shorthand.spread" -msgstr "S" - #: src/app/main/ui/handoff/attributes/stroke.cljs msgid "handoff.attributes.stroke" msgstr "Stroke" @@ -1123,17 +1057,10 @@ msgstr "Text" msgid "handoff.tabs.info" msgstr "Info" -msgid "history.alert-message" -msgstr "You are seeing version %s" - #: src/app/main/ui/workspace/header.cljs msgid "label.shortcuts" msgstr "Shortcuts" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "labels.about-penpot" -msgstr "About Penpot" - msgid "labels.accept" msgstr "Accept" @@ -1168,9 +1095,6 @@ msgstr "Bad Gateway" msgid "labels.cancel" msgstr "Cancel" -msgid "labels.centered" -msgstr "Center" - msgid "labels.close" msgstr "Close" @@ -1186,9 +1110,6 @@ msgstr "Community" msgid "labels.confirm-password" msgstr "Confirm password" -msgid "labels.content" -msgstr "Content" - msgid "labels.continue" msgstr "Continue" @@ -1217,9 +1138,6 @@ msgstr "Custom fonts" msgid "labels.dashboard" msgstr "Dashboard" -msgid "labels.default" -msgstr "default" - #: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "labels.delete" msgstr "Delete" @@ -1255,10 +1173,6 @@ msgstr "Edit file" msgid "labels.editor" msgstr "Editor" -#: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs -msgid "labels.email" -msgstr "Email" - #: src/app/main/ui/dashboard/team.cljs msgid "labels.expired-invitation" msgstr "Expired" @@ -1305,12 +1219,6 @@ msgstr "Help Center" msgid "labels.hide-resolved-comments" msgstr "Hide resolved comments" -msgid "labels.icons" -msgstr "Icons" - -msgid "labels.images" -msgstr "Images" - msgid "labels.installed-fonts" msgstr "Installed fonts" @@ -1336,9 +1244,6 @@ msgstr "Language" msgid "labels.libraries-and-templates" msgstr "Libraries & Templates" -msgid "labels.link" -msgstr "Link" - msgid "labels.log-or-sign" msgstr "Log in or sign up" @@ -1346,9 +1251,6 @@ msgstr "Log in or sign up" msgid "labels.logout" msgstr "Logout" -msgid "labels.manage-fonts" -msgstr "Manage fonts" - #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "labels.member" msgstr "Member" @@ -1357,17 +1259,10 @@ msgstr "Member" msgid "labels.members" msgstr "Members" -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.name" -msgstr "Name" - #: src/app/main/ui/settings/password.cljs msgid "labels.new-password" msgstr "New password" -msgid "labels.next" -msgstr "Next" - #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/dashboard/comments.cljs msgid "labels.no-comments-available" msgstr "You have no pending comment notifications" @@ -1380,10 +1275,6 @@ msgstr "There are no invitations." msgid "labels.no-invitations-hint" msgstr "Press the button \"Invite to team\" to invite more members to this team." -#: src/app/main/ui/static.cljs -msgid "labels.not-found.auth-info" -msgstr "You’re signed in as" - #: src/app/main/ui/static.cljs msgid "labels.not-found.desc-message" msgstr "This page might not exist or you don’t have permissions to access to it." @@ -1432,10 +1323,6 @@ msgstr "Password" msgid "labels.pending-invitation" msgstr "Pending" -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.permissions" -msgstr "Permissions" - #: src/app/main/ui/settings/sidebar.cljs msgid "labels.profile" msgstr "Profile" @@ -1444,9 +1331,6 @@ msgstr "Profile" msgid "labels.projects" msgstr "Projects" -msgid "labels.recent" -msgstr "Recent" - #: src/app/main/ui/settings/sidebar.cljs msgid "labels.release-notes" msgstr "Release notes" @@ -1523,16 +1407,6 @@ msgstr "Show comments list" msgid "labels.show-your-comments" msgstr "Show only your comments" -#: src/app/main/ui/static.cljs -msgid "labels.sign-out" -msgstr "Sign out" - -msgid "labels.skip" -msgstr "Skip" - -msgid "labels.start" -msgstr "Start" - #: src/app/main/ui/dashboard/team.cljs msgid "labels.status" msgstr "Status" @@ -1562,9 +1436,6 @@ msgstr "Uploading…" msgid "labels.viewer" msgstr "Viewer" -msgid "labels.workspace" -msgstr "Workspace" - #: src/app/main/ui/comments.cljs msgid "labels.write-new-comment" msgstr "Write new comment" @@ -1619,12 +1490,6 @@ msgstr "Change email" msgid "modals.change-email.title" msgstr "Change your email" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.change-owner-and-leave-confirm.message" -msgstr "" -"You are the owner of this team. Please select another member to promote to " -"owner before you leave." - #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.cancel" msgstr "Cancel and keep my account" @@ -1745,10 +1610,6 @@ msgid_plural "modals.delete-shared-confirm.title" msgstr[0] "Deleting file" msgstr[1] "Deleting files" -#: src/app/main/ui/delete_shared.cljs -msgid "modals.delete-shared.title" -msgstr "Deleting file" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "Delete team" @@ -2040,29 +1901,6 @@ msgstr "Create team and send invites" msgid "onboarding.choice.team-up.roles" msgstr "Invite with the role:" -msgid "onboarding.choice.title" -msgstr "Welcome to Penpot" - -msgid "onboarding.contrib.alt" -msgstr "Open Source" - -msgid "onboarding.contrib.desc1" -msgstr "" -"Penpot is Open Source, made by and for the community. If you want to " -"collaborate, you are more than welcome!" - -msgid "onboarding.contrib.desc2.1" -msgstr "You can access the" - -msgid "onboarding.contrib.desc2.2" -msgstr "and follow the contribution instructions :)" - -msgid "onboarding.contrib.link" -msgstr "project on github" - -msgid "onboarding.contrib.title" -msgstr "Open Source Contributor?" - msgid "onboarding.newsletter.accept" msgstr "Yes, subscribe" @@ -2071,68 +1909,12 @@ msgstr "" "Your subscription request has been sent, we will send you an email to " "confirm it." -msgid "onboarding.newsletter.decline" -msgstr "No, thanks" - msgid "onboarding.newsletter.policy" msgstr "Privacy Policy." msgid "onboarding.newsletter.title" msgstr "Want to receive Penpot news?" -msgid "onboarding.slide.0.alt" -msgstr "Create designs" - -msgid "onboarding.slide.0.desc1" -msgstr "Create beautiful user interfaces in collaboration with all team members." - -msgid "onboarding.slide.0.desc2" -msgstr "Maintain consistency at scale with components, libraries and design systems." - -msgid "onboarding.slide.0.title" -msgstr "Design libraries, styles and components" - -msgid "onboarding.slide.1.alt" -msgstr "Interactive prototypes" - -msgid "onboarding.slide.1.desc1" -msgstr "Create rich interactions to mimic the product behaviour." - -msgid "onboarding.slide.1.desc2" -msgstr "" -"Share to stakeholders, present proposals to your team and start user " -"testing with your designs, all in one place." - -msgid "onboarding.slide.1.title" -msgstr "Bring your designs to life with interactions" - -msgid "onboarding.slide.2.alt" -msgstr "Get feedback" - -msgid "onboarding.slide.2.desc1" -msgstr "" -"All team members working simultaneously with real time design multiplayer " -"and centralised comments, ideas and feedback right over the designs." - -msgid "onboarding.slide.2.title" -msgstr "Get feedback, present and share your work" - -msgid "onboarding.slide.3.alt" -msgstr "Handoff and lowcode" - -msgid "onboarding.slide.3.desc1" -msgstr "" -"Sync the design and code of all your components and styles and get code " -"snippets." - -msgid "onboarding.slide.3.desc2" -msgstr "" -"Get and provide code specifications like markup (SVG, HTML) or styles (CSS, " -"Less, Stylus…)." - -msgid "onboarding.slide.3.title" -msgstr "One shared source of truth" - msgid "onboarding.team-modal.create-team" msgstr "Create a team" @@ -2165,9 +1947,6 @@ msgstr "Start designing" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.title" -msgstr "Welcome to Penpot" - #: src/app/main/ui/auth/recovery.cljs msgid "profile.recovery.go-to-login" msgstr "Go to login" @@ -2701,22 +2480,6 @@ msgstr "Interactions (%s)" msgid "viewer.header.share.copy-link" msgstr "Copy link" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.create-link" -msgstr "Create link" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.placeholder" -msgstr "Share link will appear here" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.remove-link" -msgstr "Remove link" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.subtitle" -msgstr "Anyone with the link will have access" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Show interactions" @@ -2769,9 +2532,6 @@ msgstr "Assets" msgid "workspace.assets.box-filter-all" msgstr "All assets" -msgid "workspace.assets.box-filter-graphics" -msgstr "Graphics" - #: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.colors" msgstr "Colors" @@ -3065,10 +2825,6 @@ msgstr "Add" msgid "workspace.libraries.colors" msgstr "%s colors" -#: src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.big-thumbnails" -msgstr "Big thumbnails" - #: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs msgid "workspace.libraries.colors.file-library" msgstr "File library" @@ -3093,10 +2849,6 @@ msgstr "RGBA" msgid "workspace.libraries.colors.save-color" msgstr "Save color style" -#: src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.small-thumbnails" -msgstr "Small thumbnails" - #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.components" msgstr "%s components" @@ -3161,28 +2913,10 @@ msgstr "Update" msgid "workspace.libraries.updates" msgstr "UPDATES" -msgid "workspace.library.all" -msgstr "All libraries" - -msgid "workspace.library.libraries" -msgstr "Libraries" - -msgid "workspace.library.own" -msgstr "My libraries" - -msgid "workspace.library.store" -msgstr "Store libraries" - #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.add-interaction" msgstr "Click the + button to add interactions." -msgid "workspace.options.blur-options.background-blur" -msgstr "Background" - -msgid "workspace.options.blur-options.layer-blur" -msgstr "Layer" - #: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs msgid "workspace.options.blur-options.title" msgstr "Blur" @@ -3878,10 +3612,6 @@ msgstr "Search font" msgid "workspace.options.select-a-shape" msgstr "Select a shape, board or group to drag a connection to other board." -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.select-artboard" -msgstr "Select board" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.selection-color" msgstr "Selected colors" @@ -4046,9 +3776,6 @@ msgstr "Align right" msgid "workspace.options.text-options.align-top" msgstr "Align top" -msgid "workspace.options.text-options.decoration" -msgstr "Decoration" - #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.direction-ltr" msgstr "LTR" @@ -4057,10 +3784,6 @@ msgstr "LTR" msgid "workspace.options.text-options.direction-rtl" msgstr "RTL" -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.google" -msgstr "Google" - #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.grow-auto-height" msgstr "Auto height" @@ -4089,17 +3812,10 @@ msgstr "Lowercase" msgid "workspace.options.text-options.none" msgstr "None" -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.preset" -msgstr "Preset" - #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.strikethrough" msgstr "Strikethrough" -msgid "workspace.options.text-options.text-case" -msgstr "Case" - #: src/app/main/ui/workspace/sidebar/options/menus/text.cljs msgid "workspace.options.text-options.title" msgstr "Text" @@ -4124,9 +3840,6 @@ msgstr "Underline" msgid "workspace.options.text-options.uppercase" msgstr "Uppercase" -msgid "workspace.options.text-options.vertical-align" -msgstr "Vertical align" - #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.use-play-button" msgstr "Use the play button at the header to run the prototype view." @@ -4562,7 +4275,4 @@ msgstr "There are updates in shared libraries" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.update" -msgstr "Update" - -msgid "workspace.viewport.click-to-close-path" -msgstr "Click to close the path" \ No newline at end of file +msgstr "Update" \ No newline at end of file From eafb7234156720286f5d43c6e8bc68bc730185a4 Mon Sep 17 00:00:00 2001 From: nautilusx Date: Tue, 18 Oct 2022 21:54:04 +0000 Subject: [PATCH 140/682] :globe_with_meridians: Add translations for: German. Currently translated at 100.0% (1215 of 1215 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 40 +++---------------------------------- 1 file changed, 3 insertions(+), 37 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 6647cbf28d..857accd50a 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-18 08:01+0000\n" -"Last-Translator: Stas Haas \n" +"PO-Revision-Date: 2022-10-19 22:02+0000\n" +"Last-Translator: nautilusx \n" "Language-Team: German \n" "Language: de\n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.15-dev\n" +"X-Generator: Weblate 4.14.2-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -2181,12 +2181,6 @@ msgstr "Nein, danke" msgid "onboarding.newsletter.policy" msgstr "Datenschutzbestimmungen." -msgid "onboarding.newsletter.privacy2" -msgstr "" -"Wir werden Ihnen nur relevante E-Mails schicken. Sie können sich jederzeit " -"in Ihrem Benutzerprofil oder über den Abmeldelink in jedem unserer " -"Newsletter abmelden." - msgid "onboarding.newsletter.title" msgstr "Möchten Sie die Penpot-Nachrichten erhalten?" @@ -2272,17 +2266,9 @@ msgstr "Unbegrenzte Anzahl von Mitgliedern" msgid "onboarding.team-modal.create-team-feature-5" msgstr "100% kostenlos!" -msgid "onboarding.team.create.desc1" -msgstr "" -"Arbeiten Sie mit jemandem zusammen? Erstellen Sie ein Team, um gemeinsam an " -"Projekten zu arbeiten und Design-Assets zu teilen." - msgid "onboarding.team.create.title" msgstr "Team erstellen" -msgid "onboarding.team.start.button" -msgstr "Gleich loslegen" - msgid "onboarding.team.start.title" msgstr "Mit der Gestaltung beginnen" @@ -2295,12 +2281,6 @@ msgstr "Beginnen Sie mit dem Entwerfen" msgid "onboarding.welcome.alt" msgstr "Penpot" -msgid "onboarding.welcome.desc2" -msgstr "" -"Penpot ist in seiner ersten Beta-Version dank der Kombination von " -"Kernfunktionen, Reife, Stabilität und der erstaunlichen Validierung durch " -"die gesamte Community, zu der Sie mehr als willkommen sind." - msgid "onboarding.welcome.title" msgstr "Willkommen bei Penpot" @@ -3420,12 +3400,6 @@ msgstr "Exportieren" msgid "workspace.options.export-multiple" msgstr "Auswahl exportieren" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, -#: src/app/main/ui/handoff/exports.cljs -#, fuzzy -msgid "workspace.options.export-object" -msgstr "1 Element exportieren" - #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs msgid "workspace.options.export.suffix" msgstr "Suffix" @@ -3868,10 +3842,6 @@ msgstr "Min.Höhe" msgid "workspace.options.layout-item.layout-min-w" msgstr "Min.Breite" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.max-w" -msgstr "Max. Breite" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.min-w" msgstr "Min. Breite" @@ -3896,10 +3866,6 @@ msgstr "Mindesthöhe" msgid "workspace.options.layout-item.title.layout-min-w" msgstr "Mindestbreite" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.max-w" -msgstr "Maximale Breite" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.min-w" msgstr "Minimale Breite" From 59e6ef560939acfcb998d44eceaa5ac8ddff6f07 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 20 Oct 2022 00:02:24 +0200 Subject: [PATCH 141/682] :tada: Add new langs (gl, ja_jp, pt_pt, hr) --- frontend/gulpfile.js | 16 ++++++++++++++-- frontend/src/app/util/i18n.cljs | 12 ++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index 86c9325deb..4dfd8bc86e 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -44,11 +44,23 @@ marked.use({renderer}); // Templates function readLocales() { - const langs = ["ar", "ca", "de", "el", "en", "eu", "it", "es", "fa", "fr", "he", "nb_NO", "pl", "pt_BR", "ro", "ru", "tr", "zh_CN", "zh_Hant"]; + const langs = ["ar", "ca", "de", "el", "en", "eu", "it", "es", + "fa", "fr", "he", "nb_NO", "pl", "pt_BR", "ro", + "ru", "tr", "zh_CN", "zh_Hant", "hr", "gl", "pt_PT", + // this happens when file does not matches correct + // iso code for the language. + ["ja_jp", "jpn_JP"] + ]; const result = {}; for (let lang of langs) { - const content = fs.readFileSync(`./translations/${lang}.po`, {encoding:"utf-8"}); + let filename = `${lang}.po`; + if (l.isArray(lang)) { + filename = `${lang[1]}.po`; + lang = lang[0] + } + + const content = fs.readFileSync(`./translations/${filename}`, {encoding:"utf-8"}); lang = lang.toLowerCase(); diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 9fab69a46c..e308d7e1f9 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -24,20 +24,24 @@ [{:label "English" :value "en"} {:label "Español" :value "es"} {:label "Català" :value "ca"} - {:label "Français (community)" :value "fr"} {:label "Deutsch (community)" :value "de"} - {:label "Italiano (community)" :value "it"} {:label "Euskera (community)" :value "eu"} + {:label "Français (community)" :value "fr"} + {:label "Gallego (Community)" :value "gl"} + {:label "Hrvatski (Community)" :value "hr"} + {:label "Italiano (community)" :value "it"} {:label "Norsk - Bokmål (community)" :value "nb_no"} - {:label "Portuguese - Brazil (community)" :value "pt_br"} {:label "Polski (community)" :value "pl"} - {:label "Русский (community)" :value "ru"} + {:label "Portuguese - Brazil (community)" :value "pt_br"} + {:label "Portuguese - Portugal (community)" :value "pt_pt"} {:label "Rumanian (community)" :value "ro"} {:label "Türkçe (community)" :value "tr"} {:label "Ελληνική γλώσσα (community)" :value "el"} + {:label "Русский (community)" :value "ru"} {:label "עִבְרִית (community)" :value "he"} {:label "عربي/عربى (community)" :value "ar"} {:label "فارسی (community)" :value "fa"} + {:label "日本語 (Community)" :value "ja_jp"} {:label "简体中文 (community)" :value "zh_cn"} {:label "繁體中文 (community)" :value "zh_hant"}]) From 6323c3ac9217f6a1cc3d0d83e739b69354c86f97 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 20 Oct 2022 00:06:50 +0200 Subject: [PATCH 142/682] :bug: Fix autodetect language issues --- CHANGES.md | 1 + frontend/src/app/util/i18n.cljs | 21 ++++++++++----------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2ac85e15f9..f8a5710ccb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ - Fix shortcut texts alignment [Taiga #4275](https://tree.taiga.io/project/penpot/issue/4275) - Fix some texts and a typo [Taiga #4215](https://tree.taiga.io/project/penpot/issue/4215) - Fix twitter support account link [Taiga #4279](https://tree.taiga.io/project/penpot/issue/4279) +- Fix lang autodetect issue [Taiga #4277](https://tree.taiga.io/project/penpot/issue/4277) ### :arrow_up: Deps updates diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index e308d7e1f9..5241b6c28b 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -72,19 +72,22 @@ (defonce locale (l/atom (or (get @storage ::locale) (autodetect)))) -;; The translations `data` is a javascript object and should be treated -;; with `goog.object` namespace functions instead of a standard -;; clojure functions. This is for performance reasons because this -;; code is executed in the critical part (application bootstrap) and -;; used in many parts of the application. - (defn init! + "Initialize the i18n module with translations. + + The `data` is a javascript object for performance reasons. This code + is executed in the critical part (application bootstrap) and used in + many parts of the application." [data] (set! translations data)) (defn set-locale! [lname] - (if lname + (if (or (nil? lname) + (str/empty? lname)) + (let [lname (autodetect)] + (swap! storage dissoc ::locale) + (reset! locale lname)) (let [supported (into #{} (map :value supported-locales)) lname (loop [locales (seq (parse-locale lname))] (if-let [locale (first locales)] @@ -92,11 +95,7 @@ locale (recur (rest locales))) cfg/default-language))] - (swap! storage assoc ::locale lname) - (reset! locale lname)) - (let [lname (autodetect)] - (swap! storage dissoc ::locale) (reset! locale lname)))) (defn reset-locale From f51e35aa9ce20d3346f8e06e13c3d42e5c2e230b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 20 Oct 2022 00:11:20 +0200 Subject: [PATCH 143/682] :bug: Prevent duplicate locale watcher on hot code reload --- frontend/src/app/util/i18n.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 5241b6c28b..97bfedd937 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -105,7 +105,7 @@ (swap! storage dissoc ::locale) (reset! locale (autodetect))) -(add-watch locale ::browser-font +(add-watch locale "browser-font" (fn [_ _ _ locale] (log/info :hint "locale changed" :locale locale) (dom/set-html-lang! locale) From e16da8bd2d1fa337eddb547d400c44787e3b2324 Mon Sep 17 00:00:00 2001 From: Eva Date: Mon, 3 Oct 2022 09:50:25 +0200 Subject: [PATCH 144/682] :tada: Add new flex layout menu --- common/src/app/common/types/shape/layout.cljc | 28 +- .../icons/align-content-column-around.svg | 3 + .../icons/align-content-column-between.svg | 3 + .../icons/align-content-column-center.svg | 3 + .../images/icons/align-content-column-end.svg | 3 + .../icons/align-content-column-start.svg | 3 + .../images/icons/align-content-row-around.svg | 3 + .../icons/align-content-row-between.svg | 3 + .../images/icons/align-content-row-center.svg | 3 + .../images/icons/align-content-row-end.svg | 3 + .../images/icons/align-content-row-start.svg | 3 + .../icons/align-items-column-baseline.svg | 3 + .../icons/align-items-column-center.svg | 3 + .../images/icons/align-items-column-end.svg | 3 + .../images/icons/align-items-column-start.svg | 3 + .../icons/align-items-column-strech.svg | 3 + .../images/icons/align-items-row-baseline.svg | 3 + .../images/icons/align-items-row-center.svg | 3 + .../images/icons/align-items-row-end.svg | 3 + .../images/icons/align-items-row-start.svg | 3 + .../images/icons/align-items-row-strech.svg | 3 + .../icons/align-self-column-baseline.svg | 3 + .../images/icons/align-self-column-bottom.svg | 3 + .../images/icons/align-self-column-center.svg | 3 + .../images/icons/align-self-column-strech.svg | 3 + .../images/icons/align-self-column-top.svg | 3 + .../images/icons/align-self-row-baseline.svg | 3 + .../images/icons/align-self-row-center.svg | 3 + .../images/icons/align-self-row-left.svg | 3 + .../images/icons/align-self-row-right.svg | 3 + .../images/icons/align-self-row-strech.svg | 3 + .../resources/images/icons/auto-direction.svg | 2 +- frontend/resources/images/icons/auto-fix.svg | 4 +- frontend/resources/images/icons/auto-flex.svg | 3 + frontend/resources/images/icons/auto-gap.svg | 2 +- frontend/resources/images/icons/auto-grid.svg | 3 + frontend/resources/images/icons/auto-wrap.svg | 3 + .../icons/justify-content-column-around.svg | 3 + .../icons/justify-content-column-between.svg | 3 + .../icons/justify-content-column-center.svg | 3 + .../icons/justify-content-column-end.svg | 3 + .../icons/justify-content-column-start.svg | 3 + .../icons/justify-content-row-around.svg | 3 + .../icons/justify-content-row-between.svg | 3 + .../icons/justify-content-row-center.svg | 3 + .../images/icons/justify-content-row-end.svg | 3 + .../icons/justify-content-row-start.svg | 3 + .../partials/sidebar-element-options.scss | 287 +++++--- .../styles/main/partials/sidebar-sitemap.scss | 47 ++ .../app/main/data/workspace/shape_layout.cljs | 42 +- frontend/src/app/main/ui/icons.cljs | 41 ++ .../ui/viewer/handoff/attributes/layout.cljs | 2 +- .../options/menus/layout_container.cljs | 617 ++++++++++-------- .../sidebar/options/menus/layout_item.cljs | 176 ++--- .../sidebar/options/shapes/frame.cljs | 4 +- .../sidebar/options/shapes/multiple.cljs | 4 +- frontend/translations/ca.po | 20 +- frontend/translations/de.po | 12 +- frontend/translations/en.po | 28 +- frontend/translations/es.po | 28 +- frontend/translations/eu.po | 20 +- frontend/translations/he.po | 24 +- frontend/translations/tr.po | 24 +- 63 files changed, 955 insertions(+), 586 deletions(-) create mode 100644 frontend/resources/images/icons/align-content-column-around.svg create mode 100644 frontend/resources/images/icons/align-content-column-between.svg create mode 100644 frontend/resources/images/icons/align-content-column-center.svg create mode 100644 frontend/resources/images/icons/align-content-column-end.svg create mode 100644 frontend/resources/images/icons/align-content-column-start.svg create mode 100644 frontend/resources/images/icons/align-content-row-around.svg create mode 100644 frontend/resources/images/icons/align-content-row-between.svg create mode 100644 frontend/resources/images/icons/align-content-row-center.svg create mode 100644 frontend/resources/images/icons/align-content-row-end.svg create mode 100644 frontend/resources/images/icons/align-content-row-start.svg create mode 100644 frontend/resources/images/icons/align-items-column-baseline.svg create mode 100644 frontend/resources/images/icons/align-items-column-center.svg create mode 100644 frontend/resources/images/icons/align-items-column-end.svg create mode 100644 frontend/resources/images/icons/align-items-column-start.svg create mode 100644 frontend/resources/images/icons/align-items-column-strech.svg create mode 100644 frontend/resources/images/icons/align-items-row-baseline.svg create mode 100644 frontend/resources/images/icons/align-items-row-center.svg create mode 100644 frontend/resources/images/icons/align-items-row-end.svg create mode 100644 frontend/resources/images/icons/align-items-row-start.svg create mode 100644 frontend/resources/images/icons/align-items-row-strech.svg create mode 100644 frontend/resources/images/icons/align-self-column-baseline.svg create mode 100644 frontend/resources/images/icons/align-self-column-bottom.svg create mode 100644 frontend/resources/images/icons/align-self-column-center.svg create mode 100644 frontend/resources/images/icons/align-self-column-strech.svg create mode 100644 frontend/resources/images/icons/align-self-column-top.svg create mode 100644 frontend/resources/images/icons/align-self-row-baseline.svg create mode 100644 frontend/resources/images/icons/align-self-row-center.svg create mode 100644 frontend/resources/images/icons/align-self-row-left.svg create mode 100644 frontend/resources/images/icons/align-self-row-right.svg create mode 100644 frontend/resources/images/icons/align-self-row-strech.svg create mode 100644 frontend/resources/images/icons/auto-flex.svg create mode 100644 frontend/resources/images/icons/auto-grid.svg create mode 100644 frontend/resources/images/icons/auto-wrap.svg create mode 100644 frontend/resources/images/icons/justify-content-column-around.svg create mode 100644 frontend/resources/images/icons/justify-content-column-between.svg create mode 100644 frontend/resources/images/icons/justify-content-column-center.svg create mode 100644 frontend/resources/images/icons/justify-content-column-end.svg create mode 100644 frontend/resources/images/icons/justify-content-column-start.svg create mode 100644 frontend/resources/images/icons/justify-content-row-around.svg create mode 100644 frontend/resources/images/icons/justify-content-row-between.svg create mode 100644 frontend/resources/images/icons/justify-content-row-center.svg create mode 100644 frontend/resources/images/icons/justify-content-row-end.svg create mode 100644 frontend/resources/images/icons/justify-content-row-start.svg diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 8309afa9d2..8c983341de 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -9,10 +9,13 @@ [app.common.spec :as us] [clojure.spec.alpha :as s])) -(s/def ::layout boolean?) -(s/def ::layout-dir #{:right :left :top :bottom}) +(s/def ::layout #{:flex :grid}) +(s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column}) +(s/def ::layout-gap-type #{:simple :multiple}) (s/def ::layout-gap ::us/safe-number) -(s/def ::layout-type #{:packed :space-between :space-around}) +(s/def ::layout-align-items #{:start :end :center :strech}) +(s/def ::layout-align-content #{:start :end :center :space-between :space-around :strech}) +(s/def ::layout-justify-content #{:start :center :end :space-between :space-around}) (s/def ::layout-wrap-type #{:wrap :no-wrap}) (s/def ::layout-padding-type #{:simple :multiple}) @@ -25,19 +28,24 @@ (s/keys :req-un [::p1] :opt-un [::p2 ::p3 ::p4])) -(s/def ::layout-h-orientation #{:left :center :right}) -(s/def ::layout-v-orientation #{:top :center :bottom}) +(s/def ::row-gap ::us/safe-number) +(s/def ::column-gap ::us/safe-number) + +(s/def ::layout-gap + (s/keys :req-un [::row-gap ::column-gap])) (s/def ::layout-container-props (s/keys :opt-un [::layout - ::layout-dir + ::layout-flex-dir ::layout-gap + ::layout-gap-type ::layout-type ::layout-wrap-type ::layout-padding-type ::layout-padding - ::layout-h-orientation - ::layout-v-orientation])) + ::layout-justify-content + ::layout-align-items + ::layout-align-content])) (s/def ::m1 ::us/safe-number) (s/def ::m2 ::us/safe-number) @@ -50,6 +58,7 @@ (s/def ::layout-margin-type #{:simple :multiple}) (s/def ::layout-h-behavior #{:fill :fix :auto}) (s/def ::layout-v-behavior #{:fill :fix :auto}) +(s/def ::layout-align-self #{:start :end :center :strech :baseline}) (s/def ::layout-max-h ::us/safe-number) (s/def ::layout-min-h ::us/safe-number) (s/def ::layout-max-w ::us/safe-number) @@ -63,4 +72,5 @@ ::layout-max-h ::layout-min-h ::layout-max-w - ::layout-min-w])) + ::layout-min-w + ::layout-align-self])) diff --git a/frontend/resources/images/icons/align-content-column-around.svg b/frontend/resources/images/icons/align-content-column-around.svg new file mode 100644 index 0000000000..d7f54514ab --- /dev/null +++ b/frontend/resources/images/icons/align-content-column-around.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-content-column-between.svg b/frontend/resources/images/icons/align-content-column-between.svg new file mode 100644 index 0000000000..f07dbe1dba --- /dev/null +++ b/frontend/resources/images/icons/align-content-column-between.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-content-column-center.svg b/frontend/resources/images/icons/align-content-column-center.svg new file mode 100644 index 0000000000..56317ce068 --- /dev/null +++ b/frontend/resources/images/icons/align-content-column-center.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-content-column-end.svg b/frontend/resources/images/icons/align-content-column-end.svg new file mode 100644 index 0000000000..eab99cd626 --- /dev/null +++ b/frontend/resources/images/icons/align-content-column-end.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-content-column-start.svg b/frontend/resources/images/icons/align-content-column-start.svg new file mode 100644 index 0000000000..d3490501fa --- /dev/null +++ b/frontend/resources/images/icons/align-content-column-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-content-row-around.svg b/frontend/resources/images/icons/align-content-row-around.svg new file mode 100644 index 0000000000..f49bb584f3 --- /dev/null +++ b/frontend/resources/images/icons/align-content-row-around.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-content-row-between.svg b/frontend/resources/images/icons/align-content-row-between.svg new file mode 100644 index 0000000000..6723db3d9b --- /dev/null +++ b/frontend/resources/images/icons/align-content-row-between.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-content-row-center.svg b/frontend/resources/images/icons/align-content-row-center.svg new file mode 100644 index 0000000000..b318cd9d2a --- /dev/null +++ b/frontend/resources/images/icons/align-content-row-center.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-content-row-end.svg b/frontend/resources/images/icons/align-content-row-end.svg new file mode 100644 index 0000000000..56aecaae97 --- /dev/null +++ b/frontend/resources/images/icons/align-content-row-end.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-content-row-start.svg b/frontend/resources/images/icons/align-content-row-start.svg new file mode 100644 index 0000000000..d666646cfa --- /dev/null +++ b/frontend/resources/images/icons/align-content-row-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-column-baseline.svg b/frontend/resources/images/icons/align-items-column-baseline.svg new file mode 100644 index 0000000000..862f7afd1c --- /dev/null +++ b/frontend/resources/images/icons/align-items-column-baseline.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-column-center.svg b/frontend/resources/images/icons/align-items-column-center.svg new file mode 100644 index 0000000000..e359734362 --- /dev/null +++ b/frontend/resources/images/icons/align-items-column-center.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-column-end.svg b/frontend/resources/images/icons/align-items-column-end.svg new file mode 100644 index 0000000000..7bdb1d6326 --- /dev/null +++ b/frontend/resources/images/icons/align-items-column-end.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-column-start.svg b/frontend/resources/images/icons/align-items-column-start.svg new file mode 100644 index 0000000000..5c231d4863 --- /dev/null +++ b/frontend/resources/images/icons/align-items-column-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-column-strech.svg b/frontend/resources/images/icons/align-items-column-strech.svg new file mode 100644 index 0000000000..dd441d93fc --- /dev/null +++ b/frontend/resources/images/icons/align-items-column-strech.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-row-baseline.svg b/frontend/resources/images/icons/align-items-row-baseline.svg new file mode 100644 index 0000000000..b8b397e0a8 --- /dev/null +++ b/frontend/resources/images/icons/align-items-row-baseline.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-row-center.svg b/frontend/resources/images/icons/align-items-row-center.svg new file mode 100644 index 0000000000..a07e43a476 --- /dev/null +++ b/frontend/resources/images/icons/align-items-row-center.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-row-end.svg b/frontend/resources/images/icons/align-items-row-end.svg new file mode 100644 index 0000000000..ddeb660a13 --- /dev/null +++ b/frontend/resources/images/icons/align-items-row-end.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-row-start.svg b/frontend/resources/images/icons/align-items-row-start.svg new file mode 100644 index 0000000000..69516fc68b --- /dev/null +++ b/frontend/resources/images/icons/align-items-row-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-items-row-strech.svg b/frontend/resources/images/icons/align-items-row-strech.svg new file mode 100644 index 0000000000..22b308e143 --- /dev/null +++ b/frontend/resources/images/icons/align-items-row-strech.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-column-baseline.svg b/frontend/resources/images/icons/align-self-column-baseline.svg new file mode 100644 index 0000000000..4ed4d63570 --- /dev/null +++ b/frontend/resources/images/icons/align-self-column-baseline.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-column-bottom.svg b/frontend/resources/images/icons/align-self-column-bottom.svg new file mode 100644 index 0000000000..6ef7f1e75e --- /dev/null +++ b/frontend/resources/images/icons/align-self-column-bottom.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-column-center.svg b/frontend/resources/images/icons/align-self-column-center.svg new file mode 100644 index 0000000000..c45bfb53f6 --- /dev/null +++ b/frontend/resources/images/icons/align-self-column-center.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-column-strech.svg b/frontend/resources/images/icons/align-self-column-strech.svg new file mode 100644 index 0000000000..5803cb4470 --- /dev/null +++ b/frontend/resources/images/icons/align-self-column-strech.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-column-top.svg b/frontend/resources/images/icons/align-self-column-top.svg new file mode 100644 index 0000000000..67adbc722e --- /dev/null +++ b/frontend/resources/images/icons/align-self-column-top.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-row-baseline.svg b/frontend/resources/images/icons/align-self-row-baseline.svg new file mode 100644 index 0000000000..db26606849 --- /dev/null +++ b/frontend/resources/images/icons/align-self-row-baseline.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-row-center.svg b/frontend/resources/images/icons/align-self-row-center.svg new file mode 100644 index 0000000000..df911a89c6 --- /dev/null +++ b/frontend/resources/images/icons/align-self-row-center.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-row-left.svg b/frontend/resources/images/icons/align-self-row-left.svg new file mode 100644 index 0000000000..7ff6caee15 --- /dev/null +++ b/frontend/resources/images/icons/align-self-row-left.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-row-right.svg b/frontend/resources/images/icons/align-self-row-right.svg new file mode 100644 index 0000000000..665e7b5624 --- /dev/null +++ b/frontend/resources/images/icons/align-self-row-right.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/align-self-row-strech.svg b/frontend/resources/images/icons/align-self-row-strech.svg new file mode 100644 index 0000000000..426db52b5c --- /dev/null +++ b/frontend/resources/images/icons/align-self-row-strech.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-direction.svg b/frontend/resources/images/icons/auto-direction.svg index d002174b69..3429588feb 100644 --- a/frontend/resources/images/icons/auto-direction.svg +++ b/frontend/resources/images/icons/auto-direction.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/resources/images/icons/auto-fix.svg b/frontend/resources/images/icons/auto-fix.svg index 38c4d54e88..f2be41986b 100644 --- a/frontend/resources/images/icons/auto-fix.svg +++ b/frontend/resources/images/icons/auto-fix.svg @@ -1,3 +1,3 @@ - - + + diff --git a/frontend/resources/images/icons/auto-flex.svg b/frontend/resources/images/icons/auto-flex.svg new file mode 100644 index 0000000000..9023c8fcf0 --- /dev/null +++ b/frontend/resources/images/icons/auto-flex.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-gap.svg b/frontend/resources/images/icons/auto-gap.svg index 307740492a..25593da80c 100644 --- a/frontend/resources/images/icons/auto-gap.svg +++ b/frontend/resources/images/icons/auto-gap.svg @@ -1,3 +1,3 @@ - + diff --git a/frontend/resources/images/icons/auto-grid.svg b/frontend/resources/images/icons/auto-grid.svg new file mode 100644 index 0000000000..c2acf2476e --- /dev/null +++ b/frontend/resources/images/icons/auto-grid.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/auto-wrap.svg b/frontend/resources/images/icons/auto-wrap.svg new file mode 100644 index 0000000000..08a7a19a0a --- /dev/null +++ b/frontend/resources/images/icons/auto-wrap.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-column-around.svg b/frontend/resources/images/icons/justify-content-column-around.svg new file mode 100644 index 0000000000..19fbfcc574 --- /dev/null +++ b/frontend/resources/images/icons/justify-content-column-around.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-column-between.svg b/frontend/resources/images/icons/justify-content-column-between.svg new file mode 100644 index 0000000000..6e1bb12974 --- /dev/null +++ b/frontend/resources/images/icons/justify-content-column-between.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-column-center.svg b/frontend/resources/images/icons/justify-content-column-center.svg new file mode 100644 index 0000000000..f4c7746535 --- /dev/null +++ b/frontend/resources/images/icons/justify-content-column-center.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-column-end.svg b/frontend/resources/images/icons/justify-content-column-end.svg new file mode 100644 index 0000000000..9ac3177270 --- /dev/null +++ b/frontend/resources/images/icons/justify-content-column-end.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-column-start.svg b/frontend/resources/images/icons/justify-content-column-start.svg new file mode 100644 index 0000000000..f73b2c7543 --- /dev/null +++ b/frontend/resources/images/icons/justify-content-column-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-row-around.svg b/frontend/resources/images/icons/justify-content-row-around.svg new file mode 100644 index 0000000000..ff52e78571 --- /dev/null +++ b/frontend/resources/images/icons/justify-content-row-around.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-row-between.svg b/frontend/resources/images/icons/justify-content-row-between.svg new file mode 100644 index 0000000000..57cba5490b --- /dev/null +++ b/frontend/resources/images/icons/justify-content-row-between.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-row-center.svg b/frontend/resources/images/icons/justify-content-row-center.svg new file mode 100644 index 0000000000..2a7e32c756 --- /dev/null +++ b/frontend/resources/images/icons/justify-content-row-center.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-row-end.svg b/frontend/resources/images/icons/justify-content-row-end.svg new file mode 100644 index 0000000000..1b3a33d482 --- /dev/null +++ b/frontend/resources/images/icons/justify-content-row-end.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/justify-content-row-start.svg b/frontend/resources/images/icons/justify-content-row-start.svg new file mode 100644 index 0000000000..7ba66c7562 --- /dev/null +++ b/frontend/resources/images/icons/justify-content-row-start.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 6d14fe79be..3c785391e1 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -59,6 +59,7 @@ padding: $size-2 $size-1; .element-set-title { + height: 35px; color: $color-gray-20; display: flex; font-size: $fs14; @@ -1165,12 +1166,6 @@ fill: $color-primary; } } - - svg { - } - - .multiple-typography-button:hover svg { - } } .font-selector { @@ -1588,58 +1583,133 @@ } } -.layout-menu { +.layout-menu, +.layout-item-menu { + font-family: "worksans", sans-serif; svg { height: 16px; width: 16px; fill: $color-gray-30; } - .direction-gap { - display: flex; - justify-content: space-between; - .direction { + .layout-row { + display: grid; + grid-template-columns: 60px 1fr; + margin-bottom: 5px; + .row-title { + font-size: $fs12; display: flex; - .dir { - margin-right: 4px; + justify-content: start; + align-items: center; + margin-right: 5px; + font-family: "worksans", sans-serif; + } + .btn-wrapper { + display: flex; + width: 100%; + .direction, + .wrap-type, + .align-items-style, + .align-self-style, + .justify-content-style, + .align-content-style, + .layout-behavior { display: flex; - justify-content: center; - align-items: center; - background: none; - border: none; - cursor: pointer; - - &.right { - svg { - transform: rotate(180deg); - } - } - &.top { - svg { - transform: rotate(-90deg); - } - } - &.bottom { - svg { + border-radius: 4px; + border: 1px solid $color-gray-60; + height: 26px; + margin-right: 5px; + flex-grow: 1; + &.horizontal { + button svg { transform: rotate(90deg); } } - &.active, - &:hover { - svg { - fill: $color-primary; + button { + display: flex; + flex-grow: 1; + justify-content: center; + align-items: center; + background: none; + border: none; + cursor: pointer; + border-right: 1px solid $color-gray-60; + &.active, + &:hover { + svg { + fill: $color-primary; + } } } + .dir { + display: flex; + justify-content: center; + align-items: center; + background: none; + border: none; + cursor: pointer; + border-right: 1px solid $color-gray-60; + padding: 4px; + &.reverse-row { + svg { + transform: rotate(180deg); + } + } + + &.reverse-column { + svg { + transform: rotate(-90deg); + } + } + &.column { + svg { + transform: rotate(90deg); + } + } + &.active, + &:hover { + svg { + fill: $color-primary; + } + } + } + :last-child { + border-right: none; + } } } - .gap { + } + .no-wrap { + display: flex; + align-items: center; + justify-content: center; + height: 21px; + width: 21px; + svg { + height: 0.7rem; + width: 0.7rem; + } + } + + .gap-group { + display: flex; + margin-top: 3px; + margin-bottom: 8px; + height: 26px; + .gap-row, + .gap-column { display: flex; justify-content: center; align-items: center; + flex-grow: 1; .icon { display: flex; justify-content: center; align-items: center; margin-right: 7px; + svg { + height: 14px; + width: 14px; + } &.rotated { transform: rotate(90deg); } @@ -1653,11 +1723,79 @@ font-size: 0.75rem; min-width: 0; padding: 0.25rem; - width: 50px; + width: 70px; height: 20px; margin: 0; } } + button { + display: flex; + justify-content: center; + align-items: center; + background: none; + border: none; + cursor: pointer; + border-radius: $br-small; + &.active { + svg { + fill: $color-primary; + } + } + &:hover { + background-color: $color-primary; + svg { + fill: $color-gray-60; + } + } + } + } + + .padding-row, + .margin-row { + display: grid; + grid-template-columns: 65px auto; + height: 26px; + .padding-icons, + .margin-icons { + display: flex; + padding: 0; + border: 1px solid $color-gray-60; + border-radius: 3px; + + .padding-icon, + .margin-icon { + display: flex; + justify-content: center; + align-items: center; + padding: 6px; + flex-grow: 1; + border-right: 1px solid $color-gray-60; + cursor: pointer; + + &:hover, + &.selected { + svg { + fill: $color-primary; + } + } + svg { + height: 14px; + width: 14px; + fill: $color-gray-30; + } + } + :last-child { + border: none; + } + } + .wrapper { + display: flex; + height: 26px; + .input-element { + margin: 0; + height: 26px; + } + } } .layout-container { @@ -1783,69 +1921,40 @@ } } -.layout-item-menu { - .layout-behavior { +.advanced-ops { + margin: 10px 0; + display: flex; + align-items: center; + cursor: pointer; + font-size: $fs12; + color: $color-gray-30; + border: none; + background-color: transparent; + padding: 0; + .icon { display: flex; + justify-content: start; align-items: center; - margin: 9px 0; - .button-wrapper { - border: 1px solid $color-gray-60; - border-radius: 4px; - display: flex; - align-items: center; - .behavior-btn { - background: none; - border: none; - cursor: pointer; - display: flex; - justify-content: center; - align-items: center; - padding: 0; - .icon { - display: flex; - justify-content: center; - align-items: center; - height: 26px; - width: 26px; - svg { - height: 16px; - width: 16px; - fill: $color-gray-30; - } - } - &:hover, - &.activated { - svg { - fill: $color-primary; - } - } - } - &.horizontal { - margin-right: 8px; - svg { - transform: rotate(90deg); - } - } + margin-right: 10px; + svg { + fill: $color-gray-20; } } - .advanced-ops { - display: flex; - align-items: center; - cursor: pointer; - font-size: $fs12; - color: $color-gray-30; - &:hover { - svg { - fill: $color-primary; - } + + &:hover { + svg { + fill: $color-primary; } } - .advanced-ops-body { +} +.advanced-ops-body { + .input-wrapper { display: grid; grid-template-columns: 1fr 1fr; .input-element { width: 100%; &::after { + content: attr(alt); width: 100px; } } diff --git a/frontend/resources/styles/main/partials/sidebar-sitemap.scss b/frontend/resources/styles/main/partials/sidebar-sitemap.scss index 3568ce3605..44706d5009 100644 --- a/frontend/resources/styles/main/partials/sidebar-sitemap.scss +++ b/frontend/resources/styles/main/partials/sidebar-sitemap.scss @@ -156,6 +156,53 @@ } } +.title-actions { + align-items: center; + display: flex; + justify-content: center; + margin-left: auto; + .layout-btns { + display: flex; + align-items: center; + justify-content: end; + } + .remove-layout { + display: flex; + align-items: center; + justify-content: center; + height: 21px; + width: 21px; + padding: 4px; + svg { + height: 0.7rem; + width: 0.7rem; + } + } + button { + background-color: transparent; + border-radius: $br-small; + border: 1px solid transparent; + cursor: pointer; + color: $color-gray-20; + svg { + fill: $color-gray-20; + height: 0.7rem; + width: 0.7rem; + } + &.active { + color: $color-primary; + } + + &:hover { + background-color: $color-primary; + color: $color-gray-60; + svg { + fill: $color-gray-60 !important; + } + } + } +} + .element-set-title-actions { display: flex; flex-direction: row; diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 98003f819c..0b96b54b57 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -23,18 +23,30 @@ :layout-padding-type :layout-padding :layout-h-orientation - :layout-v-orientation]) + :layout-v-orientation -(def initial-layout - {:layout true - :layout-dir :left - :layout-gap 0 - :layout-type :packed - :layout-wrap-type :wrap + :layout-align-content + :layout-flex-dir + :layout-align-items + :layout-justify-content + :layout-gap-type + ]) + + +(def initial-flex-layout + {:layout :flex + :layout-flex-dir :row + :layout-gap-type :simple + :layout-gap {:row-gap 0 :column-gap 0} + :layout-align-items :start + :layout-justify-content :start + :layout-align-content :strech + :layout-wrap-type :no-wrap :layout-padding-type :simple - :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0} - :layout-h-orientation :left - :layout-v-orientation :top}) + :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}}) + +(def initial-grid-layout ;; TODO + {:layout :grid}) (defn update-layout-positions [ids] @@ -50,12 +62,16 @@ ;; TODO: Remove constraints from children (defn create-layout - [ids] + [ids type] (ptk/reify ::create-layout ptk/WatchEvent (watch [_ _ _] - (rx/of (dwc/update-shapes ids #(merge % initial-layout)) - (update-layout-positions ids))))) + (if (= type :flex) + (rx/of (dwc/update-shapes ids #(merge % initial-flex-layout)) + (update-layout-positions ids)) + (rx/of (dwc/update-shapes ids #(merge % initial-grid-layout)) + (update-layout-positions ids)))))) + (defn remove-layout [ids] diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 2500a60d3a..69dd22c147 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -14,6 +14,36 @@ (def action (icon-xref :action)) (def actions (icon-xref :actions)) (def align-bottom (icon-xref :align-bottom)) +(def align-content-column-around (icon-xref :align-content-column-around)) +(def align-content-column-between (icon-xref :align-content-column-between)) +(def align-content-column-center (icon-xref :align-content-column-center)) +(def align-content-column-end (icon-xref :align-content-column-end)) +(def align-content-column-start (icon-xref :align-content-column-start)) +(def align-content-row-around (icon-xref :align-content-row-around)) +(def align-content-row-between (icon-xref :align-content-row-between)) +(def align-content-row-center (icon-xref :align-content-row-center)) +(def align-content-row-end (icon-xref :align-content-row-end)) +(def align-content-row-start (icon-xref :align-content-row-start)) +(def align-items-column-baseline (icon-xref :align-items-column-baseline)) +(def align-items-column-center (icon-xref :align-items-column-center)) +(def align-items-column-end (icon-xref :align-items-column-end)) +(def align-items-column-start (icon-xref :align-items-column-start)) +(def align-items-column-strech (icon-xref :align-items-column-strech)) +(def align-items-row-baseline (icon-xref :align-items-row-baseline)) +(def align-items-row-center (icon-xref :align-items-row-center)) +(def align-items-row-end (icon-xref :align-items-row-end)) +(def align-items-row-start (icon-xref :align-items-row-start)) +(def align-items-row-strech (icon-xref :align-items-row-strech)) +(def align-self-column-baseline (icon-xref :align-self-column-baseline)) +(def align-self-column-center (icon-xref :align-self-column-center)) +(def align-self-column-top (icon-xref :align-self-column-top)) +(def align-self-column-bottom (icon-xref :align-self-column-bottom)) +(def align-self-column-strech (icon-xref :align-self-column-strech)) +(def align-self-row-baseline (icon-xref :align-self-row-baseline)) +(def align-self-row-center (icon-xref :align-self-row-center)) +(def align-self-row-left (icon-xref :align-self-row-left)) +(def align-self-row-right (icon-xref :align-self-row-right)) +(def align-self-row-strech (icon-xref :align-self-row-strech)) (def align-middle (icon-xref :align-middle)) (def align-top (icon-xref :align-top)) (def alignment (icon-xref :alignment)) @@ -39,6 +69,7 @@ (def auto-padding (icon-xref :auto-padding)) (def auto-padding-side (icon-xref :auto-padding-side)) (def auto-width (icon-xref :auto-width)) +(def auto-wrap (icon-xref :auto-wrap)) (def bool-difference (icon-xref :boolean-difference)) (def bool-exclude (icon-xref :boolean-exclude)) (def bool-flatten (icon-xref :boolean-flatten)) @@ -91,6 +122,16 @@ (def import (icon-xref :import)) (def infocard (icon-xref :infocard)) (def interaction (icon-xref :interaction)) +(def justify-content-column-around (icon-xref :justify-content-column-around)) +(def justify-content-column-between (icon-xref :justify-content-column-between)) +(def justify-content-column-center (icon-xref :justify-content-column-center)) +(def justify-content-column-end (icon-xref :justify-content-column-end)) +(def justify-content-column-start (icon-xref :justify-content-column-start)) +(def justify-content-row-around (icon-xref :justify-content-row-around)) +(def justify-content-row-between (icon-xref :justify-content-row-between)) +(def justify-content-row-center (icon-xref :justify-content-row-center)) +(def justify-content-row-end (icon-xref :justify-content-row-end)) +(def justify-content-row-start (icon-xref :justify-content-row-start)) (def layers (icon-xref :layers)) (def letter-spacing (icon-xref :letter-spacing)) (def libraries (icon-xref :libraries)) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs index 5f178da039..7f9e70aad2 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs @@ -86,7 +86,7 @@ [{:keys [shapes]}] [:div.attributes-block [:div.attributes-block-title - [:div.attributes-block-title-text (tr "handoff.attributes.layout")] + [:div.attributes-block-title-text (tr "handoff.attributes.size")] (when (= (count shapes) 1) [:& copy-button {:data (copy-data (first shapes))}])] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index 2ed93ed90b..1d1191a747 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -5,63 +5,92 @@ ;; Copyright (c) KALEIDOS INC (ns app.main.ui.workspace.sidebar.options.menus.layout-container - (:require - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.main.data.workspace.shape-layout :as dwsl] - [app.main.store :as st] - [app.main.ui.components.numeric-input :refer [numeric-input]] - [app.main.ui.icons :as i] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [tr]] - [cuerdas.core :as str] - [rumext.v2 :as mf])) + (:require [app.common.data :as d] + [app.common.data.macros :as dm] + [app.main.data.workspace.shape-layout :as dwsl] + [app.main.store :as st] + [app.main.ui.components.numeric-input :refer [numeric-input]] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [cuerdas.core :as str] + [rumext.v2 :as mf])) -(def layout-container-attrs - [:layout ;; true if active, false if not - :layout-dir ;; :right, :left, :top, :bottom - :layout-gap ;; number could be negative - :layout-type ;; :packed, :space-between, :space-around +(def layout-container-flex-attrs + [:layout ;; :flex, :grid in the future + :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column + :layout-gap-type ;; :simple, :multiple + :layout-gap ;; {:row-gap number , :column-gap number} + :layout-align-items ;; :start :end :center :strech + :layout-justify-content ;; :start :center :end :space-between :space-around + :layout-align-content ;; :start :center :end :space-between :space-around :strech (by default) :layout-wrap-type ;; :wrap, :no-wrap :layout-padding-type ;; :simple, :multiple :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative - :layout-h-orientation ;; :left, :center, :right - :layout-v-orientation]) ;; :top, :center, :bottom + ]) -(def grid-pos [[:top :left] - [:top :center] - [:top :right] - [:center :left] - [:center :center] - [:center :right] - [:bottom :left] - [:bottom :center] - [:bottom :right]]) +(defn get-layout-flex-icon + [type val is-col?] + (case type + :align-items (if is-col? + (case val + :start i/align-items-column-start + :end i/align-items-column-end + :center i/align-items-column-center + :strech i/align-items-column-strech + :baseline i/align-items-column-baseline) + (case val + :start i/align-items-row-start + :end i/align-items-row-end + :center i/align-items-row-center + :strech i/align-items-row-strech + :baseline i/align-items-row-baseline)) + :justify-content (if is-col? + (case val + :start i/justify-content-column-start + :end i/justify-content-column-end + :center i/justify-content-column-center + :space-around i/justify-content-column-around + :space-between i/justify-content-column-between) + (case val + :start i/justify-content-row-start + :end i/justify-content-row-end + :center i/justify-content-row-center + :space-around i/justify-content-row-around + :space-between i/justify-content-row-between)) -(def grid-rows [:top :center :bottom]) -(def grid-cols [:left :center :right]) + :align-content (if is-col? + (case val + :start i/align-content-column-start + :end i/align-content-column-end + :center i/align-content-column-center + :space-around i/align-content-column-around + :space-between i/align-content-column-between + :strech nil) + + (case val + :start i/align-content-row-start + :end i/align-content-row-end + :center i/align-content-row-center + :space-around i/align-content-row-around + :space-between i/align-content-row-between + :strech nil)) -(defn- get-layout-icon - [dir layout-type v h] - (let [row? (or (= dir :right) (= dir :left)) - manage-text-icon - (if row? - (case v - :top i/text-align-left - :center i/text-align-center - :bottom i/text-align-right - i/text-align-center) - (case h - :left i/text-align-left - :center i/text-align-center - :right i/text-align-right - i/text-align-center))] - (case layout-type - :packed manage-text-icon - :space-around i/space-around - :space-between i/space-between))) + :align-self (if is-col? + (case val + :start i/align-self-column-top + :end i/align-self-column-bottom + :center i/align-self-column-center + :strech i/align-self-column-strech + :baseline i/align-self-column-baseline) + (case val + :start i/align-self-row-left + :end i/align-self-row-right + :center i/align-self-row-center + :strech i/align-self-row-strech + :baseline i/align-self-row-baseline)))) -(mf/defc direction-row +(mf/defc direction-btn [{:keys [dir saved-dir set-direction] :as props}] (let [handle-on-click (mf/use-callback @@ -71,283 +100,331 @@ (set-direction dir))))] [:button.dir.tooltip.tooltip-bottom - {:class (dom/classnames :active (= saved-dir dir) - :left (= :left dir) - :right (= :right dir) - :top (= :top dir) - :bottom (= :bottom dir)) + {:class (dom/classnames :active (= saved-dir dir) + :row (= :row dir) + :reverse-row (= :reverse-row dir) + :reverse-column (= :reverse-column dir) + :column (= :column dir)) :key (dm/str "direction-" dir) - ;; Execution time translation strings: - ;; workspace.options.layout.direction.bottom - ;; workspace.options.layout.direction.left - ;; workspace.options.layout.direction.right - ;; workspace.options.layout.direction.top - :alt (tr (dm/str "workspace.options.layout.direction." (d/name dir))) + :alt (str/replace (str/capital (d/name dir)) "-" " ") :on-click handle-on-click} i/auto-direction])) -(mf/defc orientation-grid - [{:keys [on-change-orientation values] :as props}] - (let [dir (:layout-dir values) - type (:layout-type values) - is-col? (or (= dir :top) - (= dir :bottom)) - saved-pos [(:layout-v-orientation values) - (:layout-h-orientation values)]] +(mf/defc wrap-row + [{:keys [wrap-type set-wrap] :as props}] + [:* + [:button.tooltip.tooltip-bottom + {:class (dom/classnames :active (= wrap-type :no-wrap)) + :alt "No-wrap" + :on-click #(set-wrap :no-wrap) + :style {:padding 0}} + [:span.no-wrap i/minus]] + [:button.wrap.tooltip.tooltip-bottom + {:class (dom/classnames :active (= wrap-type :wrap)) + :alt "wrap" + :on-click #(set-wrap :wrap)} + i/auto-wrap]]) - (if (= type :packed) - [:div.orientation-grid - [:div.button-wrapper - (for [[pv ph] grid-pos] - [:button.orientation - {:on-click (partial on-change-orientation pv ph type) - :class (dom/classnames - :active (= [pv ph] saved-pos) - :top (= :top pv) - :center (= :center pv) - :bottom (= :bottom pv) - :left (= :left ph) - :center (= :center ph) - :right (= :right ph)) - :key (dm/str pv ph)} - [:span.icon - {:class (dom/classnames - :rotated (not is-col?))} - (get-layout-icon dir type pv ph)]])]] - (if (not is-col?) - [:div.orientation-grid.row - [:div.button-wrapper - (for [row grid-rows] - [:button.orientation - {:on-click (partial on-change-orientation row :left type) - :class (dom/classnames - :active (= row (first saved-pos)) - :top (= :top row) - :centered (= :center row) - :bottom (= :bottom row))} - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type nil row)] - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type nil row)] - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type nil row)]])]] +(mf/defc align-row + [{:keys [is-col? align-items set-align] :as props}] + + [:div.align-items-style + (for [align [:start :center :end :strech]] + [:button.align-start.tooltip + {:class (dom/classnames :active (= align-items align) + :tooltip-bottom-left (not= align :start) + :tooltip-bottom (= align :start)) + :alt (dm/str "Align items " (d/name align)) + :on-click #(set-align align) + :key (dm/str "align-items" (d/name align))} + (get-layout-flex-icon :align-items align is-col?)])]) + +(mf/defc align-content-row + [{:keys [is-col? align-content set-align-content] :as props}] + [:div.align-content-style + (for [align [:start :center :end :space-around :space-between]] + [:button.align-content.tooltip + {:class (dom/classnames :active (= align-content align) + :tooltip-bottom-left (not= align :start) + :tooltip-bottom (= align :start)) + :alt (dm/str "Align content " (d/name align)) + :on-click #(set-align-content align) + :key (dm/str "align-content" (d/name align))} + (get-layout-flex-icon :align-content align is-col?)])]) + +(mf/defc justify-content-row + [{:keys [is-col? justify-content set-justify] :as props}] + [:div.justify-content-style + (for [justify [:start :center :end :space-around :space-between]] + [:button.justify.tooltip + {:class (dom/classnames :active (= justify-content justify) + :tooltip-bottom-left (not= justify :start) + :tooltip-bottom (= justify :start)) + :alt (dm/str "Justify content " (d/name justify)) + :on-click #(set-justify justify) + :key (dm/str "justify-content" (d/name justify))} + (get-layout-flex-icon :justify-content justify is-col?)])]) - [:div.orientation-grid.col - [:div.button-wrapper - (for [[idx col] (d/enumerate grid-cols)] - [:button.orientation - {:key (dm/str idx col) - :on-click (partial on-change-orientation :top col type) - :class (dom/classnames - :active (= col (second saved-pos)) - :top (= :left col) - :centered (= :center col) - :bottom (= :right col))} - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type col nil)] - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type col nil)] - [:span.icon - {:class (dom/classnames :rotated is-col?)} - (get-layout-icon dir type col nil)]])]])))) (mf/defc padding-section [{:keys [values on-change-style on-change] :as props}] - (let [padding-type (:layout-padding-type values)] + (let [padding-type (:layout-padding-type values) + rx (if (apply = (vals (:layout-padding values))) + (:p1 (:layout-padding values)) + "--")] - [:div.row-flex - [:div.padding-options + [:div.padding-row + [:div.padding-icons [:div.padding-icon.tooltip.tooltip-bottom {:class (dom/classnames :selected (= padding-type :simple)) - :alt (tr "workspace.options.layout.padding-simple") + :alt "Padding" :on-click #(on-change-style :simple)} i/auto-padding] [:div.padding-icon.tooltip.tooltip-bottom {:class (dom/classnames :selected (= padding-type :multiple)) - :alt (tr "workspace.options.layout.padding") + :alt "Padding - sides" :on-click #(on-change-style :multiple)} i/auto-padding-side]] + [:div.wrapper + (cond + (= padding-type :simple) + [:div.tooltip.tooltip-bottom + {:alt (tr "workspace.options.layout.padding-all")} + [:div.input-element.mini - (cond - (= padding-type :simple) - [:div.tooltip.tooltip-bottom - {:alt (tr "workspace.options.layout.padding-all")} - [:div.input-element.mini + [:> numeric-input + {:placeholder "--" + :on-click #(dom/select-target %) + :on-change (partial on-change :simple) + :value rx}]]] - [:> numeric-input - {:placeholder "--" - :on-click #(dom/select-target %) - :on-change (partial on-change :simple) - :value (:p1 (:layout-padding values))}]]] + (= padding-type :multiple) + (for [num [:p1 :p2 :p3 :p4]] + [:div.tooltip.tooltip-bottom + {:key (dm/str "padding-" (d/name num)) + :alt (case num + :p1 "Top" + :p2 "Right" + :p3 "Bottom" + :p4 "Left")} + [:div.input-element.mini + [:> numeric-input + {:placeholder "--" + :on-click #(dom/select-target %) + :on-change (partial on-change num) + :value (num (:layout-padding values))}]]]))]])) - (= padding-type :multiple) - (for [num [:p1 :p2 :p3 :p4]] - [:div.tooltip.tooltip-bottom - {:key (dm/str "padding-" (d/name num)) - :alt (case num - :p1 (tr "workspace.options.layout.top") - :p2 (tr "workspace.options.layout.right") - :p3 (tr "workspace.options.layout.bottom") - :p4 (tr "workspace.options.layout.left"))} - [:div.input-element.mini - [:> numeric-input - {:placeholder "--" - :on-click #(dom/select-target %) - :on-change (partial on-change num) - :value (num (:layout-padding values))}]]]))])) +(mf/defc gap-section + [{:keys [gap-selected? set-gap gap-value toggle-gap-type gap-type]}] + (let [gap-locked? (= gap-type :simple) + some-gap-selected (not= @gap-selected? :none) + is-row-activated? (or (and (not gap-locked?) (= @gap-selected? :row-gap)) (and gap-locked? some-gap-selected)) + is-column-activated? (or (and (not gap-locked?) (= @gap-selected? :column-gap)) (and gap-locked? some-gap-selected))] + + [:div.gap-group + [:div.gap-row.tooltip.tooltip-bottom-left + {:alt "Row gap"} + [:span.icon + {:class (dom/classnames :activated is-row-activated?)} + i/auto-gap] + [:> numeric-input {:no-validate true + :placeholder "--" + :on-click (fn [event] + (reset! gap-selected? :row-gap) + (dom/select-target event)) + :on-change (partial set-gap gap-locked? :row-gap) + :on-blur #(reset! gap-selected? :none) + :value (:row-gap gap-value)}]] + + [:div.gap-column.tooltip.tooltip-bottom-left + {:alt "Column gap"} + [:span.icon.rotated + {:class (dom/classnames + :activated is-column-activated?)} + i/auto-gap] + [:> numeric-input {:no-validate true + :placeholder "--" + :on-click (fn [event] + (reset! gap-selected? :column-gap) + (dom/select-target event)) + :on-change (partial set-gap gap-locked? :column-gap) + :on-blur #(reset! gap-selected? :none) + :value (:column-gap gap-value)}]] + [:button.lock {:on-click toggle-gap-type + :class (dom/classnames :active gap-locked?)} + (if gap-locked? + i/lock + i/unlock)]])) (mf/defc layout-container-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type"]))]} [{:keys [ids _type values] :as props}] - (let [open? (mf/use-state false) - gap-selected? (mf/use-state false) - toggle-open (fn [] (swap! open? not)) + (let [open? (mf/use-state false) + + ;; Display + layout-type (:layout values) on-add-layout - (fn [_] - (st/emit! (dwsl/create-layout ids))) + (fn [type] + (st/emit! (dwsl/create-layout ids type))) on-remove-layout (fn [_] (st/emit! (dwsl/remove-layout ids)) (reset! open? false)) + set-flex (fn [] + (st/emit! (dwsl/remove-layout ids)) + (on-add-layout :flex)) + + set-grid (fn [] + (st/emit! (dwsl/remove-layout ids)) + (on-add-layout :grid)) + + ;; Flex-direction + + saved-dir (:layout-flex-dir values) + is-col? (or (= :column saved-dir) (= :reverse-column saved-dir)) set-direction (fn [dir] - (st/emit! (dwsl/update-layout ids {:layout-dir dir}))) + (st/emit! (dwsl/update-layout ids {:layout-flex-dir dir}))) - set-gap - (fn [gap] - (st/emit! (dwsl/update-layout ids {:layout-gap gap}))) + ;; Wrap type - change-padding-style + wrap-type (:layout-wrap-type values) + set-wrap (fn [type] + (st/emit! (dwsl/update-layout ids {:layout-wrap-type type}))) + ;; Align items + + align-items (:layout-align-items values) + set-align-items (fn [value] + (st/emit! (dwsl/update-layout ids {:layout-align-items value}))) + + ;; Justify content + + justify-content (:layout-justify-content values) + set-justify-content (fn [value] + (st/emit! (dwsl/update-layout ids {:layout-justify-content value}))) + + ;; Align content + + align-content (:layout-align-content values) + set-align-content (fn [value] + (if (= align-content value) + (st/emit! (dwsl/update-layout ids {:layout-align-content :strech})) + (st/emit! (dwsl/update-layout ids {:layout-align-content value})))) + + ;; Gap + + change-gap-type (fn [type] - (st/emit! (dwsl/update-layout ids {:layout-padding-type type}))) + (st/emit! (dwsl/update-layout ids {:layout-gap-type type}))) + + gap-type (:layout-gap-type values) + + gap-selected? (mf/use-state :none) + gap-locked? (= gap-type :simple) + toggle-gap-type (fn [] + (let [layout-gap (:layout-gap values) + row-gap (:row-gap layout-gap) + column-gap (:column-gap layout-gap) + max (max row-gap column-gap) + new-type (if (= gap-type :simple) + :multiple + :simple)] + (when (and (not= row-gap column-gap) (= gap-type :multiple)) + (st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap max :column-gap max}}))) + (change-gap-type new-type))) + set-gap + (fn [gap-locked? type val] + (if gap-locked? + (st/emit! (dwsl/update-layout ids {:layout-gap {:row-gap val :column-gap val}})) + (st/emit! (dwsl/update-layout ids {:layout-gap {type val}})))) select-all-gap (fn [event] - (reset! gap-selected? true) - (dom/select-target event)) + (when gap-locked? + (dom/select-target event))) + + ;; Padding + + change-padding-type + (fn [type] + (st/emit! (dwsl/update-layout ids {:layout-padding-type type}))) on-padding-change (fn [type val] (if (= type :simple) (st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p2 val :p3 val :p4 val}})) - (st/emit! (dwsl/update-layout ids {:layout-padding {type val}})))) - - handle-change-type - (fn [event] - (let [target (dom/get-target event) - value (dom/get-value target) - value (keyword value)] - (st/emit! (dwsl/update-layout ids {:layout-type value})))) - - handle-wrap-type - (mf/use-callback - (fn [event] - (let [target (dom/get-target event) - value (dom/get-value target) - value (keyword value)] - (st/emit! (dwsl/update-layout ids {:layout-wrap-type value}))))) - - handle-change-orientation - (fn [v-orientation h-orientation] - (st/emit! (dwsl/update-layout ids {:layout-h-orientation h-orientation :layout-v-orientation v-orientation}))) - - layout-info - (fn [] - (let [type (:layout-type values) - dir (:layout-dir values) - is-col? (or (= dir :top) (= dir :bottom)) - h (:layout-h-orientation values) - v (:layout-v-orientation values) - - wrap (:layout-wrap-type values) - - orientation - (if (= type :packed) - ;; Execution time translation strings: - ;; workspace.options.layout.h.center - ;; workspace.options.layout.h.left - ;; workspace.options.layout.h.right - ;; workspace.options.layout.v.bottom - ;; workspace.options.layout.v.center - ;; workspace.options.layout.v.top - (dm/str (tr (dm/str "workspace.options.layout.v." (d/name v))) ", " - (tr (dm/str "workspace.options.layout.h." (d/name h))) ", ") - - (if is-col? - (dm/str (tr (dm/str "workspace.options.layout.h." (d/name h))) ", ") - (dm/str (tr (dm/str "workspace.options.layout.v." (d/name v))) ", ")))] - - (dm/str orientation - (str/replace (tr (dm/str "workspace.options.layout." (d/name type))) "-" " ") ", " - (str/replace (tr (dm/str "workspace.options.layout." (d/name wrap))) "-" " "))))] + (st/emit! (dwsl/update-layout ids {:layout-padding {type val}}))))] [:div.element-set [:div.element-set-title [:* - [:span (tr "workspace.options.layout.title")] + [:span "Layout"] (if (:layout values) - [:div.add-page {:on-click on-remove-layout} i/minus] - [:div.add-page {:on-click on-add-layout} i/close])]] + [:div.title-actions + [:div.layout-btns + [:button {:on-click set-flex + :class (dom/classnames + :active (= :flex layout-type))} "Flex"] + [:button {:on-click set-grid + :class (dom/classnames + :active (= :grid layout-type))} "Grid"]] + [:button.remove-layout {:on-click on-remove-layout} i/minus]] + + [:button.add-page {:on-click #(on-add-layout :flex)} i/close])]] (when (:layout values) - [:div.element-set-content.layout-menu - ;; DIRECTION-GAP - [:div.direction-gap - [:div.direction - [:* - (for [dir [:left :right :bottom :top]] - [:& direction-row {:key (d/name dir) - :dir dir - :saved-dir (:layout-dir values) - :set-direction set-direction}])]] - [:div.gap.tooltip.tooltip-bottom-left - {:alt (tr "workspace.options.layout.gap")} - [:span.icon - {:class (dom/classnames - :rotated (or (= (:layout-dir values) :top) - (= (:layout-dir values) :bottom)) - :activated (= @gap-selected? true))} - i/auto-gap] - [:> numeric-input {:no-validate true - :placeholder "--" - :on-click select-all-gap - :on-change set-gap - :on-blur #(reset! gap-selected? false) - :value (:layout-gap values)}]]] + (if (= :flex layout-type) + [:div.element-set-content.layout-menu + [:div.layout-row + [:div.direction-wrap.row-title "Direction"] + [:div.btn-wrapper + [:div.direction + [:* + (for [dir [:row :reverse-row :column :reverse-column]] + [:& direction-btn {:key (d/name dir) + :dir dir + :saved-dir saved-dir + :set-direction set-direction}])]] - ;; LAYOUT FLEX - [:div.layout-container - [:div.layout-entry.tooltip.tooltip-bottom - {:on-click toggle-open - :alt (layout-info)} - [:div.element-set-actions-button i/actions] - [:div.layout-info (layout-info)]] + [:div.wrap-type + [:& wrap-row {:wrap-type wrap-type + :set-wrap set-wrap}]]]] - (when @open? - [:div.layout-body - [:& orientation-grid {:on-change-orientation handle-change-orientation :values values}] + (when (= :wrap wrap-type) + [:div.layout-row + [:div.align-content.row-title "Content"] + [:div.btn-wrapper + [:& align-content-row {:is-col? is-col? + :align-content align-content + :set-align-content set-align-content}]]]) - [:div.selects-wrapper - [:select.input-select {:value (d/name (:layout-type values)) - :on-change handle-change-type} - [:option {:value "packed" :label (tr "workspace.options.layout.packed")}] - [:option {:value "space-between" :label (tr "workspace.options.layout.space-between")}] - [:option {:value "space-around" :label (tr "workspace.options.layout.space-around")}]] + [:div.layout-row + [:div.align-items.row-title "Align"] + [:div.btn-wrapper + [:& align-row {:is-col? is-col? + :align-items align-items + :set-align set-align-items}]]] - [:select.input-select {:value (d/name (:layout-wrap-type values)) - :on-change handle-wrap-type} - [:option {:value "wrap" :label (tr "workspace.options.layout.wrap")}] - [:option {:value "no-wrap" :label (tr "workspace.options.layout.no-wrap")}]]]])] + [:div.layout-row + [:div.justify-content.row-title "Justify"] + [:div.btn-wrapper + [:& justify-content-row {:is-col? is-col? + :justify-content justify-content + :set-justify set-justify-content}]]] + [:& gap-section {:gap-selected? gap-selected? + :select-all-gap select-all-gap + :set-gap set-gap + :gap-value (:layout-gap values) + :toggle-gap-type toggle-gap-type + :gap-type gap-type}] - [:& padding-section {:values values - :on-change-style change-padding-style - :on-change on-padding-change}]])])) + + [:& padding-section {:values values + :on-change-style change-padding-type + :on-change on-padding-change}]] + + [:div "GRID TO COME"]))])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index 738ddb1dc1..7d1653690c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -12,6 +12,7 @@ [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.icons :as i] + [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [get-layout-flex-icon]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [rumext.v2 :as mf])) @@ -31,8 +32,8 @@ (let [margin-type (or (:layout-margin-type values) :simple)] - [:div.row-flex - [:div.margin-options + [:div.margin-row + [:div.margin-icons [:div.margin-icon.tooltip.tooltip-bottom {:class (dom/classnames :selected (= margin-type :simple)) :alt (tr "workspace.options.layout.margin-simple") @@ -43,80 +44,93 @@ :alt (tr "workspace.options.layout.margin") :on-click #(change-margin-style :multiple)} i/auto-margin-side]] + [:div.wrapper + (cond + (= margin-type :simple) + [:div.tooltip.tooltip-bottom + {:alt (tr "workspace.options.layout.margin-all")} + [:div.input-element.mini - (cond - (= margin-type :simple) - [:div.tooltip.tooltip-bottom - {:alt (tr "workspace.options.layout.margin-all")} - [:div.input-element.mini + [:> numeric-input + {:placeholder "--" + :on-click #(dom/select-target %) + :on-change (partial on-margin-change :simple) + :value (or (-> values :layout-margin :m1) 0)}]]] - [:> numeric-input - {:placeholder "--" - :on-click #(dom/select-target %) - :on-change (partial on-margin-change :simple) - :value (or (-> values :layout-margin :m1) 0)}]]] - - (= margin-type :multiple) - [:* + (= margin-type :multiple) (for [num [:m1 :m2 :m3 :m4]] [:div.tooltip.tooltip-bottom - {:class (dm/str "margin-" (d/name num)) - :key (dm/str "margin-" (d/name num)) + {:key (dm/str "margin-" (d/name num)) :alt (case num - :m1 (tr "workspace.options.layout.top") - :m2 (tr "workspace.options.layout.right") - :m3 (tr "workspace.options.layout.bottom") - :m4 (tr "workspace.options.layout.left"))} + :m1 "Top" + :m2 "Right" + :m3 "Bottom" + :m4 "Left")} [:div.input-element.mini [:> numeric-input {:placeholder "--" :on-click #(dom/select-target %) :on-change (partial on-margin-change num) - :value (or (-> values :layout-margin num) 0)}]]])])])) + :value (or (-> values :layout-margin num) 0)}]]]))]])) (mf/defc element-behavior [{:keys [is-layout-container? is-layout-child? layout-h-behavior layout-v-behavior on-change-behavior] :as props}] (let [fill? is-layout-child? auto? is-layout-container?] - [:div.layout-behavior - [:div.button-wrapper.horizontal + [:div.btn-wrapper + [:div.layout-behavior.horizontal [:button.behavior-btn.tooltip.tooltip-bottom - {:alt "horizontal fix" + {:alt "Fix width" :class (dom/classnames :activated (= layout-h-behavior :fix)) :on-click #(on-change-behavior :h :fix)} - [:span.icon i/auto-fix-layout]] + i/auto-fix-layout] (when fill? [:button.behavior-btn.tooltip.tooltip-bottom - {:alt "horizontal fill" + {:alt "Width 100%" :class (dom/classnames :activated (= layout-h-behavior :fill)) :on-click #(on-change-behavior :h :fill)} - [:span.icon i/auto-fill]]) + i/auto-fill]) (when auto? [:button.behavior-btn.tooltip.tooltip-bottom - {:alt "horizontal auto" + {:alt "Fit content" :class (dom/classnames :activated (= layout-v-behavior :auto)) :on-click #(on-change-behavior :h :auto)} - [:span.icon i/auto-hug]])] + i/auto-hug])] - [:div.button-wrapper + [:div.layout-behavior [:button.behavior-btn.tooltip.tooltip-bottom - {:alt "vertical fix" + {:alt "Fix height" :class (dom/classnames :activated (= layout-v-behavior :fix)) :on-click #(on-change-behavior :v :fix)} - [:span.icon i/auto-fix-layout]] + i/auto-fix-layout] (when fill? [:button.behavior-btn.tooltip.tooltip-bottom - {:alt "vertical fill" + {:alt "Height 100%" :class (dom/classnames :activated (= layout-v-behavior :fill)) :on-click #(on-change-behavior :v :fill)} - [:span.icon i/auto-fill]]) + i/auto-fill]) (when auto? - [:button.behavior-btn.tooltip.tooltip-bottom - {:alt "vertical auto" + [:button.behavior-btn.tooltip.tooltip-bottom-left + {:alt "Fit content" :class (dom/classnames :activated (= layout-v-behavior :auto)) :on-click #(on-change-behavior :v :auto)} - [:span.icon i/auto-hug]])]])) + i/auto-hug])]])) + + +(mf/defc align-self-row + [{:keys [is-col? align-self set-align-self] :as props}] + (let [dir-v [:start :center :end :strech :baseline]] + [:div.align-self-style + (for [align dir-v] + [:button.align-self.tooltip.tooltip-bottom + {:class (dom/classnames :active (= align-self align) + :tooltip-bottom-left (not= align :start) + :tooltip-bottom (= align :start)) + :alt (dm/str "Align self " (d/name align)) ;; TODO añadir lineas de texto a tradus + :on-click #(set-align-self align) + :key (str "align-self" align)} + (get-layout-flex-icon :align-self align is-col?)])])) (mf/defc layout-item-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type"]))]} @@ -128,6 +142,13 @@ (fn [type] (st/emit! (dwsl/update-layout-child ids {:layout-margin-type type}))) + align-self (:layout-align-self values) + set-align-self (fn [value] + (st/emit! (dwsl/update-layout-child ids {:layout-align-self value}))) + + saved-dir (:layout-flex-dir values) + is-col? (or (= :column saved-dir) (= :reverse-column saved-dir)) + on-margin-change (fn [type val] (if (= type :simple) @@ -146,51 +167,54 @@ [:div.element-set [:div.element-set-title - [:span (tr "workspace.options.layout-item.title")]] + [:span "Flex elements"]] [:div.element-set-content.layout-item-menu - [:& element-behavior {:is-layout-child? is-layout-child? - :is-layout-container? is-layout-container? - :layout-v-behavior (or (:layout-v-behavior values) :fix) - :layout-h-behavior (or (:layout-h-behavior values) :fix) - :on-change-behavior on-change-behavior}] + [:div.layout-row + [:div.row-title "Sizing"] + [:& element-behavior {:is-layout-child? is-layout-child? + :is-layout-container? is-layout-container? + :layout-v-behavior (or (:layout-v-behavior values) :fix) + :layout-h-behavior (or (:layout-h-behavior values) :fix) + :on-change-behavior on-change-behavior}]] + - [:div.margin [:& margin-section {:values values - :change-margin-style change-margin-style - :on-margin-change on-margin-change}]] + [:& margin-section {:values values + :change-margin-style change-margin-style + :on-margin-change on-margin-change}] [:div.advanced-ops-container - [:div.advanced-ops.toltip.tooltip-bottom + [:button.advanced-ops.toltip.tooltip-bottom {:on-click toggle-open :alt (tr "workspace.options.layout-item.advanced-ops")} - [:div.element-set-actions-button i/actions] + [:span.icon i/actions] [:span (tr "workspace.options.layout-item.advanced-ops")]]] (when @open? [:div.advanced-ops-body - (for [item [:layout-max-h :layout-min-h :layout-max-w :layout-min-w]] - [:div.input-element - {:key (d/name item) - ;; Execution time translation strings: - ;; workspace.options.layout-item.layout-max-h - ;; workspace.options.layout-item.layout-max-w - ;; workspace.options.layout-item.layout-min-h - ;; workspace.options.layout-item.layout-min-w - ;; workspace.options.layout-item.title.layout-max-h - ;; workspace.options.layout-item.title.layout-max-w - ;; workspace.options.layout-item.title.layout-min-h - ;; workspace.options.layout-item.title.layout-min-w - :alt (tr (dm/str "workspace.options.layout-item." (d/name item))) - :title (tr (dm/str "workspace.options.layout-item." (d/name item))) - :class (dom/classnames "maxH" (= item :layout-max-h) - "minH" (= item :layout-min-h) - "maxW" (= item :layout-max-w) - "minW" (= item :layout-min-w))} - - [:> numeric-input - {:no-validate true - :min 0 - :data-wrap true - :placeholder "--" - :on-click #(dom/select-target %) - :on-change (partial on-size-change item) - :value (get values item)}]])])]])) + [:div.layout-row + [:div.direction-wrap.row-title "Align"] ;; TODO tradus + [:div.btn-wrapper + [:& align-self-row {:is-col? is-col? + :align-self align-self + :set-align-self set-align-self}]]] + [:div.input-wrapper + (for [item [:layout-max-h :layout-min-h :layout-max-w :layout-min-w]] + [:div.tooltip.tooltip-bottom + {:key (d/name item) + :alt (tr (dm/str "workspace.options.layout-item.title." (d/name item))) + :class (dom/classnames "maxH" (= item :layout-max-h) + "minH" (= item :layout-min-h) + "maxW" (= item :layout-max-w) + "minW" (= item :layout-min-w))} + [:div.input-element + {:alt (tr (dm/str "workspace.options.layout-item." (d/name item)))} + [:> numeric-input + {:no-validate true + :min 0 + :data-wrap true + :placeholder "--" + :on-click #(dom/select-target %) + :on-change (partial on-size-change item) + ;; :value (get values item) + :value 100}]]])]])]] + )) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 2a255c4c5f..bd9df2411f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -13,7 +13,7 @@ [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs-shape fill-menu]] [app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] - [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-attrs layout-container-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [select-measure-keys measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] @@ -31,7 +31,7 @@ layer-values (select-keys shape layer-attrs) measure-values (select-measure-keys shape) constraint-values (select-keys shape constraint-attrs) - layout-container-values (select-keys shape layout-container-attrs) + layout-container-values (select-keys shape layout-container-flex-attrs) layout-item-values (select-keys shape layout-item-attrs) is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 1ccb7ad7f1..09a34eb25c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -19,7 +19,7 @@ [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-attrs exports-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] - [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-attrs layout-container-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-attrs layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [select-measure-keys measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-attrs shadow-menu]] @@ -142,7 +142,7 @@ :stroke stroke-attrs :text ot/attrs :exports exports-attrs - :layout layout-container-attrs + :layout layout-container-flex-attrs :layout-item layout-item-attrs}) (def shadow-keys [:style :color :offset-x :offset-y :blur :spread]) diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 373c8b8338..883b7104c4 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -3641,10 +3641,6 @@ msgstr "Alçada mín." msgid "workspace.options.layout-item.min-w" msgstr "Amplada mín." -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title" -msgstr "Redimensió de l'element" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.max-h" msgstr "Alçada màxima" @@ -3666,19 +3662,19 @@ msgid "workspace.options.layout.bottom" msgstr "Baix" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" +msgid "workspace.options.layout.direction.column" msgstr "Columna" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" +msgid "workspace.options.layout.direction.row" msgstr "Fila" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" +msgid "workspace.options.layout.direction.reverse-row" msgstr "Fila invertida" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" +msgid "workspace.options.layout.direction.reverse-column" msgstr "Columna invertida" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -3713,10 +3709,6 @@ msgstr "Tots els costats" msgid "workspace.options.layout.margin-simple" msgstr "Marge senzill" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.no-wrap" -msgstr "no agrupis" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.packed" msgstr "ajuntat" @@ -3737,10 +3729,6 @@ msgstr "separat" msgid "workspace.options.layout.space-between" msgstr "espaiat" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.title" -msgstr "Disposició" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.top" msgstr "Dalt" diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 4019da1d85..b862ff1a5d 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -3986,19 +3986,19 @@ msgid "workspace.options.layout.bottom" msgstr "Unten" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" +msgid "workspace.options.layout.direction.column" msgstr "Spalte" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" +msgid "workspace.options.layout.direction.row" msgstr "Reihe" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" +msgid "workspace.options.layout.direction.reverse-row" msgstr "Reihe-umgekehrt" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" +msgid "workspace.options.layout.direction.reverse-column" msgstr "Spalte-umgekehrt" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -4085,10 +4085,6 @@ msgstr "Mitte" msgid "workspace.options.layout.v.top" msgstr "oben" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.wrap" -msgstr "wrap" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Weitere Farben" diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 965c62d854..a27e899725 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -899,6 +899,10 @@ msgstr "Width" msgid "handoff.attributes.layout" msgstr "Layout" +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.size" +msgstr "Size and position" + #: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.height" msgstr "Height" @@ -3436,10 +3440,6 @@ msgstr "Min.Height" msgid "workspace.options.layout-item.layout-min-w" msgstr "Min.Width" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title" -msgstr "Element resizing" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.layout-max-h" msgstr "Maximum height" @@ -3461,19 +3461,19 @@ msgid "workspace.options.layout.bottom" msgstr "Bottom" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" +msgid "workspace.options.layout.direction.column" msgstr "Column" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" +msgid "workspace.options.layout.direction.row" msgstr "Row" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" +msgid "workspace.options.layout.direction.reverse-row" msgstr "Reverse row" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" +msgid "workspace.options.layout.direction.reverse-column" msgstr "Reverse column" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -3508,10 +3508,6 @@ msgstr "All sides" msgid "workspace.options.layout.margin-simple" msgstr "Simple margin" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.no-wrap" -msgstr "no wrap" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.packed" msgstr "packed" @@ -3540,10 +3536,6 @@ msgstr "space around" msgid "workspace.options.layout.space-between" msgstr "space between" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.title" -msgstr "Layout" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.top" msgstr "Top" @@ -3560,10 +3552,6 @@ msgstr "center" msgid "workspace.options.layout.v.top" msgstr "top" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.wrap" -msgstr "wrap" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "More colors" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 9081e2c186..5d9b781770 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1015,6 +1015,10 @@ msgstr "Ancho" msgid "handoff.attributes.layout" msgstr "Estructura" +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.size" +msgstr "Tamaño y posición" + #: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.height" msgstr "Altura" @@ -3900,10 +3904,6 @@ msgstr "Altura.Min" msgid "workspace.options.layout-item.layout-min-w" msgstr "Ancho.Min" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title" -msgstr "Redimensionado de elemento" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title.layout-max-h" msgstr "Altura máxima" @@ -3925,19 +3925,19 @@ msgid "workspace.options.layout.bottom" msgstr "Abajo" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" +msgid "workspace.options.layout.direction.column" msgstr "Columna" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" +msgid "workspace.options.layout.direction.row" msgstr "Fila" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" +msgid "workspace.options.layout.direction.reverse-row" msgstr "Fila invertida" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" +msgid "workspace.options.layout.direction.reverse-column" msgstr "Columna invertida" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -3972,10 +3972,6 @@ msgstr "Todos" msgid "workspace.options.layout.margin-simple" msgstr "Margen sencillo" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.no-wrap" -msgstr "no agrupar" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.packed" msgstr "juntar" @@ -4004,10 +4000,6 @@ msgstr "separar" msgid "workspace.options.layout.space-between" msgstr "espaciar" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.title" -msgstr "Layout" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.top" msgstr "Arriba" @@ -4024,10 +4016,6 @@ msgstr "centro" msgid "workspace.options.layout.v.top" msgstr "arriba" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.wrap" -msgstr "agrupar" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Más colores" diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index ea37c44014..432660efc3 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -3812,19 +3812,19 @@ msgid "workspace.options.layout.bottom" msgstr "Behean" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" +msgid "workspace.options.layout.direction.column" msgstr "Zutabea" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" +msgid "workspace.options.layout.direction.row" msgstr "Lerroa" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" +msgid "workspace.options.layout.direction.reverse-row" msgstr "Alderantzikatu lerroa" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" +msgid "workspace.options.layout.direction.reverse-column" msgstr "Alderantzikatu zutabea" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -3859,10 +3859,6 @@ msgstr "Alde guztiak" msgid "workspace.options.layout.margin-simple" msgstr "Margin arrunta" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.no-wrap" -msgstr "Ez batu" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.packed" msgstr "Trinkotuta" @@ -3891,10 +3887,6 @@ msgstr "tarteko espazioa" msgid "workspace.options.layout.space-between" msgstr "Tarteko espazioa" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.title" -msgstr "Itxura" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.top" msgstr "Goian" @@ -3911,10 +3903,6 @@ msgstr "erdian" msgid "workspace.options.layout.v.top" msgstr "goian" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.wrap" -msgstr "Batu" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Kolore gehiago" diff --git a/frontend/translations/he.po b/frontend/translations/he.po index 2c2cafb5ba..fb0f8756db 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -3894,19 +3894,19 @@ msgid "workspace.options.layout.bottom" msgstr "תחתית" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" +msgid "workspace.options.layout.direction.column" msgstr "עמודה" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" +msgid "workspace.options.layout.direction.row" msgstr "שורה" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" +msgid "workspace.options.layout.direction.reverse-row" msgstr "היפוך שורה" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" +msgid "workspace.options.layout.direction.reverse-column" msgstr "היפוך עמודה" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -3941,10 +3941,6 @@ msgstr "כל הצדדים" msgid "workspace.options.layout.margin-simple" msgstr "שול פשוט" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.no-wrap" -msgstr "ללא גלישה" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.packed" msgstr "אסוף" @@ -3973,14 +3969,6 @@ msgstr "רווח מסביב" msgid "workspace.options.layout.space-between" msgstr "רווח בין לבין" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.title" -msgstr "פריסה" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.top" -msgstr "עליון" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.v.bottom" msgstr "תחתית" @@ -3993,10 +3981,6 @@ msgstr "מרכז" msgid "workspace.options.layout.v.top" msgstr "עליון" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.wrap" -msgstr "גלישה" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "צבעים נוספים" diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 3a2b2f79a6..586247842b 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -3941,19 +3941,19 @@ msgid "workspace.options.layout.bottom" msgstr "Alt" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.bottom" +msgid "workspace.options.layout.direction.column" msgstr "Sütun" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.left" +msgid "workspace.options.layout.direction.row" msgstr "Satır" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.right" +msgid "workspace.options.layout.direction.reverse-row" msgstr "Ters satır" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.direction.top" +msgid "workspace.options.layout.direction.reverse-column" msgstr "Ters sütun" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -3988,10 +3988,6 @@ msgstr "Tüm kenarlar" msgid "workspace.options.layout.margin-simple" msgstr "Basit kenar boşluğu" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.no-wrap" -msgstr "sarma yok" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.packed" msgstr "paketlenmiş" @@ -4020,14 +4016,6 @@ msgstr "etrafında boşluk" msgid "workspace.options.layout.space-between" msgstr "arasında boşluk" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.title" -msgstr "Yerleşim düzeni" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.top" -msgstr "Üst" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.v.bottom" msgstr "alt" @@ -4040,10 +4028,6 @@ msgstr "orta" msgid "workspace.options.layout.v.top" msgstr "üst" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.wrap" -msgstr "sar" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Daha fazla renk" From 66055a0b149907e4be1e08ff26f7fc08853753d0 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Fri, 21 Oct 2022 16:25:25 +0200 Subject: [PATCH 145/682] :sparkles: Confirm unpublish library on libraries popup --- frontend/src/app/main/ui/workspace/libraries.cljs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index 52f0909b7e..11d3aa4653 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.libraries (:require [app.common.data :as d] + [app.main.data.dashboard :as dd] [app.main.data.modal :as modal] [app.main.data.workspace.libraries :as dwl] [app.main.refs :as refs] @@ -87,7 +88,16 @@ del-shared (mf/use-callback (mf/deps file) - #(st/emit! (dwl/set-file-shared (:id file) false)))] + (fn [_] + (st/emit! (dd/fetch-libraries-using-files [file])) + (st/emit! (modal/show + {:type :delete-shared + :origin :unpublish + :on-accept (fn[] + (st/emit! (dwl/set-file-shared (:id file) false)) + (modal/show! :libraries-dialog {})) + :on-cancel #(modal/show! :libraries-dialog {}) + :count-libraries 1}))))] [:* [:div.section [:div.section-title (tr "workspace.libraries.in-this-file")] From 76675e194925804978825b1869cf0162ab49f5c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bogi=20Napoleon=20Wennerstr=C3=B8m?= Date: Fri, 21 Oct 2022 11:41:11 +0000 Subject: [PATCH 146/682] :globe_with_meridians: Add translations for: Faroese. Currently translated at 6.5% (80 of 1215 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/ --- frontend/translations/fo.po | 51 +++++++++++++++++++++++++++++++++++-- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index d4f56d1b05..0bc0e2d6d9 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-18 08:01+0000\n" +"PO-Revision-Date: 2022-10-22 12:05+0000\n" "Last-Translator: Bogi Napoleon Wennerstrøm \n" "Language-Team: Faroese \n" @@ -9,7 +9,7 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=n != 1;\n" -"X-Generator: Weblate 4.15-dev\n" +"X-Generator: Weblate 4.14.2-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -275,3 +275,50 @@ msgstr "Kladda" #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.duplicate" msgstr "Tvítøka" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"Kanna tín teldupost og trýst á leinkina fyri at vátta og byrja at nýta " +"Penpot." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "Vit hava sent ein váttanar teldupost til" + +msgid "common.share-link.confirm-deletion-link-description" +msgstr "" +"Ert tú vís(ur) í, at tú vilt strika hetta leinkið? Gert tú tað, er tað ikki " +"longur tøkt hjá nøkrum" + +msgid "common.share-link.current-tag" +msgstr "(núverandi)" + +msgid "common.share-link.get-link" +msgstr "Fá leinkið" + +msgid "common.share-link.link-copied-success" +msgstr "Leinkið avritað" + +msgid "common.share-link.link-deleted-success" +msgstr "Leinkið er strikað" + +msgid "common.share-link.manage-ops" +msgstr "Fyrisit heimildir" + +msgid "common.share-link.permissions-pages" +msgstr "Síður deildar" + +msgid "common.share-link.placeholder" +msgstr "Leinkja, ið kann deilast, verur at síggja her" + +msgid "common.share-link.title" +msgstr "Deil frumsnið" + +msgid "common.unpublish" +msgstr "Angra útgevan" + +msgid "common.share-link.page-shared" +msgid_plural "common.share-link.page-shared" +msgstr[0] "1 síða deild" +msgstr[1] "%s síður deildar" From b191df0351f61ad37d17804a4d34ab30ec78ad7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 21 Oct 2022 16:45:53 +0200 Subject: [PATCH 147/682] :bug: Fix bug about decoding :features PgArray --- backend/src/app/rpc/commands/management.clj | 3 ++- backend/src/app/rpc/mutations/files.clj | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/app/rpc/commands/management.clj b/backend/src/app/rpc/commands/management.clj index 883202fad7..c68ecaea3e 100644 --- a/backend/src/app/rpc/commands/management.clj +++ b/backend/src/app/rpc/commands/management.clj @@ -194,7 +194,8 @@ (proj/check-edition-permissions! conn profile-id (:project-id file)) (db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"]) (-> (duplicate-file* conn params {:reset-shared-flag true}) - (update :data blob/decode)))) + (update :data blob/decode) + (update :features db/decode-pgarray #{})))) ;; --- COMMAND: Duplicate Project diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 14d79347a9..5884a1a435 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -146,7 +146,8 @@ (when-not is-shared (absorb-library conn params) (unlink-files conn params)) - (set-file-shared conn params))) + (-> (set-file-shared conn params) + (update :features db/decode-pgarray #{})))) (defn- unlink-files [conn {:keys [id] :as params}] From bd1003e383e2a574da7a05f377cb52c43c45615d Mon Sep 17 00:00:00 2001 From: Vin Date: Tue, 25 Oct 2022 10:22:03 +0000 Subject: [PATCH 148/682] :globe_with_meridians: Add translations for: Russian. Currently translated at 67.1% (816 of 1215 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ru/ --- frontend/translations/ru.po | 64 +++++++++++++++++++++++++++++++------ 1 file changed, 54 insertions(+), 10 deletions(-) diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index 86c2369206..53fac49123 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-09-23 20:54+0000\n" -"Last-Translator: Stas Haas \n" -"Language-Team: Russian " -"\n" +"PO-Revision-Date: 2022-10-25 12:20+0000\n" +"Last-Translator: Vin \n" +"Language-Team: Russian \n" "Language: ru\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -"X-Generator: Weblate 4.14.1\n" +"X-Generator: Weblate 4.14.2-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -737,7 +737,7 @@ msgstr "Нельзя указывать в качестве пароля адр #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/recovery_request.cljs, src/app/main/ui/settings/change_email.cljs, src/app/main/ui/dashboard/team.cljs msgid "errors.email-has-permanent-bounces" -msgstr "Получатель «%s» постоянно не доступен." +msgstr "Эл. почта «%s» постоянно недоступна." #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-invalid-confirmation" @@ -2514,10 +2514,6 @@ msgstr "Дизайн" msgid "workspace.options.export" msgstr "Экспорт" -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.export-object" -msgstr "Экспорт элемента" - #: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs, src/app/main/ui/workspace/header.cljs msgid "workspace.options.exporting-complete" msgstr "Экспорт завершён" @@ -3109,3 +3105,51 @@ msgstr "Обновить" msgid "workspace.viewport.click-to-close-path" msgstr "Нажмите для замыкания контура" + +msgid "onboarding-v2.welcome.title" +msgstr "Добро пожаловать в Penpot!" + +msgid "onboarding.choice.team-up.create-later" +msgstr "Создать команду позже" + +msgid "onboarding.choice.team-up.invite-members-info" +msgstr "" +"Никого не забудьте. Разработчики, дизайнеры, менеджеры... разнообразие " +"развивает :)" + +msgid "onboarding.team-modal.create-team" +msgstr "Создать команду" + +msgid "onboarding.choice.title" +msgstr "Добро пожаловать в Penpot" + +msgid "onboarding.contrib.alt" +msgstr "Открытый исходный код" + +msgid "errors.bad-font" +msgstr "Шрифт %s не может быть загружен" + +msgid "onboarding-v2.newsletter.updates" +msgstr "" +"Присылать мне обновления продукта (новые функции, выпуски, исправления...)." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.auth-provider-not-configured" +msgstr "Провайдер аутентификации не настроен." + +msgid "onboarding.choice.team-up.create-team-desc" +msgstr "" +"После добавления названия команды, вы сможете пригласить людей " +"присоединиться." + +msgid "errors.email-spam-or-permanent-bounces" +msgstr "Эл. почта «%s» была отмечена как спам или постоянно недоступна." + +msgid "errors.bad-font-plural" +msgstr "Шрифты %s не могут быть загружены" + +msgid "errors.profile-blocked" +msgstr "Профиль заблокирован" + +msgid "onboarding-v2.welcome.desc3.title" +msgstr "Руководство по участию в проекте" From cc06bb7755739ebfb0b01079114618e685416d21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tummas=20J=C3=B3han=20Sigvardsen?= Date: Mon, 24 Oct 2022 15:43:17 +0000 Subject: [PATCH 149/682] :globe_with_meridians: Add translations for: Faroese. Currently translated at 7.7% (94 of 1215 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/fo/ --- frontend/translations/fo.po | 61 +++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/frontend/translations/fo.po b/frontend/translations/fo.po index 0bc0e2d6d9..78c91e3fd2 100644 --- a/frontend/translations/fo.po +++ b/frontend/translations/fo.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-22 12:05+0000\n" -"Last-Translator: Bogi Napoleon Wennerstrøm \n" +"PO-Revision-Date: 2022-10-25 12:20+0000\n" +"Last-Translator: Tummas Jóhan Sigvardsen \n" "Language-Team: Faroese \n" "Language: fo\n" @@ -82,7 +82,7 @@ msgstr "Loyniorðið er broytt" #: src/app/main/ui/auth/verify_token.cljs msgid "auth.notifications.team-invitation-accepted" -msgstr "Sameinadan í toymið var væleydnað" +msgstr "Sameinaðan í toymið var væleydnað" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.password" @@ -322,3 +322,58 @@ msgid "common.share-link.page-shared" msgid_plural "common.share-link.page-shared" msgstr[0] "1 síða deild" msgstr[1] "%s síður deildar" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Tá tú stovnar ein brúkara, góðtekur tú okkara treytir og privat politikk." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Legg afturat sum Deilt Savn" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Tvítak %s fílir" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Tú hevur enn ongar fílir her" + +#: src/app/main/ui/dashboard/grid.cljs +#, markdown +msgid "dashboard.empty-placeholder-drafts" +msgstr "" +"Áh nei! Tú hevur ongar fílir enn! Um tú vilt royna við nøkrum skapilónum, " +"vitja [Libraries & templates](https://penpot.app/libraries-templates.html)" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-frames.title" +msgstr "Útflyt til PDF" + +msgid "dashboard.export-multi" +msgstr "Útflyt Penpot %s fílir" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-multiple.selected" +msgstr "%s av %s lutum eru valdir" + +#: src/app/main/ui/workspace/header.cljs +msgid "dashboard.export-shapes" +msgstr "Útflyt" + +msgid "dashboard.export-single" +msgstr "Útflyt Penpot fílu" + +#: src/app/main/ui/export.cljs +msgid "dashboard.export-shapes.no-elements" +msgstr "Har eru ongin lutir við útflytsstillingum." + +msgid "dashboard.export.options.all.title" +msgstr "Útflyt deild søvn" + +msgid "dashboard.export.title" +msgstr "Útflyt fílur" + +msgid "dashboard.export-frames" +msgstr "Útflyt borð sum PDF" From b98cf29134510ebefa279cc8a47aac24d4ae175b Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 25 Oct 2022 12:20:35 +0000 Subject: [PATCH 150/682] :globe_with_meridians: Add translations for: German. Currently translated at 100.0% (1215 of 1215 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 857accd50a..f67f532a42 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2022-10-19 22:02+0000\n" -"Last-Translator: nautilusx \n" +"PO-Revision-Date: 2022-10-25 12:22+0000\n" +"Last-Translator: Pablo Alba \n" "Language-Team: German \n" "Language: de\n" @@ -777,8 +777,7 @@ msgstr "Sie können Ihre E-Mail-Adresse nicht als Passwort verwenden" #: src/app/main/ui/settings/change_email.cljs, #: src/app/main/ui/dashboard/team.cljs msgid "errors.email-has-permanent-bounces" -msgstr "" -"Die E-Mail-Adresse «%s» hat viele permanente Unzustellbarkeitsberichte." +msgstr "Die E-Mail-Adresse «%s» hat viele permanente Unzustellbarkeitsberichte." #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-invalid-confirmation" @@ -1846,8 +1845,7 @@ msgstr "Mitglied löschen" #: src/app/main/ui/dashboard/team.cljs msgid "modals.delete-team-member-confirm.message" -msgstr "" -"Sind Sie sicher, dass Sie dieses Mitglied aus dem Team löschen möchten?" +msgstr "Sind Sie sicher, dass Sie dieses Mitglied aus dem Team löschen möchten?" #: src/app/main/ui/dashboard/team.cljs msgid "modals.delete-team-member-confirm.title" @@ -2266,9 +2264,6 @@ msgstr "Unbegrenzte Anzahl von Mitgliedern" msgid "onboarding.team-modal.create-team-feature-5" msgstr "100% kostenlos!" -msgid "onboarding.team.create.title" -msgstr "Team erstellen" - msgid "onboarding.team.start.title" msgstr "Mit der Gestaltung beginnen" @@ -3842,10 +3837,6 @@ msgstr "Min.Höhe" msgid "workspace.options.layout-item.layout-min-w" msgstr "Min.Breite" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.min-w" -msgstr "Min. Breite" - #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs msgid "workspace.options.layout-item.title" msgstr "Elementgröße ändern" @@ -3866,10 +3857,6 @@ msgstr "Mindesthöhe" msgid "workspace.options.layout-item.title.layout-min-w" msgstr "Mindestbreite" -#: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.min-w" -msgstr "Minimale Breite" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.bottom" msgstr "Unten" From 2375f9ab834076617c8b9c11fc16bc32c0fe68b0 Mon Sep 17 00:00:00 2001 From: Eva Date: Tue, 18 Oct 2022 08:12:45 +0200 Subject: [PATCH 151/682] :tada: Add unpublish option on context menu --- .../src/app/main/ui/dashboard/file_menu.cljs | 45 +++--- frontend/src/app/main/ui/delete_shared.cljs | 51 ++++--- .../src/app/main/ui/workspace/header.cljs | 24 ++-- .../sidebar/options/menus/layout_item.cljs | 2 +- frontend/translations/ca.po | 24 ---- frontend/translations/de.po | 24 ---- frontend/translations/en.po | 92 +++++++----- frontend/translations/es.po | 134 ++++++++++-------- frontend/translations/eu.po | 24 ---- frontend/translations/fr.po | 24 ---- frontend/translations/he.po | 24 ---- frontend/translations/hr.po | 24 ---- frontend/translations/pl.po | 12 -- frontend/translations/pt_BR.po | 24 ---- frontend/translations/pt_PT.po | 24 ---- frontend/translations/tr.po | 24 ---- frontend/translations/zh_CN.po | 24 ---- 17 files changed, 200 insertions(+), 400 deletions(-) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index e7f70c2255..a3a0229d02 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -49,13 +49,14 @@ projects)) (mf/defc file-menu - [{:keys [files show? on-edit on-menu-close top left navigate?] :as props}] + [{:keys [files show? on-edit on-menu-close top left navigate? origin] :as props}] (assert (seq files) "missing `files` prop") (assert (boolean? show?) "missing `show?` prop") (assert (fn? on-edit) "missing `on-edit` prop") (assert (fn? on-menu-close) "missing `on-menu-close` prop") (assert (boolean? navigate?) "missing `navigate?` prop") - (let [top (or top 0) + (let [is-lib-page? (= :libraries origin) + top (or top 0) left (or left 0) file (first files) @@ -92,15 +93,15 @@ (fn [event] (dom/stop-propagation event) - (let [has-shared? (filter #(:is-shared %) files)] + (let [num-shared (filter #(:is-shared %) files)] - (if has-shared? + (if (< 0 (count num-shared)) (do (st/emit! (dd/fetch-libraries-using-files files)) (st/emit! (modal/show {:type :delete-shared :origin :delete :on-accept delete-fn - :count-libraries (count has-shared?)}))) + :count-libraries (count num-shared)}))) (if multi? (st/emit! (modal/show @@ -158,12 +159,12 @@ (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (st/emit! (dd/fetch-libraries-using-files [file])) + (st/emit! (dd/fetch-libraries-using-files files)) (st/emit! (modal/show {:type :delete-shared :origin :unpublish :on-accept del-shared - :count-libraries 1}))) + :count-libraries file-count}))) on-export-files (fn [event-name binary?] @@ -232,27 +233,31 @@ [(tr "dashboard.move-to-multi" file-count) nil sub-options "move-to-multi"]) [(tr "dashboard.export-binary-multi" file-count) on-export-binary-files] [(tr "dashboard.export-standard-multi" file-count) on-export-standard-files] - [:separator] - [(tr "labels.delete-multi-files" file-count) on-delete nil "delete-multi-files"]] + (when (:is-shared file) + [(tr "labels.unpublish-multi-files" file-count) on-del-shared nil "file-del-shared"]) + (when (not is-lib-page?) + [:separator] + [(tr "labels.delete-multi-files" file-count) on-delete nil "delete-multi-files"])] [[(tr "dashboard.open-in-new-tab") on-new-tab] [(tr "labels.rename") on-edit nil "file-rename"] [(tr "dashboard.duplicate") on-duplicate nil "file-duplicate"] - (when (or (seq current-projects) (seq other-teams)) - [(tr "dashboard.move-to") nil sub-options "file-move-to"]) + (when (and (not is-lib-page?) (or (seq current-projects) (seq other-teams))) + [(tr "dashboard.move-to") nil sub-options "file-move-to"]) (if (:is-shared file) [(tr "dashboard.unpublish-shared") on-del-shared nil "file-del-shared"] [(tr "dashboard.add-shared") on-add-shared nil "file-add-shared"]) [:separator] [(tr "dashboard.download-binary-file") on-export-binary-files nil "download-binary-file"] [(tr "dashboard.download-standard-file") on-export-standard-files nil "download-standard-file"] - [:separator] - [(tr "labels.delete") on-delete nil "file-delete"]])] + (when (not is-lib-page?) + [:separator] + [(tr "labels.delete") on-delete nil "file-delete"])])] - [:& context-menu {:on-close on-menu-close - :show show? - :fixed? (or (not= top 0) (not= left 0)) - :min-width? true - :top top - :left left - :options options}])))) + [:& context-menu {:on-close on-menu-close + :show show? + :fixed? (or (not= top 0) (not= left 0)) + :min-width? true + :top top + :left left + :options options}])))) diff --git a/frontend/src/app/main/ui/delete_shared.cljs b/frontend/src/app/main/ui/delete_shared.cljs index c1bfb139b2..9ea6cdcc61 100644 --- a/frontend/src/app/main/ui/delete_shared.cljs +++ b/frontend/src/app/main/ui/delete_shared.cljs @@ -43,17 +43,26 @@ accept-label (if is-delete? (tr "modals.delete-shared-confirm.accept" (i18n/c count-libraries)) (tr "modals.unpublish-shared-confirm.accept")) - scd-message (if is-delete? + + no-files-message (if is-delete? + (tr "modals.delete-shared-confirm.no-files-message" (i18n/c count-libraries)) + (tr "modals.unpublish-shared-confirm.no-files-message" (i18n/c count-libraries)) + ) + scd-message (if is-delete? (if (> count-libraries 1) - (tr "modals.delete-shared-confirm.scd-message-plural" (i18n/c count-files)) + (tr "modals.delete-shared-confirm.scd-message" (i18n/c count-files)) (tr "modals.delete-shared-confirm.scd-message" (i18n/c count-files))) (if (> count-libraries 1) - (tr "modals.unpublish-shared-confirm.scd-message-plural" (i18n/c count-files)) - (tr "modals.unpublish-shared-confirm.scd-message" (i18n/c count-files))) - ) + (tr "modals.unpublish-shared-confirm.scd-message-plural" (i18n/c count-files)) + (tr "modals.unpublish-shared-confirm.scd-message" (i18n/c count-files)))) + hint (if is-delete? - "" - (tr "modals.unpublish-shared-confirm.hint" (i18n/c count-files))) + (if (> count-libraries 1) + (tr "modals.delete-shared-confirm.hint-plural" (i18n/c count-files)) + (tr "modals.delete-shared-confirm.hint" (i18n/c count-files))) + (if (> count-libraries 1) + (tr "modals.unpublish-shared-confirm.hint-plural" (i18n/c count-files)) + (tr "modals.unpublish-shared-confirm.hint" (i18n/c count-files)))) accept-fn (mf/use-callback @@ -91,19 +100,21 @@ [:div.modal-content.delete-shared (when (and (string? message) (not= message "")) [:h3 message]) - - (when (> (count files->shared) 0) - [:* - [:div - (when (and (string? scd-message) (not= scd-message "")) - [:h3 scd-message]) - [:ul.file-list - (for [[id file] files->shared] - [:li.modal-item-element - {:key id} - [:span "- " (:name file)]])]] - (when (and (string? hint) (not= hint "")) - [:h3 hint])])] + (when (not= 0 count-libraries) + (if (> (count files->shared) 0) + [:* + [:div + (when (and (string? scd-message) (not= scd-message "")) + [:h3 scd-message]) + [:ul.file-list + (for [[id file] files->shared] + [:li.modal-item-element + {:key id} + [:span "- " (:name file)]])]] + (when (and (string? hint) (not= hint "")) + [:h3 hint])] + [:* + [:h3 no-files-message]]))] [:div.modal-footer [:div.action-buttons diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 78b80bbbc8..254d4194d0 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -9,6 +9,7 @@ [app.common.pages.helpers :as cph] [app.common.uuid :as uuid] [app.config :as cf] + [app.main.data.dashboard :as dd] [app.main.data.events :as ev] [app.main.data.exports :as de] [app.main.data.modal :as modal] @@ -116,8 +117,8 @@ add-shared-fn #(st/emit! (dwl/set-file-shared (:id file) true)) - del-shared-fn - #(st/emit! (dwl/set-file-shared (:id file) false)) + + on-add-shared (mf/use-fn @@ -135,14 +136,15 @@ on-remove-shared (mf/use-fn (mf/deps file) - #(st/emit! (modal/show - {:type :confirm - :message "" - :title (tr "modals.remove-shared-confirm.message" (:name file)) - :hint (tr "modals.remove-shared-confirm.hint") - :cancel-label :omit - :accept-label (tr "modals.remove-shared-confirm.accept") - :on-accept del-shared-fn}))) + (fn [event] + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (dd/fetch-libraries-using-files [file])) + (st/emit! (modal/show + {:type :delete-shared + :origin :unpublish + :on-accept #(st/emit! (dwl/set-file-shared (:id file) false)) + :count-libraries 1})))) handle-blur (fn [_] (let [value (-> edit-input-ref mf/ref-val dom/get-value)] @@ -281,7 +283,7 @@ [:ul.sub-menu.file (if (:is-shared file) [:li {:on-click on-remove-shared} - [:span (tr "dashboard.remove-shared")]] + [:span (tr "dashboard.unpublish-shared")]] [:li {:on-click on-add-shared} [:span (tr "dashboard.add-shared")]]) [:li.export-file {:on-click on-export-shapes} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index 7d1653690c..c9177130d2 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -192,7 +192,7 @@ (when @open? [:div.advanced-ops-body [:div.layout-row - [:div.direction-wrap.row-title "Align"] ;; TODO tradus + [:div.direction-wrap.row-title "Align"] [:div.btn-wrapper [:& align-self-row {:is-col? is-col? :align-self align-self diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index f144ad0c94..d151549182 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -3585,18 +3585,6 @@ msgstr "Fila" msgid "workspace.options.layout.gap" msgstr "Espaiat" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "centre" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "esquerra" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "dreta" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "Esquerra" @@ -3637,18 +3625,6 @@ msgstr "espaiat" msgid "workspace.options.layout.top" msgstr "Dalt" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "baix" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "centre" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "dalt" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Més colors" diff --git a/frontend/translations/de.po b/frontend/translations/de.po index fd8d9908c8..f046c5808e 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -3886,18 +3886,6 @@ msgstr "Reihe" msgid "workspace.options.layout.gap" msgstr "Abstand" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "Mitte" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "links" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "rechts" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "Links" @@ -3954,18 +3942,6 @@ msgstr "Layout" msgid "workspace.options.layout.top" msgstr "Oben" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "unten" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "Mitte" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "oben" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Weitere Farben" diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 5ad5e4b922..f5a489dd24 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1162,6 +1162,10 @@ msgstr "Delete invitation" msgid "labels.delete-multi-files" msgstr "Delete %s files" +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.unpublish-multi-files" +msgstr "Unpublish %s files" + #: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/files.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "labels.drafts" msgstr "Drafts" @@ -1599,14 +1603,20 @@ msgstr[1] "Are you sure you want to delete these files?" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message" msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "This file has libraries that are being used in this file:" -msgstr[1] "This file has libraries that are being used in these files:" +msgstr[0] "Some of the assets in this file's library are in use here:" +msgstr[1] "Some of the assets in these file's libraries are in use here:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "These files have libraries that are being used in this file:" -msgstr[1] "These files have libraries that are being used in these files:" +msgid "modals.delete-shared-confirm.no-files-message" +msgid_plural "modals.delete-shared-confirm.no-files-message" +msgstr[0] "None of the assets in this file's library are in use. They will be deleted along with the file." +msgstr[1] "None of the assets in these file's libraries are in use. They will be deleted along with the files." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.no-files-message" +msgid_plural "modals.unpublish-shared-confirm.no-files-message" +msgstr[0] "None of the assets in this file's library are in use." +msgstr[1] "None of the assets in these file's libraries are in use." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" @@ -1614,6 +1624,26 @@ msgid_plural "modals.delete-shared-confirm.title" msgstr[0] "Deleting file" msgstr[1] "Deleting files" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.hint" +msgid_plural "modals.delete-shared-confirm.hint" +msgstr[0] "" +"If you delete it, those assets will move tothe local library" +"of this file. Any unsued assets will be lost." +msgstr[1] "" +"If you delete it, those assets will move tothe local library" +"of these files. Any unsued assets will be lost." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.hint-plural" +msgid_plural "modals.delete-shared-confirm.hint-plural" +msgstr[0] "" +"If you delete them, those assets will move tothe local library" +"of this file. Any unsued assets will be lost." +msgstr[1] "" +"If you delete them, those assets will move tothe local library" +"of these files. Any unsued assets will be lost." + #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" msgstr "Delete team" @@ -1745,8 +1775,20 @@ msgstr "Unpublish" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "If you unpublish it, the assets in it became a library of this file." -msgstr[1] "If you unpublish it, the assets in it became a library of these files." +msgstr[0] "If you unpublish it, those assets will move to the local library of this file." +msgstr[1] "If you unpublish it, those assets will move to the local library of these files." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint-plural" +msgid_plural "modals.unpublish-shared-confirm.hint-plural" +msgstr[0] "If you unpublish them, those assets will move to the local library of this file." +msgstr[1] "If you unpublish them, those assets will move to the local library of these files." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint-plural" +msgid_plural "modals.unpublish-shared-confirm.hint-plural" +msgstr[0] "If you unpublish them, the assets in them became a library of this file." +msgstr[1] "If you unpublish them, the assets in them became a library of these files." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" @@ -1757,8 +1799,14 @@ msgstr[1] "Are you sure you want to unpublish these libraries?" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "It's in use in this file:" -msgstr[1] "It's in use in these files:" +msgstr[0] "Some of the assets in this file's library are in use here:" +msgstr[1] "Some of the assets in these file's libraries are in use here:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message-plural" +msgid_plural "modals.unpublish-shared-confirm.scd-message-plural" +msgstr[0] "Some of the assets in this file's library are in use here:" +msgstr[1] "Some of the assets in these file's libraries are in use here:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.title" @@ -3480,18 +3528,6 @@ msgstr "Row" msgid "workspace.options.layout.gap" msgstr "Gap" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "center" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "left" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "right" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "Left" @@ -3540,18 +3576,6 @@ msgstr "space between" msgid "workspace.options.layout.top" msgstr "Top" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "bottom" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "center" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "top" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "More colors" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index c50392f7d0..0a72bbf05d 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -391,8 +391,8 @@ msgstr "Exportar librerias compartidas" msgid "dashboard.export.options.detach.message" msgstr "" -"Las librerias compartidas no se incluirán en la exportación y ningún " -"recurso será incluido en la librería. " +"Las biblioteca compartidas no se incluirán en la exportación y ningún " +"recurso será incluido en la biblioteca. " msgid "dashboard.export.options.detach.title" msgstr "Usar los recursos como objetos básicos" @@ -1290,6 +1290,10 @@ msgstr "Eliminar invitation" msgid "labels.delete-multi-files" msgstr "Borrar %s archivos" +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "labels.unpublish-multi-files" +msgstr "Despublicar %s archivos" + #: src/app/main/ui/dashboard/projects.cljs, #: src/app/main/ui/dashboard/sidebar.cljs, #: src/app/main/ui/dashboard/files.cljs, @@ -1724,27 +1728,27 @@ msgstr "Eliminar conversación" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-file-confirm.accept" -msgstr "Eliminar archivo" +msgstr "Borrar archivo" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-file-confirm.message" -msgstr "¿Seguro que quieres eliminar este archivo?" +msgstr "¿Seguro que quieres borrar este archivo?" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-file-confirm.title" -msgstr "Eliminando archivo" +msgstr "Borrando archivo" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-file-multi-confirm.accept" -msgstr "Eliminar archivos" +msgstr "Borrar archivos" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-file-multi-confirm.message" -msgstr "¿Seguro que quieres eliminar %s archivos?" +msgstr "¿Seguro que quieres borrar %s archivos?" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-file-multi-confirm.title" -msgstr "Eliminando %s archivos" +msgstr "Borrando %s archivos" msgid "modals.delete-font-variant.message" msgstr "" @@ -1800,30 +1804,46 @@ msgstr[1] "¿Seguro que quieres borrar estos archivos?" #: src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.scd-message" msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "" -"El archivo que quieres borrar tiene una librería que se está usando en este " -"archivo:" -msgstr[1] "" -"El archivo que quieres borrar tiene una librería que se está usando en " -"estos archivos:" +msgstr[0] "Algunos elementos de esta biblioteca están siendo usados por:" +msgstr[1] "Algunos elementos de estas biblitecas están siendo usados por:" -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message-plural" -msgid_plural "modals.delete-shared-confirm.scd-message-plural" -msgstr[0] "" -"Los archivos que quieres borrar tienen una librería que se está usando en " -"este archivo:" -msgstr[1] "" -"Los archivos que quieres borrar tienen una librería que se está usando en " -"estos archivos:" - -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "Borrar archivo" -msgstr[1] "Borrar archivos" +msgstr[0] "Borrando archivo" +msgstr[1] "Borrando archivos" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.hint" +msgid_plural "modals.delete-shared-confirm.hint" +msgstr[0] "" +"Si lo borras, esos elementos pasarán a formar parte de la biblioteca local " +"de este archivo. Cualquier elemento en desuso se perderá." +msgstr[1] "" +"Si lo borras, los elementos pasarán a formar parte de la biblioteca local de " +"estos archivos. Cualquier elemento en desuso se perderá." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.hint-plural" +msgid_plural "modals.delete-shared-confirm.hint-plural" +msgstr[0] "" +"Si los borras, esos elementos pasarán a formar parte de la biblioteca local " +"de este archivo. Cualquier elemento en desuso se perderá." +msgstr[1] "" +"Si los borras, los elementos pasarán a formar parte de la biblioteca local de " +"estos archivos. Cualquier elemento en desuso se perderá." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.no-files-message" +msgid_plural "modals.delete-shared-confirm.no-files-message" +msgstr[0] "Ninguno de los elementos de su biblioteca están en uso. Se borrarán junto con el archivo." +msgstr[1] "Ninguno de los elementos de sus bibliotecas están en uso. Se borrarán junto con el archivo." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.no-files-message" +msgid_plural "modals.unpublish-shared-confirm.no-files-message" +msgstr[0] "Ninguno de los elementos de su biblioteca están en uso." +msgstr[1] "Ninguno de los elementos de sus bibliotecas están en uso." #: src/app/main/ui/delete_shared.cljs msgid "modals.delete-shared.title" @@ -1972,8 +1992,17 @@ msgstr[1] "" "Si la despublicas, los elementos pasarán a formar parte de la biblioteca de " "los archivos." -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint-plural" +msgid_plural "modals.unpublish-shared-confirm.hint-plural" +msgstr[0] "" +"Si las despublicas, los elementos pasarán a formar parte de la biblioteca " +"del archivo." +msgstr[1] "" +"Si las despublicas, los elementos pasarán a formar parte de la biblioteca de " +"los archivos." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" msgid_plural "modals.unpublish-shared-confirm.message" msgstr[0] "¿Seguro que quieres despublicar esta biblioteca?" @@ -1986,8 +2015,13 @@ msgid_plural "modals.unpublish-shared-confirm.scd-message" msgstr[0] "Está siendo usada en este archivo:" msgstr[1] "Está siendo usada en estos archivos:" -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.scd-message-plural" +msgid_plural "modals.unpublish-shared-confirm.scd-message-plural" +msgstr[0] "Están siendo usadas en este archivo:" +msgstr[1] "Están siendo usadas en estos archivos:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.title" msgid_plural "modals.unpublish-shared-confirm.title" msgstr[0] "Despublicar biblioteca" @@ -1997,13 +2031,13 @@ msgstr[1] "Despublicar bibliotecas" #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" msgstr "" -"Vas a actualizar componentes en una librería compartida. Esto puede afectar " +"Vas a actualizar componentes en una biblioteca compartida. Esto puede afectar " "a otros archivos que la usen." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.message" -msgstr "Actualizar componentes en librería" +msgstr "Actualizar componentes en biblioteca" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs @@ -2019,13 +2053,13 @@ msgstr "Cancelar" #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" msgstr "" -"Vas a actualizar un componente en una librería compartida. Esto puede " +"Vas a actualizar un componente en una biblioteca compartida. Esto puede " "afectar a otros archivos que la usen." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.message" -msgstr "Actualizar un componente en librería" +msgstr "Actualizar un componente en biblioteca" #: src/app/main/ui/dashboard/team.cljs msgid "notifications.invitation-email-sent" @@ -3897,18 +3931,6 @@ msgstr "Fila" msgid "workspace.options.layout.gap" msgstr "Espacio" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "izquierda" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "derecha" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "Izquierda" @@ -3957,18 +3979,6 @@ msgstr "espaciar" msgid "workspace.options.layout.top" msgstr "Arriba" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "abajo" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "arriba" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Más colores" @@ -4719,7 +4729,7 @@ msgstr "Ignorar" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.there-are-updates" -msgstr "Hay actualizaciones en librerías compartidas" +msgstr "Hay actualizaciones en bibliotecas compartidas" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.update" diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index 7a704ea56a..c2064616d6 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -3749,18 +3749,6 @@ msgstr "Lerroa" msgid "workspace.options.layout.gap" msgstr "Saltoa" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "erdian" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "ezkerrean" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "eskuman" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "Ezkerrean" @@ -3809,18 +3797,6 @@ msgstr "Tarteko espazioa" msgid "workspace.options.layout.top" msgstr "Goian" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "behean" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "erdian" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "goian" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Kolore gehiago" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 33caf2b739..0efd2d5606 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -3451,18 +3451,6 @@ msgstr "Colonne" msgid "workspace.options.layout.direction.top" msgstr "Colonne inversée" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "au centre" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "a gauche" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "a droite" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "A gauche" @@ -3495,18 +3483,6 @@ msgstr "Mise en page" msgid "workspace.options.layout.top" msgstr "En haut" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "en bas" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "au centre" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "en haut" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Plus de couleurs" diff --git a/frontend/translations/he.po b/frontend/translations/he.po index 4b7b77e0b9..5c58ec20cc 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -3857,18 +3857,6 @@ msgstr "שורה" msgid "workspace.options.layout.gap" msgstr "מרווח" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "מרכז" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "שמאל" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "ימין" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "שמאל" @@ -3913,18 +3901,6 @@ msgstr "רווח מסביב" msgid "workspace.options.layout.space-between" msgstr "רווח בין לבין" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "תחתית" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "מרכז" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "עליון" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "צבעים נוספים" diff --git a/frontend/translations/hr.po b/frontend/translations/hr.po index 6c045ea50b..83f6625286 100644 --- a/frontend/translations/hr.po +++ b/frontend/translations/hr.po @@ -3790,18 +3790,6 @@ msgstr "Obrnuta kolumna" msgid "workspace.options.layout.gap" msgstr "Razmak" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "sredina" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "lijevo" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "desno" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "Lijevo" @@ -3861,18 +3849,6 @@ msgstr "Layout" msgid "workspace.options.layout.top" msgstr "Vrh" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "dno" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "sredina" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "vrh" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs #, fuzzy msgid "workspace.options.layout.wrap" diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po index 6a4bd2bc05..3fdb268ff0 100644 --- a/frontend/translations/pl.po +++ b/frontend/translations/pl.po @@ -3596,18 +3596,6 @@ msgstr "Prawa" msgid "workspace.options.layout.top" msgstr "Góra" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "dół" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "środek" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "góra" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Więcej kolorów" diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 96567ea623..0b83476979 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -3740,18 +3740,6 @@ msgstr "Coluna inversa" msgid "workspace.options.layout.gap" msgstr "Espaço" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "esquerda" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "direita" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "Esquerda" @@ -3808,18 +3796,6 @@ msgstr "Layout" msgid "workspace.options.layout.top" msgstr "Topo" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "abaixo" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "topo" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.wrap" msgstr "quebrado" diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index b42e430270..423d51d110 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -3737,18 +3737,6 @@ msgstr "Coluna inversa" msgid "workspace.options.layout.gap" msgstr "Espaço" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "esquerda" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "direita" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "Esquerda" @@ -3805,18 +3793,6 @@ msgstr "Layout" msgid "workspace.options.layout.top" msgstr "Topo" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "abaixo" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "topo" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.wrap" msgstr "envolver" diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 03b054e6d4..239d4c3155 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -3895,18 +3895,6 @@ msgstr "Satır" msgid "workspace.options.layout.gap" msgstr "Boşluk" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "orta" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "sol" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "sağ" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "Sol" @@ -3951,18 +3939,6 @@ msgstr "etrafında boşluk" msgid "workspace.options.layout.space-between" msgstr "arasında boşluk" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "alt" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "orta" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "üst" - #: src/app/main/ui/workspace/sidebar/options/menus/color_selection.cljs msgid "workspace.options.more-colors" msgstr "Daha fazla renk" diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index deb3229e4f..9f55473b38 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -3624,18 +3624,6 @@ msgstr "倒排列" msgid "workspace.options.layout.gap" msgstr "差距" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.center" -msgstr "居中" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.left" -msgstr "居左" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.h.right" -msgstr "居右" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.left" msgstr "左" @@ -3688,18 +3676,6 @@ msgstr "布局" msgid "workspace.options.layout.top" msgstr "顶部" -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.bottom" -msgstr "底部" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.center" -msgstr "居中" - -#: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs -msgid "workspace.options.layout.v.top" -msgstr "顶部" - #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs msgid "workspace.options.layout.wrap" msgstr "底部" From c3fe8c8ebdf648ff9a29ab8ef858f5a22e9fc06a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 27 Oct 2022 11:36:32 +0200 Subject: [PATCH 152/682] :bug: Upgrade gitpod docker file --- docker/gitpod/Dockerfile | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docker/gitpod/Dockerfile b/docker/gitpod/Dockerfile index 1ed6876348..cc4f7f9be0 100644 --- a/docker/gitpod/Dockerfile +++ b/docker/gitpod/Dockerfile @@ -12,17 +12,18 @@ RUN set -ex; \ brew install ghostscript; \ brew install mailhog; \ brew install openldap; \ - brew install poppler-utils; \ + brew install poppler; \ sudo mkdir -p /var/log/nginx; \ sudo chown gitpod:gitpod /var/log/nginx -COPY docker/gitpod/files/nginx.conf /etc/nginx/nginx.conf +COPY files/nginx.conf /etc/nginx/nginx.conf USER root -ENV CLOJURE_VERSION=1.10.3.822 \ - CLJKONDO_VERSION=2021.03.31 \ - BABASHKA_VERSION=0.3.2 \ +ENV NODE_VERSION=v16.17.0 \ + CLOJURE_VERSION=1.11.1.1165 \ + CLJKONDO_VERSION=2022.09.08 \ + BABASHKA_VERSION=0.9.162 \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 @@ -114,7 +115,7 @@ ENV PATH="/usr/lib/jvm/openjdk16/bin:/usr/local/nodejs/bin:$PATH" \ PENPOT_SMTP_TLS=false \ PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com \ PENPOT_SMTP_DEFAULT_FROM=no-reply@example.com \ - PENPOT_DATABASE_URI="postgresql://localhost/penpot" + PENPOT_DATABASE_URI="postgresql://localhost/penpot" \ PENPOT_REDIS_URI="redis://localhost/0" # TODO Retrieve OpenLDAP from rroemhild/docker-test-openldap From d663d2bebf0a2965ec49b5ed437bc7e2edaa9630 Mon Sep 17 00:00:00 2001 From: Eva Date: Mon, 24 Oct 2022 13:22:31 +0200 Subject: [PATCH 153/682] :sparkles: Add new handoff flex item section" --- common/src/app/common/geom/shapes/layout.cljc | 14 +- .../styles/main/partials/handoff.scss | 7 +- .../app/main/data/workspace/shape_layout.cljs | 33 ++-- .../main/data/workspace/state_helpers.cljs | 4 + frontend/src/app/main/refs.cljs | 12 ++ frontend/src/app/main/ui/formats.cljs | 20 +++ .../main/ui/viewer/handoff/attributes.cljs | 34 ++-- .../ui/viewer/handoff/attributes/layout.cljs | 3 +- .../handoff/attributes/layout_flex.cljs | 152 ++++++++++++++++++ .../attributes/layout_flex_element.cljs | 133 +++++++++++++++ .../sidebar/options/menus/layout_item.cljs | 18 ++- frontend/src/app/util/code_gen.cljs | 6 +- 12 files changed, 381 insertions(+), 55 deletions(-) create mode 100644 frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex.cljs create mode 100644 frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index 9d7300aca9..dc27fb429f 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -11,7 +11,7 @@ [app.common.geom.shapes.rect :as gre])) ;; :layout ;; true if active, false if not -;; :layout-dir ;; :right, :left, :top, :bottom +;; :layout-flex-dir ;; :row, :column, :reverse-row, :reverse-column ;; :layout-gap ;; number could be negative ;; :layout-type ;; :packed, :space-between, :space-around ;; :layout-wrap-type ;; :wrap, :no-wrap @@ -21,12 +21,12 @@ ;; :layout-v-orientation ;; :left, :center, :right (defn col? - [{:keys [layout-dir]}] - (or (= :right layout-dir) (= :left layout-dir))) + [{:keys [layout-flex-dir]}] + (or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir))) (defn row? - [{:keys [layout-dir]}] - (or (= :top layout-dir) (= :bottom layout-dir))) + [{:keys [layout-flex-dir]}] + (or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir))) (defn h-start? [{:keys [layout-h-orientation]}] @@ -247,9 +247,9 @@ (defn calc-layout-data "Digest the layout data to pass it to the constrains" - [{:keys [layout-dir] :as shape} children layout-bounds] + [{:keys [layout-flex-dir] :as shape} children layout-bounds] - (let [reverse? (or (= :left layout-dir) (= :bottom layout-dir)) + (let [reverse? (or (= :reverse-row layout-flex-dir) (= :reverse-column layout-flex-dir)) layout-bounds (-> layout-bounds (add-padding shape)) children (cond->> children reverse? reverse) layout-lines diff --git a/frontend/resources/styles/main/partials/handoff.scss b/frontend/resources/styles/main/partials/handoff.scss index a8e2293971..b5f2fdfb04 100644 --- a/frontend/resources/styles/main/partials/handoff.scss +++ b/frontend/resources/styles/main/partials/handoff.scss @@ -87,7 +87,7 @@ position: relative; display: flex; flex-direction: row; - padding: 1rem 1.6rem 1rem 0.5rem; + padding: 0.6rem 1.6rem 0.6rem 0.5rem; .attributes-label, .attributes-value { @@ -96,9 +96,12 @@ text-overflow: ellipsis; white-space: nowrap; width: 50%; + .items { + margin-right: 5px; + } } .copy-button { - padding: 1rem 0.5rem; + padding: 0.6rem 0.5rem; margin-top: 0.25rem; } } diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 0b96b54b57..710995c9bc 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -16,34 +16,29 @@ (def layout-keys [:layout - :layout-dir + :layout-flex-dir + :layout-gap-type :layout-gap - :layout-type + :layout-align-items + :layout-justify-content + :layout-align-content :layout-wrap-type :layout-padding-type :layout-padding - :layout-h-orientation - :layout-v-orientation - - :layout-align-content - :layout-flex-dir - :layout-align-items - :layout-justify-content - :layout-gap-type ]) (def initial-flex-layout - {:layout :flex - :layout-flex-dir :row - :layout-gap-type :simple - :layout-gap {:row-gap 0 :column-gap 0} - :layout-align-items :start + {:layout :flex + :layout-flex-dir :row + :layout-gap-type :simple + :layout-gap {:row-gap 0 :column-gap 0} + :layout-align-items :start :layout-justify-content :start - :layout-align-content :strech - :layout-wrap-type :no-wrap - :layout-padding-type :simple - :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}}) + :layout-align-content :strech + :layout-wrap-type :no-wrap + :layout-padding-type :simple + :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}}) (def initial-grid-layout ;; TODO {:layout :grid}) diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index fab39b72c1..c2f68bd627 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -28,6 +28,10 @@ ([state page-id] (dm/get-in state [:workspace-data :pages-index page-id :objects]))) +(defn lookup-viewer-objects + ([state page-id] + (dm/get-in state [:viewer :pages page-id :objects]))) + (defn lookup-page-options ([state] (lookup-page-options state (:current-page-id state))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 4b0c22d8c0..5717f7c86d 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -363,6 +363,10 @@ ;; ---- Viewer refs +(defn lookup-viewer-objects-by-id + [page-id] + (l/derived #(wsh/lookup-viewer-objects % page-id) st/state =)) + (def viewer-data (l/derived :viewer st/state)) @@ -427,5 +431,13 @@ (some #(-> (cph/get-parent objects %) :layout)))) workspace-page-objects)) +(defn get-flex-child-viewer? + [ids page-id] + (l/derived + (fn [state] + (let [objects (wsh/lookup-viewer-objects state page-id)] + (filterv #(= :flex (:layout (cph/get-parent objects %))) ids))) + st/state =)) + (def colorpicker (l/derived :colorpicker st/state)) diff --git a/frontend/src/app/main/ui/formats.cljs b/frontend/src/app/main/ui/formats.cljs index f093677365..c90ec87ab6 100644 --- a/frontend/src/app/main/ui/formats.cljs +++ b/frontend/src/app/main/ui/formats.cljs @@ -41,3 +41,23 @@ (when (d/num? value) (let [value (mth/precision value 0)] (dm/str value)))) + +(defn format-padding-margin-shorthand + [values] + ;; Values come in [p1 p2 p3 p4] + (let [[p1 p2 p3 p4] values] + (cond + (apply = values) + {:p1 p1} + + (= 4 (count (set values))) + {:p1 p1 :p2 p2 :p3 p3} + + (and (= p1 p3) (= p2 p4)) + {:p1 p1 :p3 p3} + + (and (not= p1 p3) (= p2 p4)) + {:p1 p1 :p2 p2 :p3 p3} + + :else + {:p1 p1 :p2 p2 :p3 p3}))) \ No newline at end of file diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes.cljs index c175378e5c..91e803da18 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes.cljs @@ -12,6 +12,8 @@ [app.main.ui.viewer.handoff.attributes.fill :refer [fill-panel]] [app.main.ui.viewer.handoff.attributes.image :refer [image-panel]] [app.main.ui.viewer.handoff.attributes.layout :refer [layout-panel]] + [app.main.ui.viewer.handoff.attributes.layout-flex :refer [layout-flex-panel]] + [app.main.ui.viewer.handoff.attributes.layout-flex-element :refer [layout-flex-element-panel]] [app.main.ui.viewer.handoff.attributes.shadow :refer [shadow-panel]] [app.main.ui.viewer.handoff.attributes.stroke :refer [stroke-panel]] [app.main.ui.viewer.handoff.attributes.svg :refer [svg-panel]] @@ -20,14 +22,14 @@ [rumext.v2 :as mf])) (def type->options - {:multiple [:fill :stroke :image :text :shadow :blur] - :frame [:layout :fill :stroke :shadow :blur] - :group [:layout :svg] - :rect [:layout :fill :stroke :shadow :blur :svg] - :circle [:layout :fill :stroke :shadow :blur :svg] + {:multiple [:fill :stroke :image :text :shadow :blur :layout-flex-item] + :frame [:layout :fill :stroke :shadow :blur :layout-flex :layout-flex-item] + :group [:layout :svg :layout-flex-item] + :rect [:layout :fill :stroke :shadow :blur :svg :layout-flex-item] + :circle [:layout :fill :stroke :shadow :blur :svg :layout-flex-item] :path [:layout :fill :stroke :shadow :blur :svg] - :image [:image :layout :fill :stroke :shadow :blur :svg] - :text [:layout :text :shadow :blur :stroke]}) + :image [:image :layout :fill :stroke :shadow :blur :svg :layout-flex-item] + :text [:layout :text :shadow :blur :stroke :layout-flex-item]}) (mf/defc attributes [{:keys [page-id file-id shapes frame]}] @@ -39,14 +41,16 @@ [:div.element-options (for [option options] [:> (case option - :layout layout-panel - :fill fill-panel - :stroke stroke-panel - :shadow shadow-panel - :blur blur-panel - :image image-panel - :text text-panel - :svg svg-panel) + :layout layout-panel + :layout-flex layout-flex-panel + :layout-flex-item layout-flex-element-panel + :fill fill-panel + :stroke stroke-panel + :shadow shadow-panel + :blur blur-panel + :image image-panel + :text text-panel + :svg svg-panel) {:shapes shapes :frame frame}]) [:& exports diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs index 7f9e70aad2..a3068085c4 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs @@ -91,4 +91,5 @@ [:& copy-button {:data (copy-data (first shapes))}])] (for [shape shapes] - [:& layout-block {:shape shape}])]) + [:& layout-block {:shape shape + :key (:id shape)}])]) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex.cljs new file mode 100644 index 0000000000..2c7e61b734 --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex.cljs @@ -0,0 +1,152 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.viewer.handoff.attributes.layout-flex + (:require + [app.common.data :as d] + [app.main.ui.components.copy-button :refer [copy-button]] + [app.main.ui.formats :as fm] + [app.util.code-gen :as cg] + [cuerdas.core :as str] + [rumext.v2 :as mf])) + +(defn format-gap + [gap-values] + (let [row-gap (:row-gap gap-values) + column-gap (:column-gap gap-values)] + (if (= row-gap column-gap) + (str/fmt "%spx" row-gap) + (str/fmt "%spx %spx" row-gap column-gap)))) + +(defn format-padding + [padding-values] + (let [short-hand (fm/format-padding-margin-shorthand (vals padding-values)) + parsed-values (map #(str/fmt "%spx" %) (vals short-hand))] + (str/join " " parsed-values))) + +(def properties [:layout + :layout-flex-dir + :layout-align-items + :layout-justify-content + :layout-gap + :layout-padding + :layout-wrap-type]) + +(def align-contet-prop [:layout-align-content]) + +(def layout-flex-params + {:props [:layout + :layout-align-items + :layout-flex-dir + :layout-justify-content + :layout-gap + :layout-padding + :layout-wrap-type] + :to-prop {:layout "display" + :layout-flex-dir "flex-direction" + :layout-align-items "align-items" + :layout-justify-content "justify-content" + :layout-wrap-type "wrap" + :layout-gap "gap" + :layout-padding "padding"} + :format {:layout name + :layout-flex-dir name + :layout-align-items name + :layout-justify-content name + :layout-wrap-type name + :layout-gap format-gap + :layout-padding format-padding}}) + +(def layout-align-content-params + {:props [:layout-align-content] + :to-prop {:layout-align-content "align-content"} + :format {:layout-align-content name}}) + +(defn copy-data + ([shape] + (let [properties-for-copy (if (:layout-align-content shape) + (into [] (concat properties align-contet-prop)) + properties)] + (apply copy-data shape properties-for-copy))) + + ([shape & properties] + (let [params (if (:layout-align-content shape) + (d/deep-merge layout-align-content-params layout-flex-params ) + layout-flex-params)] + (cg/generate-css-props shape properties params)))) + +(mf/defc manage-padding + [{:keys [padding type]}] + (let [values (fm/format-padding-margin-shorthand (vals padding))] + [:div.attributes-value + (for [[k v] values] + [:span.items {:key (str type "-" k "-" v)} v "px"])])) + +(mf/defc layout-block + [{:keys [shape]}] + [:* + [:div.attributes-unit-row + [:div.attributes-label "Display"] + [:div.attributes-value "Flex"] + [:& copy-button {:data (copy-data shape)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Direction"] + [:div.attributes-value (str/capital (d/name (:layout-flex-dir shape)))] + [:& copy-button {:data (copy-data shape :layout-flex-dir)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Align-items"] + [:div.attributes-value (str/capital (d/name (:layout-align-items shape)))] + [:& copy-button {:data (copy-data shape :layout-align-items)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Justify-content"] + [:div.attributes-value (str/capital (d/name (:layout-justify-content shape)))] + [:& copy-button {:data (copy-data shape :layout-justify-content)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Wrap"] + [:div.attributes-value (str/capital (d/name (:layout-wrap-type shape)))] + [:& copy-button {:data (copy-data shape :layout-wrap-type)}]] + + (when (= :wrap (:layout-wrap-type shape)) + [:div.attributes-unit-row + [:div.attributes-label "Align-content"] + [:div.attributes-value (str/capital (d/name (:layout-align-content shape)))] + [:& copy-button {:data (copy-data shape :layout-align-content)}]]) + + [:div.attributes-unit-row + [:div.attributes-label "Gap"] + (if (= (:row-gap (:layout-gap shape)) (:column-gap (:layout-gap shape))) + [:div.attributes-value + [:span (str/capital (d/name (:row-gap (:layout-gap shape)))) "px"]] + [:div.attributes-value + [:span.items (:row-gap (:layout-gap shape)) "px"] + [:span (:column-gap (:layout-gap shape)) "px"]]) + [:& copy-button {:data (copy-data shape :layout-gap)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Padding"] + [:& manage-padding {:padding (:layout-padding shape) :type "padding"}] + [:& copy-button {:data (copy-data shape :layout-padding)}]]]) + +(defn has-flex? [shape] + (= :flex (:layout shape))) + +(mf/defc layout-flex-panel + [{:keys [shapes]}] + (let [shapes (->> shapes (filter has-flex?))] + (when (seq shapes) + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text "Layout"] + (when (= (count shapes) 1) + [:& copy-button {:data (copy-data (first shapes))}])] + + (for [shape shapes] + [:& layout-block {:shape shape + :key (:id shape)}])]))) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs new file mode 100644 index 0000000000..31d7da405b --- /dev/null +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs @@ -0,0 +1,133 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.viewer.handoff.attributes.layout-flex-element + (:require + [app.common.data :as d] + [app.main.refs :as refs] + [app.main.ui.components.copy-button :refer [copy-button]] + [app.main.ui.formats :as fmt] + [app.main.ui.hooks :as hooks] + [app.util.code-gen :as cg] + [cuerdas.core :as str] + [rumext.v2 :as mf])) + + +(defn format-margin + [margin-values] + (let [short-hand (fmt/format-padding-margin-shorthand (vals margin-values)) + parsed-values (map #(str/fmt "%spx" %) (vals short-hand))] + (str/join " " parsed-values))) + +(def properties [:layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} + :layout-item-h-sizing ;; :fill-width :fix-width :auto-width + :layout-item-v-sizing ;; :fill-height :fix-height :auto-height + :layout-item-max-h ;; num + :layout-item-min-h ;; num + :layout-item-max-w ;; num + :layout-item-min-w ;; num + :layout-item-align-self ;; :start :end :center :strech :baseline + ]) + + +(def layout-flex-item-params + {:props [:layout-item-margin + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self] + :to-prop {:layout-item-margin "margin" + :layout-item-h-sizing "width" + :layout-item-v-sizing "height" + :layout-item-align-self "align-self" + :layout-item-max-h "max. height" + :layout-item-min-h "min. height" + :layout-item-max-w "max. width" + :layout-item-min-w "min. width"} + :format {:layout-item-margin format-margin + :layout-item-h-sizing name + :layout-item-v-sizing name + :layout-item-align-self name}}) + +(defn copy-data + ([shape] + (apply copy-data shape properties)) + + ([shape & properties] + (cg/generate-css-props shape properties layout-flex-item-params))) + +(mf/defc manage-margin + [{:keys [margin type]}] + (let [values (fmt/format-padding-margin-shorthand (vals margin))] + [:div.attributes-value + (for [[k v] values] + [:span.items {:key (str type "-" k "-" v)} v "px"])])) + +(mf/defc layout-element-block + [{:keys [shape]}] + [:* + [:div.attributes-unit-row + [:div.attributes-label "Width"] + [:div.attributes-value (str/capital (d/name (:layout-item-h-sizing shape)))] + [:& copy-button {:data (copy-data shape :layout-item-h-sizing)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Height"] + [:div.attributes-value (str/capital (d/name (:layout-item-v-sizing shape)))] + [:& copy-button {:data (copy-data shape :layout-item-v-sizing)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Align self"] + [:div.attributes-value (str/capital (d/name (:layout-item-align-self shape)))] + [:& copy-button {:data (copy-data shape :layout-item-align-self)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Margin"] + [:& manage-margin {:margin (:layout-item-margin shape) :type "margin"}] + [:& copy-button {:data (copy-data shape :layout-item-margin)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Max. width"] + [:div.attributes-value (fmt/format-pixels (:layout-item-max-w shape))] + [:& copy-button {:data (copy-data shape :layout-item-max-w)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Min. width"] + [:div.attributes-value (fmt/format-pixels (:layout-item-min-w shape))] + [:& copy-button {:data (copy-data shape :layout-item-min-w)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Max. height"] + [:div.attributes-value (fmt/format-pixels (:layout-item-max-h shape))] + [:& copy-button {:data (copy-data shape :layout-item-max-h)}]] + + [:div.attributes-unit-row + [:div.attributes-label "Min. height"] + [:div.attributes-value (fmt/format-pixels (:layout-item-min-w shape))] + [:& copy-button {:data (copy-data shape :layout-item-min-h)}]]]) + +(defn get-flex-elements [page-id shapes] + (let [ids (mapv :id shapes) + ids (hooks/use-equal-memo ids) + get-layout-children-refs (mf/use-memo (mf/deps ids page-id) #(refs/get-flex-child-viewer? ids page-id))] + + (mf/deref get-layout-children-refs))) + +(mf/defc layout-flex-element-panel + [{:keys [shapes]}] + (let [route (mf/deref refs/route) + page-id (:page-id (:query-params route)) + shapes (get-flex-elements page-id shapes)] + (when (and (= (count shapes) 1) (seq shapes)) + [:div.attributes-block + [:div.attributes-block-title + [:div.attributes-block-title-text "Flex element"] + [:& copy-button {:data (copy-data (first shapes))}]] + + [:& layout-element-block {:shape (first shapes)}]]))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index c9177130d2..78b9ccbd40 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -18,14 +18,16 @@ [rumext.v2 :as mf])) (def layout-item-attrs - [:layout-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} - :layout-margin-type ;; :simple :multiple - :layout-h-behavior ;; :fill :fix :auto - :layout-v-behavior ;; :fill :fix :auto - :layout-max-h ;; num - :layout-min-h ;; num - :layout-max-w ;; num - :layout-min-w ]) ;; num + [:layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} + :layout-item-margin-type ;; :simple :multiple + :layout-item-h-sizing ;; :fill :fix :auto + :layout-item-v-sizing ;; :fill :fix :auto + :layout-item-max-h ;; num + :layout-item-min-h ;; num + :layout-item-max-w ;; num + :layout-item-min-w ;; num + :layout-item-align-self ;; :start :end :center :strech :baseline + ]) (mf/defc margin-section [{:keys [values change-margin-style on-margin-change] :as props}] diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index 37d398e04a..804d9af4bc 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -55,9 +55,9 @@ :shadow {:props [:shadow] :to-prop {:shadow :box-shadow} :format {:shadow #(str/join ", " (map shadow->css %1))}} - :blur {:props [:blur] - :to-prop {:blur "filter"} - :format {:blur #(str/fmt "blur(%spx)" (:value %))}}}) + :blur {:props [:blur] + :to-prop {:blur "filter"} + :format {:blur #(str/fmt "blur(%spx)" (:value %))}}}) (def style-text {:props [:fill-color From 38d74b93b332d904560c75b70ad3bc7ee28ca883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 3 Nov 2022 14:06:02 +0100 Subject: [PATCH 154/682] :bug: Fix some typos in library modals --- frontend/src/app/main/ui/delete_shared.cljs | 73 +++++----- frontend/translations/en.po | 114 +++++++-------- frontend/translations/es.po | 150 ++++++++++---------- 3 files changed, 170 insertions(+), 167 deletions(-) diff --git a/frontend/src/app/main/ui/delete_shared.cljs b/frontend/src/app/main/ui/delete_shared.cljs index 9ea6cdcc61..e670e195cb 100644 --- a/frontend/src/app/main/ui/delete_shared.cljs +++ b/frontend/src/app/main/ui/delete_shared.cljs @@ -26,43 +26,42 @@ accept-style origin count-libraries] :as props}] - (let [on-accept (or on-accept identity) - on-cancel (or on-cancel identity) - cancel-label (tr "labels.cancel") - accept-style (or accept-style :danger) - is-delete? (= origin :delete) - dashboard-local (mf/deref refs/dashboard-local) - files->shared (:files-with-shared dashboard-local) - count-files (count (keys files->shared)) - title (if is-delete? - (tr "modals.delete-shared-confirm.title" (i18n/c count-libraries)) - (tr "modals.unpublish-shared-confirm.title" (i18n/c count-libraries))) - message (if is-delete? - (tr "modals.delete-shared-confirm.message" (i18n/c count-libraries)) - (tr "modals.unpublish-shared-confirm.message" (i18n/c count-libraries))) - accept-label (if is-delete? - (tr "modals.delete-shared-confirm.accept" (i18n/c count-libraries)) - (tr "modals.unpublish-shared-confirm.accept")) - - no-files-message (if is-delete? - (tr "modals.delete-shared-confirm.no-files-message" (i18n/c count-libraries)) - (tr "modals.unpublish-shared-confirm.no-files-message" (i18n/c count-libraries)) - ) - scd-message (if is-delete? - (if (> count-libraries 1) - (tr "modals.delete-shared-confirm.scd-message" (i18n/c count-files)) - (tr "modals.delete-shared-confirm.scd-message" (i18n/c count-files))) - (if (> count-libraries 1) - (tr "modals.unpublish-shared-confirm.scd-message-plural" (i18n/c count-files)) - (tr "modals.unpublish-shared-confirm.scd-message" (i18n/c count-files)))) - - hint (if is-delete? - (if (> count-libraries 1) - (tr "modals.delete-shared-confirm.hint-plural" (i18n/c count-files)) - (tr "modals.delete-shared-confirm.hint" (i18n/c count-files))) - (if (> count-libraries 1) - (tr "modals.unpublish-shared-confirm.hint-plural" (i18n/c count-files)) - (tr "modals.unpublish-shared-confirm.hint" (i18n/c count-files)))) + (let [on-accept (or on-accept identity) + on-cancel (or on-cancel identity) + cancel-label (tr "labels.cancel") + accept-style (or accept-style :danger) + dashboard-local (mf/deref refs/dashboard-local) + files->shared (:files-with-shared dashboard-local) + + is-delete? (= origin :delete) + count-files (count (keys files->shared)) + + title (if is-delete? + (tr "modals.delete-shared-confirm.title" (i18n/c count-libraries)) + (tr "modals.unpublish-shared-confirm.title" (i18n/c count-libraries))) + message (if is-delete? + (tr "modals.delete-shared-confirm.message" (i18n/c count-libraries)) + (tr "modals.unpublish-shared-confirm.message" (i18n/c count-libraries))) + accept-label (if is-delete? + (tr "modals.delete-shared-confirm.accept" (i18n/c count-libraries)) + (tr "modals.unpublish-shared-confirm.accept" (i18n/c count-libraries))) + no-files-message (if is-delete? + (tr "modals.delete-shared-confirm.no-files-message" (i18n/c count-libraries)) + (tr "modals.unpublish-shared-confirm.no-files-message" (i18n/c count-libraries))) + scd-message (if is-delete? + (if (= count-files 1) + (tr "modals.delete-shared-confirm.scd-message" (i18n/c count-libraries)) + (tr "modals.delete-shared-confirm.scd-message-many" (i18n/c count-libraries))) + (if (= count-files 1) + (tr "modals.unpublish-shared-confirm.scd-message" (i18n/c count-libraries)) + (tr "modals.unpublish-shared-confirm.scd-message-many" (i18n/c count-libraries)))) + hint (if is-delete? + (if (= count-files 1) + (tr "modals.delete-shared-confirm.hint" (i18n/c count-files)) + (tr "modals.delete-shared-confirm.hint-many" (i18n/c count-files))) + (if (= count-files 1) + (tr "modals.unpublish-shared-confirm.hint" (i18n/c count-libraries)) + (tr "modals.unpublish-shared-confirm.hint-many" (i18n/c count-libraries)))) accept-fn (mf/use-callback diff --git a/frontend/translations/en.po b/frontend/translations/en.po index ef189c7945..5ed6e246ef 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1593,10 +1593,10 @@ msgid "modals.delete-project-confirm.title" msgstr "Delete project" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Delete file" -msgstr[1] "Delete files" +msgid "modals.delete-shared-confirm.title" +msgid_plural "modals.delete-shared-confirm.title" +msgstr[0] "Deleting file" +msgstr[1] "Deleting files" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.message" @@ -1605,10 +1605,10 @@ msgstr[0] "Are you sure you want to delete this file?" msgstr[1] "Are you sure you want to delete these files?" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Some of the assets in this file's library are in use here:" -msgstr[1] "Some of the assets in these file's libraries are in use here:" +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Delete file" +msgstr[1] "Delete files" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.no-files-message" @@ -1617,36 +1617,36 @@ msgstr[0] "None of the assets in this file's library are in use. They will be de msgstr[1] "None of the assets in these file's libraries are in use. They will be deleted along with the files." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.no-files-message" -msgid_plural "modals.unpublish-shared-confirm.no-files-message" -msgstr[0] "None of the assets in this file's library are in use." -msgstr[1] "None of the assets in these file's libraries are in use." +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Some of the assets in this file's library are in use here:" +msgstr[1] "Some of the assets in these file's libraries are in use here:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.title" -msgid_plural "modals.delete-shared-confirm.title" -msgstr[0] "Deleting file" -msgstr[1] "Deleting files" +msgid "modals.delete-shared-confirm.scd-message-many" +msgid_plural "modals.delete-shared-confirm.scd-message-many" +msgstr[0] "Some of the assets in this file's library are in use here:" +msgstr[1] "Some of the assets in these file's libraries are in use here:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.hint" msgid_plural "modals.delete-shared-confirm.hint" msgstr[0] "" -"If you delete it, those assets will move tothe local library" -"of this file. Any unsued assets will be lost." +"If you delete it, those assets will move to the local library " +"of this file. Any unused assets will be lost." msgstr[1] "" -"If you delete it, those assets will move tothe local library" -"of these files. Any unsued assets will be lost." +"If you delete it, those assets will move to the local library " +"of these files. Any unused assets will be lost." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.hint-plural" -msgid_plural "modals.delete-shared-confirm.hint-plural" +msgid "modals.delete-shared-confirm.hint-many" +msgid_plural "modals.delete-shared-confirm.hint-many" msgstr[0] "" -"If you delete them, those assets will move tothe local library" -"of this file. Any unsued assets will be lost." +"If you delete them, those assets will move to the local library " +"of this file. Any unused assets will be lost." msgstr[1] "" -"If you delete them, those assets will move tothe local library" -"of these files. Any unsued assets will be lost." +"If you delete them, those assets will move to the local library " +"of these files. Any unused assets will be lost." #: src/app/main/ui/dashboard/sidebar.cljs msgid "modals.delete-team-confirm.accept" @@ -1773,26 +1773,10 @@ msgid "modals.small-nudge" msgstr "Small nudge" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "Unpublish" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "If you unpublish it, those assets will move to the local library of this file." -msgstr[1] "If you unpublish it, those assets will move to the local library of these files." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint-plural" -msgid_plural "modals.unpublish-shared-confirm.hint-plural" -msgstr[0] "If you unpublish them, those assets will move to the local library of this file." -msgstr[1] "If you unpublish them, those assets will move to the local library of these files." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint-plural" -msgid_plural "modals.unpublish-shared-confirm.hint-plural" -msgstr[0] "If you unpublish them, the assets in them became a library of this file." -msgstr[1] "If you unpublish them, the assets in them became a library of these files." +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Unpublish library" +msgstr[1] "Unpublish libraries" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" @@ -1800,23 +1784,41 @@ msgid_plural "modals.unpublish-shared-confirm.message" msgstr[0] "Are you sure you want to unpublish this library?" msgstr[1] "Are you sure you want to unpublish these libraries?" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgid_plural "modals.unpublish-shared-confirm.accept" +msgstr[0] "Unpublish" +msgstr[1] "Unpublish" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.no-files-message" +msgid_plural "modals.unpublish-shared-confirm.no-files-message" +msgstr[0] "None of the assets in this library are in use." +msgstr[1] "None of the assets in these libraries are in use." + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "Some of the assets in this file's library are in use here:" -msgstr[1] "Some of the assets in these file's libraries are in use here:" +msgstr[0] "Some of the assets in this library are in use here:" +msgstr[1] "Some of the assets in these libraries are in use here:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.scd-message-plural" -msgid_plural "modals.unpublish-shared-confirm.scd-message-plural" -msgstr[0] "Some of the assets in this file's library are in use here:" -msgstr[1] "Some of the assets in these file's libraries are in use here:" +msgid "modals.unpublish-shared-confirm.scd-message-many" +msgid_plural "modals.unpublish-shared-confirm.scd-message-many" +msgstr[0] "Some of the assets in this library are in use here:" +msgstr[1] "Some of the assets in these libraries are in use here:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "Unpublish library" -msgstr[1] "Unpublish libraries" +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "If you unpublish it, those assets will move to the local library of this file." +msgstr[1] "If you unpublish it, those assets will move to the local libraries of these files." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint-many" +msgid_plural "modals.unpublish-shared-confirm.hint-many" +msgstr[0] "If you unpublish them, those assets will move to the local library of this file." +msgstr[1] "If you unpublish them, those assets will move to the local libraries of these files." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component-in-bulk.hint" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 17d8dc0b02..1389add56f 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1790,33 +1790,42 @@ msgstr "¿Seguro que quieres eliminar este proyecto?" msgid "modals.delete-project-confirm.title" msgstr "Eliminar proyecto" -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.accept" -msgid_plural "modals.delete-shared-confirm.accept" -msgstr[0] "Borrar archivo" -msgstr[1] "Borrar archivos" - -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.message" -msgid_plural "modals.delete-shared-confirm.message" -msgstr[0] "¿Seguro que quieres borrar este archivo?" -msgstr[1] "¿Seguro que quieres borrar estos archivos?" - -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.scd-message" -msgid_plural "modals.delete-shared-confirm.scd-message" -msgstr[0] "Algunos elementos de esta biblioteca están siendo usados por:" -msgstr[1] "Algunos elementos de estas biblitecas están siendo usados por:" - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.title" msgid_plural "modals.delete-shared-confirm.title" msgstr[0] "Borrando archivo" msgstr[1] "Borrando archivos" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.message" +msgid_plural "modals.delete-shared-confirm.message" +msgstr[0] "¿Seguro que quieres borrar este archivo?" +msgstr[1] "¿Seguro que quieres borrar estos archivos?" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.accept" +msgid_plural "modals.delete-shared-confirm.accept" +msgstr[0] "Borrar archivo" +msgstr[1] "Borrar archivos" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.no-files-message" +msgid_plural "modals.delete-shared-confirm.no-files-message" +msgstr[0] "Ninguno de los elementos de su biblioteca están en uso. Se borrarán junto con el archivo." +msgstr[1] "Ninguno de los elementos de sus bibliotecas están en uso. Se borrarán junto con los archivos." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message" +msgid_plural "modals.delete-shared-confirm.scd-message" +msgstr[0] "Algunos elementos de su biblioteca están siendo usados por:" +msgstr[1] "Algunos elementos de sus bibliotecas están siendo usados por:" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-shared-confirm.scd-message-many" +msgid_plural "modals.delete-shared-confirm.scd-message-many" +msgstr[0] "Algunos elementos de su biblioteca están siendo usados por:" +msgstr[1] "Algunos elementos de sus bibliotecas están siendo usados por:" + #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.hint" msgid_plural "modals.delete-shared-confirm.hint" @@ -1824,31 +1833,19 @@ msgstr[0] "" "Si lo borras, esos elementos pasarán a formar parte de la biblioteca local " "de este archivo. Cualquier elemento en desuso se perderá." msgstr[1] "" -"Si lo borras, los elementos pasarán a formar parte de la biblioteca local de " +"Si lo borras, esos elementos pasarán a formar parte de las bibliotecas locales de " "estos archivos. Cualquier elemento en desuso se perderá." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.hint-plural" -msgid_plural "modals.delete-shared-confirm.hint-plural" +msgid "modals.delete-shared-confirm.hint-many" +msgid_plural "modals.delete-shared-confirm.hint-many" msgstr[0] "" "Si los borras, esos elementos pasarán a formar parte de la biblioteca local " "de este archivo. Cualquier elemento en desuso se perderá." msgstr[1] "" -"Si los borras, los elementos pasarán a formar parte de la biblioteca local de " +"Si los borras, esos elementos pasarán a formar parte de las bibliotecas locales de " "estos archivos. Cualquier elemento en desuso se perderá." -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-shared-confirm.no-files-message" -msgid_plural "modals.delete-shared-confirm.no-files-message" -msgstr[0] "Ninguno de los elementos de su biblioteca están en uso. Se borrarán junto con el archivo." -msgstr[1] "Ninguno de los elementos de sus bibliotecas están en uso. Se borrarán junto con el archivo." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.no-files-message" -msgid_plural "modals.unpublish-shared-confirm.no-files-message" -msgstr[0] "Ninguno de los elementos de su biblioteca están en uso." -msgstr[1] "Ninguno de los elementos de sus bibliotecas están en uso." - #: src/app/main/ui/delete_shared.cljs msgid "modals.delete-shared.title" msgstr "Borrar archivo" @@ -1980,31 +1977,11 @@ msgstr "Añadir “%s” como Biblioteca Compartida" msgid "modals.small-nudge" msgstr "Mínimo" -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.accept" -msgstr "Despublicar" - -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint" -msgid_plural "modals.unpublish-shared-confirm.hint" -msgstr[0] "" -"Si la despublicas, los elementos pasarán a formar parte de la biblioteca " -"del archivo." -msgstr[1] "" -"Si la despublicas, los elementos pasarán a formar parte de la biblioteca de " -"los archivos." - #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.hint-plural" -msgid_plural "modals.unpublish-shared-confirm.hint-plural" -msgstr[0] "" -"Si las despublicas, los elementos pasarán a formar parte de la biblioteca " -"del archivo." -msgstr[1] "" -"Si las despublicas, los elementos pasarán a formar parte de la biblioteca de " -"los archivos." +msgid "modals.unpublish-shared-confirm.title" +msgid_plural "modals.unpublish-shared-confirm.title" +msgstr[0] "Despublicar biblioteca" +msgstr[1] "Despublicar bibliotecas" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.message" @@ -2012,24 +1989,49 @@ msgid_plural "modals.unpublish-shared-confirm.message" msgstr[0] "¿Seguro que quieres despublicar esta biblioteca?" msgstr[1] "¿Seguro que quieres despublicar estas bibliotecas?" -#: src/app/main/ui/workspace/header.cljs, -#: src/app/main/ui/dashboard/file_menu.cljs +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.accept" +msgid_plural "modals.unpublish-shared-confirm.accept" +msgstr[0] "Despublicar" +msgstr[1] "Despublicar" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.no-files-message" +msgid_plural "modals.unpublish-shared-confirm.no-files-message" +msgstr[0] "Ninguno de los elementos de esta biblioteca están en uso." +msgstr[1] "Ninguno de los elementos de estas bibliotecas están en uso." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.scd-message" msgid_plural "modals.unpublish-shared-confirm.scd-message" -msgstr[0] "Está siendo usada en este archivo:" -msgstr[1] "Está siendo usada en estos archivos:" +msgstr[0] "Algunos elementos de esta bibioteca están siendo usados por:" +msgstr[1] "Algunos elementos de estas bibiotecas están siendo usados por:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.scd-message-plural" -msgid_plural "modals.unpublish-shared-confirm.scd-message-plural" -msgstr[0] "Están siendo usadas en este archivo:" -msgstr[1] "Están siendo usadas en estos archivos:" +msgid "modals.unpublish-shared-confirm.scd-message-many" +msgid_plural "modals.unpublish-shared-confirm.scd-message-many" +msgstr[0] "Algunos elementos de esta bibioteca están siendo usados por:" +msgstr[1] "Algunos elementos de estas bibiotecas están siendo usados por:" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.unpublish-shared-confirm.title" -msgid_plural "modals.unpublish-shared-confirm.title" -msgstr[0] "Despublicar biblioteca" -msgstr[1] "Despublicar bibliotecas" +msgid "modals.unpublish-shared-confirm.hint" +msgid_plural "modals.unpublish-shared-confirm.hint" +msgstr[0] "" +"Si la despublicas, esos elementos pasarán a formar parte de la biblioteca local " +"de este archivo." +msgstr[1] "" +"Si las despublicas, esos elementos pasarán a formar parte de la biblioteca local " +"de este archivo." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.unpublish-shared-confirm.hint-many" +msgid_plural "modals.unpublish-shared-confirm.hint-many" +msgstr[0] "" +"Si la despublicas, esos elementos pasarán a formar parte de las bibliotecas locales " +"de estos archivos." +msgstr[1] "" +"Si las despublicas, esos elementos pasarán a formar parte de las bibliotecas locales " +"de estos archivos." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, #: src/app/main/ui/workspace/context_menu.cljs @@ -4740,4 +4742,4 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" \ No newline at end of file +msgstr "Pulsar para cerrar la ruta" From ee4f063889d844912d0caaa33dc114ef63349429 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 3 Nov 2022 14:13:31 +0100 Subject: [PATCH 155/682] :bug: Fix one more typo --- frontend/src/app/main/ui/delete_shared.cljs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/ui/delete_shared.cljs b/frontend/src/app/main/ui/delete_shared.cljs index e670e195cb..02804ec8c4 100644 --- a/frontend/src/app/main/ui/delete_shared.cljs +++ b/frontend/src/app/main/ui/delete_shared.cljs @@ -57,8 +57,8 @@ (tr "modals.unpublish-shared-confirm.scd-message-many" (i18n/c count-libraries)))) hint (if is-delete? (if (= count-files 1) - (tr "modals.delete-shared-confirm.hint" (i18n/c count-files)) - (tr "modals.delete-shared-confirm.hint-many" (i18n/c count-files))) + (tr "modals.delete-shared-confirm.hint" (i18n/c count-libraries)) + (tr "modals.delete-shared-confirm.hint-many" (i18n/c count-libraries))) (if (= count-files 1) (tr "modals.unpublish-shared-confirm.hint" (i18n/c count-libraries)) (tr "modals.unpublish-shared-confirm.hint-many" (i18n/c count-libraries)))) From 358d25680b11c1542368e2422836ea010875454a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 3 Nov 2022 15:36:18 +0100 Subject: [PATCH 156/682] :bug: Yet some more typos --- frontend/translations/en.po | 14 +++++++------- frontend/translations/es.po | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 5ed6e246ef..3da66fe2a1 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1635,17 +1635,17 @@ msgstr[0] "" "If you delete it, those assets will move to the local library " "of this file. Any unused assets will be lost." msgstr[1] "" -"If you delete it, those assets will move to the local library " -"of these files. Any unused assets will be lost." +"If you delete them, those assets will move to the local library " +"of this file. Any unused assets will be lost." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.hint-many" msgid_plural "modals.delete-shared-confirm.hint-many" msgstr[0] "" -"If you delete them, those assets will move to the local library " -"of this file. Any unused assets will be lost." +"If you delete it, those assets will move to the local libraries " +"of these files. Any unused assets will be lost." msgstr[1] "" -"If you delete them, those assets will move to the local library " +"If you delete them, those assets will move to the local libraries " "of these files. Any unused assets will be lost." #: src/app/main/ui/dashboard/sidebar.cljs @@ -1812,12 +1812,12 @@ msgstr[1] "Some of the assets in these libraries are in use here:" msgid "modals.unpublish-shared-confirm.hint" msgid_plural "modals.unpublish-shared-confirm.hint" msgstr[0] "If you unpublish it, those assets will move to the local library of this file." -msgstr[1] "If you unpublish it, those assets will move to the local libraries of these files." +msgstr[1] "If you unpublish them, those assets will move to the local library of this file." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.unpublish-shared-confirm.hint-many" msgid_plural "modals.unpublish-shared-confirm.hint-many" -msgstr[0] "If you unpublish them, those assets will move to the local library of this file." +msgstr[0] "If you unpublish it, those assets will move to the local libraries of these files." msgstr[1] "If you unpublish them, those assets will move to the local libraries of these files." #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 1389add56f..e37b25d2bf 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1833,18 +1833,18 @@ msgstr[0] "" "Si lo borras, esos elementos pasarán a formar parte de la biblioteca local " "de este archivo. Cualquier elemento en desuso se perderá." msgstr[1] "" -"Si lo borras, esos elementos pasarán a formar parte de las bibliotecas locales de " -"estos archivos. Cualquier elemento en desuso se perderá." +"Si los borras, esos elementos pasarán a formar parte de la biblioteca local " +"de este archivo. Cualquier elemento en desuso se perderá." #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "modals.delete-shared-confirm.hint-many" msgid_plural "modals.delete-shared-confirm.hint-many" msgstr[0] "" -"Si los borras, esos elementos pasarán a formar parte de la biblioteca local " -"de este archivo. Cualquier elemento en desuso se perderá." +"Si lo borras, esos elementos pasarán a formar parte de las bibliotecas locales " +"de estos archivos. Cualquier elemento en desuso se perderá." msgstr[1] "" -"Si los borras, esos elementos pasarán a formar parte de las bibliotecas locales de " -"estos archivos. Cualquier elemento en desuso se perderá." +"Si los borras, esos elementos pasarán a formar parte de las bibliotecas locales " +"de estos archivos. Cualquier elemento en desuso se perderá." #: src/app/main/ui/delete_shared.cljs msgid "modals.delete-shared.title" From dbe516f725dd72a6ab38531d1476f0e7d41a5859 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 14 Oct 2022 13:52:16 +0200 Subject: [PATCH 157/682] :arrow_up: Update deps (bugfixes on deps) --- backend/deps.edn | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backend/deps.edn b/backend/deps.edn index b18dfd124a..92fa269d57 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -16,19 +16,18 @@ :exclusions [org.eclipse.jetty/jetty-server org.eclipse.jetty/jetty-servlet]} - io.prometheus/simpleclient_httpserver {:mvn/version "0.16.0"} - io.lettuce/lettuce-core {:mvn/version "6.2.0.RELEASE"} + io.lettuce/lettuce-core {:mvn/version "6.2.1.RELEASE"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} funcool/yetti - {:git/tag "v9.9" - :git/sha "f0a455d" + {:git/tag "v9.10" + :git/sha "9744349" :git/url "https://github.com/funcool/yetti.git" :exclusions [org.slf4j/slf4j-api]} - com.github.seancorfield/next.jdbc {:mvn/version "1.3.828"} + com.github.seancorfield/next.jdbc {:mvn/version "1.3.834"} metosin/reitit-core {:mvn/version "0.5.18"} org.postgresql/postgresql {:mvn/version "42.5.0"} com.zaxxer/HikariCP {:mvn/version "5.0.1"} From 43ab19f69044104fa091441c231498679ce97d65 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 14 Oct 2022 13:53:31 +0200 Subject: [PATCH 158/682] :recycle: Refactor (minor) of http session code The rationale behind the refactor: - Make available profile data to other middlewares without the need to access to the database (mainly for error reporting). - Align with codestyle with the rest of internal modules. - Simplify code. --- backend/src/app/auth/oidc.clj | 6 +- backend/src/app/http.clj | 10 +- backend/src/app/http/debug.clj | 7 +- backend/src/app/http/session.clj | 333 ++++++++++-------- backend/src/app/main.clj | 13 +- backend/src/app/rpc.clj | 8 +- backend/src/app/rpc/commands/auth.clj | 11 +- backend/src/app/rpc/commands/ldap.clj | 5 +- backend/src/app/rpc/commands/verify_token.clj | 5 +- backend/src/app/rpc/mutations/profile.clj | 5 +- 10 files changed, 214 insertions(+), 189 deletions(-) diff --git a/backend/src/app/auth/oidc.clj b/backend/src/app/auth/oidc.clj index 330195d4d0..d2a285ad79 100644 --- a/backend/src/app/auth/oidc.clj +++ b/backend/src/app/auth/oidc.clj @@ -17,6 +17,7 @@ [app.db :as db] [app.http.client :as http] [app.http.middleware :as hmw] + [app.http.session :as session] [app.loggers.audit :as audit] [app.rpc.queries.profile :as profile] [app.tokens :as tokens] @@ -423,7 +424,7 @@ (defn- generate-redirect [{:keys [sprops session audit] :as cfg} request info profile] (if profile - (let [sxf ((:create session) (:id profile)) + (let [sxf (session/create-fn session (:id profile)) token (or (:invitation-token info) (tokens/generate sprops {:iss :auth :exp (dt/in-future "15m") @@ -502,14 +503,13 @@ (s/def ::public-uri ::us/not-empty-string) (s/def ::http-client ::http/client) -(s/def ::session map?) (s/def ::sprops map?) (s/def ::providers map?) (defmethod ig/pre-init-spec ::routes [_] (s/keys :req-un [::public-uri - ::session + ::session/session ::sprops ::http-client ::providers diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 897bd9fcb5..58df5e81c6 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -11,6 +11,7 @@ [app.common.transit :as t] [app.http.errors :as errors] [app.http.middleware :as mw] + [app.http.session :as session] [app.metrics :as mtx] [app.worker :as wrk] [clojure.spec.alpha :as s] @@ -123,7 +124,7 @@ (s/def ::oauth map?) (s/def ::oidc-routes (s/nilable vector?)) (s/def ::rpc-routes (s/nilable vector?)) -(s/def ::session map?) +(s/def ::session ::session/session) (s/def ::storage map?) (s/def ::ws fn?) @@ -148,13 +149,14 @@ [mw/format-response] [mw/params] [mw/parse-request] + [session/middleware-1 session] [mw/errors errors/handle] [mw/restrict-methods]]} ["/metrics" {:handler (::mtx/handler metrics) :allowed-methods #{:get}}] - ["/assets" {:middleware [(:middleware session)]} + ["/assets" {:middleware [[session/middleware-2 session]]} ["/by-id/:id" {:handler (:objects-handler assets)}] ["/by-file-media-id/:id" {:handler (:file-objects-handler assets)}] ["/by-file-media-id/:id/thumbnail" {:handler (:file-thumbnails-handler assets)}]] @@ -165,12 +167,12 @@ ["/sns" {:handler (:awsns-handler cfg) :allowed-methods #{:post}}]] - ["/ws/notifications" {:middleware [(:middleware session)] + ["/ws/notifications" {:middleware [[session/middleware-2 session]] :handler ws :allowed-methods #{:get}}] ["/api" {:middleware [[mw/cors] - [(:middleware session)]]} + [session/middleware-2 session]]} ["/audit/events" {:handler (:audit-handler cfg) :allowed-methods #{:post}}] ["/feedback" {:handler feedback diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index 96258b1e61..79da866cd6 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -14,6 +14,7 @@ [app.config :as cf] [app.db :as db] [app.http.middleware :as mw] + [app.http.session :as session] [app.rpc.commands.binfile :as binf] [app.rpc.mutations.files :refer [create-file]] [app.rpc.queries.profile :as profile] @@ -377,17 +378,15 @@ :code :only-admins-allowed))))))}) -(s/def ::session map?) - (defmethod ig/pre-init-spec ::routes [_] - (s/keys :req-un [::db/pool ::wrk/executor ::session])) + (s/keys :req-un [::db/pool ::wrk/executor ::session/session])) (defmethod ig/init-key ::routes [_ {:keys [session pool executor] :as cfg}] [["/readyz" {:middleware [[mw/with-dispatch executor] [mw/with-config cfg]] :handler health-handler}] - ["/dbg" {:middleware [[(:middleware session)] + ["/dbg" {:middleware [[session/middleware-2 session] [with-authorization pool] [mw/with-dispatch executor] [mw/with-config cfg]]} diff --git a/backend/src/app/http/session.clj b/backend/src/app/http/session.clj index 0c7caf7925..6141ed66d0 100644 --- a/backend/src/app/http/session.clj +++ b/backend/src/app/http/session.clj @@ -5,6 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.http.session + (:refer-clojure :exclude [read]) (:require [app.common.data :as d] [app.common.logging :as l] @@ -20,6 +21,10 @@ [promesa.exec :as px] [yetti.request :as yrq])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; DEFAULTS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + ;; A default cookie name for storing the session. (def default-auth-token-cookie-name "auth-token") @@ -33,35 +38,55 @@ ;; Default age for automatic session renewal (def default-renewal-max-age (dt/duration {:hours 6})) -(defprotocol ISessionStore - (read-session [store key]) - (write-session [store key data]) - (update-session [store data]) - (delete-session [store key])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; PROTOCOLS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- make-database-store +(defprotocol ISessionManager + (read [_ key]) + (decode [_ key]) + (write! [_ key data]) + (update! [_ data]) + (delete! [_ key])) + +(s/def ::session #(satisfies? ISessionManager %)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; STORAGE IMPL +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- prepare-session-params + [sprops data] + (let [profile-id (:profile-id data) + user-agent (:user-agent data) + created-at (or (:created-at data) (dt/now)) + token (tokens/generate sprops {:iss "authentication" + :iat created-at + :uid profile-id})] + {:user-agent user-agent + :profile-id profile-id + :created-at created-at + :updated-at created-at + :id token})) + +(defn- database-manager [{:keys [pool sprops executor]}] - (reify ISessionStore - (read-session [_ token] + (reify ISessionManager + (read [_ token] (px/with-dispatch executor (db/exec-one! pool (sql/select :http-session {:id token})))) - (write-session [_ _ data] + (decode [_ token] (px/with-dispatch executor - (let [profile-id (:profile-id data) - user-agent (:user-agent data) - created-at (or (:created-at data) (dt/now)) - token (tokens/generate sprops {:iss "authentication" - :iat created-at - :uid profile-id}) - params {:user-agent user-agent - :profile-id profile-id - :created-at created-at - :updated-at created-at - :id token}] - (db/insert! pool :http-session params)))) + (tokens/verify sprops {:token token :iss "authentication"}))) - (update-session [_ data] + (write! [_ _ data] + (px/with-dispatch executor + (let [params (prepare-session-params sprops data)] + (db/insert! pool :http-session params) + params))) + + (update! [_ data] (let [updated-at (dt/now)] (px/with-dispatch executor (db/update! pool :http-session @@ -69,83 +94,154 @@ {:id (:id data)}) (assoc data :updated-at updated-at)))) - (delete-session [_ token] + (delete! [_ token] (px/with-dispatch executor (db/delete! pool :http-session {:id token}) nil)))) -(defn make-inmemory-store - [{:keys [sprops]}] +(defn inmemory-manager + [{:keys [sprops executor]}] (let [cache (atom {})] - (reify ISessionStore - (read-session [_ token] + (reify ISessionManager + (read [_ token] (p/do (get @cache token))) - (write-session [_ _ data] - (p/do - (let [profile-id (:profile-id data) - user-agent (:user-agent data) - created-at (or (:created-at data) (dt/now)) - token (tokens/generate sprops {:iss "authentication" - :iat created-at - :uid profile-id}) - params {:user-agent user-agent - :created-at created-at - :updated-at created-at - :profile-id profile-id - :id token}] + (decode [_ token] + (px/with-dispatch executor + (tokens/verify sprops {:token token :iss "authentication"}))) + (write! [_ _ data] + (p/do + (let [{:keys [token] :as params} (prepare-session-params sprops data)] (swap! cache assoc token params) params))) - (update-session [_ data] - (let [updated-at (dt/now)] - (swap! cache update (:id data) assoc :updated-at updated-at) - (assoc data :updated-at updated-at))) + (update! [_ data] + (p/do + (let [updated-at (dt/now)] + (swap! cache update (:id data) assoc :updated-at updated-at) + (assoc data :updated-at updated-at)))) - (delete-session [_ token] + (delete! [_ token] (p/do (swap! cache dissoc token) nil))))) (s/def ::sprops map?) -(defmethod ig/pre-init-spec ::store [_] +(defmethod ig/pre-init-spec ::manager [_] (s/keys :req-un [::db/pool ::wrk/executor ::sprops])) -(defmethod ig/init-key ::store +(defmethod ig/init-key ::manager [_ {:keys [pool] :as cfg}] (if (db/read-only? pool) - (make-inmemory-store cfg) - (make-database-store cfg))) + (inmemory-manager cfg) + (database-manager cfg))) -(defmethod ig/halt-key! ::store +(defmethod ig/halt-key! ::manager [_ _]) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; MANAGER IMPL +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare assign-auth-token-cookie) +(declare assign-authenticated-cookie) +(declare clear-auth-token-cookie) +(declare clear-authenticated-cookie) + +(defn create-fn + [manager profile-id] + (fn [request response] + (let [uagent (yrq/get-header request "user-agent") + params {:profile-id profile-id + :user-agent uagent}] + (-> (write! manager nil params) + (p/then (fn [session] + (l/trace :hint "create" :profile-id profile-id) + (-> response + (assign-auth-token-cookie session) + (assign-authenticated-cookie session)))))))) +(defn delete-fn + [manager] + (letfn [(delete [{:keys [profile-id] :as request}] + (let [cname (cf/get :auth-token-cookie-name default-auth-token-cookie-name) + cookie (yrq/get-cookie request cname)] + (l/trace :hint "delete" :profile-id profile-id) + (some->> (:value cookie) (delete! manager))))] + (fn [request response] + (p/do + (delete request) + (-> response + (assoc :status 204) + (assoc :body nil) + (clear-auth-token-cookie) + (clear-authenticated-cookie)))))) + +(def middleware-1 + (letfn [(wrap-handler [manager handler request respond raise] + (try + (let [claims (some->> (cf/get :auth-token-cookie-name default-auth-token-cookie-name) + (yrq/get-cookie request) + (decode manager)) + request (cond-> request + (some? claims) + (assoc :session-token-claims claims))] + (handler request respond raise)) + (catch Throwable _ + (handler request respond raise))))] + + {:name :session-1 + :compile (fn [& _] + (fn [handler manager] + (partial wrap-handler manager handler)))})) + +(def middleware-2 + (letfn [(wrap-handler [manager handler request respond raise] + (-> (retrieve-session manager request) + (p/finally (fn [session cause] + (cond + (some? cause) + (raise cause) + + (nil? session) + (handler request respond raise) + + :else + (let [request (-> request + (assoc :profile-id (:profile-id session)) + (assoc :session-id (:id session))) + respond (cond-> respond + (renew-session? session) + (wrap-respond manager session))] + (handler request respond raise))))))) + + (retrieve-session [manager request] + (let [cname (cf/get :auth-token-cookie-name default-auth-token-cookie-name) + cookie (yrq/get-cookie request cname)] + (some->> (:value cookie) (read manager)))) + + (renew-session? [{:keys [updated-at] :as session}] + (and (dt/instant? updated-at) + (let [elapsed (dt/diff updated-at (dt/now))] + (neg? (compare default-renewal-max-age elapsed))))) + + ;; Wrap respond with session renewal code + (wrap-respond [respond manager session] + (fn [response] + (p/let [session (update! manager session)] + (-> response + (assign-auth-token-cookie session) + (assign-authenticated-cookie session) + (respond)))))] + + {:name :session-2 + :compile (fn [& _] + (fn [handler manager] + (partial wrap-handler manager handler)))})) + ;; --- IMPL -(defn- create-session! - [store profile-id user-agent] - (let [params {:user-agent user-agent - :profile-id profile-id}] - (write-session store nil params))) - -(defn- update-session! - [store session] - (update-session store session)) - -(defn- delete-session! - [store {:keys [cookies] :as request}] - (let [name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] - (when-let [token (get-in cookies [name :value])] - (delete-session store token)))) - -(defn- retrieve-session - [store request] - (let [cookie-name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] - (when-let [cookie (yrq/get-cookie request cookie-name)] - (read-session store (:value cookie))))) - -(defn assign-auth-token-cookie +(defn- assign-auth-token-cookie [response {token :id updated-at :updated-at}] (let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age) created-at (or updated-at (dt/now)) @@ -164,7 +260,7 @@ :secure secure?}] (update response :cookies assoc name cookie))) -(defn assign-authenticated-cookie +(defn- assign-authenticated-cookie [response {updated-at :updated-at}] (let [max-age (cf/get :auth-token-cookie-max-age default-cookie-max-age) created-at (or updated-at (dt/now)) @@ -185,96 +281,23 @@ (string? domain) (update :cookies assoc name cookie)))) -(defn clear-auth-token-cookie +(defn- clear-auth-token-cookie [response] - (let [name (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] - (update response :cookies assoc name {:path "/" :value "" :max-age -1}))) + (let [cname (cf/get :auth-token-cookie-name default-auth-token-cookie-name)] + (update response :cookies assoc cname {:path "/" :value "" :max-age -1}))) (defn- clear-authenticated-cookie [response] - (let [name (cf/get :authenticated-cookie-name default-authenticated-cookie-name) + (let [cname (cf/get :authenticated-cookie-name default-authenticated-cookie-name) domain (cf/get :authenticated-cookie-domain)] (cond-> response (string? domain) - (update :cookies assoc name {:domain domain :path "/" :value "" :max-age -1})))) - -(defn- make-middleware - [{:keys [store] :as cfg}] - (letfn [;; Check if time reached for automatic session renewal - (renew-session? [{:keys [updated-at] :as session}] - (and (dt/instant? updated-at) - (let [elapsed (dt/diff updated-at (dt/now))] - (neg? (compare default-renewal-max-age elapsed))))) - - ;; Wrap respond with session renewal code - (wrap-respond [respond session] - (fn [response] - (p/let [session (update-session! store session)] - (-> response - (assign-auth-token-cookie session) - (assign-authenticated-cookie session) - (respond)))))] - - {:name :session - :compile (fn [& _] - (fn [handler] - (fn [request respond raise] - (try - (-> (retrieve-session store request) - (p/finally (fn [session cause] - (cond - (some? cause) - (raise cause) - - (nil? session) - (handler request respond raise) - - :else - (let [request (-> request - (assoc :profile-id (:profile-id session)) - (assoc :session-id (:id session))) - respond (cond-> respond - (renew-session? session) - (wrap-respond session))] - (handler request respond raise)))))) - - (catch Throwable cause - (raise cause))))))})) + (update :cookies assoc cname {:domain domain :path "/" :value "" :max-age -1})))) -;; --- STATE INIT: SESSION - -(s/def ::store #(satisfies? ISessionStore %)) - -(defmethod ig/pre-init-spec :app.http/session [_] - (s/keys :req-un [::store])) - -(defmethod ig/prep-key :app.http/session - [_ cfg] - (d/merge {:buffer-size 128} - (d/without-nils cfg))) - -(defmethod ig/init-key :app.http/session - [_ {:keys [store] :as cfg}] - (-> cfg - (assoc :middleware (make-middleware cfg)) - (assoc :create (fn [profile-id] - (fn [request response] - (p/let [uagent (yrq/get-header request "user-agent") - session (create-session! store profile-id uagent)] - (-> response - (assign-auth-token-cookie session) - (assign-authenticated-cookie session)))))) - (assoc :delete (fn [request response] - (p/do - (delete-session! store request) - (-> response - (assoc :status 204) - (assoc :body nil) - (clear-auth-token-cookie) - (clear-authenticated-cookie))))))) - -;; --- STATE INIT: SESSION GC +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; TASK: SESSION GC +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (declare sql:delete-expired) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 4b7232ef6c..97bcdc7a2f 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -79,10 +79,7 @@ :app.http/client {:executor (ig/ref [::default :app.worker/executor])} - :app.http/session - {:store (ig/ref :app.http.session/store)} - - :app.http.session/store + :app.http.session/manager {:pool (ig/ref :app.db/pool) :sprops (ig/ref :app.setup/props) :executor (ig/ref [::default :app.worker/executor])} @@ -163,7 +160,7 @@ :sprops (ig/ref :app.setup/props) :http-client (ig/ref :app.http/client) :pool (ig/ref :app.db/pool) - :session (ig/ref :app.http/session) + :session (ig/ref :app.http.session/manager) :public-uri (cf/get :public-uri) :executor (ig/ref [::default :app.worker/executor])} @@ -171,7 +168,7 @@ :app.http/router {:assets (ig/ref :app.http.assets/handlers) :feedback (ig/ref :app.http.feedback/handler) - :session (ig/ref :app.http/session) + :session (ig/ref :app.http.session/manager) :awsns-handler (ig/ref :app.http.awsns/handler) :debug-routes (ig/ref :app.http.debug/routes) :oidc-routes (ig/ref :app.auth.oidc/routes) @@ -188,7 +185,7 @@ {:pool (ig/ref :app.db/pool) :executor (ig/ref [::worker :app.worker/executor]) :storage (ig/ref :app.storage/storage) - :session (ig/ref :app.http/session)} + :session (ig/ref :app.http.session/manager)} :app.http.websocket/handler {:pool (ig/ref :app.db/pool) @@ -217,7 +214,7 @@ :app.rpc/methods {:pool (ig/ref :app.db/pool) - :session (ig/ref :app.http/session) + :session (ig/ref :app.http.session/manager) :sprops (ig/ref :app.setup/props) :metrics (ig/ref :app.metrics/metrics) :storage (ig/ref :app.storage/storage) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 2b4b204aa8..21332a10ca 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -11,12 +11,14 @@ [app.common.spec :as us] [app.db :as db] [app.http :as-alias http] + [app.http.session :as-alias session] [app.loggers.audit :as audit] [app.metrics :as mtx] [app.msgbus :as-alias mbus] [app.rpc.retry :as retry] [app.rpc.rlimit :as rlimit] [app.rpc.semaphore :as-alias rsem] + [app.storage :as-alias sto] [app.util.services :as sv] [app.util.time :as ts] [clojure.spec.alpha :as s] @@ -236,13 +238,11 @@ (s/def ::ldap (s/nilable map?)) (s/def ::msgbus ::mbus/msgbus) (s/def ::public-uri ::us/not-empty-string) -(s/def ::session map?) -(s/def ::storage some?) (s/def ::sprops map?) (defmethod ig/pre-init-spec ::methods [_] - (s/keys :req-un [::storage - ::session + (s/keys :req-un [::sto/storage + ::session/session ::sprops ::audit ::public-uri diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index 110e0f1395..f66ed1358e 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -13,6 +13,7 @@ [app.config :as cf] [app.db :as db] [app.emails :as eml] + [app.http.session :as session] [app.loggers.audit :as audit] [app.rpc :as-alias rpc] [app.rpc.doc :as-alias doc] @@ -135,7 +136,7 @@ profile)] (with-meta response - {::rpc/transform-response ((:create session) (:id profile)) + {::rpc/transform-response (session/create-fn session (:id profile)) ::audit/props (audit/profile->props profile) ::audit/profile-id (:id profile)}))))) @@ -162,7 +163,7 @@ ::doc/added "1.15"} [{:keys [session] :as cfg} _] (with-meta {} - {::rpc/transform-response (:delete session)})) + {::rpc/transform-response (session/delete-fn session)})) ;; ---- COMMAND: Recover Profile @@ -403,7 +404,7 @@ token (tokens/generate sprops claims) resp {:invitation-token token}] (with-meta resp - {::rpc/transform-response ((:create session) (:id profile)) + {::rpc/transform-response (session/create-fn session (:id profile)) ::audit/replace-props (audit/profile->props profile) ::audit/profile-id (:id profile)})) @@ -412,7 +413,7 @@ ;; we need to mark this session as logged. (not= "penpot" (:auth-backend profile)) (with-meta (profile/strip-private-attrs profile) - {::rpc/transform-response ((:create session) (:id profile)) + {::rpc/transform-response (session/create-fn session (:id profile)) ::audit/replace-props (audit/profile->props profile) ::audit/profile-id (:id profile)}) @@ -420,7 +421,7 @@ ;; to sign in the user directly, without email verification. (true? is-active) (with-meta (profile/strip-private-attrs profile) - {::rpc/transform-response ((:create session) (:id profile)) + {::rpc/transform-response (session/create-fn session (:id profile)) ::audit/replace-props (audit/profile->props profile) ::audit/profile-id (:id profile)}) diff --git a/backend/src/app/rpc/commands/ldap.clj b/backend/src/app/rpc/commands/ldap.clj index d7633336fd..8e1b464579 100644 --- a/backend/src/app/rpc/commands/ldap.clj +++ b/backend/src/app/rpc/commands/ldap.clj @@ -10,6 +10,7 @@ [app.common.exceptions :as ex] [app.common.spec :as us] [app.db :as db] + [app.http.session :as session] [app.loggers.audit :as-alias audit] [app.rpc :as-alias rpc] [app.rpc.commands.auth :as cmd.auth] @@ -63,12 +64,12 @@ :member-email (:email profile)) token (tokens :generate claims)] (with-meta {:invitation-token token} - {::rpc/transform-response ((:create session) (:id profile)) + {::rpc/transform-response (session/create-fn session (:id profile)) ::audit/props (:props profile) ::audit/profile-id (:id profile)})) (with-meta profile - {::rpc/transform-response ((:create session) (:id profile)) + {::rpc/transform-response (session/create-fn session (:id profile)) ::audit/props (:props profile) ::audit/profile-id (:id profile)}))))) diff --git a/backend/src/app/rpc/commands/verify_token.clj b/backend/src/app/rpc/commands/verify_token.clj index f02db88399..75e0b25f12 100644 --- a/backend/src/app/rpc/commands/verify_token.clj +++ b/backend/src/app/rpc/commands/verify_token.clj @@ -9,6 +9,7 @@ [app.common.exceptions :as ex] [app.common.spec :as us] [app.db :as db] + [app.http.session :as session] [app.loggers.audit :as audit] [app.rpc :as-alias rpc] [app.rpc.doc :as-alias doc] @@ -68,7 +69,7 @@ {:id (:id profile)})) (with-meta claims - {::rpc/transform-response ((:create session) profile-id) + {::rpc/transform-response (session/create-fn session profile-id) ::audit/name "verify-profile-email" ::audit/props (audit/profile->props profile) ::audit/profile-id (:id profile)}))) @@ -172,7 +173,7 @@ (let [profile (accept-invitation cfg claims invitation member)] (with-meta (assoc claims :state :created) - {::rpc/transform-response ((:create session) (:id profile)) + {::rpc/transform-response (session/create-fn session (:id profile)) ::audit/name "accept-team-invitation" ::audit/props (merge (audit/profile->props profile) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 73555557a2..56846c30d0 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -12,6 +12,7 @@ [app.config :as cf] [app.db :as db] [app.emails :as eml] + [app.http.session :as session] [app.loggers.audit :as audit] [app.media :as media] [app.rpc :as-alias rpc] @@ -278,7 +279,7 @@ {:id profile-id}) (with-meta {} - {::rpc/transform-response (:delete session)})))) + {::rpc/transform-response (session/delete-fn session)})))) (def sql:owned-teams "with owner_teams as ( @@ -324,7 +325,7 @@ ::doc/deprecated "1.15"} [{:keys [session] :as cfg} _] (with-meta {} - {::rpc/transform-response (:delete session)})) + {::rpc/transform-response (session/delete-fn session)})) ;; --- MUTATION: Recover Profile From 82d72fd3884272df913b7fcc5221fba0874998d6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 14 Oct 2022 13:55:59 +0200 Subject: [PATCH 159/682] :bug: Add missing profile-id on the internal error reports --- backend/src/app/http/errors.clj | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index 865fd2037d..b66b416ecc 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -7,6 +7,7 @@ (ns app.http.errors "A errors handling for the http server." (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.spec :as us] @@ -26,16 +27,18 @@ (defn get-context [request] - (merge - *context* - {:path (:path request) - :method (:method request) - :params (:params request) - :ip-addr (parse-client-ip request) - :profile-id (:profile-id request)} - (let [headers (:headers request)] - {:user-agent (get headers "user-agent") - :frontend-version (get headers "x-frontend-version" "unknown")}))) + (let [claims (:session-token-claims request)] + (merge + *context* + {:path (:path request) + :method (:method request) + :params (:params request) + :ip-addr (parse-client-ip request)} + (d/without-nils + {:user-agent (yrq/get-header request "user-agent") + :frontend-version (or (yrq/get-header request "x-frontend-version") + "unknown") + :profile-id (:uid claims)})))) (defmulti handle-exception (fn [err & _rest] From 9c33dc529d81eff849d1dd205c07741556db01f9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 14 Oct 2022 14:19:12 +0200 Subject: [PATCH 160/682] :sparkles: Improve error report list template --- backend/resources/app/templates/error-list.tmpl | 3 ++- backend/resources/app/templates/styles.css | 16 +++++++++++++--- backend/src/app/http/debug.clj | 10 +++++++--- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/backend/resources/app/templates/error-list.tmpl b/backend/resources/app/templates/error-list.tmpl index d86d983d35..73520aa364 100644 --- a/backend/resources/app/templates/error-list.tmpl +++ b/backend/resources/app/templates/error-list.tmpl @@ -11,7 +11,8 @@ penpot - error list
diff --git a/backend/resources/app/templates/styles.css b/backend/resources/app/templates/styles.css index 32fcea8883..d57fd04602 100644 --- a/backend/resources/app/templates/styles.css +++ b/backend/resources/app/templates/styles.css @@ -137,8 +137,6 @@ nav > div:not(:last-child) { margin: 0px; padding: 0px; flex-direction: column; - flex-wrap: wrap; - height: calc(100vh - 75px); justify-content: flex-start; } @@ -151,19 +149,31 @@ nav > div:not(:last-child) { margin: 0px 20px; cursor: pointer; display: flex; - justify-content: center; border-radius: 3px; } + + .horizontal-list li:hover { background-color: #e9e9e9; } +.horizontal-list li > *:not(:last-child) { + margin-right: 10px; +} + .horizontal-list li > a { text-decoration: none; color: inherit; } +.horizontal-list li > .date { + font-weight: 200; + color: #686868; + min-width: 210px; +} + + form .row { padding: 5px 0; } diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index 79da866cd6..56642f1605 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -244,15 +244,19 @@ (yrs/response 404 "not found"))))) (def sql:error-reports - "select id, created_at from server_error_report order by created_at desc limit 100") + "SELECT id, created_at, + content->>'~:hint' AS hint + FROM server_error_report + ORDER BY created_at DESC + LIMIT 100") (defn error-list-handler [{:keys [pool]} request] (when-not (authorized? pool request) (ex/raise :type :authentication :code :only-admins-allowed)) - (let [items (db/exec! pool [sql:error-reports]) - items (map #(update % :created-at dt/format-instant :rfc1123) items)] + (let [items (->> (db/exec! pool [sql:error-reports]) + (map #(update % :created-at dt/format-instant :rfc1123)))] (yrs/response :status 200 :body (-> (io/resource "app/templates/error-list.tmpl") (tmpl/render {:items items})) From 6ad9a5aadbe89d56fdb338efb94d7fa246edb8a1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 18 Oct 2022 17:13:00 +0200 Subject: [PATCH 161/682] :bug: Fix many bugs on rlimit module --- backend/resources/rlimit.edn | 4 ++ backend/src/app/rpc.clj | 7 +- backend/src/app/rpc/rlimit.clj | 105 +++++++++++++++--------------- backend/src/app/util/services.clj | 25 +++++-- 4 files changed, 79 insertions(+), 62 deletions(-) diff --git a/backend/resources/rlimit.edn b/backend/resources/rlimit.edn index bba62dfc95..c7df92bdf1 100644 --- a/backend/resources/rlimit.edn +++ b/backend/resources/rlimit.edn @@ -1,6 +1,10 @@ +;; Example rlimit.edn file ^{:refresh "30s"} {:default [[:default :window "200000/h"]] + #{:query/teams} + [[:burst :bucket "5/1/5s"]] + #{:query/profile} [[:burst :bucket "100/60/1m"]]} diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 21332a10ca..01adf6b07a 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -126,7 +126,8 @@ (with-meta (fn [cfg params] (-> (px/submit! executor #(f cfg params)) - (p/bind p/wrap))) + (p/bind p/wrap) + (p/then' sv/wrap))) mdata)) (defn- wrap-audit @@ -237,6 +238,8 @@ (s/def ::http-client fn?) (s/def ::ldap (s/nilable map?)) (s/def ::msgbus ::mbus/msgbus) +(s/def ::rlimit (s/nilable ::rlimit/rlimit)) + (s/def ::public-uri ::us/not-empty-string) (s/def ::sprops map?) @@ -249,7 +252,7 @@ ::msgbus ::http-client ::rsem/semaphores - ::rlimit/rlimit + ::rlimit ::mtx/metrics ::db/pool ::ldap])) diff --git a/backend/src/app/rpc/rlimit.clj b/backend/src/app/rpc/rlimit.clj index e7d80b7a8c..390892e33c 100644 --- a/backend/src/app/rpc/rlimit.clj +++ b/backend/src/app/rpc/rlimit.clj @@ -44,7 +44,6 @@ " (:require [app.common.data :as d] - [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.spec :as us] @@ -111,7 +110,7 @@ "m" :minutes "s" :seconds "w" :weeks) - ::key (dm/str "ratelimit.window." (d/name name)) + ::key (str "ratelimit.window." (d/name name)) ::opts opts}) (ex/raise :type :validation :code :invalid-window-limit-opts @@ -132,7 +131,7 @@ ::interval interval ::opts opts ::params [(dt/->seconds interval) rate capacity] - ::key (dm/str "ratelimit.bucket." (d/name name))}) + ::key (str "ratelimit.bucket." (d/name name))}) (ex/raise :type :validation :code :invalid-bucket-limit-opts :hint (str/ffmt "looks like '%' does not have a valid format" opts))))) @@ -140,7 +139,7 @@ (defmethod process-limit :bucket [redis user-id now {:keys [::key ::params ::service ::capacity ::interval ::rate] :as limit}] (let [script (-> bucket-rate-limit-script - (assoc ::rscript/keys [(dm/str key "." service "." user-id)]) + (assoc ::rscript/keys [(str key "." service "." user-id)]) (assoc ::rscript/vals (conj params (dt/->seconds now))))] (-> (redis/eval! redis script) (p/then (fn [result] @@ -165,7 +164,7 @@ (let [ts (dt/truncate now unit) ttl (dt/diff now (dt/plus ts {unit 1})) script (-> window-rate-limit-script - (assoc ::rscript/keys [(dm/str key "." service "." user-id "." (dt/format-instant ts))]) + (assoc ::rscript/keys [(str key "." service "." user-id "." (dt/format-instant ts))]) (assoc ::rscript/vals [nreq (dt/->seconds ttl)]))] (-> (redis/eval! redis script) (p/then (fn [result] @@ -197,67 +196,65 @@ (filter (complement ::lresult/allowed?)) (first))] - (when (and rejected (contains? cf/flags :warn-rpc-rate-limits)) + (when rejected (l/warn :hint "rejected rate limit" - :user-id (dm/str user-id) + :user-id (str user-id) :limit-service (-> rejected ::service name) :limit-name (-> rejected ::name name) :limit-strategy (-> rejected ::strategy name))) {:enabled? true - :allowed? (some? rejected) + :allowed? (not (some? rejected)) :headers {"x-rate-limit-remaining" remaining "x-rate-limit-reset" reset}}))))) (defn- handle-response - [f cfg params rres] - (if (:enabled? rres) - (let [headers {"x-rate-limit-remaining" (:remaining rres) - "x-rate-limit-reset" (:reset rres)}] - (when-not (:allowed? rres) + [f cfg params result] + (if (:enabled? result) + (let [headers (:headers result)] + (when-not (:allowed? result) (ex/raise :type :rate-limit :code :request-blocked :hint "rate limit reached" ::http/headers headers)) (-> (f cfg params) (p/then (fn [response] - (with-meta response - {::http/headers headers}))))) - + (vary-meta response update ::http/headers merge headers))))) (f cfg params))) (defn wrap [{:keys [rlimit redis] :as cfg} f mdata] - (let [skey (keyword (::rpc/type cfg) (->> mdata ::sv/spec name)) - sname (dm/str (::rpc/type cfg) "." (->> mdata ::sv/spec name)) - default-rresp (p/resolved {:enabled? false})] - (if (or (contains? cf/flags :rpc-rate-limit) - (contains? cf/flags :soft-rpc-rate-limit)) + (if rlimit + (let [skey (keyword (::rpc/type cfg) (->> mdata ::sv/spec name)) + sname (str (::rpc/type cfg) "." (->> mdata ::sv/spec name))] (fn [cfg {:keys [::http/request] :as params}] - (let [user-id (or (:profile-id params) - (some-> request parse-client-ip) - uuid/zero) + (let [uid (or (:profile-id params) + (some-> request parse-client-ip) + uuid/zero) - rresp (when (and user-id @enabled?) - (when-let [limits (get-in @rlimit [::limits skey])] - (let [redis (redis/get-or-connect redis ::rlimit default-options) - limits (map #(assoc % ::service sname) limits) - rresp (-> (process-limits redis user-id limits (dt/now)) - (p/catch (fn [cause] - ;; If we have an error on processing the - ;; rate-limit we just skip it for do not cause - ;; service interruption because of redis downtime - ;; or similar situation. - (l/error :hint "error on processing rate-limit" :cause cause) - {:enabled? false})))] + rsp (when (and uid @enabled?) + (when-let [limits (or (get-in @rlimit [::limits skey]) + (get-in @rlimit [::limits :default]))] + (let [redis (redis/get-or-connect redis ::rlimit default-options) + limits (map #(assoc % ::service sname) limits) + resp (-> (process-limits redis uid limits (dt/now)) + (p/catch (fn [cause] + ;; If we have an error on processing the rate-limit we just skip + ;; it for do not cause service interruption because of redis + ;; downtime or similar situation. + (l/error :hint "error on processing rate-limit" :cause cause) + {:enabled? false})))] - ;; If soft rate are enabled, we process the rate-limit but return - ;; unprotected response. - (and (contains? cf/flags :soft-rpc-rate-limit) rresp))))] + ;; If soft rate are enabled, we process the rate-limit but return unprotected + ;; response. + (if (contains? cf/flags :soft-rpc-rlimit) + (p/resolved {:enabled? false}) + resp)))) - (p/then (or rresp default-rresp) - (partial handle-response f cfg params)))) - f))) + rsp (or rsp (p/resolved {:enabled? false}))] + + (p/then rsp (partial handle-response f cfg params))))) + f)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; CONFIG WATCHER @@ -376,20 +373,20 @@ (defmethod ig/pre-init-spec :app.rpc/rlimit [_] (s/keys :req-un [::wrk/executor ::wrk/scheduler])) -(defmethod ig/init-key :app.rpc/rlimit +(defmethod ig/init-key ::rpc/rlimit [_ {:keys [executor] :as params}] - (let [state (agent {})] + (when (contains? cf/flags :rpc-rlimit) + (let [state (agent {})] + (set-error-handler! state on-refresh-error) + (set-error-mode! state :continue) - (set-error-handler! state on-refresh-error) - (set-error-mode! state :continue) + (when-let [path (get-config-path)] + (l/info :hint "initializing rlimit config reader" :path (str path)) - (when-let [path (get-config-path)] - (l/info :hint "initializing rlimit config reader" :path (str path)) + ;; Initialize the state with initial refresh value + (send-via executor state (constantly {::refresh (dt/duration "5s")})) - ;; Initialize the state with initial refresh value - (send-via executor state (constantly {::refresh (dt/duration "5s")})) + ;; Force a refresh + (refresh-config (assoc params :path path :state state))) - ;; Force a refresh - (refresh-config (assoc params :path path :state state))) - - state)) + state))) diff --git a/backend/src/app/util/services.clj b/backend/src/app/util/services.clj index f8f8fc0045..59a048e1e4 100644 --- a/backend/src/app/util/services.clj +++ b/backend/src/app/util/services.clj @@ -11,19 +11,32 @@ [app.common.data :as d] [cuerdas.core :as str])) -(defrecord WrappedValue [obj] +;; A utilty wrapper object for wrap service responses that does not +;; implements the IObj interface that make possible attach metadata to +;; it. + +(deftype MetadataWrapper [obj ^:unsynchronized-mutable metadata] clojure.lang.IDeref - (deref [_] obj)) + (deref [_] obj) + + clojure.lang.IObj + (withMeta [_ meta] + (MetadataWrapper. obj meta)) + + (meta [_] metadata)) (defn wrap - ([] - (WrappedValue. nil)) + "Conditionally wrap a value into MetadataWrapper instance. If the + object already implements IObj interface it will be returned as is." + ([] (wrap nil)) ([o] - (WrappedValue. o))) + (if (instance? clojure.lang.IObj o) + o + (MetadataWrapper. o {})))) (defn wrapped? [o] - (instance? WrappedValue o)) + (instance? MetadataWrapper o)) (defmacro defmethod [sname & body] From 37ad04d2a6d20594a7d50bf29abe04b362467805 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 16 Oct 2022 23:44:16 +0200 Subject: [PATCH 162/682] :tada: Add robust concurrency limiter for RPC --- backend/deps.edn | 2 + backend/resources/climit.edn | 7 + backend/resources/log4j2-devenv.xml | 1 + backend/src/app/config.clj | 8 +- backend/src/app/http/errors.clj | 17 ++ backend/src/app/main.clj | 4 +- backend/src/app/metrics.clj | 18 +- backend/src/app/rpc.clj | 9 +- backend/src/app/rpc/climit.clj | 205 ++++++++++++++++++++++ backend/src/app/rpc/commands/auth.clj | 8 +- backend/src/app/rpc/mutations/files.clj | 6 +- backend/src/app/rpc/mutations/fonts.clj | 12 +- backend/src/app/rpc/mutations/media.clj | 30 ++-- backend/src/app/rpc/mutations/profile.clj | 10 +- backend/src/app/rpc/mutations/teams.clj | 19 +- backend/src/app/rpc/semaphore.clj | 149 ---------------- common/deps.edn | 2 +- 17 files changed, 296 insertions(+), 211 deletions(-) create mode 100644 backend/resources/climit.edn create mode 100644 backend/src/app/rpc/climit.clj delete mode 100644 backend/src/app/rpc/semaphore.clj diff --git a/backend/deps.edn b/backend/deps.edn index 92fa269d57..b449a6ecc1 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -37,6 +37,8 @@ buddy/buddy-hashers {:mvn/version "1.8.158"} buddy/buddy-sign {:mvn/version "3.4.333"} + com.github.ben-manes.caffeine/caffeine {:mvn/version "3.1.1"} + org.jsoup/jsoup {:mvn/version "1.15.1"} org.im4java/im4java {:git/tag "1.4.0-penpot-2" diff --git a/backend/resources/climit.edn b/backend/resources/climit.edn new file mode 100644 index 0000000000..697d165392 --- /dev/null +++ b/backend/resources/climit.edn @@ -0,0 +1,7 @@ +;; Example climit.edn file +;; Required: concurrency +;; Optional: queue-size, ommited means Integer/MAX_VALUE +{:update-file {:concurrency 1 :queue-size 3} + :auth {:concurrency 128} + :process-font {:concurrency 4 :queue-size 32} + :process-image {:concurrency 8 :queue-size 32}} diff --git a/backend/resources/log4j2-devenv.xml b/backend/resources/log4j2-devenv.xml index b653c60fa4..6e4c305720 100644 --- a/backend/resources/log4j2-devenv.xml +++ b/backend/resources/log4j2-devenv.xml @@ -32,6 +32,7 @@ + diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 9e65cd3d38..5ce6f52b11 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -52,7 +52,9 @@ :default-blob-version 5 :loggers-zmq-uri "tcp://localhost:45556" + :rpc-rlimit-config (fs/path "resources/rlimit.edn") + :rpc-climit-config (fs/path "resources/climit.edn") :file-change-snapshot-every 5 :file-change-snapshot-timeout "3h" @@ -90,6 +92,7 @@ (s/def ::default-rpc-rlimit ::us/vector-of-strings) (s/def ::rpc-rlimit-config ::fs/path) +(s/def ::rpc-climit-config ::fs/path) (s/def ::media-max-file-size ::us/integer) @@ -172,11 +175,6 @@ (s/def ::redis-uri ::us/string) (s/def ::registration-domain-whitelist ::us/set-of-strings) -(s/def ::semaphore-process-font ::us/integer) -(s/def ::semaphore-process-image ::us/integer) -(s/def ::semaphore-update-file ::us/integer) -(s/def ::semaphore-auth ::us/integer) - (s/def ::smtp-default-from ::us/string) (s/def ::smtp-default-reply-to ::us/string) (s/def ::smtp-host ::us/string) diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index b66b416ecc..f6764dbabc 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -94,6 +94,23 @@ [err _] (yrs/response 404 (ex-data err))) +(defmethod handle-exception :internal + [error request] + (let [{:keys [code] :as edata} (ex-data error)] + (cond + (= :concurrency-limit-reached code) + (yrs/response 429) + + :else + (do + (l/error ::l/raw (ex-message error) + ::l/context (get-context request) + :cause error) + (yrs/response 500 {:type :server-error + :code :unhandled + :hint (ex-message error) + :data edata}))))) + (defmethod handle-exception org.postgresql.util.PSQLException [error request] (let [state (.getSQLState ^java.sql.SQLException error)] diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 97bcdc7a2f..9f3d5a1501 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -204,7 +204,7 @@ {:pool (ig/ref :app.db/pool) :executor (ig/ref [::default :app.worker/executor])} - :app.rpc/semaphores + :app.rpc/climit {:metrics (ig/ref :app.metrics/metrics) :executor (ig/ref [::default :app.worker/executor])} @@ -224,11 +224,11 @@ :audit (ig/ref :app.loggers.audit/collector) :ldap (ig/ref :app.auth.ldap/provider) :http-client (ig/ref :app.http/client) + :climit (ig/ref :app.rpc/climit) :rlimit (ig/ref :app.rpc/rlimit) :executors (ig/ref :app.worker/executors) :executor (ig/ref [::default :app.worker/executor]) :templates (ig/ref :app.setup/builtin-templates) - :semaphores (ig/ref :app.rpc/semaphores) } :app.rpc.doc/routes diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj index 1429b2f578..9f4d4a4729 100644 --- a/backend/src/app/metrics.clj +++ b/backend/src/app/metrics.clj @@ -100,21 +100,21 @@ ::mdef/labels ["name"] ::mdef/type :summary} - :semaphore-queued-submissions - {::mdef/name "penpot_semaphore_queued_submissions" - ::mdef/help "Current number of queued submissions on SEMAPHORE." + :rpc-climit-queue-size + {::mdef/name "penpot_rpc_climit_queue_size" + ::mdef/help "Current number of queued submissions on the CLIMIT." ::mdef/labels ["name"] ::mdef/type :gauge} - :semaphore-used-permits - {::mdef/name "penpot_semaphore_used_permits" - ::mdef/help "Current number of used permits on SEMAPHORE." + :rpc-climit-concurrency + {::mdef/name "penpot_rpc_climit_concurrency" + ::mdef/help "Current number of used concurrency capacity on the CLIMIT" ::mdef/labels ["name"] ::mdef/type :gauge} - :semaphore-timing - {::mdef/name "penpot_semaphore_timing" - ::mdef/help "Total timing of SEMAPHORE." + :rpc-climit-timing + {::mdef/name "penpot_rpc_climit_timing" + ::mdef/help "Summary of the time between queuing and executing on the CLIMIT" ::mdef/labels ["name"] ::mdef/type :summary} diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 01adf6b07a..6a5e280355 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -15,9 +15,9 @@ [app.loggers.audit :as audit] [app.metrics :as mtx] [app.msgbus :as-alias mbus] + [app.rpc.climit :as climit] [app.rpc.retry :as retry] [app.rpc.rlimit :as rlimit] - [app.rpc.semaphore :as-alias rsem] [app.storage :as-alias sto] [app.util.services :as sv] [app.util.time :as ts] @@ -163,7 +163,7 @@ (wrap-dispatch cfg $ mdata) (wrap-metrics cfg $ mdata) (retry/wrap-retry cfg $ mdata) - (rsem/wrap cfg $ mdata) + (climit/wrap cfg $ mdata) (rlimit/wrap cfg $ mdata) (wrap-audit cfg $ mdata)) @@ -175,6 +175,7 @@ (fn [{:keys [::request] :as params}] ;; Raise authentication error when rpc method requires auth but ;; no profile-id is found in the request. + (p/do! (if (and auth? (not (uuid? (:profile-id params)))) (ex/raise :type :authentication @@ -182,7 +183,6 @@ :hint "authentication required for this endpoint") (let [params (us/conform spec (dissoc params ::request))] (f cfg (assoc params ::request request)))))) - mdata))) (defn- process-method @@ -238,6 +238,7 @@ (s/def ::http-client fn?) (s/def ::ldap (s/nilable map?)) (s/def ::msgbus ::mbus/msgbus) +(s/def ::climit (s/nilable ::climit/climit)) (s/def ::rlimit (s/nilable ::rlimit/rlimit)) (s/def ::public-uri ::us/not-empty-string) @@ -251,7 +252,7 @@ ::public-uri ::msgbus ::http-client - ::rsem/semaphores + ::climit ::rlimit ::mtx/metrics ::db/pool diff --git a/backend/src/app/rpc/climit.clj b/backend/src/app/rpc/climit.clj new file mode 100644 index 0000000000..76c6f44e79 --- /dev/null +++ b/backend/src/app/rpc/climit.clj @@ -0,0 +1,205 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.climit + "Concurrencly limiter for RPC." + (:require + [app.common.data :as d] + [app.common.exceptions :as ex] + [app.common.logging :as l] + [app.common.spec :as us] + [app.config :as cf] + [app.metrics :as mtx] + [app.rpc :as-alias rpc] + [app.util.services :as-alias sv] + [app.util.time :as dt] + [app.worker :as-alias wrk] + [clojure.edn :as edn] + [clojure.spec.alpha :as s] + [datoteka.fs :as fs] + [integrant.core :as ig] + [promesa.core :as p] + [promesa.exec :as px] + [promesa.exec.bulkhead :as pxb]) + (:import + com.github.benmanes.caffeine.cache.Cache + com.github.benmanes.caffeine.cache.CacheLoader + com.github.benmanes.caffeine.cache.Caffeine + com.github.benmanes.caffeine.cache.RemovalListener)) + +(defn- capacity-exception? + [o] + (and (ex/ex-info? o) + (let [data (ex-data o)] + (and (= :bulkhead-error (:type data)) + (= :capacity-limit-reached (:code data)))))) + +(defn invoke! + [limiter f] + (p/handle + (px/submit! limiter f) + (fn [result cause] + (cond + (capacity-exception? cause) + (p/rejected + (ex/error :type :internal + :code :concurrency-limit-reached + :queue (-> limiter meta :bkey name) + :cause cause)) + + (some? cause) + (p/rejected cause) + + :else + (p/resolved result))))) + +(defn- create-limiter + [{:keys [executor metrics concurrency queue-size bkey skey]}] + (let [labels (into-array String [(name bkey)]) + on-queue (fn [instance] + (l/trace :hint "enqueued" + :key (name bkey) + :skey (str skey) + :queue-size (get instance :current-queue-size) + :concurrency (get instance :current-concurrency) + (mtx/run! metrics + :id :rpc-climit-queue-size + :val (get instance :current-queue-size) + :labels labels) + (mtx/run! metrics + :id :rpc-climit-concurrency + :val (get instance :current-concurrency) + :labels labels))) + + on-run (fn [instance task] + (let [elapsed (- (inst-ms (dt/now)) + (inst-ms task))] + (l/trace :hint "execute" + :key (name bkey) + :skey (str skey) + :elapsed (str elapsed "ms")) + (mtx/run! metrics + :id :rpc-climit-timing + :val elapsed + :labels labels) + (mtx/run! metrics + :id :rpc-climit-queue-size + :val (get instance :current-queue-size) + :labels labels) + (mtx/run! metrics + :id :rpc-climit-concurrency + :val (get instance :current-concurrency) + :labels labels))) + + options {:executor executor + :concurrency concurrency + :queue-size (or queue-size Integer/MAX_VALUE) + :on-queue on-queue + :on-run on-run}] + + (-> (pxb/create options) + (vary-meta assoc :bkey bkey :skey skey)))) + +(defn- create-cache + [{:keys [executor] :as params} config] + (let [listener (reify RemovalListener + (onRemoval [_ key _val cause] + (l/trace :hint "cache: remove" :key key :reason (str cause)))) + + loader (reify CacheLoader + (load [_ key] + (let [[bkey skey] key] + (when-let [config (get config bkey)] + (-> (merge params config) + (assoc :bkey bkey) + (assoc :skey skey) + (create-limiter))))))] + + (.. (Caffeine/newBuilder) + (weakValues) + (executor executor) + (removalListener listener) + (build loader)))) + +(defprotocol IConcurrencyManager) + +(s/def ::concurrency ::us/integer) +(s/def ::queue-size ::us/integer) +(s/def ::config + (s/map-of keyword? + (s/keys :req-un [::concurrency] + :opt-un [::queue-size]))) + +(defmethod ig/prep-key ::rpc/climit + [_ cfg] + (merge {:path (cf/get :rpc-climit-config)} + (d/without-nils cfg))) + +(defmethod ig/pre-init-spec ::rpc/climit [_] + (s/keys :req-un [::wrk/executor ::mtx/metrics ::fs/path])) + +(defmethod ig/init-key ::rpc/climit + [_ {:keys [path] :as params}] + (when (contains? cf/flags :rpc-climit) + (if-let [config (some->> path slurp edn/read-string)] + (do + (l/info :hint "initializing concurrency limit" :config (str path)) + (us/verify! ::config config) + + (let [cache (create-cache params config)] + ^{::cache cache} + (reify + IConcurrencyManager + clojure.lang.IDeref + (deref [_] config) + + clojure.lang.ILookup + (valAt [_ key] + (let [key (if (vector? key) key [key])] + (.get ^Cache cache key)))))) + + (l/warn :hint "unable to load configuration" :config (str path))))) + + +(s/def ::climit #(satisfies? IConcurrencyManager %)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; PUBLIC API +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defmacro with-dispatch + [lim & body] + `(if ~lim + (invoke! ~lim (^:once fn [] (p/wrap (do ~@body)))) + (p/wrap (do ~@body)))) + +(defn wrap + [{:keys [climit]} f {:keys [::queue ::key-fn] :as mdata}] + (if (and (some? climit) + (some? queue)) + (if-let [config (get @climit queue)] + (do + (l/debug :hint "wrap: instrumenting method" + :limit-name (name queue) + :service-name (::sv/name mdata) + :queue-size (or (:queue-size config) Integer/MAX_VALUE) + :concurrency (:concurrency config) + :keyed? (some? key-fn)) + (if (some? key-fn) + (fn [cfg params] + (let [key [queue (key-fn params)] + lim (get climit key)] + (invoke! lim (partial f cfg params)))) + + (let [lim (get climit queue)] + (fn [cfg params] + (invoke! lim (partial f cfg params)))))) + (do + (l/warn :hint "wrap: no config found" + :queue (name queue) + :service (::sv/name mdata)) + f)) + f)) diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index f66ed1358e..f41f6bf92d 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -16,10 +16,10 @@ [app.http.session :as session] [app.loggers.audit :as audit] [app.rpc :as-alias rpc] + [app.rpc.climit :as climit] [app.rpc.doc :as-alias doc] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] - [app.rpc.semaphore :as rsem] [app.tokens :as tokens] [app.util.services :as sv] [app.util.time :as dt] @@ -147,7 +147,7 @@ (sv/defmethod ::login-with-password "Performs authentication using penpot password." {:auth false - ::rsem/queue :auth + ::climit/queue :auth ::doc/added "1.15"} [cfg params] (login-with-password cfg params)) @@ -188,7 +188,7 @@ (sv/defmethod ::recover-profile {:auth false - ::rsem/queue :auth + ::climit/queue :auth ::doc/added "1.15"} [cfg params] (recover-profile cfg params)) @@ -438,7 +438,7 @@ (sv/defmethod ::register-profile {:auth false - ::rsem/queue :auth + ::climit/queue :auth ::doc/added "1.15"} [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 5884a1a435..d29382a370 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -22,10 +22,10 @@ [app.msgbus :as mbus] [app.rpc :as-alias rpc] [app.rpc.doc :as-alias doc] + [app.rpc.climit :as climit] [app.rpc.permissions :as perms] [app.rpc.queries.files :as files] [app.rpc.queries.projects :as proj] - [app.rpc.semaphore :as rsem] [app.storage.impl :as simpl] [app.util.blob :as blob] [app.util.objects-map :as omap] @@ -346,8 +346,8 @@ FOR KEY SHARE") (sv/defmethod ::update-file - {::rsem/queue :update-file - ::doc/added "1.0"} + {::climit/queue :update-file + ::climit/key-fn :id} [{:keys [pool] :as cfg} {:keys [id profile-id components-v2] :as params}] (db/with-atomic [conn pool] (db/xact-lock! conn id) diff --git a/backend/src/app/rpc/mutations/fonts.clj b/backend/src/app/rpc/mutations/fonts.clj index 1868feae28..61f4b3ee54 100644 --- a/backend/src/app/rpc/mutations/fonts.clj +++ b/backend/src/app/rpc/mutations/fonts.clj @@ -12,14 +12,15 @@ [app.common.uuid :as uuid] [app.db :as db] [app.media :as media] + [app.rpc.climit :as-alias climit] [app.rpc.doc :as-alias doc] [app.rpc.queries.teams :as teams] - [app.rpc.semaphore :as rsem] [app.storage :as sto] [app.util.services :as sv] [app.util.time :as dt] [clojure.spec.alpha :as s] - [promesa.core :as p])) + [promesa.core :as p] + [promesa.exec :as px])) (declare create-font-variant) @@ -46,15 +47,15 @@ (create-font-variant cfg params))) (defn create-font-variant - [{:keys [storage pool executor semaphores] :as cfg} {:keys [data] :as params}] + [{:keys [storage pool executor climit] :as cfg} {:keys [data] :as params}] (letfn [(generate-fonts [data] - (rsem/with-dispatch (:process-font semaphores) + (climit/with-dispatch (:process-font climit) (media/run {:cmd :generate-fonts :input data}))) ;; Function responsible of calculating cryptographyc hash of ;; the provided data. (calculate-hash [data] - (rsem/with-dispatch (:process-font semaphores) + (px/with-dispatch executor (sto/calculate-hash data))) (validate-data [data] @@ -120,6 +121,7 @@ and font_id = ?") (sv/defmethod ::update-font + {::climit/queue :process-font} [{:keys [pool] :as cfg} {:keys [team-id profile-id id name] :as params}] (db/with-atomic [conn pool] (teams/check-edition-permissions! conn profile-id team-id) diff --git a/backend/src/app/rpc/mutations/media.clj b/backend/src/app/rpc/mutations/media.clj index 2e04836475..2971d64bca 100644 --- a/backend/src/app/rpc/mutations/media.clj +++ b/backend/src/app/rpc/mutations/media.clj @@ -14,8 +14,8 @@ [app.config :as cf] [app.db :as db] [app.media :as media] + [app.rpc.climit :as climit] [app.rpc.queries.teams :as teams] - [app.rpc.semaphore :as rsem] [app.storage :as sto] [app.storage.tmp :as tmp] [app.util.services :as sv] @@ -23,7 +23,8 @@ [clojure.spec.alpha :as s] [cuerdas.core :as str] [datoteka.io :as io] - [promesa.core :as p])) + [promesa.core :as p] + [promesa.exec :as px])) (def default-max-file-size (* 1024 1024 10)) ; 10 MiB @@ -104,25 +105,25 @@ ;; inverse, soft referential integrity). (defn create-file-media-object - [{:keys [storage pool semaphores] :as cfg} + [{:keys [storage pool climit executor] :as cfg} {:keys [id file-id is-local name content] :as params}] (letfn [;; Function responsible to retrieve the file information, as ;; it is synchronous operation it should be wrapped into ;; with-dispatch macro. (get-info [content] - (rsem/with-dispatch (:process-image semaphores) + (climit/with-dispatch (:process-image climit) (media/run {:cmd :info :input content}))) ;; Function responsible of calculating cryptographyc hash of ;; the provided data. (calculate-hash [data] - (rsem/with-dispatch (:process-image semaphores) + (px/with-dispatch executor (sto/calculate-hash data))) ;; Function responsible of generating thumnail. As it is synchronous ;; opetation, it should be wrapped into with-dispatch macro (generate-thumbnail [info] - (rsem/with-dispatch (:process-image semaphores) + (climit/with-dispatch (:process-image climit) (media/run (assoc thumbnail-options :cmd :generic-thumbnail :input info)))) @@ -154,14 +155,15 @@ :bucket "file-media-object"}))) (insert-into-database [info image thumb] - (db/exec-one! pool [sql:create-file-media-object - (or id (uuid/next)) - file-id is-local name - (:id image) - (:id thumb) - (:width info) - (:height info) - (:mtype info)]))] + (px/with-dispatch executor + (db/exec-one! pool [sql:create-file-media-object + (or id (uuid/next)) + file-id is-local name + (:id image) + (:id thumb) + (:width info) + (:height info) + (:mtype info)])))] (p/let [info (get-info content) thumb (create-thumbnail info) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 56846c30d0..da087c8c14 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -16,11 +16,11 @@ [app.loggers.audit :as audit] [app.media :as media] [app.rpc :as-alias rpc] + [app.rpc.climit :as-alias climit] [app.rpc.commands.auth :as cmd.auth] [app.rpc.doc :as-alias doc] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] - [app.rpc.semaphore :as rsem] [app.storage :as sto] [app.tokens :as tokens] [app.util.services :as sv] @@ -83,11 +83,11 @@ (s/keys :req-un [::profile-id ::password ::old-password])) (sv/defmethod ::update-profile-password - {::rsem/queue :auth} + {::climit/queue :auth} [{:keys [pool] :as cfg} {:keys [password] :as params}] (db/with-atomic [conn pool] (let [profile (validate-password! conn params) - session-id (:app.rpc/session-id params)] + session-id (::rpc/session-id params)] (when (= (str/lower (:email profile)) (str/lower (:password params))) (ex/raise :type :validation @@ -309,7 +309,7 @@ (sv/defmethod ::login {:auth false - ::rsem/queue :auth + ::climit/queue :auth ::doc/added "1.0" ::doc/deprecated "1.15"} [cfg params] @@ -354,7 +354,7 @@ (sv/defmethod ::register-profile {:auth false - ::rsem/queue :auth + ::climit/queue :auth ::doc/added "1.0" ::doc/deprecated "1.15"} [{:keys [pool] :as cfg} params] diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index da04e4c65f..288cfdf768 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -17,11 +17,11 @@ [app.loggers.audit :as audit] [app.media :as media] [app.rpc :as-alias rpc] + [app.rpc.climit :as climit] [app.rpc.mutations.projects :as projects] [app.rpc.permissions :as perms] [app.rpc.queries.profile :as profile] [app.rpc.queries.teams :as teams] - [app.rpc.semaphore :as rsem] [app.storage :as sto] [app.tokens :as tokens] [app.util.services :as sv] @@ -316,13 +316,13 @@ (assoc team :photo-id (:id photo)))) (defn upload-photo - [{:keys [storage semaphores] :as cfg} {:keys [file]}] + [{:keys [storage executor climit] :as cfg} {:keys [file]}] (letfn [(get-info [content] - (rsem/with-dispatch (:process-image semaphores) + (climit/with-dispatch (:process-image climit) (media/run {:cmd :info :input content}))) (generate-thumbnail [info] - (rsem/with-dispatch (:process-image semaphores) + (climit/with-dispatch (:process-image climit) (media/run {:cmd :profile-thumbnail :format :jpeg :quality 85 @@ -333,7 +333,7 @@ ;; Function responsible of calculating cryptographyc hash of ;; the provided data. (calculate-hash [data] - (rsem/with-dispatch (:process-image semaphores) + (px/with-dispatch executor (sto/calculate-hash data)))] (p/let [info (get-info file) @@ -341,11 +341,10 @@ hash (calculate-hash (:data thumb)) content (-> (sto/content (:data thumb) (:size thumb)) (sto/wrap-with-hash hash))] - (rsem/with-dispatch (:process-image semaphores) - (sto/put-object! storage {::sto/content content - ::sto/deduplicate? true - :bucket "profile" - :content-type (:mtype thumb)}))))) + (sto/put-object! storage {::sto/content content + ::sto/deduplicate? true + :bucket "profile" + :content-type (:mtype thumb)})))) ;; --- Mutation: Invite Member diff --git a/backend/src/app/rpc/semaphore.clj b/backend/src/app/rpc/semaphore.clj deleted file mode 100644 index 5e8a5a5ed7..0000000000 --- a/backend/src/app/rpc/semaphore.clj +++ /dev/null @@ -1,149 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.rpc.semaphore - "Resource usage limits (in other words: semaphores)." - (:require - [app.common.data :as d] - [app.common.logging :as l] - [app.common.spec :as us] - [app.config :as cf] - [app.metrics :as mtx] - [app.rpc :as-alias rpc] - [app.util.locks :as locks] - [app.util.time :as ts] - [app.worker :as-alias wrk] - [clojure.spec.alpha :as s] - [integrant.core :as ig] - [promesa.core :as p])) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; ASYNC SEMAPHORE IMPL -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defprotocol IAsyncSemaphore - (acquire! [_]) - (release! [_ tp])) - -(defn create - [& {:keys [permits metrics name executor]}] - (let [used (volatile! 0) - queue (volatile! (d/queue)) - labels (into-array String [(d/name name)]) - lock (locks/create) - permits (or permits Long/MAX_VALUE)] - - (when (>= permits Long/MAX_VALUE) - (l/warn :hint "permits value too high" :permits permits :semaphore name)) - - ^{::wrk/executor executor - ::name name} - (reify IAsyncSemaphore - (acquire! [_] - (let [d (p/deferred)] - (locks/locking lock - (if (< @used permits) - (do - (vswap! used inc) - (p/resolve! d)) - (vswap! queue conj d))) - - (mtx/run! metrics - :id :semaphore-used-permits - :val @used - :labels labels) - (mtx/run! metrics - :id :semaphore-queued-submissions - :val (count @queue) - :labels labels) - d)) - - (release! [_ tp] - (locks/locking lock - (if-let [item (peek @queue)] - (do - (vswap! queue pop) - (p/resolve! item)) - (when (pos? @used) - (vswap! used dec)))) - - (mtx/run! metrics - :id :semaphore-timing - :val (inst-ms (tp)) - :labels labels) - (mtx/run! metrics - :id :semaphore-used-permits - :val @used - :labels labels) - (mtx/run! metrics - :id :semaphore-queued-submissions - :val (count @queue) - :labels labels))))) - -(defn semaphore? - [v] - (satisfies? IAsyncSemaphore v)) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; PREDEFINED SEMAPHORES -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(s/def ::semaphore semaphore?) -(s/def ::semaphores - (s/map-of ::us/keyword ::semaphore)) - -(defmethod ig/pre-init-spec ::rpc/semaphores [_] - (s/keys :req-un [::mtx/metrics])) - -(defn- create-default-semaphores - [metrics executor] - [(create :permits (cf/get :semaphore-process-font) - :metrics metrics - :name :process-font - :executor executor) - (create :permits (cf/get :semaphore-update-file) - :metrics metrics - :name :update-file - :executor executor) - (create :permits (cf/get :semaphore-process-image) - :metrics metrics - :name :process-image - :executor executor) - (create :permits (cf/get :semaphore-auth) - :metrics metrics - :name :auth - :executor executor)]) - -(defmethod ig/init-key ::rpc/semaphores - [_ {:keys [metrics executor]}] - (->> (create-default-semaphores metrics executor) - (d/index-by (comp ::name meta)))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; PUBLIC API -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defmacro with-dispatch - [queue & body] - `(let [tpoint# (ts/tpoint) - queue# ~queue - executor# (-> queue# meta ::wrk/executor)] - (-> (acquire! queue#) - (p/then (fn [_#] ~@body) executor#) - (p/finally (fn [_# _#] - (release! queue# tpoint#)))))) - -(defn wrap - [{:keys [semaphores]} f {:keys [::queue]}] - (let [queue' (get semaphores queue)] - (if (semaphore? queue') - (fn [cfg params] - (with-dispatch queue' - (f cfg params))) - (do - (when (some? queue) - (l/warn :hint "undefined semaphore" :name queue)) - f)))) diff --git a/common/deps.edn b/common/deps.edn index 25ff87865d..c8a61a63eb 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -21,7 +21,7 @@ com.cognitect/transit-cljs {:mvn/version "0.8.280"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} - funcool/promesa {:mvn/version "8.0.450"} + funcool/promesa {:mvn/version "9.0.507"} funcool/cuerdas {:mvn/version "2022.06.16-403"} lambdaisland/uri {:mvn/version "1.13.95" From 1f73558f1bf1299775356dd0945fe0e03a134ce1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 2 Nov 2022 09:30:08 +0100 Subject: [PATCH 163/682] :paperclip: Fix linter issues --- backend/scripts/repl | 2 +- backend/src/app/rpc/mutations/files.clj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/scripts/repl b/backend/scripts/repl index ded1640c3e..a2af6734d5 100755 --- a/backend/scripts/repl +++ b/backend/scripts/repl @@ -31,7 +31,7 @@ export PENPOT_STORAGE_ASSETS_S3_ENDPOINT=http://minio:9000 export PENPOT_STORAGE_ASSETS_S3_BUCKET=penpot export OPTIONS=" - -A:dev:jmx-remote \ + -A:jmx-remote -A:dev \ -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager \ -J-Dlog4j2.configurationFile=log4j2-devenv.xml \ -J-XX:+UseG1GC \ diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index d29382a370..8c036fbb1d 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -21,8 +21,8 @@ [app.metrics :as mtx] [app.msgbus :as mbus] [app.rpc :as-alias rpc] + [app.rpc.climit :as-alias climit] [app.rpc.doc :as-alias doc] - [app.rpc.climit :as climit] [app.rpc.permissions :as perms] [app.rpc.queries.files :as files] [app.rpc.queries.projects :as proj] From 111cf54ff6be367cc941c0e9484727255b680a56 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 2 Nov 2022 18:11:50 +0100 Subject: [PATCH 164/682] :recycle: Refactor common module tests and add generative testing for types --- .circleci/config.yml | 2 +- backend/deps.edn | 1 - backend/dev/user.clj | 2 +- common/deps.edn | 3 +- common/dev/user.clj | 15 +- common/scripts/repl | 2 +- common/shadow-cljs.edn | 2 +- common/src/app/common/geom/matrix.cljc | 28 ++- common/src/app/common/geom/point.cljc | 8 +- common/src/app/common/spec.cljc | 136 ++++++++---- common/src/app/common/types/color.cljc | 20 +- common/src/app/common/types/container.cljc | 7 +- common/src/app/common/types/file.cljc | 5 +- common/src/app/common/types/page.cljc | 3 +- common/src/app/common/types/shape.cljc | 193 +++++++++--------- common/src/app/common/types/shape/export.cljc | 4 +- .../app/common/types/shape/interactions.cljc | 74 +++---- common/src/app/common/types/shape/layout.cljc | 1 + common/test/app/common/data_test.clj | 58 ------ .../test_data.cljc} | 4 +- .../test_geom.cljc} | 4 +- .../test_geom_shapes.cljc} | 4 +- .../test_helpers/components.cljc | 10 +- .../test_helpers/files.cljc | 4 +- .../test_pages.cljc} | 6 +- .../test_pages_helpers.cljc} | 4 +- .../test_pages_migrations.cljc} | 4 +- .../test_setup.cljc} | 2 +- .../test_text.cljc} | 8 +- common/test/common_tests/test_types.cljc | 59 ++++++ .../test_types_file.cljc} | 41 ++-- .../test_types_shape_interactions.cljc} | 11 +- .../test_uuid.cljc} | 22 +- 33 files changed, 415 insertions(+), 332 deletions(-) delete mode 100644 common/test/app/common/data_test.clj rename common/test/{app/common/data_test.cljc => common_tests/test_data.cljc} (96%) rename common/test/{app/common/geom_test.cljc => common_tests/test_geom.cljc} (98%) rename common/test/{app/common/geom_shapes_test.cljc => common_tests/test_geom_shapes.cljc} (99%) rename common/test/{app/common => common_tests}/test_helpers/components.cljc (93%) rename common/test/{app/common => common_tests}/test_helpers/files.cljc (98%) rename common/test/{app/common/pages_test.cljc => common_tests/test_pages.cljc} (99%) rename common/test/{app/common/pages_helpers_test.cljc => common_tests/test_pages_helpers.cljc} (96%) rename common/test/{app/common/pages_migrations_test.cljc => common_tests/test_pages_migrations.cljc} (97%) rename common/test/{app/common/setup_test.cljc => common_tests/test_setup.cljc} (88%) rename common/test/{app/common/text_test.cljc => common_tests/test_text.cljc} (78%) create mode 100644 common/test/common_tests/test_types.cljc rename common/test/{app/common/types/file_test.cljc => common_tests/test_types_file.cljc} (91%) rename common/test/{app/common/types/shape/spec_interactions_test.cljc => common_tests/test_types_shape_interactions.cljc} (99%) rename common/test/{app/common/uuid_test.cljc => common_tests/test_uuid.cljc} (50%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 73f5bef6dd..41160d33b1 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -94,7 +94,7 @@ jobs: working_directory: "./common" name: common tests (clj) command: | - clojure -X:dev:test + clojure -X:dev:test :patterns '["common-tests.test-.*"]' environment: PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin diff --git a/backend/deps.edn b/backend/deps.edn index b449a6ecc1..f2bcb7eca0 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -63,7 +63,6 @@ {:extra-deps {com.bhauman/rebel-readline {:mvn/version "RELEASE"} org.clojure/tools.namespace {:mvn/version "RELEASE"} - org.clojure/test.check {:mvn/version "RELEASE"} clojure-humanize/clojure-humanize {:mvn/version "0.2.2"} org.clojure/data.csv {:mvn/version "RELEASE"} com.clojure-goes-fast/clj-async-profiler {:mvn/version "RELEASE"} diff --git a/backend/dev/user.clj b/backend/dev/user.clj index fb6aa9a726..53a10faaa7 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -29,8 +29,8 @@ [clojure.pprint :refer [pprint print-table]] [clojure.repl :refer :all] [clojure.spec.alpha :as s] - [clojure.spec.gen.alpha :as sgen] [clojure.test :as test] + [clojure.test.check.generators :as gen] [clojure.tools.namespace.repl :as repl] [clojure.walk :refer [macroexpand-all]] [criterium.core :as crit] diff --git a/common/deps.edn b/common/deps.edn index c8a61a63eb..2ffa930007 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -4,6 +4,7 @@ org.clojure/tools.cli {:mvn/version "1.0.206"} metosin/jsonista {:mvn/version "0.3.6"} org.clojure/clojurescript {:mvn/version "1.11.60"} + org.clojure/test.check {:mvn/version "1.1.1"} ;; Logging org.apache.logging.log4j/log4j-api {:mvn/version "2.19.0"} @@ -16,6 +17,7 @@ selmer/selmer {:mvn/version "1.12.55"} criterium/criterium {:mvn/version "0.4.6"} + expound/expound {:mvn/version "0.9.0"} com.cognitect/transit-clj {:mvn/version "1.0.329"} com.cognitect/transit-cljs {:mvn/version "0.8.280"} @@ -41,7 +43,6 @@ {:dev {:extra-deps {org.clojure/tools.namespace {:mvn/version "RELEASE"} - org.clojure/test.check {:mvn/version "RELEASE"} thheller/shadow-cljs {:mvn/version "2.20.2"} com.bhauman/rebel-readline {:mvn/version "RELEASE"} criterium/criterium {:mvn/version "RELEASE"} diff --git a/common/dev/user.clj b/common/dev/user.clj index 414a751f3a..fc379cc343 100644 --- a/common/dev/user.clj +++ b/common/dev/user.clj @@ -2,7 +2,7 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC (ns user (:require @@ -12,32 +12,33 @@ [clojure.spec.alpha :as s] [clojure.spec.gen.alpha :as sgen] [clojure.test :as test] + [clojure.test.check.generators :as gen] [clojure.tools.namespace.repl :as repl] [clojure.walk :refer [macroexpand-all]] - [criterium.core :refer [quick-bench bench with-progress-reporting]])) + [criterium.core :as crit])) ;; --- Benchmarking Tools (defmacro run-quick-bench [& exprs] - `(with-progress-reporting (quick-bench (do ~@exprs) :verbose))) + `(crit/with-progress-reporting (crit/quick-bench (do ~@exprs) :verbose))) (defmacro run-quick-bench' [& exprs] - `(quick-bench (do ~@exprs))) + `(crit/quick-bench (do ~@exprs))) (defmacro run-bench [& exprs] - `(with-progress-reporting (bench (do ~@exprs) :verbose))) + `(crit/with-progress-reporting (crit/bench (do ~@exprs) :verbose))) (defmacro run-bench' [& exprs] - `(bench (do ~@exprs))) + `(crit/bench (do ~@exprs))) ;; --- Development Stuff (defn- run-tests - ([] (run-tests #"^app.common.*-test$")) + ([] (run-tests #"^common-tests.test-.*$")) ([o] (repl/refresh) (cond diff --git a/common/scripts/repl b/common/scripts/repl index 4317cc23ea..e139dba257 100755 --- a/common/scripts/repl +++ b/common/scripts/repl @@ -1,7 +1,7 @@ #!/usr/bin/env bash export PENPOT_FLAGS="enable-asserts enable-audit-log $PENPOT_FLAGS" -export OPTIONS="-A:dev -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -J-XX:+UseG1GC -J-XX:-OmitStackTraceInFastThrow -J-Xms50m -J-Xmx512m"; +export OPTIONS="-A:dev -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -J-XX:+UseG1GC -J-XX:-OmitStackTraceInFastThrow -J-Xms50m -J-Xmx1024m"; export OPTIONS_EVAL="nil" # export OPTIONS_EVAL="(set! *warn-on-reflection* true)" diff --git a/common/shadow-cljs.edn b/common/shadow-cljs.edn index f6ff2bbf27..bca945b8e1 100644 --- a/common/shadow-cljs.edn +++ b/common/shadow-cljs.edn @@ -8,7 +8,7 @@ {:target :node-test :output-to "target/test.js" :output-dir "target/test/" - :ns-regexp "^app.common.*-test$" + :ns-regexp "^common-tests.test-.*$" :autorun true :compiler-options diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index 4d9e793265..e4448acb78 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -12,7 +12,8 @@ [app.common.geom.point :as gpt] [app.common.math :as mth] [app.common.spec :as us] - [clojure.spec.alpha :as s])) + [clojure.spec.alpha :as s] + [clojure.test.check.generators :as tgen])) (def precision 6) @@ -39,16 +40,6 @@ [v] (instance? Matrix v)) -(s/def ::a ::us/safe-number) -(s/def ::b ::us/safe-number) -(s/def ::c ::us/safe-number) -(s/def ::d ::us/safe-number) -(s/def ::e ::us/safe-number) -(s/def ::f ::us/safe-number) - -(s/def ::matrix - (s/and (s/keys :req-un [::a ::b ::c ::d ::e ::f]) matrix?)) - (defn matrix "Create a new matrix instance." ([] @@ -56,6 +47,21 @@ ([a b c d e f] (Matrix. a b c d e f))) +(s/def ::a ::us/safe-float) +(s/def ::b ::us/safe-float) +(s/def ::c ::us/safe-float) +(s/def ::d ::us/safe-float) +(s/def ::e ::us/safe-float) +(s/def ::f ::us/safe-float) + +(s/def ::matrix-attrs + (s/keys :req-un [::a ::b ::c ::d ::e ::f])) + +(s/def ::matrix + (s/with-gen + (s/and ::matrix-attrs matrix?) + #(tgen/fmap map->Matrix (s/gen ::matrix-attrs)))) + (def number-regex #"[+-]?\d*(\.\d+)?(e[+-]?\d+)?") (defn str->matrix diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 8cbd5dabf0..9ce97f8af5 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -13,7 +13,8 @@ :clj [clojure.core :as c]) [app.common.math :as mth] [app.common.spec :as us] - [clojure.spec.alpha :as s])) + [clojure.spec.alpha :as s] + [clojure.test.check.generators :as tgen])) ;; --- Point Impl @@ -30,8 +31,11 @@ (s/def ::x ::us/safe-number) (s/def ::y ::us/safe-number) +(s/def ::point-attrs (s/keys :req-un [::x ::y])) + (s/def ::point - (s/and (s/keys :req-un [::x ::y]) point?)) + (s/with-gen (s/and ::point-attrs point?) + #(tgen/fmap map->Point (s/gen ::point-attrs)))) (defn point-like? [{:keys [x y] :as v}] diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 0435116306..3b63a6f127 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -18,6 +18,7 @@ [app.common.exceptions :as ex] [app.common.uri :as u] [app.common.uuid :as uuid] + [clojure.test.check.generators :as tgen] [cuerdas.core :as str] [expound.alpha :as expound])) @@ -47,7 +48,9 @@ ::s/invalid))) (unformer [v] (dm/str v))] - (s/def ::uuid (s/conformer conformer unformer))) + (s/def ::uuid + (s/with-gen (s/conformer conformer unformer) + #(tgen/fmap (fn [_] (uuid/random)) tgen/any)))) ;; --- SPEC: boolean @@ -61,7 +64,9 @@ ::s/invalid))) (unformer [v] (if v "true" "false"))] - (s/def ::boolean (s/conformer conformer unformer))) + (s/def ::boolean + (s/with-gen (s/conformer conformer unformer) + (constantly tgen/boolean)))) ;; --- SPEC: number @@ -71,7 +76,9 @@ (str/numeric? v) #?(:cljs (js/parseFloat v) :clj (Double/parseDouble v)) :else ::s/invalid))] - (s/def ::number (s/conformer conformer str))) + (s/def ::number + (s/with-gen (s/conformer conformer str) + #(s/gen ::safe-number)))) ;; --- SPEC: integer @@ -84,7 +91,9 @@ :cljs (js/parseInt v 10)) ::s/invalid) :else ::s/invalid))] - (s/def ::integer (s/conformer conformer str))) + (s/def ::integer + (s/with-gen (s/conformer conformer str) + #(s/gen ::safe-integer)))) ;; --- SPEC: keyword @@ -96,7 +105,10 @@ (unformer [v] (name v))] - (s/def ::keyword (s/conformer conformer unformer))) + (s/def ::keyword + (s/with-gen (s/conformer conformer unformer) + #(->> (s/gen ::not-empty-string) + (tgen/fmap keyword))))) ;; --- SPEC: email @@ -110,7 +122,13 @@ (or (parse-email v) ::s/invalid)) (unformer [v] (dm/str v))] - (s/def ::email (s/conformer conformer unformer))) + (s/def ::email + (s/with-gen (s/conformer conformer unformer) + #(as-> (tgen/let [p1 (s/gen ::not-empty-string) + p2 (s/gen ::not-empty-string) + p3 (tgen/elements ["com" "net"])] + (str p1 "@" p2 "." p3)) $ + (tgen/such-that (partial re-matches email-re) $ 50))))) ;; -- SPEC: uri @@ -121,17 +139,34 @@ :else ::s/invalid)) (unformer [v] (dm/str v))] - (s/def ::uri (s/conformer conformer unformer))) + (s/def ::uri + (s/with-gen (s/conformer conformer unformer) + #(tgen/let [scheme (tgen/elements ["http" "https"]) + domain (as-> (s/gen ::not-empty-string) $ + (tgen/such-that (fn [x] (> (count x) 5)) $ 100) + (tgen/fmap str/lower $)) + ext (tgen/elements ["net" "com" "org" "app" "io"])] + (u/uri (str scheme "://" domain "." ext)))))) ;; --- SPEC: color string +(def rgb-color-str-re + #"^#(?:[0-9a-fA-F]{3}){1,2}$") + (letfn [(conformer [v] - (if (and (string? v) (re-matches #"^#(?:[0-9a-fA-F]{3}){1,2}$" v)) + (if (and (string? v) (re-matches rgb-color-str-re v)) v ::s/invalid)) (unformer [v] (dm/str v))] - (s/def ::rgb-color-str (s/conformer conformer unformer))) + (s/def ::rgb-color-str + (s/with-gen (s/conformer conformer unformer) + #(->> tgen/any + (tgen/fmap (fn [_] + #?(:clj (format "%x" (rand-int 16rFFFFFF)) + :cljs (.toString (rand-int 16rFFFFFF) 16)))) + (tgen/fmap (fn [x] + (str "#" x))))))) ;; --- SPEC: set/vector of Keywords @@ -142,17 +177,19 @@ (keyword? s) s :else nil)))] (cond - (set? s) (into dest xform s) + (coll? s) (into dest xform s) (string? s) (into dest xform (str/words s)) :else ::s/invalid))) (unformer-fn [v] (str/join " " (map name v)))] (s/def ::set-of-keywords - (s/conformer (partial conformer-fn #{}) unformer-fn)) + (s/with-gen (s/conformer (partial conformer-fn #{}) unformer-fn) + #(tgen/set (s/gen ::keyword)))) (s/def ::vector-of-keywords - (s/conformer (partial conformer-fn []) unformer-fn))) + (s/with-gen (s/conformer (partial conformer-fn []) unformer-fn) + #(tgen/vector (s/gen ::keyword))))) ;; --- SPEC: set/vector of strings @@ -164,18 +201,18 @@ (letfn [(conformer-fn [dest v] (cond + (coll? v) (into dest non-empty-strings-xf v) (string? v) (into dest non-empty-strings-xf (str/split v #"[\s,]+")) - (vector? v) (into dest non-empty-strings-xf v) - (set? v) (into dest non-empty-strings-xf v) :else ::s/invalid)) (unformer-fn [v] (str/join "," v))] - (s/def ::set-of-strings - (s/conformer (partial conformer-fn #{}) unformer-fn)) + (s/with-gen (s/conformer (partial conformer-fn #{}) unformer-fn) + #(tgen/set (s/gen ::not-empty-string)))) (s/def ::vector-of-strings - (s/conformer (partial conformer-fn []) unformer-fn))) + (s/with-gen (s/conformer (partial conformer-fn []) unformer-fn) + #(tgen/vector (s/gen ::not-empty-string))))) ;; --- SPEC: set-of-valid-emails @@ -192,24 +229,27 @@ :else ::s/invalid)) (unformer [v] (str/join " " v))] - (s/def ::set-of-valid-emails (s/conformer conformer unformer))) - -;; --- SPEC: query-string - -(letfn [(conformer [s] - (if (string? s) - (ex/try* #(u/query-string->map s) (constantly ::s/invalid)) - s)) - (unformer [s] - (u/map->query-string s))] - (s/def ::query-string (s/conformer conformer unformer))) + (s/def ::set-of-valid-emails + (s/with-gen (s/conformer conformer unformer) + #(tgen/set (s/gen ::email))))) ;; --- SPECS WITHOUT CONFORMER (s/def ::inst inst?) -(s/def ::string string?) -(s/def ::not-empty-string (s/and string? #(not (str/empty? %)))) -(s/def ::url string?) + +(s/def ::string + (s/with-gen string? + (fn [] + (tgen/such-that (fn [o] + (re-matches #"\w+" o)) + tgen/string-alphanumeric + 50)))) + +(s/def ::not-empty-string + (s/with-gen (s/and string? #(not (str/empty? %))) + #(tgen/such-that (complement str/empty?) (s/gen ::string)))) + +(s/def ::url ::string) (s/def ::fn fn?) (s/def ::id ::uuid) @@ -231,20 +271,34 @@ :cljs (or (instance? js/Uint8Array x) (instance? js/ArrayBuffer x))))) -(s/def ::bytes bytes?) +(s/def ::bytes + #?(:clj (s/with-gen bytes? (constantly tgen/bytes)) + :cljs bytes?)) + +(defn safe-number? + [x] + (and (number? x) + (>= x min-safe-int) + (<= x max-safe-int))) + +(defn safe-int? [x] + (and (safe-number? x) (int? x))) + +(defn safe-float? [x] + (and (safe-number? x) (float? x))) (s/def ::safe-integer - #(and - (int? %) - (>= % min-safe-int) - (<= % max-safe-int))) + (s/with-gen safe-int? (constantly tgen/small-integer))) + +(s/def ::safe-float + (s/with-gen safe-float? #(tgen/double* {:inifinite? false + :NaN? false + :min min-safe-int + :max max-safe-int}))) (s/def ::safe-number - #(and - (or (int? %) - (float? %)) - (>= % min-safe-int) - (<= % max-safe-int))) + (s/with-gen safe-number? #(tgen/one-of [(s/gen ::safe-integer) + (s/gen ::safe-float)]))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; MACROS diff --git a/common/src/app/common/types/color.cljc b/common/src/app/common/types/color.cljc index ffe1da4109..2032cd0477 100644 --- a/common/src/app/common/types/color.cljc +++ b/common/src/app/common/types/color.cljc @@ -27,7 +27,7 @@ (s/def ::color-gradient/end-y ::us/safe-number) (s/def ::color-gradient/width ::us/safe-number) -(s/def ::color-gradient-stop/color string?) +(s/def ::color-gradient-stop/color ::us/rgb-color-str) (s/def ::color-gradient-stop/opacity ::us/safe-number) (s/def ::color-gradient-stop/offset ::us/safe-number) @@ -53,7 +53,7 @@ (s/def ::color-generic/name string?) (s/def ::color-generic/path (s/nilable string?)) (s/def ::color-generic/value (s/nilable string?)) -(s/def ::color-generic/color (s/nilable string?)) +(s/def ::color-generic/color (s/nilable ::us/rgb-color-str)) (s/def ::color-generic/opacity (s/nilable ::us/safe-number)) (s/def ::color-generic/gradient (s/nilable ::gradient)) (s/def ::color-generic/ref-id uuid?) @@ -76,10 +76,14 @@ ::color-generic/gradient])) (s/def ::recent-color - (s/keys :opt-un [::color-generic/value - ::color-generic/color - ::color-generic/opacity - ::color-generic/gradient])) + (s/and + (s/keys :opt-un [::color-generic/value + ::color-generic/color + ::color-generic/opacity + ::color-generic/gradient]) + (fn [o] + (or (contains? o :gradient) + (contains? o :color))))) ;; --- Helpers for color in different parts of a shape @@ -159,7 +163,7 @@ [shape position color opacity gradient] (update-in shape [:shadow position :color] (fn [shadow-color] - (d/without-nils (assoc shadow-color + (d/without-nils (assoc shadow-color :color color :opacity opacity :gradient gradient))))) @@ -190,7 +194,7 @@ [shape position color opacity gradient] (update-in shape [:grids position :params :color] (fn [grid-color] - (d/without-nils (assoc grid-color + (d/without-nils (assoc grid-color :color color :opacity opacity :gradient gradient))))) diff --git a/common/src/app/common/types/container.cljc b/common/src/app/common/types/container.cljc index 8d08bb123a..5660f1ae25 100644 --- a/common/src/app/common/types/container.cljc +++ b/common/src/app/common/types/container.cljc @@ -15,13 +15,12 @@ (s/def ::type #{:page :component}) (s/def ::id uuid?) -(s/def ::name string?) -(s/def ::path (s/nilable string?)) +(s/def ::name ::us/string) +(s/def ::path (s/nilable ::us/string)) (s/def ::container - ;; (s/keys :req-un [::id ::name ::ctst/objects] (s/keys :req-un [::id ::name] - :opt-un [::type ::path])) + :opt-un [::type ::path ::ctst/objects])) (defn make-container [page-or-component type] diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index 9ad0748f4a..a4b7b26bae 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -37,7 +37,7 @@ (s/coll-of ::ctc/recent-color :kind vector?)) (s/def ::typographies - (s/map-of uuid? :ctst/typography)) + (s/map-of uuid? ::cty/typography)) (s/def ::pages (s/coll-of uuid? :kind vector?)) @@ -49,12 +49,13 @@ (s/map-of uuid? ::ctp/page)) (s/def ::components - (s/map-of uuid? ::ctp/container)) + (s/map-of uuid? ::ctn/container)) (s/def ::data (s/keys :req-un [::pages-index ::pages] :opt-un [::colors + ::components ::recent-colors ::typographies ::media])) diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index 8f3da96c85..dd34854815 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.files.features :as ffeat] + [app.common.spec :as us] [app.common.types.page.flow :as ctpf] [app.common.types.page.grid :as ctpg] [app.common.types.page.guide :as ctpu] @@ -17,7 +18,7 @@ ;; --- Background color -(s/def ::background string?) +(s/def ::background ::us/rgb-color-str) ;; --- Page options diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 29b4c2b976..c994f3c340 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -25,18 +25,19 @@ [app.common.types.shape.text :as ctsx] [app.common.uuid :as uuid] [clojure.set :as set] - [clojure.spec.alpha :as s])) + [clojure.spec.alpha :as s] + [clojure.test.check.generators :as tgen])) ;; --- Specs (s/def ::frame-id uuid?) (s/def ::id uuid?) -(s/def ::name string?) -(s/def ::path (s/nilable string?)) +(s/def ::name ::us/string) +(s/def ::path (s/nilable ::us/string)) (s/def ::page-id uuid?) (s/def ::parent-id uuid?) -(s/def ::string string?) -(s/def ::type keyword?) +(s/def ::string ::us/string) +(s/def ::type #{:frame :text :rect :path :image :circle :group :bool :svg-raw}) (s/def ::uuid uuid?) (s/def ::component-id uuid?) @@ -54,7 +55,7 @@ (s/def ::blocked boolean?) (s/def ::collapsed boolean?) -(s/def ::fill-color string?) +(s/def ::fill-color ::us/rgb-color-str) (s/def ::fill-opacity ::us/safe-number) (s/def ::fill-color-gradient (s/nilable ::ctc/gradient)) (s/def ::fill-color-ref-file (s/nilable uuid?)) @@ -66,10 +67,10 @@ (s/def ::file-thumbnail boolean?) (s/def ::masked-group? boolean?) -(s/def ::font-family string?) +(s/def ::font-family ::us/string) (s/def ::font-size ::us/safe-integer) -(s/def ::font-style string?) -(s/def ::font-weight string?) +(s/def ::font-style ::us/string) +(s/def ::font-weight ::us/string) (s/def ::hidden boolean?) (s/def ::letter-spacing ::us/safe-number) (s/def ::line-height ::us/safe-number) @@ -77,7 +78,7 @@ (s/def ::page-id uuid?) (s/def ::proportion ::us/safe-number) (s/def ::proportion-lock boolean?) -(s/def ::stroke-color string?) +(s/def ::stroke-color ::us/string) (s/def ::stroke-color-gradient (s/nilable ::ctc/gradient)) (s/def ::stroke-color-ref-file (s/nilable uuid?)) (s/def ::stroke-color-ref-id (s/nilable uuid?)) @@ -120,11 +121,12 @@ (s/every uuid? :kind vector?)) (s/def ::fill - (s/keys :opt-un [::fill-color - ::fill-opacity - ::fill-color-gradient - ::fill-color-ref-file - ::fill-color-ref-id])) + (s/and (s/keys :opt-un [::fill-color + ::fill-opacity + ::fill-color-gradient + ::fill-color-ref-file + ::fill-color-ref-id]) + (comp boolean seq))) (s/def ::fills (s/coll-of ::fill :kind vector?)) @@ -164,72 +166,70 @@ :color :luminosity}) +(s/def ::shape-base-attrs + (s/keys :opt-un [::id + ::name + ::component-id + ::component-file + ::component-root? + ::shape-ref + ::selrect + ::points + ::blocked + ::collapsed + ::fills + ::hide-fill-on-export + ::font-family + ::font-size + ::font-style + ::font-weight + ::hidden + ::letter-spacing + ::line-height + ::locked + ::proportion + ::proportion-lock + ::constraints-h + ::constraints-v + ::fixed-scroll + ::ctsr/rx + ::ctsr/ry + ::ctsr/r1 + ::ctsr/r2 + ::ctsr/r3 + ::ctsr/r4 + ::x + ::y + ::exports + ::shapes + ::strokes + ::text-align + ::transform + ::transform-inverse + ::width + ::height + ::masked-group? + ::ctsi/interactions + ::ctss/shadow + ::ctsb/blur + ::opacity + ::blend-mode])) + (s/def ::shape-attrs - (s/and - ::ctsl/layout-container-props - ::ctsl/layout-child-props - (s/keys :opt-un [::id - ::type - ::name - ::component-id - ::component-file - ::component-root? - ::shape-ref - ::selrect - ::points - ::blocked - ::collapsed - ::fills - ::fill-color ;; TODO: remove these attributes - ::fill-opacity ;; when backward compatibility - ::fill-color-gradient ;; is no longer needed - ::fill-color-ref-file ;; - ::fill-color-ref-id ;; - ::hide-fill-on-export - ::font-family - ::font-size - ::font-style - ::font-weight - ::hidden - ::letter-spacing - ::line-height - ::locked - ::proportion - ::proportion-lock - ::constraints-h - ::constraints-v - ::fixed-scroll - ::ctsr/rx - ::ctsr/ry - ::ctsr/r1 - ::ctsr/r2 - ::ctsr/r3 - ::ctsr/r4 - ::x - ::y - ::exports - ::shapes - ::strokes - ::stroke-color ;; TODO: same thing - ::stroke-color-ref-file ;; - ::stroke-color-ref-id ;; - ::stroke-opacity ;; - ::stroke-style - ::stroke-width - ::stroke-alignment - ::stroke-cap-start - ::stroke-cap-end - ::text-align - ::transform - ::transform-inverse - ::width - ::height - ::masked-group? - ::ctsi/interactions - ::ctss/shadow - ::ctsb/blur - ::opacity - ::blend-mode]))) + (s/with-gen + (s/merge + ::shape-base-attrs + ::ctsl/layout-container-props + ::ctsl/layout-child-props + + ;; For BACKWARD COMPATIBILITY we need to spec fill and stroke + ;; attrs as shape toplevel attrs + ::fill + ::stroke) + #(tgen/let [attrs1 (s/gen ::shape-base-attrs) + attrs2 (s/gen ::ctsl/layout-container-props) + attrs3 (s/gen ::ctsl/layout-child-props)] + (merge attrs1 attrs2 attrs3)))) (defmulti shape-spec :type) @@ -237,26 +237,31 @@ (s/spec ::shape-attrs)) (defmethod shape-spec :text [_] - (s/and ::shape-attrs - (s/keys :opt-un [::ctsx/content - ::ctsx/position-data]))) + (s/merge ::shape-attrs + (s/keys :opt-un [::ctsx/content + ::ctsx/position-data]))) (defmethod shape-spec :path [_] - (s/and ::shape-attrs - (s/keys :opt-un [::ctsp/content]))) + (s/merge ::shape-attrs + (s/keys :opt-un [::ctsp/content]))) (defmethod shape-spec :frame [_] - (s/and ::shape-attrs - (s/keys :opt-un [::file-thumbnail - ::hide-fill-on-export - ::show-content - ::hide-in-viewer]))) + (s/merge ::shape-attrs + (s/keys :opt-un [::file-thumbnail + ::hide-fill-on-export + ::show-content + ::hide-in-viewer]))) (s/def ::shape - (s/and (s/multi-spec shape-spec :type) - #(contains? % :type) - #(contains? % :name))) - + (s/with-gen + (s/merge + (s/keys :req-un [::type ::name]) + (s/multi-spec shape-spec :type)) + (fn [] + (tgen/let [type (s/gen ::type) + name (s/gen ::name) + attrs (s/gen ::shape-attrs)] + (assoc attrs :type type :name name))))) ;; --- Initialization diff --git a/common/src/app/common/types/shape/export.cljc b/common/src/app/common/types/shape/export.cljc index e63430b285..084ee6f54b 100644 --- a/common/src/app/common/types/shape/export.cljc +++ b/common/src/app/common/types/shape/export.cljc @@ -9,9 +9,9 @@ [app.common.spec :as us] [clojure.spec.alpha :as s])) -(s/def ::suffix string?) +(s/def ::suffix ::us/string) (s/def ::scale ::us/safe-number) -(s/def ::type keyword?) +(s/def ::type ::us/keyword) (s/def ::export (s/keys :req-un [::type diff --git a/common/src/app/common/types/shape/interactions.cljc b/common/src/app/common/types/shape/interactions.cljc index 103c32a837..806e7494ca 100644 --- a/common/src/app/common/types/shape/interactions.cljc +++ b/common/src/app/common/types/shape/interactions.cljc @@ -22,12 +22,13 @@ ;; -- Options depending on event type -(s/def ::event-type #{:click - :mouse-press - :mouse-over - :mouse-enter - :mouse-leave - :after-delay}) +(s/def ::event-type + #{:click + :mouse-press + :mouse-over + :mouse-enter + :mouse-leave + :after-delay}) (s/def ::delay ::us/safe-integer) @@ -40,26 +41,21 @@ (s/keys :req-un [])) (s/def ::event-opts - (s/multi-spec event-opts-spec ::event-type)) + (s/multi-spec event-opts-spec :event-type)) ;; -- Animation options -(s/def ::animation-type #{:dissolve - :slide - :push}) +(s/def ::animation-type #{:dissolve :slide :push}) (s/def ::duration ::us/safe-integer) -(s/def ::easing #{:linear - :ease - :ease-in - :ease-out - :ease-in-out}) -(s/def ::way #{:in - :out}) -(s/def ::direction #{:right - :left - :up - :down}) +(s/def ::way #{:in :out}) +(s/def ::direction #{:right :left :up :down}) (s/def ::offset-effect ::us/boolean) +(s/def ::easing + #{:linear + :ease + :ease-in + :ease-out + :ease-in-out}) (defmulti animation-spec :animation-type) @@ -80,26 +76,29 @@ ::direction])) (s/def ::animation - (s/multi-spec animation-spec ::animation-type)) + (s/multi-spec animation-spec :animation-type)) ;; -- Options depending on action type -(s/def ::action-type #{:navigate - :open-overlay - :toggle-overlay - :close-overlay - :prev-screen - :open-url}) +(s/def ::action-type + #{:navigate + :open-overlay + :toggle-overlay + :close-overlay + :prev-screen + :open-url}) + +(s/def ::overlay-pos-type + #{:manual + :center + :top-left + :top-right + :top-center + :bottom-left + :bottom-right + :bottom-center}) (s/def ::destination (s/nilable ::us/uuid)) -(s/def ::overlay-pos-type #{:manual - :center - :top-left - :top-right - :top-center - :bottom-left - :bottom-right - :bottom-center}) (s/def ::overlay-position ::gpt/point) (s/def ::url ::us/string) (s/def ::close-click-outside ::us/boolean) @@ -140,7 +139,7 @@ (s/keys :req-un [::url])) (s/def ::action-opts - (s/multi-spec action-opts-spec ::action-type)) + (s/multi-spec action-opts-spec :action-type)) ;; -- Interaction @@ -412,6 +411,7 @@ (us/verify (s/nilable ::animation-type) animation-type) (assert (has-animation? interaction)) (assert (allowed-animation? (:action-type interaction) animation-type)) + (if (= (-> interaction :animation :animation-type) animation-type) interaction (if (nil? animation-type) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 8c983341de..498df01ec6 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -30,6 +30,7 @@ (s/def ::row-gap ::us/safe-number) (s/def ::column-gap ::us/safe-number) +(s/def ::layout-type #{:flex :grid}) (s/def ::layout-gap (s/keys :req-un [::row-gap ::column-gap])) diff --git a/common/test/app/common/data_test.clj b/common/test/app/common/data_test.clj deleted file mode 100644 index 8a85bb97b1..0000000000 --- a/common/test/app/common/data_test.clj +++ /dev/null @@ -1,58 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) UXBOX Labs SL - -(ns app.common.data-test - (:require - [app.common.data :as d] - [clojure.test :as t])) - -(t/deftest concat-vec - (t/is (= [1 2 3] - (d/concat-vec [1] #{2} [3]))) - - (t/is (= [1 2] - (d/concat-vec '(1) [2]))) - - (t/is (= [1] - (d/concat-vec [1]))) - - (t/is (= [] (d/concat-vec)))) - -(t/deftest concat-set - (t/is (= #{} (d/concat-set))) - (t/is (= #{1 2} - (d/concat-set [1] [2])))) - -(t/deftest remove-at-index - (t/is (= [1 2 3 4] - (d/remove-at-index [1 2 3 4 5] 4))) - - - (t/is (= [1 2 3 4] - (d/remove-at-index [5 1 2 3 4] 0))) - - (t/is (= [1 2 3 4] - (d/remove-at-index [1 5 2 3 4] 1))) - ) - -(t/deftest with-next - (t/is (= [[0 1] [1 2] [2 3] [3 4] [4 nil]] - (d/with-next (range 5))))) - -(t/deftest with-prev - (t/is (= [[0 nil] [1 0] [2 1] [3 2] [4 3]] - (d/with-prev (range 5))))) - -(t/deftest with-prev-next - (t/is (= [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]] - (d/with-prev-next (range 5))))) - -(t/deftest join - (t/is (= [[1 :a] [1 :b] [2 :a] [2 :b] [3 :a] [3 :b]] - (d/join [1 2 3] [:a :b]))) - (t/is (= [1 10 100 2 20 200 3 30 300] - (d/join [1 2 3] [1 10 100] *)))) - diff --git a/common/test/app/common/data_test.cljc b/common/test/common_tests/test_data.cljc similarity index 96% rename from common/test/app/common/data_test.cljc rename to common/test/common_tests/test_data.cljc index 4cf9c661f2..85b942f744 100644 --- a/common/test/app/common/data_test.cljc +++ b/common/test/common_tests/test_data.cljc @@ -2,9 +2,9 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.data-test +(ns common-tests.test-data (:require [app.common.data :as d] [clojure.test :as t])) diff --git a/common/test/app/common/geom_test.cljc b/common/test/common_tests/test_geom.cljc similarity index 98% rename from common/test/app/common/geom_test.cljc rename to common/test/common_tests/test_geom.cljc index 6807ed282f..46786c54d2 100644 --- a/common/test/app/common/geom_test.cljc +++ b/common/test/common_tests/test_geom.cljc @@ -2,9 +2,9 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.geom-test +(ns common-tests.test-geom (:require [clojure.test :as t] [app.common.math :as mth] diff --git a/common/test/app/common/geom_shapes_test.cljc b/common/test/common_tests/test_geom_shapes.cljc similarity index 99% rename from common/test/app/common/geom_shapes_test.cljc rename to common/test/common_tests/test_geom_shapes.cljc index b7307b3c1e..1358be6acb 100644 --- a/common/test/app/common/geom_shapes_test.cljc +++ b/common/test/common_tests/test_geom_shapes.cljc @@ -2,9 +2,9 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.geom-shapes-test +(ns common-tests.test-geom-shapes (:require [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] diff --git a/common/test/app/common/test_helpers/components.cljc b/common/test/common_tests/test_helpers/components.cljc similarity index 93% rename from common/test/app/common/test_helpers/components.cljc rename to common/test/common_tests/test_helpers/components.cljc index 843b74a4e7..f459ae0059 100644 --- a/common/test/app/common/test_helpers/components.cljc +++ b/common/test/common_tests/test_helpers/components.cljc @@ -1,4 +1,10 @@ -(ns app.common.test-helpers.components +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns common-tests.test-helpers.components (:require [clojure.test :as t] [app.common.pages.helpers :as cph] @@ -136,7 +142,7 @@ [shapes-inst shapes-main component])) (defn resolve-component - "Get the component with the given id and all its shapes." + "Get the component with the given id and all its shapes." [page component-id libraries] (let [component (cph/get-component libraries component-id) root-main (ctk/get-component-root component) diff --git a/common/test/app/common/test_helpers/files.cljc b/common/test/common_tests/test_helpers/files.cljc similarity index 98% rename from common/test/app/common/test_helpers/files.cljc rename to common/test/common_tests/test_helpers/files.cljc index 1175907e89..69a51bbfad 100644 --- a/common/test/app/common/test_helpers/files.cljc +++ b/common/test/common_tests/test_helpers/files.cljc @@ -2,9 +2,9 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.test-helpers.files +(ns common-tests.test-helpers.files (:require [app.common.files.features :as ffeat] [app.common.geom.point :as gpt] diff --git a/common/test/app/common/pages_test.cljc b/common/test/common_tests/test_pages.cljc similarity index 99% rename from common/test/app/common/pages_test.cljc rename to common/test/common_tests/test_pages.cljc index d9f5f2ee42..aa50164b73 100644 --- a/common/test/app/common/pages_test.cljc +++ b/common/test/common_tests/test_pages.cljc @@ -2,9 +2,9 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.pages-test +(ns common-tests.test-pages (:require [app.common.files.features :as ffeat] [app.common.pages :as cp] @@ -438,7 +438,7 @@ :frame-id frame-id :parent-id frame-id :id shape-1-id - :obj {:type :shape + :obj {:type :rect :name "Shape 1"}} {:type :add-obj :page-id page-id diff --git a/common/test/app/common/pages_helpers_test.cljc b/common/test/common_tests/test_pages_helpers.cljc similarity index 96% rename from common/test/app/common/pages_helpers_test.cljc rename to common/test/common_tests/test_pages_helpers.cljc index a6265a1161..50b87c8139 100644 --- a/common/test/app/common/pages_helpers_test.cljc +++ b/common/test/common_tests/test_pages_helpers.cljc @@ -2,9 +2,9 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.pages-helpers-test +(ns common-tests.test-pages-helpers (:require [clojure.test :as t] [clojure.pprint :refer [pprint]] diff --git a/common/test/app/common/pages_migrations_test.cljc b/common/test/common_tests/test_pages_migrations.cljc similarity index 97% rename from common/test/app/common/pages_migrations_test.cljc rename to common/test/common_tests/test_pages_migrations.cljc index ada1c97e4e..75d593665c 100644 --- a/common/test/app/common/pages_migrations_test.cljc +++ b/common/test/common_tests/test_pages_migrations.cljc @@ -2,9 +2,9 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.pages-migrations-test +(ns common-tests.test-pages-migrations (:require [clojure.test :as t] [clojure.pprint :refer [pprint]] diff --git a/common/test/app/common/setup_test.cljc b/common/test/common_tests/test_setup.cljc similarity index 88% rename from common/test/app/common/setup_test.cljc rename to common/test/common_tests/test_setup.cljc index 5b11a5af28..8dc2e87d83 100644 --- a/common/test/app/common/setup_test.cljc +++ b/common/test/common_tests/test_setup.cljc @@ -1,4 +1,4 @@ -(ns app.common.setup-test +(ns common-tests.test-setup (:require [clojure.test :as t])) diff --git a/common/test/app/common/text_test.cljc b/common/test/common_tests/test_text.cljc similarity index 78% rename from common/test/app/common/text_test.cljc rename to common/test/common_tests/test_text.cljc index 9cd52a2531..a6bd1c47e9 100644 --- a/common/test/app/common/text_test.cljc +++ b/common/test/common_tests/test_text.cljc @@ -1,4 +1,10 @@ -(ns app.common.text-test +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns common-tests.test-text (:require [app.common.data :as d] [app.common.text :as txt] diff --git a/common/test/common_tests/test_types.cljc b/common/test/common_tests/test_types.cljc new file mode 100644 index 0000000000..f64828d3ab --- /dev/null +++ b/common/test/common_tests/test_types.cljc @@ -0,0 +1,59 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns common-tests.test-types + (:require + [clojure.spec.alpha :as s] + [clojure.test :as t] + [clojure.test.check.clojure-test :refer [defspec]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as props] + [app.common.spec :as us] + [app.common.transit :as transit] + [app.common.types.shape :as cts] + [app.common.types.page :as ctp] + [app.common.types.file :as ctf])) + +(defspec transit-encode-decode-with-shape 30 + (props/for-all + [fdata (s/gen ::cts/shape)] + (let [res (-> fdata transit/encode-str transit/decode-str)] + (t/is (= res fdata))))) + +(defspec types-shape-spec 10 + (props/for-all + [fdata (s/gen ::cts/shape)] + (t/is (us/valid? ::cts/shape fdata)))) + +(defspec types-page-spec 10 + (props/for-all + [fdata (s/gen ::ctp/page)] + (t/is (us/valid? ::ctp/page fdata)))) + +(defspec types-file-colors-spec 30 + (props/for-all + [fdata (s/gen ::ctf/colors)] + (t/is (us/valid? ::ctf/colors fdata)))) + +(defspec types-file-recent-colors-spec 30 + (props/for-all + [fdata (s/gen ::ctf/recent-colors)] + (t/is (us/valid? ::ctf/recent-colors fdata)))) + +(defspec types-file-typographies-spec 30 + (props/for-all + [fdata (s/gen ::ctf/typographies)] + (t/is (us/valid? ::ctf/typographies fdata)))) + +(defspec types-file-media-spec 30 + (props/for-all + [fdata (s/gen ::ctf/media)] + (t/is (us/valid? ::ctf/media fdata)))) + +(defspec types-file-components-spec 10 + (props/for-all + [fdata (s/gen ::ctf/components)] + (t/is (us/valid? ::ctf/components fdata)))) diff --git a/common/test/app/common/types/file_test.cljc b/common/test/common_tests/test_types_file.cljc similarity index 91% rename from common/test/app/common/types/file_test.cljc rename to common/test/common_tests/test_types_file.cljc index 0935baf0d0..a5e0017abe 100644 --- a/common/test/app/common/types/file_test.cljc +++ b/common/test/common_tests/test_types_file.cljc @@ -2,29 +2,28 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.types.file-test +(ns common-tests.test-types-file (:require - ;; Uncomment to debug - ;; [clojure.pprint :refer [pprint]] - ;; [cuerdas.core :as str] - [clojure.test :as t] - [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.common.text :as txt] - [app.common.types.colors-list :as ctcl] - [app.common.types.component :as ctk] - [app.common.types.components-list :as ctkl] - [app.common.types.container :as ctn] - [app.common.types.file :as ctf] - [app.common.types.pages-list :as ctpl] - [app.common.types.shape :as cts] - [app.common.types.shape-tree :as ctst] - [app.common.types.typographies-list :as ctyl] - [app.common.uuid :as uuid] - [app.common.test-helpers.files :as thf] - [app.common.test-helpers.components :as thk])) + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.text :as txt] + [app.common.types.colors-list :as ctcl] + [app.common.types.component :as ctk] + [app.common.types.components-list :as ctkl] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] + [app.common.types.shape :as cts] + [app.common.types.shape-tree :as ctst] + [app.common.types.typographies-list :as ctyl] + [app.common.uuid :as uuid] + [clojure.pprint :refer [pprint]] + [clojure.test :as t] + [common-tests.test-helpers.components :as thk] + [common-tests.test-helpers.files :as thf] + [cuerdas.core :as str])) (t/use-fixtures :each thf/reset-idmap!) diff --git a/common/test/app/common/types/shape/spec_interactions_test.cljc b/common/test/common_tests/test_types_shape_interactions.cljc similarity index 99% rename from common/test/app/common/types/shape/spec_interactions_test.cljc rename to common/test/common_tests/test_types_shape_interactions.cljc index a840194963..b92f6c725e 100644 --- a/common/test/app/common/types/shape/spec_interactions_test.cljc +++ b/common/test/common_tests/test_types_shape_interactions.cljc @@ -2,17 +2,17 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.types.shape.spec-interactions-test +(ns common-tests.test-types-shape-interactions (:require - [clojure.test :as t] - [clojure.pprint :refer [pprint]] [app.common.exceptions :as ex] + [app.common.geom.point :as gpt] [app.common.types.shape :as cts] [app.common.types.shape.interactions :as ctsi] [app.common.uuid :as uuid] - [app.common.geom.point :as gpt])) + [clojure.pprint :refer [pprint]] + [clojure.test :as t])) (t/deftest set-event-type (let [interaction ctsi/default-interaction @@ -497,7 +497,6 @@ (t/is (= :down (:direction a-up'))) (t/is (= :up (:direction a-down'))))))) - (t/deftest option-offset-effect (let [i1 ctsi/default-interaction i2 (ctsi/set-animation-type ctsi/default-interaction :slide) diff --git a/common/test/app/common/uuid_test.cljc b/common/test/common_tests/test_uuid.cljc similarity index 50% rename from common/test/app/common/uuid_test.cljc rename to common/test/common_tests/test_uuid.cljc index 9f773ae97e..f57c84914a 100644 --- a/common/test/app/common/uuid_test.cljc +++ b/common/test/common_tests/test_uuid.cljc @@ -2,26 +2,22 @@ ;; License, v. 2.0. If a copy of the MPL was not distributed with this ;; file, You can obtain one at http://mozilla.org/MPL/2.0/. ;; -;; Copyright (c) UXBOX Labs SL +;; Copyright (c) KALEIDOS INC -(ns app.common.uuid-test +(ns common-tests.test-uuid (:require + [app.common.spec :as us] [app.common.uuid :as uuid] + [clojure.spec.alpha :as s] [clojure.test :as t] - [clojure.test.check.clojure-test :refer (defspec)] + [clojure.test.check.clojure-test :refer [defspec]] [clojure.test.check.generators :as gen] [clojure.test.check.properties :as props])) -(def uuid-gen - (->> gen/large-integer (gen/fmap (fn [_] (uuid/next))))) - -(defspec non-repeating-uuid-next-1 100000 +(defspec non-repeating-uuid-next-1 5000 (props/for-all - [uuid1 uuid-gen - uuid2 uuid-gen - uuid3 uuid-gen - uuid4 uuid-gen - uuid5 uuid-gen] - (t/is (not= uuid1 uuid2 uuid3 uuid4 uuid5)))) + [uuid1 (s/gen ::us/uuid) + uuid2 (s/gen ::us/uuid)] + (t/is (not= uuid1 uuid2)))) From e36d611f1951c2cec203da327b4504a6634a540f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Nov 2022 11:17:49 +0100 Subject: [PATCH 165/682] :fire: Remove obsolete code from scripts/repl --- backend/scripts/repl | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/scripts/repl b/backend/scripts/repl index a2af6734d5..e6b3c6f247 100755 --- a/backend/scripts/repl +++ b/backend/scripts/repl @@ -2,7 +2,7 @@ export PENPOT_HOST=devenv export PENPOT_TENANT=dev -export PENPOT_FLAGS="$PENPOT_FLAGS enable-backend-asserts enable-audit-log enable-transit-readable-response enable-demo-users disable-secure-session-cookies enable-rpc-rate-limit enable-warn-rpc-rate-limits enable-smtp" +export PENPOT_FLAGS="$PENPOT_FLAGS enable-backend-asserts enable-audit-log enable-transit-readable-response enable-demo-users disable-secure-session-cookies enable-smtp" # export PENPOT_DATABASE_URI="postgresql://172.17.0.1:5432/penpot" # export PENPOT_DATABASE_USERNAME="penpot" @@ -16,8 +16,6 @@ export PENPOT_FLAGS="$PENPOT_FLAGS enable-backend-asserts enable-audit-log enabl # export PENPOT_LOGGERS_LOKI_URI="http://172.17.0.1:3100/loki/api/v1/push" # export PENPOT_AUDIT_LOG_ARCHIVE_URI="http://localhost:6070/api/audit" -export PENPOT_DEFAULT_RATE_LIMIT="default,window,10000/h" - # Initialize MINIO config mc alias set penpot-s3/ http://minio:9000 minioadmin minioadmin mc admin user add penpot-s3 penpot-devenv penpot-devenv From 6713d8eb3fc15a66d312427da05e279a8fb9f556 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Nov 2022 11:18:22 +0100 Subject: [PATCH 166/682] :sparkles: Make metrics more modular --- backend/src/app/main.clj | 109 +++++++++++++++++++++++++++++++++- backend/src/app/metrics.clj | 113 +++--------------------------------- backend/src/app/rpc.clj | 2 +- 3 files changed, 115 insertions(+), 109 deletions(-) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 9f3d5a1501..ae32a063c3 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -9,11 +9,116 @@ [app.auth.oidc] [app.common.logging :as l] [app.config :as cf] + [app.metrics.definition :as-alias mdef] [app.util.time :as dt] [cuerdas.core :as str] [integrant.core :as ig]) (:gen-class)) +(def default-metrics + {:update-file-changes + {::mdef/name "penpot_rpc_update_file_changes_total" + ::mdef/help "A total number of changes submitted to update-file." + ::mdef/type :counter} + + :update-file-bytes-processed + {::mdef/name "penpot_rpc_update_file_bytes_processed_total" + ::mdef/help "A total number of bytes processed by update-file." + ::mdef/type :counter} + + :rpc-mutation-timing + {::mdef/name "penpot_rpc_mutation_timing" + ::mdef/help "RPC mutation method call timing." + ::mdef/labels ["name"] + ::mdef/type :histogram} + + :rpc-command-timing + {::mdef/name "penpot_rpc_command_timing" + ::mdef/help "RPC command method call timing." + ::mdef/labels ["name"] + ::mdef/type :histogram} + + :rpc-query-timing + {::mdef/name "penpot_rpc_query_timing" + ::mdef/help "RPC query method call timing." + ::mdef/labels ["name"] + ::mdef/type :histogram} + + :websocket-active-connections + {::mdef/name "penpot_websocket_active_connections" + ::mdef/help "Active websocket connections gauge" + ::mdef/type :gauge} + + :websocket-messages-total + {::mdef/name "penpot_websocket_message_total" + ::mdef/help "Counter of processed messages." + ::mdef/labels ["op"] + ::mdef/type :counter} + + :websocket-session-timing + {::mdef/name "penpot_websocket_session_timing" + ::mdef/help "Websocket session timing (seconds)." + ::mdef/type :summary} + + :session-update-total + {::mdef/name "penpot_http_session_update_total" + ::mdef/help "A counter of session update batch events." + ::mdef/type :counter} + + :tasks-timing + {::mdef/name "penpot_tasks_timing" + ::mdef/help "Background tasks timing (milliseconds)." + ::mdef/labels ["name"] + ::mdef/type :summary} + + :redis-eval-timing + {::mdef/name "penpot_redis_eval_timing" + ::mdef/help "Redis EVAL commands execution timings (ms)" + ::mdef/labels ["name"] + ::mdef/type :summary} + + :rpc-climit-queue-size + {::mdef/name "penpot_rpc_climit_queue_size" + ::mdef/help "Current number of queued submissions on the CLIMIT." + ::mdef/labels ["name"] + ::mdef/type :gauge} + + :rpc-climit-concurrency + {::mdef/name "penpot_rpc_climit_concurrency" + ::mdef/help "Current number of used concurrency capacity on the CLIMIT" + ::mdef/labels ["name"] + ::mdef/type :gauge} + + :rpc-climit-timing + {::mdef/name "penpot_rpc_climit_timing" + ::mdef/help "Summary of the time between queuing and executing on the CLIMIT" + ::mdef/labels ["name"] + ::mdef/type :summary} + + :executors-active-threads + {::mdef/name "penpot_executors_active_threads" + ::mdef/help "Current number of threads available in the executor service." + ::mdef/labels ["name"] + ::mdef/type :gauge} + + :executors-completed-tasks + {::mdef/name "penpot_executors_completed_tasks_total" + ::mdef/help "Approximate number of completed tasks by the executor." + ::mdef/labels ["name"] + ::mdef/type :counter} + + :executors-running-threads + {::mdef/name "penpot_executors_running_threads" + ::mdef/help "Current number of threads with state RUNNING." + ::mdef/labels ["name"] + ::mdef/type :gauge} + + :executors-queued-submissions + {::mdef/name "penpot_executors_queued_submissions" + ::mdef/help "Current number of queued submissions." + ::mdef/labels ["name"] + ::mdef/type :gauge}}) + (def system-config {:app.db/pool {:uri (cf/get :database-uri) @@ -50,7 +155,7 @@ {} :app.metrics/metrics - {} + {:default default-metrics} :app.migrations/all {:main (ig/ref :app.migrations/migrations)} @@ -205,7 +310,7 @@ :executor (ig/ref [::default :app.worker/executor])} :app.rpc/climit - {:metrics (ig/ref :app.metrics/metrics) + {:metrics (ig/ref :app.metrics/metrics) :executor (ig/ref [::default :app.worker/executor])} :app.rpc/rlimit diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj index 9f4d4a4729..aa5979c4a4 100644 --- a/backend/src/app/metrics.clj +++ b/backend/src/app/metrics.clj @@ -38,110 +38,6 @@ ;; METRICS SERVICE PROVIDER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(def default-metrics - {:update-file-changes - {::mdef/name "penpot_rpc_update_file_changes_total" - ::mdef/help "A total number of changes submitted to update-file." - ::mdef/type :counter} - - :update-file-bytes-processed - {::mdef/name "penpot_rpc_update_file_bytes_processed_total" - ::mdef/help "A total number of bytes processed by update-file." - ::mdef/type :counter} - - :rpc-mutation-timing - {::mdef/name "penpot_rpc_mutation_timing" - ::mdef/help "RPC mutation method call timing." - ::mdef/labels ["name"] - ::mdef/type :histogram} - - :rpc-command-timing - {::mdef/name "penpot_rpc_command_timing" - ::mdef/help "RPC command method call timing." - ::mdef/labels ["name"] - ::mdef/type :histogram} - - :rpc-query-timing - {::mdef/name "penpot_rpc_query_timing" - ::mdef/help "RPC query method call timing." - ::mdef/labels ["name"] - ::mdef/type :histogram} - - :websocket-active-connections - {::mdef/name "penpot_websocket_active_connections" - ::mdef/help "Active websocket connections gauge" - ::mdef/type :gauge} - - :websocket-messages-total - {::mdef/name "penpot_websocket_message_total" - ::mdef/help "Counter of processed messages." - ::mdef/labels ["op"] - ::mdef/type :counter} - - :websocket-session-timing - {::mdef/name "penpot_websocket_session_timing" - ::mdef/help "Websocket session timing (seconds)." - ::mdef/type :summary} - - :session-update-total - {::mdef/name "penpot_http_session_update_total" - ::mdef/help "A counter of session update batch events." - ::mdef/type :counter} - - :tasks-timing - {::mdef/name "penpot_tasks_timing" - ::mdef/help "Background tasks timing (milliseconds)." - ::mdef/labels ["name"] - ::mdef/type :summary} - - :redis-eval-timing - {::mdef/name "penpot_redis_eval_timing" - ::mdef/help "Redis EVAL commands execution timings (ms)" - ::mdef/labels ["name"] - ::mdef/type :summary} - - :rpc-climit-queue-size - {::mdef/name "penpot_rpc_climit_queue_size" - ::mdef/help "Current number of queued submissions on the CLIMIT." - ::mdef/labels ["name"] - ::mdef/type :gauge} - - :rpc-climit-concurrency - {::mdef/name "penpot_rpc_climit_concurrency" - ::mdef/help "Current number of used concurrency capacity on the CLIMIT" - ::mdef/labels ["name"] - ::mdef/type :gauge} - - :rpc-climit-timing - {::mdef/name "penpot_rpc_climit_timing" - ::mdef/help "Summary of the time between queuing and executing on the CLIMIT" - ::mdef/labels ["name"] - ::mdef/type :summary} - - :executors-active-threads - {::mdef/name "penpot_executors_active_threads" - ::mdef/help "Current number of threads available in the executor service." - ::mdef/labels ["name"] - ::mdef/type :gauge} - - :executors-completed-tasks - {::mdef/name "penpot_executors_completed_tasks_total" - ::mdef/help "Approximate number of completed tasks by the executor." - ::mdef/labels ["name"] - ::mdef/type :counter} - - :executors-running-threads - {::mdef/name "penpot_executors_running_threads" - ::mdef/help "Current number of threads with state RUNNING." - ::mdef/labels ["name"] - ::mdef/type :gauge} - - :executors-queued-submissions - {::mdef/name "penpot_executors_queued_submissions" - ::mdef/help "Current number of queued submissions." - ::mdef/labels ["name"] - ::mdef/type :gauge}}) - (s/def ::mdef/name string?) (s/def ::mdef/help string?) (s/def ::mdef/labels (s/every string? :kind vector?)) @@ -169,8 +65,13 @@ ::handler ::definitions])) +(s/def ::default ::definitions) + +(defmethod ig/pre-init-spec ::metrics [_] + (s/keys :req-un [::default])) + (defmethod ig/init-key ::metrics - [_ _] + [_ cfg] (l/info :action "initialize metrics") (let [registry (create-registry) definitions (reduce-kv (fn [res k v] @@ -178,7 +79,7 @@ (create-collector) (assoc res k))) {} - default-metrics)] + (:default cfg))] (us/verify! ::definitions definitions) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 6a5e280355..1f1274b8f3 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -252,8 +252,8 @@ ::public-uri ::msgbus ::http-client - ::climit ::rlimit + ::climit ::mtx/metrics ::db/pool ::ldap])) From 751b99bf47ff3159304dc27c5fa3faea54106082 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 31 Oct 2022 15:42:24 +0100 Subject: [PATCH 167/682] :arrow_up: Update devenv dockerfiles --- docker/devenv/Dockerfile | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index 129d26e2c9..15c007e2e8 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -3,9 +3,9 @@ LABEL maintainer="Andrey Antukh " ARG DEBIAN_FRONTEND=noninteractive -ENV NODE_VERSION=v16.17.0 \ - CLOJURE_VERSION=1.11.1.1165 \ - CLJKONDO_VERSION=2022.09.08 \ +ENV NODE_VERSION=v18.12.0 \ + CLOJURE_VERSION=1.11.1.1189 \ + CLJKONDO_VERSION=2022.10.14 \ BABASHKA_VERSION=0.9.162 \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 @@ -108,12 +108,12 @@ RUN set -eux; \ ARCH="$(dpkg --print-architecture)"; \ case "${ARCH}" in \ aarch64|arm64) \ - ESUM='c640fc5e5710dba3f92099a791be50fab54f91cf2c3838cb536ded27ecc562a6'; \ - BINARY_URL='https://cdn.azul.com/zulu/bin/zulu19.28.81-ca-jdk19.0.0-linux_aarch64.tar.gz'; \ + ESUM='5f9c1ea91000a271afad3726149a6aefbca3c3b9e0fa790e9aa7fbf0f38aa9ed'; \ + BINARY_URL='https://cdn.azul.com/zulu/bin/zulu19.30.11-ca-jdk19.0.1-linux_aarch64.tar.gz'; \ ;; \ amd64|x86_64) \ - ESUM='6813da339124261092daab369a1c60dea5f27f4ba9608a16517191d30511a087'; \ - BINARY_URL='https://cdn.azul.com/zulu/bin/zulu19.28.81-ca-jdk19.0.0-linux_x64.tar.gz'; \ + ESUM='2ac8cd9e7e1e30c8fba107164a2ded9fad698326899564af4b1254815adfaa8a'; \ + BINARY_URL='https://cdn.azul.com/zulu/bin/zulu19.30.11-ca-jdk19.0.1-linux_x64.tar.gz'; \ ;; \ *) \ echo "Unsupported arch: ${ARCH}"; \ From ce99ca0aa80a4b69c3e8f7f5b703f7bece21ab8b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 1 Nov 2022 09:41:04 +0100 Subject: [PATCH 168/682] :tada: Add generic PointerMap abstraction --- backend/src/app/util/pointer_map.clj | 224 +++++++++++++++++++++++++++ 1 file changed, 224 insertions(+) create mode 100644 backend/src/app/util/pointer_map.clj diff --git a/backend/src/app/util/pointer_map.clj b/backend/src/app/util/pointer_map.clj new file mode 100644 index 0000000000..0942f77e7b --- /dev/null +++ b/backend/src/app/util/pointer_map.clj @@ -0,0 +1,224 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.util.pointer-map + (:require + [app.common.logging :as l] + [app.common.transit :as t] + [app.common.uuid :as uuid] + [app.util.fressian :as fres] + [app.util.time :as dt] + [clojure.core :as c]) + (:import + clojure.lang.Counted + clojure.lang.IDeref + clojure.lang.IHashEq + clojure.lang.IObj + clojure.lang.IPersistentCollection + clojure.lang.IPersistentMap + clojure.lang.PersistentArrayMap + clojure.lang.PersistentHashMap + clojure.lang.Seqable + java.util.List)) + +(def ^:dynamic *load-fn* nil) +(def ^:dynamic *tracked* nil) +(def ^:dynamic *metadata* {}) + +(declare create) + +(defprotocol IPointerMap + (get-id [_]) + (load! [_]) + (modified? [_]) + (clone [_])) + +(deftype PointerMap [id mdata + ^:unsynchronized-mutable odata + ^:unsynchronized-mutable modified? + ^:unsynchronized-mutable loaded?] + + IPointerMap + (load! [_] + (l/trace :hint "pointer-map:load" :id id) + (set! loaded? true) + + (when-not *load-fn* + (throw (UnsupportedOperationException. "load is not supported when *load-fn* is not bind"))) + + (when-let [data (*load-fn* id)] + (set! odata data)) + (or odata {})) + + (modified? [_] modified?) + (get-id [_] id) + + (clone [this] + (when-not loaded? (load! this)) + (let [mdata (assoc mdata :created-at (dt/now)) + id (uuid/next) + pmap (PointerMap. id + mdata + odata + true + true)] + (some-> *tracked* (swap! assoc id pmap)) + pmap)) + + IDeref + (deref [this] + (when-not loaded? (load! this)) + (or odata {})) + + ;; We don't need to load the data for calculate hash because this + ;; map has specific behavior + IHashEq + (hasheq [_] (hash id)) + + Object + (hashCode [this] + (.hasheq ^IHashEq this)) + + IObj + (meta [_] + (or mdata {})) + + (withMeta [_ mdata'] + (let [pmap (PointerMap. id mdata' odata (not= mdata mdata') loaded?)] + (some-> *tracked* (swap! assoc id pmap)) + pmap)) + + Seqable + (seq [this] + (when-not loaded? (load! this)) + (.seq ^Seqable odata)) + + IPersistentCollection + (equiv [this other] + (identical? this other)) + + IPersistentMap + (cons [this o] + (when-not loaded? (load! this)) + (if (map-entry? o) + (assoc this (key o) (val o)) + (if (vector? o) + (assoc this (nth o 0) (nth o 1)) + (throw (UnsupportedOperationException. "invalid arguments to cons"))))) + + (empty [_] + (create)) + + (containsKey [this key] + (when-not loaded? (load! this)) + (contains? odata key)) + + (entryAt [this key] + (when-not loaded? (load! this)) + (.entryAt ^IPersistentMap odata key)) + + (valAt [this key] + (when-not loaded? (load! this)) + (.valAt ^IPersistentMap odata key)) + + (valAt [this key not-found] + (when-not loaded? (load! this)) + (.valAt ^IPersistentMap odata key not-found)) + + (assoc [this key val] + (when-not loaded? (load! this)) + (let [odata (assoc odata key val) + mdata (assoc mdata :created-at (dt/now)) + id (if modified? id (uuid/next)) + pmap (PointerMap. id + mdata + odata + true + true)] + (some-> *tracked* (swap! assoc id pmap)) + pmap)) + + (assocEx [_ _ _] + (throw (UnsupportedOperationException. "method not implemented"))) + + (without [this key] + (when-not loaded? (load! this)) + (let [odata (dissoc odata key) + mdata (assoc mdata :created-at (dt/now)) + id (if modified? id (uuid/next)) + pmap (PointerMap. id + mdata + odata + true + true)] + (some-> *tracked* (swap! assoc id pmap)) + pmap)) + + Counted + (count [this] + (when-not loaded? (load! this)) + (count odata)) + + Iterable + (iterator [this] + (when-not loaded? (load! this)) + (.iterator ^Iterable odata))) + +(defn create + ([] + (let [id (uuid/next) + mdata (assoc *metadata* :created-at (dt/now)) + pmap (PointerMap. id mdata {} true true)] + (some-> *tracked* (swap! assoc id pmap)) + pmap)) + ([id mdata] + (let [pmap (PointerMap. id mdata {} false false)] + (some-> *tracked* (swap! assoc id pmap)) + pmap))) + +(defn pointer-map? + [o] + (instance? PointerMap o)) + +(defn wrap + [data] + (if (pointer-map? data) + (do + (some-> *tracked* (swap! assoc (get-id data) data)) + data) + (into (create) data))) + +(fres/add-handlers! + {:name "penpot/experimental/pointer-map/v2" + :class PointerMap + :wfn (fn [n w o] + (fres/write-tag! w n 3) + (let [id (get-id o)] + (fres/write-int! w (uuid/get-word-high id)) + (fres/write-int! w (uuid/get-word-low id))) + (fres/begin-closed-list! w) + (loop [items (-> o meta seq)] + (when-let [^clojure.lang.MapEntry item (first items)] + (fres/write-object! w (.key item) true) + (fres/write-object! w (.val item)) + (recur (rest items)))) + (fres/end-list! w)) + :rfn (fn [r] + (let [msb (fres/read-object! r) + lsb (fres/read-object! r) + kvs (fres/read-object! r)] + (create (uuid/custom msb lsb) + (if (< (.size ^List kvs) 16) + (PersistentArrayMap. (.toArray ^List kvs)) + (PersistentHashMap/create (seq kvs))))))}) + +(t/add-handlers! + {:id "penpot/pointer" + :class PointerMap + :wfn (fn [val] + [(get-id val) (meta val)])}) + + From c027de25929f1dfe3820a1b86358dec9c5e8be61 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 1 Nov 2022 09:41:39 +0100 Subject: [PATCH 169/682] :sparkles: Make nil safe some decode helpers on db ns --- backend/src/app/db.clj | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 7739580390..e62b8290b8 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -420,21 +420,23 @@ (defn decode-json-pgobject [^PGobject o] - (let [typ (.getType o) - val (.getValue o)] - (if (or (= typ "json") - (= typ "jsonb")) - (json/read val) - val))) + (when o + (let [typ (.getType o) + val (.getValue o)] + (if (or (= typ "json") + (= typ "jsonb")) + (json/read val) + val)))) (defn decode-transit-pgobject [^PGobject o] - (let [typ (.getType o) - val (.getValue o)] - (if (or (= typ "json") - (= typ "jsonb")) - (t/decode-str val) - val))) + (when o + (let [typ (.getType o) + val (.getValue o)] + (if (or (= typ "json") + (= typ "jsonb")) + (t/decode-str val) + val)))) (defn inet [ip-addr] From a42d7164adf564f8efb11bd198350ad47019d135 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 1 Nov 2022 09:43:02 +0100 Subject: [PATCH 170/682] :tada: Add more helpers on srepl ns --- backend/src/app/srepl/helpers.clj | 7 ++++-- backend/src/app/srepl/main.clj | 37 +++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/backend/src/app/srepl/helpers.clj b/backend/src/app/srepl/helpers.clj index 3e89feb8db..5da3f6c199 100644 --- a/backend/src/app/srepl/helpers.clj +++ b/backend/src/app/srepl/helpers.clj @@ -30,6 +30,8 @@ [cuerdas.core :as str] [expound.alpha :as expound])) +(def ^:dynamic *conn*) + (defn reset-password! "Reset a password to a specific one for a concrete user or all users if email is `:all` keyword." @@ -69,8 +71,9 @@ (update :features db/decode-pgarray #{}) (update :data blob/decode) (cond-> migrate? (update :data pmg/migrate-data))) - file (-> (update-fn file) - (cond-> inc-revn? (update :revn inc)))] + file (binding [*conn* conn] + (-> (update-fn file) + (cond-> inc-revn? (update :revn inc))))] (when save? (let [features (db/create-array conn "text" (:features file)) data (blob/encode (:data file))] diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index b113064882..01730dbb6c 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -16,7 +16,9 @@ [app.rpc.queries.profile :as profile] [app.srepl.fixes :as f] [app.srepl.helpers :as h] + [app.util.blob :as blob] [app.util.objects-map :as omap] + [app.util.pointer-map :as pmap] [app.util.time :as dt] [clojure.pprint :refer [pprint]] [cuerdas.core :as str])) @@ -103,12 +105,15 @@ (db/delete! conn :http-session {:profile-id (:id profile)}) :blocked)))) -(defn enable-objects-map-on-file + +(defn enable-objects-map-feature-on-file! [system & {:keys [save? id]}] (letfn [(update-file [{:keys [features] :as file}] (if (contains? features "storage/objects-map") file - (update file :data migrate-to-omap))) + (-> file + (update :data migrate-to-omap) + (update :features conj "storage/objects-map")))) (migrate-to-omap [data] (-> data @@ -119,3 +124,31 @@ :id id :update-fn update-file :save? save?))) + +(defn enable-pointer-map-feature-on-file! + [system & {:keys [save? id]}] + (letfn [(update-file [{:keys [features id] :as file}] + (if (contains? features "storage/pointer-map") + file + (-> file + (update :data migrate-to-omap id) + (update :features conj "storage/pointer-map")))) + + (migrate-to-omap [data file-id] + (binding [pmap/*tracked* (atom {}) + pmap/*metadata* {:file-id file-id}] + (let [data (-> data + (update :pages-index update-vals pmap/wrap) + (update :components pmap/wrap))] + (doseq [[id item] @pmap/*tracked*] + (db/insert! h/*conn* :file-data-fragment + {:id id + :file-id file-id + :metadata (-> item meta db/tjson) + :content (-> item deref blob/encode)})) + data)))] + + (h/update-file! system + :id id + :update-fn update-file + :save? save?))) From 76333cec26bbff3e91700e1a3db1d9303adf5743 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 1 Nov 2022 09:46:54 +0100 Subject: [PATCH 171/682] :tada: Integrate storage/pointer-map file feature --- backend/src/app/http/debug.clj | 2 +- backend/src/app/migrations.clj | 5 + .../sql/0083-add-file-data-fragment-table.sql | 15 + ...d-features-column-to-file-change-table.sql | 8 + backend/src/app/rpc.clj | 5 +- backend/src/app/rpc/commands/binfile.clj | 11 +- backend/src/app/rpc/commands/comments.clj | 2 +- backend/src/app/rpc/commands/files.clj | 885 +++++++++++++++++- backend/src/app/rpc/commands/files/create.clj | 83 ++ backend/src/app/rpc/commands/files/temp.clj | 96 ++ backend/src/app/rpc/commands/files/update.clj | 295 ++++++ backend/src/app/rpc/commands/management.clj | 41 +- backend/src/app/rpc/commands/viewer.clj | 89 ++ backend/src/app/rpc/mutations/comments.clj | 8 +- backend/src/app/rpc/mutations/files.clj | 656 +++---------- backend/src/app/rpc/mutations/share_link.clj | 2 +- backend/src/app/rpc/queries/comments.clj | 8 +- backend/src/app/rpc/queries/files.clj | 575 ++---------- backend/src/app/rpc/queries/fonts.clj | 2 +- backend/src/app/rpc/queries/viewer.clj | 92 +- backend/src/app/srepl/main.clj | 4 +- backend/src/app/tasks/file_gc.clj | 62 +- backend/src/app/util/fressian.clj | 12 +- backend/test/app/services_files_test.clj | 8 +- backend/test/app/test_helpers.clj | 44 +- common/src/app/common/files/features.cljc | 9 +- common/src/app/common/transit.cljc | 9 + .../src/app/common/types/components_list.cljc | 6 +- common/src/app/common/types/page.cljc | 6 +- frontend/src/app/main/data/dashboard.cljs | 38 +- frontend/src/app/main/data/workspace.cljs | 2 +- .../app/main/data/workspace/libraries.cljs | 144 +-- .../app/main/data/workspace/persistence.cljs | 29 +- .../app/main/data/workspace/thumbnails.cljs | 2 +- frontend/src/app/main/errors.cljs | 21 + frontend/src/app/main/features.cljs | 10 +- frontend/src/app/main/repo.cljs | 33 +- frontend/src/app/main/ui/dashboard/grid.cljs | 24 +- frontend/src/app/render.cljs | 54 +- frontend/src/app/util/http.cljs | 1 + frontend/src/app/worker/export.cljs | 17 +- frontend/src/app/worker/impl.cljs | 11 +- frontend/src/app/worker/import.cljs | 34 +- frontend/src/app/worker/thumbnails.cljs | 38 +- frontend/translations/en.po | 10 +- 45 files changed, 2100 insertions(+), 1408 deletions(-) create mode 100644 backend/src/app/migrations/sql/0083-add-file-data-fragment-table.sql create mode 100644 backend/src/app/migrations/sql/0084-add-features-column-to-file-change-table.sql create mode 100644 backend/src/app/rpc/commands/files/create.clj create mode 100644 backend/src/app/rpc/commands/files/temp.clj create mode 100644 backend/src/app/rpc/commands/files/update.clj create mode 100644 backend/src/app/rpc/commands/viewer.clj diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index 56642f1605..4233909b48 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -16,7 +16,7 @@ [app.http.middleware :as mw] [app.http.session :as session] [app.rpc.commands.binfile :as binf] - [app.rpc.mutations.files :refer [create-file]] + [app.rpc.commands.files.create :refer [create-file]] [app.rpc.queries.profile :as profile] [app.util.blob :as blob] [app.util.template :as tmpl] diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index 77171d3455..7b2d22bc9c 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -257,6 +257,11 @@ {:name "0082-add-features-column-to-file-table" :fn (mg/resource "app/migrations/sql/0082-add-features-column-to-file-table.sql")} + {:name "0083-add-file-data-fragment-table" + :fn (mg/resource "app/migrations/sql/0083-add-file-data-fragment-table.sql")} + + {:name "0084-add-features-column-to-file-change-table" + :fn (mg/resource "app/migrations/sql/0084-add-features-column-to-file-change-table.sql")} ]) diff --git a/backend/src/app/migrations/sql/0083-add-file-data-fragment-table.sql b/backend/src/app/migrations/sql/0083-add-file-data-fragment-table.sql new file mode 100644 index 0000000000..6cb817e0bc --- /dev/null +++ b/backend/src/app/migrations/sql/0083-add-file-data-fragment-table.sql @@ -0,0 +1,15 @@ +CREATE TABLE file_data_fragment ( + id uuid NOT NULL, + file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE DEFERRABLE, + + created_at timestamptz NOT NULL DEFAULT now(), + + metadata jsonb NULL, + content bytea NOT NULL, + + PRIMARY KEY (file_id, id) +); + +ALTER TABLE file_data_fragment + ALTER COLUMN metadata SET STORAGE external, + ALTER COLUMN content SET STORAGE external; diff --git a/backend/src/app/migrations/sql/0084-add-features-column-to-file-change-table.sql b/backend/src/app/migrations/sql/0084-add-features-column-to-file-change-table.sql new file mode 100644 index 0000000000..4fa0f94562 --- /dev/null +++ b/backend/src/app/migrations/sql/0084-add-features-column-to-file-change-table.sql @@ -0,0 +1,8 @@ +ALTER TABLE file_change + ADD COLUMN features text[] DEFAULT NULL; + +ALTER TABLE file_change + ALTER COLUMN features SET STORAGE external; + +ALTER TABLE file + ALTER COLUMN features SET STORAGE external; diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 1f1274b8f3..e7e39fc34a 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -230,7 +230,10 @@ 'app.rpc.commands.auth 'app.rpc.commands.ldap 'app.rpc.commands.demo - 'app.rpc.commands.files) + 'app.rpc.commands.files + 'app.rpc.commands.files.update + 'app.rpc.commands.files.create + 'app.rpc.commands.files.temp) (map (partial process-method cfg)) (into {})))) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index d4275b281d..301366d032 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -17,14 +17,15 @@ [app.db :as db] [app.media :as media] [app.rpc :as-alias rpc] + [app.rpc.commands.files :as files] [app.rpc.doc :as-alias doc] - [app.rpc.queries.files :as files] [app.rpc.queries.projects :as projects] [app.storage :as sto] [app.storage.tmp :as tmp] [app.tasks.file-gc] [app.util.blob :as blob] [app.util.fressian :as fres] + [app.util.pointer-map :as pmap] [app.util.services :as sv] [app.util.time :as dt] [clojure.spec.alpha :as s] @@ -290,9 +291,11 @@ (defn- retrieve-file [pool file-id] - (->> (db/query pool :file {:id file-id}) - (map files/decode-row) - (first))) + (with-open [conn (db/open pool)] + (binding [pmap/*load-fn* (partial files/load-pointer conn file-id)] + (some-> (db/get* conn :file {:id file-id}) + (files/decode-row) + (update :data files/process-pointers deref))))) (def ^:private sql:file-media-objects "SELECT * FROM file_media_object WHERE id = ANY(?)") diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj index 9871f5da36..0214e31117 100644 --- a/backend/src/app/rpc/commands/comments.clj +++ b/backend/src/app/rpc/commands/comments.clj @@ -10,8 +10,8 @@ [app.common.geom.point :as gpt] [app.common.spec :as us] [app.db :as db] + [app.rpc.commands.files :as files] [app.rpc.doc :as-alias doc] - [app.rpc.queries.files :as files] [app.rpc.queries.teams :as teams] [app.rpc.retry :as retry] [app.util.blob :as blob] diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 3a21ff28d0..82d2dd779b 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -6,18 +6,312 @@ (ns app.rpc.commands.files (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.exceptions :as ex] + [app.common.geom.shapes :as gsh] + [app.common.pages.helpers :as cph] + [app.common.pages.migrations :as pmg] [app.common.spec :as us] + [app.common.types.file :as ctf] + [app.common.types.shape-tree :as ctt] [app.db :as db] + [app.db.sql :as sql] + [app.rpc :as-alias rpc] + [app.rpc.commands.files.thumbnails :as-alias thumbs] [app.rpc.doc :as-alias doc] - [app.rpc.queries.files :as files] + [app.rpc.helpers :as rpch] + [app.rpc.permissions :as perms] + [app.rpc.queries.projects :as projects] + [app.rpc.queries.share-link :refer [retrieve-share-link]] + [app.rpc.queries.teams :as teams] + [app.util.blob :as blob] + [app.util.pointer-map :as pmap] [app.util.services :as sv] - [clojure.spec.alpha :as s])) + [app.util.time :as dt] + [clojure.set :as set] + [clojure.spec.alpha :as s] + [cuerdas.core :as str])) + +;; --- FEATURES + +(def supported-features + #{"storage/objects-map" + "storage/pointer-map" + "components/v2"}) + +(def default-features #{}) + +;; --- SPECS + +(s/def ::features ::us/set-of-strings) +(s/def ::file-id ::us/uuid) +(s/def ::frame-id ::us/uuid) +(s/def ::id ::us/uuid) +(s/def ::is-shared ::us/boolean) +(s/def ::name ::us/string) +(s/def ::profile-id ::us/uuid) +(s/def ::project-id ::us/uuid) +(s/def ::search-term ::us/string) +(s/def ::team-id ::us/uuid) + +(defn decode-row + [{:keys [data changes features] :as row}] + (when row + (cond-> row + features (assoc :features (db/decode-pgarray features #{})) + changes (assoc :changes (blob/decode changes)) + data (assoc :data (blob/decode data))))) + +;; --- FILE PERMISSIONS + +(def ^:private sql:file-permissions + "select fpr.is_owner, + fpr.is_admin, + fpr.can_edit + from file_profile_rel as fpr + where fpr.file_id = ? + and fpr.profile_id = ? + union all + select tpr.is_owner, + tpr.is_admin, + tpr.can_edit + from team_profile_rel as tpr + inner join project as p on (p.team_id = tpr.team_id) + inner join file as f on (p.id = f.project_id) + where f.id = ? + and tpr.profile_id = ? + union all + select ppr.is_owner, + ppr.is_admin, + ppr.can_edit + from project_profile_rel as ppr + inner join file as f on (f.project_id = ppr.project_id) + where f.id = ? + and ppr.profile_id = ?") + +(defn retrieve-file-permissions + [conn profile-id file-id] + (when (and profile-id file-id) + (db/exec! conn [sql:file-permissions + file-id profile-id + file-id profile-id + file-id profile-id]))) + +(defn get-permissions + ([conn profile-id file-id] + (let [rows (retrieve-file-permissions conn profile-id file-id) + is-owner (boolean (some :is-owner rows)) + is-admin (boolean (some :is-admin rows)) + can-edit (boolean (some :can-edit rows))] + (when (seq rows) + {:type :membership + :is-owner is-owner + :is-admin (or is-owner is-admin) + :can-edit (or is-owner is-admin can-edit) + :can-read true + :is-logged (some? profile-id)}))) + ([conn profile-id file-id share-id] + (let [perms (get-permissions conn profile-id file-id) + ldata (retrieve-share-link conn file-id share-id)] + + ;; NOTE: in a future when share-link becomes more powerful and + ;; will allow us specify which parts of the app is available, we + ;; will probably need to tweak this function in order to expose + ;; this flags to the frontend. + (cond + (some? perms) perms + (some? ldata) {:type :share-link + :can-read true + :is-logged (some? profile-id) + :who-comment (:who-comment ldata) + :who-inspect (:who-inspect ldata)})))) + +(def has-edit-permissions? + (perms/make-edition-predicate-fn get-permissions)) + +(def has-read-permissions? + (perms/make-read-predicate-fn get-permissions)) + +(def has-comment-permissions? + (perms/make-comment-predicate-fn get-permissions)) + +(def check-edition-permissions! + (perms/make-check-fn has-edit-permissions?)) + +(def check-read-permissions! + (perms/make-check-fn has-read-permissions?)) + +;; A user has comment permissions if she has read permissions, or comment permissions +(defn check-comment-permissions! + [conn profile-id file-id share-id] + (let [can-read (has-read-permissions? conn profile-id file-id) + can-comment (has-comment-permissions? conn profile-id file-id share-id)] + (when-not (or can-read can-comment) + (ex/raise :type :not-found + :code :object-not-found + :hint "not found")))) + +;; --- HELPERS + +(defn retrieve-team-id + [conn project-id] + (:team-id (db/get-by-id conn :project project-id {:columns [:team-id]}))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; FEATURES: pointer-map +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn check-features-compatibility! + [features] + (let [not-supported (set/difference features supported-features)] + (when (seq not-supported) + (ex/raise :type :restriction + :code :features-not-supported + :feature (first not-supported) + :hint (format "features %s not supported" (str/join "," not-supported)))) + features)) + +(defn load-pointer + [conn file-id id] + (let [row (db/get conn :file-data-fragment + {:id id :file-id file-id} + {:columns [:content] + :check-deleted? false})] + (blob/decode (:content row)))) + +(defn persist-pointers! + [conn file-id] + (doseq [[id item] @pmap/*tracked*] + (when (pmap/modified? item) + (let [content (-> item deref blob/encode)] + (db/insert! conn :file-data-fragment + {:id id + :file-id file-id + :content content}))))) + +(defn process-pointers + [file update-fn] + (update file :data (fn resolve-fn [data] + (cond-> data + (contains? data :pages-index) + (update :pages-index resolve-fn) + + :always + (update-vals (fn [val] + (if (pmap/pointer-map? val) + (update-fn val) + val))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUERY COMMANDS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; --- Query: File Libraries used by a File +;; --- COMMAND QUERY: get-file (by id) + +(defn retrieve-object-thumbnails + ([conn file-id] + (let [sql (str/concat + "select object_id, data " + " from file_object_thumbnail" + " where file_id=?")] + (->> (db/exec! conn [sql file-id]) + (d/index-by :object-id :data)))) + + ([conn file-id object-ids] + (let [sql (str/concat + "select object_id, data " + " from file_object_thumbnail" + " where file_id=? and object_id = ANY(?)") + ids (db/create-array conn "text" (seq object-ids))] + (->> (db/exec! conn [sql file-id ids]) + (d/index-by :object-id :data))))) + +(defn retrieve-file + [conn id client-features] + ;; here we check if client requested features are supported + (check-features-compatibility! client-features) + + (binding [pmap/*load-fn* (partial load-pointer conn id)] + (let [file (->> (db/get-by-id conn :file id) + (decode-row) + (pmg/migrate-file)) + features (:features file) + file (cond-> file + (and (contains? client-features "components/v2") + (not (contains? features "components/v2"))) + (update :data ctf/migrate-to-components-v2) + + (and (contains? features "storage/pointer-map") + (not (contains? client-features "storage/pointer-map"))) + (process-pointers deref))] + + (when (and (contains? features "components/v2") + (not (contains? client-features "components/v2"))) + (ex/raise :type :restriction + :code :feature-mismatch + :feature "components/v2" + :hint "file has 'components/v2' feature enabled but frontend didn't specifies it")) + + file))) + +(defn get-file + [conn id features] + (let [file (retrieve-file conn id features) + thumbs (retrieve-object-thumbnails conn id)] + (assoc file :thumbnails thumbs) + #_file)) + +(s/def ::get-file + (s/keys :req-un [::profile-id ::id] + :opt-un [::features])) + + +;; TODO: this should be changed probably because thumbnails will not be included + +(sv/defmethod ::get-file + "Retrieve a file by its ID. Only authenticated users." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id id features] :as params}] + (with-open [conn (db/open pool)] + (let [perms (get-permissions conn profile-id id)] + (check-read-permissions! perms) + (-> (get-file conn id features) + (assoc :permissions perms))))) + + +;; --- COMMAND QUERY: get-project-files + +(def ^:private sql:project-files + "select f.id, + f.project_id, + f.created_at, + f.modified_at, + f.name, + f.revn, + f.is_shared + from file as f + where f.project_id = ? + and f.deleted_at is null + order by f.modified_at desc") + +(s/def ::get-project-files + (s/keys :req-un [::profile-id ::project-id])) + +(defn get-project-files + [conn project-id] + (db/exec! conn [sql:project-files project-id])) + +(sv/defmethod ::get-project-files + "Get all files for the specified project." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] + (with-open [conn (db/open pool)] + (projects/check-read-permissions! conn profile-id project-id) + (get-project-files conn project-id))) + + +;; --- COMMAND QUERY: has-file-libraries (declare retrieve-has-file-libraries) @@ -32,7 +326,7 @@ {::doc/added "1.15.1"} [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (with-open [conn (db/open pool)] - (files/check-read-permissions! pool profile-id file-id) + (check-read-permissions! pool profile-id file-id) (retrieve-has-file-libraries conn params))) (def ^:private sql:has-file-libraries @@ -48,3 +342,586 @@ (let [row (db/exec-one! conn [sql:has-file-libraries file-id])] (:has-libraries row))) + +;; --- QUERY COMMAND: get-page + +(defn- prune-objects + "Given the page data and the object-id returns the page data with all + other not needed objects removed from the `:objects` data + structure." + [{:keys [objects] :as page} object-id] + (let [objects (cph/get-children-with-self objects object-id)] + (assoc page :objects (d/index-by :id objects)))) + +(defn- prune-thumbnails + "Given the page data, removes the `:thumbnail` prop from all + shapes." + [page] + (update page :objects d/update-vals #(dissoc % :thumbnail))) + +(defn get-page + [conn {:keys [file-id page-id object-id features]}] + (let [file (retrieve-file conn file-id features) + page-id (or page-id (-> file :data :pages first)) + page (dm/get-in file [:data :pages-index page-id])] + (cond-> (prune-thumbnails page) + (uuid? object-id) + (prune-objects object-id)))) + +(s/def ::page-id ::us/uuid) +(s/def ::object-id ::us/uuid) +(s/def ::get-page + (s/and + (s/keys :req-un [::profile-id ::file-id] + :opt-un [::page-id ::object-id ::features]) + (fn [obj] + (if (contains? obj :object-id) + (contains? obj :page-id) + true)))) + +(sv/defmethod ::get-page + "Retrieves the page data from file and returns it. If no page-id is + specified, the first page will be returned. If object-id is + specified, only that object and its children will be returned in the + page objects data structure. + + If you specify the object-id, the page-id parameter becomes + mandatory. + + Mainly used for rendering purposes." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id file-id) + (get-page conn params))) + + +;; --- COMMAND QUERY: get-team-shared-files + +(def ^:private sql:team-shared-files + "select f.id, + f.revn, + f.data, + f.project_id, + f.created_at, + f.modified_at, + f.name, + f.is_shared + from file as f + inner join project as p on (p.id = f.project_id) + where f.is_shared = true + and f.deleted_at is null + and p.deleted_at is null + and p.team_id = ? + order by f.modified_at desc") + +(defn get-team-shared-files + [conn {:keys [team-id] :as params}] + (let [assets-sample + (fn [assets limit] + (let [sorted-assets (->> (vals assets) + (sort-by #(str/lower (:name %))))] + + {:count (count sorted-assets) + :sample (into [] (take limit sorted-assets))})) + + library-summary + (fn [data] + {:components (assets-sample (:components data) 4) + :colors (assets-sample (:colors data) 3) + :typographies (assets-sample (:typographies data) 3)}) + + xform (comp + (map decode-row) + (map #(assoc % :library-summary (library-summary (:data %)))) + (map #(dissoc % :data)))] + + (into #{} xform (db/exec! conn [sql:team-shared-files team-id])))) + +(s/def ::get-team-shared-files + (s/keys :req-un [::profile-id ::team-id])) + +(sv/defmethod ::get-team-shared-files + "Get all file (libraries) for the specified team." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} params] + (with-open [conn (db/open pool)] + (get-team-shared-files conn params))) + + +;; --- COMMAND QUERY: get-file-libraries + +(def ^:private sql:file-libraries + "WITH RECURSIVE libs AS ( + SELECT fl.*, flr.synced_at + FROM file AS fl + JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id) + WHERE flr.file_id = ?::uuid + UNION + SELECT fl.*, flr.synced_at + FROM file AS fl + JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id) + JOIN libs AS l ON (flr.file_id = l.id) + ) + SELECT l.id, + l.data, + l.project_id, + l.created_at, + l.modified_at, + l.deleted_at, + l.name, + l.revn, + l.synced_at + FROM libs AS l + WHERE l.deleted_at IS NULL OR l.deleted_at > now();") + +(defn get-file-libraries + [conn is-indirect file-id] + (let [xform (comp + (map #(assoc % :is-indirect is-indirect)) + (map decode-row))] + (into #{} xform (db/exec! conn [sql:file-libraries file-id])))) + +(s/def ::get-file-libraries + (s/keys :req-un [::profile-id ::file-id])) + +(sv/defmethod ::get-file-libraries + "Get libraries used by the specified file." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id file-id) + (get-file-libraries conn false file-id))) + + +;; --- COMMAND QUERY: Files that use this File library + +(def ^:private sql:library-using-files + "SELECT f.id, + f.name + FROM file_library_rel AS flr + JOIN file AS f ON (f.id = flr.file_id) + WHERE flr.library_file_id = ? + AND (f.deleted_at IS NULL OR f.deleted_at > now())") + +(defn get-library-file-references + [conn file-id] + (db/exec! conn [sql:library-using-files file-id])) + +(s/def ::get-library-file-references + (s/keys :req-un [::profile-id ::file-id])) + +(sv/defmethod ::get-library-file-references + "Returns all the file references that use specified file (library) id." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id file-id) + (get-library-file-references conn file-id))) + + +;; --- COMMAND QUERY: get-team-recent-files + +(def sql:team-recent-files + "with recent_files as ( + select f.id, + f.revn, + f.project_id, + f.created_at, + f.modified_at, + f.name, + f.is_shared, + row_number() over w as row_num + from file as f + join project as p on (p.id = f.project_id) + where p.team_id = ? + and p.deleted_at is null + and f.deleted_at is null + window w as (partition by f.project_id order by f.modified_at desc) + order by f.modified_at desc + ) + select * from recent_files where row_num <= 10;") + +(defn get-team-recent-files + [conn team-id] + (db/exec! conn [sql:team-recent-files team-id])) + +(s/def ::get-team-recent-files + (s/keys :req-un [::profile-id ::team-id])) + +(sv/defmethod ::get-team-recent-files + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id team-id]}] + (with-open [conn (db/open pool)] + (teams/check-read-permissions! conn profile-id team-id) + (get-team-recent-files conn team-id))) + + +;; --- COMMAND QUERY: get-file-thumbnail + +(defn get-file-thumbnail + [conn file-id revn] + (let [sql (sql/select :file-thumbnail + (cond-> {:file-id file-id} + revn (assoc :revn revn)) + {:limit 1 + :order-by [[:revn :desc]]}) + row (db/exec-one! conn sql)] + (when-not row + (ex/raise :type :not-found + :code :file-thumbnail-not-found)) + + {:data (:data row) + :props (some-> (:props row) db/decode-transit-pgobject) + :revn (:revn row) + :file-id (:file-id row)})) + +(s/def ::revn ::us/integer) + +(s/def ::get-file-thumbnail + (s/keys :req-un [::profile-id ::file-id] + :opt-un [::revn])) + +(sv/defmethod ::get-file-thumbnail + {::doc/added "1.17"} + [{:keys [pool]} {:keys [profile-id file-id revn]}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id file-id) + (-> (get-file-thumbnail conn file-id revn) + (with-meta {::rpc/transform-response (rpch/http-cache {:max-age (* 1000 60 60)})})))) + + + +;; --- COMMAND QUERY: get-file-data-for-thumbnail + +(defn get-file-data-for-thumbnail + [conn {:keys [data id] :as file}] + (letfn [;; function responsible on finding the frame marked to be + ;; used as thumbnail; the returned frame always have + ;; the :page-id set to the page that it belongs. + (get-thumbnail-frame [data] + (d/seek :use-for-thumbnail? + (for [page (-> data :pages-index vals) + frame (-> page :objects ctt/get-frames)] + (assoc frame :page-id (:id page))))) + + ;; function responsible to filter objects data structure of + ;; all unneeded shapes if a concrete frame is provided. If no + ;; frame, the objects is returned untouched. + (filter-objects [objects frame-id] + (d/index-by :id (cph/get-children-with-self objects frame-id))) + + ;; function responsible of assoc available thumbnails + ;; to frames and remove all children shapes from objects if + ;; thumbnails is available + (assoc-thumbnails [objects page-id thumbnails] + (loop [objects objects + frames (filter cph/frame-shape? (vals objects))] + + (if-let [frame (-> frames first)] + (let [frame-id (:id frame) + object-id (str page-id frame-id) + frame (if-let [thumb (get thumbnails object-id)] + (assoc frame :thumbnail thumb :shapes []) + (dissoc frame :thumbnail)) + + children-ids + (cph/get-children-ids objects frame-id) + + bounds + (when (:show-content frame) + (gsh/selection-rect (concat [frame] (->> children-ids (map (d/getf objects)))))) + + frame + (cond-> frame + (some? bounds) + (assoc :children-bounds bounds))] + + (if (:thumbnail frame) + (recur (-> objects + (assoc frame-id frame) + (d/without-keys children-ids)) + (rest frames)) + (recur (assoc objects frame-id frame) + (rest frames)))) + + objects)))] + + (let [frame (get-thumbnail-frame data) + frame-id (:id frame) + page-id (or (:page-id frame) + (-> data :pages first)) + + page (dm/get-in data [:pages-index page-id]) + frame-ids (if (some? frame) (list frame-id) (map :id (ctt/get-frames (:objects page)))) + + obj-ids (map #(str page-id %) frame-ids) + thumbs (retrieve-object-thumbnails conn id obj-ids)] + + (cond-> page + ;; If we have frame, we need to specify it on the page level + ;; and remove the all other unrelated objects. + (some? frame-id) + (-> (assoc :thumbnail-frame-id frame-id) + (update :objects filter-objects frame-id)) + + ;; Assoc the available thumbnails and prune not visible shapes + ;; for avoid transfer unnecessary data. + :always + (update :objects assoc-thumbnails page-id thumbs))))) + +(s/def ::get-file-data-for-thumbnail + (s/keys :req-un [::profile-id ::file-id] + :opt-un [::features])) + +(sv/defmethod ::get-file-data-for-thumbnail + "Retrieves the data for generate the thumbnail of the file. Used + mainly for render thumbnails on dashboard." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id features] :as props}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id file-id) + (let [file (retrieve-file conn file-id features)] + {:file-id file-id + :revn (:revn file) + :page (get-file-data-for-thumbnail conn file)}))) + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; MUTATION COMMANDS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; --- MUTATION COMMAND: rename-file + +(defn rename-file + [conn {:keys [id name] :as params}] + (-> (db/update! conn :file + {:name name + :modified-at (dt/now)} + {:id id}) + (select-keys [:id :name :created-at :modified-at]))) + +(s/def ::rename-file + (s/keys :req-un [::profile-id ::name ::id])) + +(sv/defmethod ::rename-file + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id id) + (rename-file conn params))) + + +;; --- MUTATION COMMAND: set-file-shared + +(defn unlink-files + [conn {:keys [id] :as params}] + (db/delete! conn :file-library-rel {:library-file-id id})) + +(defn set-file-shared + [conn {:keys [id is-shared] :as params}] + (-> (db/update! conn :file + {:is-shared is-shared} + {:id id}) + (select-keys [:id :name :is-shared]))) + +(defn absorb-library + "Find all files using a shared library, and absorb all library assets + into the file local libraries" + [conn {:keys [id] :as params}] + (let [library (db/get-by-id conn :file id)] + (when (:is-shared library) + (let [ldata (-> library decode-row pmg/migrate-file :data)] + (->> (db/query conn :file-library-rel {:library-file-id id}) + (map :file-id) + (keep #(db/get-by-id conn :file % {:check-deleted? false})) + (map decode-row) + (map pmg/migrate-file) + (run! (fn [{:keys [id data revn] :as file}] + (let [data (ctf/absorb-assets data ldata)] + (db/update! conn :file + {:revn (inc revn) + :data (blob/encode data) + :modified-at (dt/now)} + {:id id}))))))))) + +(s/def ::set-file-shared + (s/keys :req-un [::profile-id ::id ::is-shared])) + +(sv/defmethod ::set-file-shared + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [id profile-id is-shared] :as params}] + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id id) + (when-not is-shared + (absorb-library conn params) + (unlink-files conn params)) + (set-file-shared conn params))) + + +;; --- MUTATION COMMAND: delete-file + +(defn mark-file-deleted + [conn {:keys [id] :as params}] + (db/update! conn :file + {:deleted-at (dt/now)} + {:id id}) + nil) + +(s/def ::delete-file + (s/keys :req-un [::id ::profile-id])) + +(sv/defmethod ::delete-file + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id id) + (absorb-library conn params) + (mark-file-deleted conn params))) + + +;; --- MUTATION COMMAND: link-file-to-library + +(def sql:link-file-to-library + "insert into file_library_rel (file_id, library_file_id) + values (?, ?) + on conflict do nothing;") + +(defn link-file-to-library + [conn {:keys [file-id library-id] :as params}] + (db/exec-one! conn [sql:link-file-to-library file-id library-id])) + +(s/def ::link-file-to-library + (s/keys :req-un [::profile-id ::file-id ::library-id])) + +(sv/defmethod ::link-file-to-library + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id library-id] :as params}] + (when (= file-id library-id) + (ex/raise :type :validation + :code :invalid-library + :hint "A file cannot be linked to itself")) + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id file-id) + (check-edition-permissions! conn profile-id library-id) + (link-file-to-library conn params))) + + +;; --- MUTATION COMMAND: unlink-file-from-library + +(defn unlink-file-from-library + [conn {:keys [file-id library-id] :as params}] + (db/delete! conn :file-library-rel + {:file-id file-id + :library-file-id library-id})) + +(s/def ::unlink-file-from-library + (s/keys :req-un [::profile-id ::file-id ::library-id])) + +(sv/defmethod ::unlink-file-from-library + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id file-id) + (unlink-file-from-library conn params))) + + +;; --- MUTATION COMMAND: update-sync + +(defn update-sync + [conn {:keys [file-id library-id] :as params}] + (db/update! conn :file-library-rel + {:synced-at (dt/now)} + {:file-id file-id + :library-file-id library-id})) + +(s/def ::update-file-library-sync-status + (s/keys :req-un [::profile-id ::file-id ::library-id])) + +;; TODO: improve naming + +(sv/defmethod ::update-file-library-sync-status + "Update the synchronization statos of a file->library link" + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id file-id) + (update-sync conn params))) + + +;; --- MUTATION COMMAND: ignore-sync + +(defn ignore-sync + [conn {:keys [file-id date] :as params}] + (db/update! conn :file + {:ignore-sync-until date} + {:id file-id})) + +(s/def ::ignore-file-library-sync-status + (s/keys :req-un [::profile-id ::file-id ::date])) + +;; TODO: improve naming +(sv/defmethod ::ignore-file-library-sync-status + "Ignore updates in linked files" + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id file-id) + (ignore-sync conn params))) + + +;; --- MUTATION COMMAND: upsert-file-object-thumbnail + +(def sql:upsert-object-thumbnail + "insert into file_object_thumbnail(file_id, object_id, data) + values (?, ?, ?) + on conflict(file_id, object_id) do + update set data = ?;") + +(defn upsert-file-object-thumbnail! + [conn {:keys [file-id object-id data]}] + (if data + (db/exec-one! conn [sql:upsert-object-thumbnail file-id object-id data data]) + (db/delete! conn :file-object-thumbnail {:file-id file-id :object-id object-id}))) + +(s/def ::data (s/nilable ::us/string)) +(s/def ::thumbs/object-id ::us/string) +(s/def ::upsert-file-object-thumbnail + (s/keys :req-un [::profile-id ::file-id ::thumbs/object-id ::data])) + +(sv/defmethod ::upsert-file-object-thumbnail + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id file-id) + (upsert-file-object-thumbnail! conn params) + nil)) + +;; --- MUTATION COMMAND: upsert-file-thumbnail + +(def sql:upsert-file-thumbnail + "insert into file_thumbnail (file_id, revn, data, props) + values (?, ?, ?, ?::jsonb) + on conflict(file_id, revn) do + update set data = ?, props=?, updated_at=now();") + +(defn upsert-file-thumbnail + [conn {:keys [file-id revn data props]}] + (let [props (db/tjson (or props {}))] + (db/exec-one! conn [sql:upsert-file-thumbnail + file-id revn data props data props]))) + +(s/def ::revn ::us/integer) +(s/def ::props map?) +(s/def ::upsert-file-thumbnail + (s/keys :req-un [::profile-id ::file-id ::revn ::data ::props])) + +(sv/defmethod ::upsert-file-thumbnail + "Creates or updates the file thumbnail. Mainly used for paint the + grid thumbnails." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id file-id) + (upsert-file-thumbnail conn params) + nil)) diff --git a/backend/src/app/rpc/commands/files/create.clj b/backend/src/app/rpc/commands/files/create.clj new file mode 100644 index 0000000000..0baa572969 --- /dev/null +++ b/backend/src/app/rpc/commands/files/create.clj @@ -0,0 +1,83 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.commands.files.create + (:require + [app.common.data :as d] + [app.common.files.features :as ffeat] + [app.common.types.file :as ctf] + [app.common.uuid :as uuid] + [app.db :as db] + [app.loggers.audit :as audit] + [app.rpc.commands.files :as files] + [app.rpc.doc :as-alias doc] + [app.rpc.permissions :as perms] + [app.rpc.queries.projects :as proj] + [app.util.blob :as blob] + [app.util.objects-map :as omap] + [app.util.pointer-map :as pmap] + [app.util.services :as sv] + [clojure.spec.alpha :as s])) + +(defn create-file-role! + [conn {:keys [file-id profile-id role]}] + (let [params {:file-id file-id + :profile-id profile-id}] + (->> (perms/assign-role-flags params role) + (db/insert! conn :file-profile-rel)))) + +(defn create-file + [conn {:keys [id name project-id is-shared data revn + modified-at deleted-at + ignore-sync-until features] + :or {is-shared false revn 0} + :as params}] + (let [id (or id (:id data) (uuid/next)) + features (-> (into files/default-features features) + (files/check-features-compatibility!)) + + data (or data + (binding [ffeat/*current* features + ffeat/*wrap-with-objects-map-fn* (if (features "storate/objects-map") omap/wrap identity) + ffeat/*wrap-with-pointer-map-fn* (if (features "storage/pointer-map") pmap/wrap identity)] + (ctf/make-file-data id))) + + features (db/create-array conn "text" features) + file (db/insert! conn :file + (d/without-nils + {:id id + :project-id project-id + :name name + :revn revn + :is-shared is-shared + :data (blob/encode data) + :features features + :ignore-sync-until ignore-sync-until + :modified-at modified-at + :deleted-at deleted-at}))] + + (->> (assoc params :file-id id :role :owner) + (create-file-role! conn)) + + (files/decode-row file))) + +(s/def ::create-file + (s/keys :req-un [::files/profile-id + ::files/name + ::files/project-id] + :opt-un [::files/id + ::files/is-shared + ::files/features])) + +(sv/defmethod ::create-file + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] + (db/with-atomic [conn pool] + (proj/check-edition-permissions! conn profile-id project-id) + (let [team-id (files/retrieve-team-id conn project-id)] + (-> (create-file conn params) + (vary-meta assoc ::audit/props {:team-id team-id}))))) + diff --git a/backend/src/app/rpc/commands/files/temp.clj b/backend/src/app/rpc/commands/files/temp.clj new file mode 100644 index 0000000000..350ddb23cd --- /dev/null +++ b/backend/src/app/rpc/commands/files/temp.clj @@ -0,0 +1,96 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.commands.files.temp + (:require + [app.common.exceptions :as ex] + [app.common.pages :as cp] + [app.common.uuid :as uuid] + [app.db :as db] + [app.rpc.commands.files :as files] + [app.rpc.commands.files.create :as files.create] + [app.rpc.commands.files.update :as files.update] + [app.rpc.doc :as-alias doc] + [app.rpc.queries.projects :as proj] + [app.util.blob :as blob] + [app.util.services :as sv] + [app.util.time :as dt] + [clojure.spec.alpha :as s])) + +;; --- MUTATION COMMAND: create-temp-file + +(s/def ::create-temp-file ::files.create/create-file) + +(sv/defmethod ::create-temp-file + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] + (db/with-atomic [conn pool] + (proj/check-edition-permissions! conn profile-id project-id) + (files.create/create-file conn (assoc params :deleted-at (dt/in-future {:days 1}))))) + +;; --- MUTATION COMMAND: update-temp-file + +(defn update-temp-file + [conn {:keys [profile-id session-id id revn changes] :as params}] + (db/insert! conn :file-change + {:id (uuid/next) + :session-id session-id + :profile-id profile-id + :created-at (dt/now) + :file-id id + :revn revn + :data nil + :changes (blob/encode changes)})) + +(s/def ::update-temp-file + (s/keys :req-un [::files.update/changes + ::files.update/revn + ::files.update/session-id + ::files/id])) + +(sv/defmethod ::update-temp-file + {::doc/added "1.17"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (update-temp-file conn params) + nil)) + +;; --- MUTATION COMMAND: persist-temp-file + +(defn persist-temp-file + [conn {:keys [id] :as params}] + (let [file (db/get-by-id conn :file id) + revs (db/query conn :file-change + {:file-id id} + {:order-by [[:revn :asc]]}) + revn (count revs)] + + (when (nil? (:deleted-at file)) + (ex/raise :type :validation + :code :cant-persist-already-persisted-file)) + + (loop [revs (seq revs) + data (blob/decode (:data file))] + (if-let [rev (first revs)] + (recur (rest revs) + (->> rev :changes blob/decode (cp/process-changes data))) + (db/update! conn :file + {:deleted-at nil + :revn revn + :data (blob/encode data)} + {:id id}))) + nil)) + +(s/def ::persist-temp-file + (s/keys :req-un [::files/id + ::files/profile-id])) + +(sv/defmethod ::persist-temp-file + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + (db/with-atomic [conn pool] + (files/check-edition-permissions! conn profile-id id) + (persist-temp-file conn params))) diff --git a/backend/src/app/rpc/commands/files/update.clj b/backend/src/app/rpc/commands/files/update.clj new file mode 100644 index 0000000000..9a65686636 --- /dev/null +++ b/backend/src/app/rpc/commands/files/update.clj @@ -0,0 +1,295 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.commands.files.update + (:require + [app.common.exceptions :as ex] + [app.common.files.features :as ffeat] + [app.common.logging :as l] + [app.common.pages :as cp] + [app.common.pages.migrations :as pmg] + [app.common.spec :as us] + [app.common.types.file :as ctf] + [app.common.uuid :as uuid] + [app.config :as cf] + [app.db :as db] + [app.loggers.audit :as audit] + [app.metrics :as mtx] + [app.msgbus :as mbus] + [app.rpc :as-alias rpc] + [app.rpc.climit :as-alias climit] + [app.rpc.commands.files :as files] + [app.rpc.doc :as-alias doc] + [app.util.blob :as blob] + [app.util.objects-map :as omap] + [app.util.pointer-map :as pmap] + [app.util.services :as sv] + [app.util.time :as dt] + [clojure.spec.alpha :as s])) + +;; --- SPECS + +(s/def ::changes + (s/coll-of map? :kind vector?)) + +(s/def ::hint-origin ::us/keyword) +(s/def ::hint-events + (s/every ::us/keyword :kind vector?)) + +(s/def ::change-with-metadata + (s/keys :req-un [::changes] + :opt-un [::hint-origin + ::hint-events])) + +(s/def ::changes-with-metadata + (s/every ::change-with-metadata :kind vector?)) + +(s/def ::session-id ::us/uuid) +(s/def ::revn ::us/integer) +(s/def ::update-file + (s/and + (s/keys :req-un [::files/id ::files/profile-id ::session-id ::revn] + :opt-un [::changes ::changes-with-metadata ::features]) + (fn [o] + (or (contains? o :changes) + (contains? o :changes-with-metadata))))) + +;; --- HELPERS + +;; File changes that affect to the library, and must be notified +;; to all clients using it. + +(def ^:private library-change-types + #{:add-color :mod-color :del-color + :add-media :mod-media :del-media + :add-component :mod-component :del-component + :add-typography :mod-typography :del-typography}) + +(def ^:private file-change-types + #{:add-obj :mod-obj :del-obj + :reg-objects :mov-objects}) + +(defn- library-change? + [{:keys [type] :as change}] + (or (contains? library-change-types type) + (and (contains? file-change-types type) + (some? (:component-id change))))) + +(def ^:private sql:get-file + "SELECT f.*, p.team_id + FROM file AS f + JOIN project AS p ON (p.id = f.project_id) + WHERE f.id = ? + AND (f.deleted_at IS NULL OR + f.deleted_at > now()) + FOR KEY SHARE") + +(defn get-file + [conn id] + (let [file (db/exec-one! conn [sql:get-file id])] + (when-not file + (ex/raise :type :not-found + :code :object-not-found + :hint (format "file with id '%s' does not exists" id))) + (update file :features db/decode-pgarray #{}))) + +(defn- wrap-with-pointer-map-context + [f] + (fn [{:keys [conn] :as cfg} {:keys [id] :as file}] + (binding [pmap/*tracked* (atom {}) + pmap/*load-fn* (partial files/load-pointer conn id) + ffeat/*wrap-with-pointer-map-fn* pmap/wrap] + (let [result (f cfg file)] + (files/persist-pointers! conn id) + result)))) + +(defn- wrap-with-objects-map-context + [f] + (fn [cfg file] + (binding [ffeat/*wrap-with-objects-map-fn* omap/wrap] + (f cfg file)))) + +(declare get-lagged-changes) +(declare send-notifications!) +(declare update-file) +(declare update-file*) +(declare take-snapshot?) + +;; If features are specified from params and the final feature +;; set is different than the persisted one, update it on the +;; database. + +(sv/defmethod ::update-file + {::climit/queue :update-file + ::climit/key-fn :id + ::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + + (db/with-atomic [conn pool] + (files/check-edition-permissions! conn profile-id id) + (db/xact-lock! conn id) + + (let [cfg (assoc cfg :conn conn) + tpoint (dt/tpoint)] + (-> (update-file cfg params) + (vary-meta assoc ::rpc/before-complete + (fn [] + (let [elapsed (tpoint)] + (l/trace :hint "update-file" :time (dt/format-duration elapsed))))))))) + +(defn update-file + [{:keys [conn metrics] :as cfg} {:keys [id profile-id changes changes-with-metadata] :as params}] + (let [file (get-file conn id) + features (->> (concat (:features file) + (:features params)) + (into files/default-features) + (files/check-features-compatibility!))] + + (files/check-edition-permissions! conn profile-id (:id file)) + + (binding [ffeat/*current* features + ffeat/*previous* (:features file)] + (let [update-fn (cond-> update-file* + (contains? features "storage/pointer-map") + (wrap-with-pointer-map-context) + + (contains? features "storage/objects-map") + (wrap-with-objects-map-context)) + + file (assoc file :features features) + changes (if changes-with-metadata + (->> changes-with-metadata (mapcat :changes) vec) + (vec changes)) + + params (assoc params :file file :changes changes)] + + (mtx/run! metrics {:id :update-file-changes :inc (count changes)}) + + (when (not= features (:features file)) + (let [features (db/create-array conn "text" features)] + (db/update! conn :file + {:features features} + {:id id}))) + + (-> (update-fn cfg params) + (vary-meta assoc ::audit/props {:project-id (:project-id file) + :team-id (:team-id file)})))))) + +(defn- update-file* + [{:keys [conn] :as cfg} {:keys [file changes session-id profile-id] :as params}] + (when (> (:revn params) + (:revn file)) + (ex/raise :type :validation + :code :revn-conflict + :hint "The incoming revision number is greater that stored version." + :context {:incoming-revn (:revn params) + :stored-revn (:revn file)})) + + (let [ts (dt/now) + file (-> file + (update :revn inc) + (update :data (fn [data] + (cond-> data + :always + (-> (blob/decode) + (assoc :id (:id file)) + (pmg/migrate-data)) + + (and (contains? ffeat/*current* "components/v2") + (not (contains? ffeat/*previous* "components/v2"))) + (ctf/migrate-to-components-v2) + + :always + (-> (cp/process-changes changes) + (blob/encode))))))] + (db/insert! conn :file-change + {:id (uuid/next) + :session-id session-id + :profile-id profile-id + :created-at ts + :file-id (:id file) + :revn (:revn file) + :features (db/create-array conn "text" (:features file)) + :data (when (take-snapshot? file) + (:data file)) + :changes (blob/encode changes)}) + + (db/update! conn :file + {:revn (:revn file) + :data (:data file) + :data-backend nil + :modified-at ts + :has-media-trimmed false} + {:id (:id file)}) + + (db/update! conn :project + {:modified-at ts} + {:id (:project-id file)}) + + (let [params (assoc params :file file)] + ;; Send asynchronous notifications + (send-notifications! cfg params) + + ;; Retrieve and return lagged data + (get-lagged-changes conn params)))) + +(defn- take-snapshot? + "Defines the rule when file `data` snapshot should be saved." + [{:keys [revn modified-at] :as file}] + (let [freq (or (cf/get :file-change-snapshot-every) 20) + timeout (or (cf/get :file-change-snapshot-timeout) + (dt/duration {:hours 1}))] + (or (= 1 freq) + (zero? (mod revn freq)) + (> (inst-ms (dt/diff modified-at (dt/now))) + (inst-ms timeout))))) + +(def ^:private + sql:lagged-changes + "select s.id, s.revn, s.file_id, + s.session_id, s.changes + from file_change as s + where s.file_id = ? + and s.revn > ? + order by s.created_at asc") + +(defn- get-lagged-changes + [conn params] + (->> (db/exec! conn [sql:lagged-changes (:id params) (:revn params)]) + (into [] (comp (map files/decode-row) + (map (fn [row] + (cond-> row + (= (:revn row) (:revn (:file params))) + (assoc :changes [])))))))) + +(defn- send-notifications! + [{:keys [conn] :as cfg} {:keys [file changes session-id] :as params}] + (let [lchanges (filter library-change? changes) + msgbus (:msgbus cfg)] + + ;; Asynchronously publish message to the msgbus + (mbus/pub! msgbus + :topic (:id file) + :message {:type :file-change + :profile-id (:profile-id params) + :file-id (:id file) + :session-id (:session-id params) + :revn (:revn file) + :changes changes}) + + (when (and (:is-shared file) (seq lchanges)) + (let [team-id (or (:team-id file) + (files/retrieve-team-id conn (:project-id file)))] + ;; Asynchronously publish message to the msgbus + (mbus/pub! msgbus + :topic team-id + :message {:type :library-change + :profile-id (:profile-id params) + :file-id (:id file) + :session-id session-id + :revn (:revn file) + :modified-at (dt/now) + :changes lchanges}))))) diff --git a/backend/src/app/rpc/commands/management.clj b/backend/src/app/rpc/commands/management.clj index c68ecaea3e..7e56bec6fc 100644 --- a/backend/src/app/rpc/commands/management.clj +++ b/backend/src/app/rpc/commands/management.clj @@ -14,11 +14,13 @@ [app.common.uuid :as uuid] [app.db :as db] [app.rpc.commands.binfile :as binfile] + [app.rpc.commands.files :as files] [app.rpc.doc :as-alias doc] [app.rpc.mutations.projects :refer [create-project-role create-project]] [app.rpc.queries.projects :as proj] [app.rpc.queries.teams :as teams] [app.util.blob :as blob] + [app.util.pointer-map :as pmap] [app.util.services :as sv] [app.util.time :as dt] [clojure.spec.alpha :as s] @@ -53,7 +55,7 @@ (assoc key (get index (get item key) (get item key))))) (defn- process-file - [file index] + [conn {:keys [id] :as file} index] (letfn [(process-form [form] (cond-> form ;; Relink library items @@ -97,18 +99,25 @@ res))) media media))] - - (update file :data - (fn [data] - (-> data - (blob/decode) - (assoc :id (:id file)) - (pmg/migrate-data) - (update :pages-index relink-shapes) - (update :components relink-shapes) - (update :media relink-media) - (d/without-nils) - (blob/encode)))))) + (-> file + (update :id #(get index %)) + (update :data + (fn [data] + (binding [pmap/*load-fn* (partial files/load-pointer conn id) + pmap/*tracked* (atom {})] + (let [file-id (get index id) + data (-> data + (blob/decode) + (assoc :id file-id) + (pmg/migrate-data) + (update :pages-index relink-shapes) + (update :components relink-shapes) + (update :media relink-media) + (d/without-nils) + (files/process-pointers pmap/clone) + (blob/encode))] + (files/persist-pointers! conn file-id) + data))))))) (def sql:retrieve-used-libraries "select flr.* @@ -166,9 +175,9 @@ file (-> file (assoc :created-at now) (assoc :modified-at now) - (assoc :ignore-sync-until ignore) - (update :id #(get index %)) - (process-file index))] + (assoc :ignore-sync-until ignore)) + + file (process-file conn file index)] (db/insert! conn :file file) (db/insert! conn :file-profile-rel diff --git a/backend/src/app/rpc/commands/viewer.clj b/backend/src/app/rpc/commands/viewer.clj new file mode 100644 index 0000000000..1f757b86b5 --- /dev/null +++ b/backend/src/app/rpc/commands/viewer.clj @@ -0,0 +1,89 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.commands.viewer + (:require + [app.common.exceptions :as ex] + [app.db :as db] + [app.rpc.commands.comments :as comments] + [app.rpc.commands.files :as files] + [app.rpc.doc :as-alias doc] + [app.rpc.queries.share-link :as slnk] + [app.util.services :as sv] + [clojure.spec.alpha :as s])) + +;; --- Query: View Only Bundle + +(defn- get-project + [conn id] + (db/get-by-id conn :project id {:columns [:id :name :team-id]})) + +(defn- get-bundle + [conn file-id profile-id features] + (let [file (files/get-file conn file-id features) + project (get-project conn (:project-id file)) + libs (files/get-file-libraries conn false file-id) + users (comments/get-file-comments-users conn file-id profile-id) + + links (->> (db/query conn :share-link {:file-id file-id}) + (mapv slnk/decode-share-link-row)) + + fonts (db/query conn :team-font-variant + {:team-id (:team-id project) + :deleted-at nil})] + {:file file + :users users + :fonts fonts + :project project + :share-links links + :libraries libs})) + +(defn get-view-only-bundle + [conn {:keys [profile-id file-id share-id features] :as params}] + (let [slink (slnk/retrieve-share-link conn file-id share-id) + perms (files/get-permissions conn profile-id file-id share-id) + thumbs (files/retrieve-object-thumbnails conn file-id) + bundle (-> (get-bundle conn file-id profile-id features) + (assoc :permissions perms) + (assoc-in [:file :thumbnails] thumbs))] + + ;; When we have neither profile nor share, we just return a not + ;; found response to the user. + (when (and (not profile-id) + (not slink)) + (ex/raise :type :not-found + :code :object-not-found)) + + ;; When we have only profile, we need to check read permissions + ;; on file. + (when (and profile-id (not slink)) + (files/check-read-permissions! conn profile-id file-id)) + + (cond-> bundle + (some? slink) + (assoc :share slink) + + (and (some? slink) + (not (contains? (:flags slink) "view-all-pages"))) + (update-in [:file :data] (fn [data] + (let [allowed-pages (:pages slink)] + (-> data + (update :pages (fn [pages] (filterv #(contains? allowed-pages %) pages))) + (update :pages-index (fn [index] (select-keys index allowed-pages)))))))))) + +(s/def ::get-view-only-bundle + (s/keys :req-un [::files/file-id] + :opt-un [::files/profile-id + ::files/share-id + ::files/features])) + +(sv/defmethod ::get-view-only-bundle + {:auth false + ::doc/added "1.17"} + [{:keys [pool]} params] + (with-open [conn (db/open pool)] + (get-view-only-bundle conn params))) + diff --git a/backend/src/app/rpc/mutations/comments.clj b/backend/src/app/rpc/mutations/comments.clj index 771947e21e..6b606ba359 100644 --- a/backend/src/app/rpc/mutations/comments.clj +++ b/backend/src/app/rpc/mutations/comments.clj @@ -10,8 +10,8 @@ [app.common.spec :as us] [app.db :as db] [app.rpc.commands.comments :as cmd.comments] + [app.rpc.commands.files :as cmd.files] [app.rpc.doc :as-alias doc] - [app.rpc.queries.files :as files] [app.rpc.retry :as retry] [app.util.services :as sv] [clojure.spec.alpha :as s])) @@ -27,7 +27,7 @@ ::doc/deprecated "1.15"} [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] (db/with-atomic [conn pool] - (files/check-comment-permissions! conn profile-id file-id share-id) + (cmd.files/check-comment-permissions! conn profile-id file-id share-id) (cmd.comments/create-comment-thread conn params))) ;; --- Mutation: Update Comment Thread Status @@ -44,7 +44,7 @@ (db/with-atomic [conn pool] (let [cthr (db/get-by-id conn :comment-thread id {:for-update true})] (when-not cthr (ex/raise :type :not-found)) - (files/check-comment-permissions! conn profile-id (:file-id cthr) share-id) + (cmd.files/check-comment-permissions! conn profile-id (:file-id cthr) share-id) (cmd.comments/upsert-comment-thread-status! conn profile-id (:id cthr))))) @@ -61,7 +61,7 @@ (when-not thread (ex/raise :type :not-found)) - (files/check-comment-permissions! conn profile-id (:file-id thread) share-id) + (cmd.files/check-comment-permissions! conn profile-id (:file-id thread) share-id) (db/update! conn :comment-thread {:is-resolved is-resolved} {:id id}) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 8c036fbb1d..20b3a54482 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -6,641 +6,233 @@ (ns app.rpc.mutations.files (:require - [app.common.data :as d] [app.common.exceptions :as ex] - [app.common.files.features :as ffeat] [app.common.logging :as l] - [app.common.pages :as cp] - [app.common.pages.migrations :as pmg] [app.common.spec :as us] - [app.common.types.file :as ctf] - [app.common.uuid :as uuid] - [app.config :as cf] [app.db :as db] [app.loggers.audit :as audit] - [app.metrics :as mtx] - [app.msgbus :as mbus] [app.rpc :as-alias rpc] [app.rpc.climit :as-alias climit] + [app.rpc.commands.files :as cmd.files] + [app.rpc.commands.files.create :as cmd.files.create] + [app.rpc.commands.files.temp :as cmd.files.temp] + [app.rpc.commands.files.update :as cmd.files.update] [app.rpc.doc :as-alias doc] - [app.rpc.permissions :as perms] - [app.rpc.queries.files :as files] [app.rpc.queries.projects :as proj] - [app.storage.impl :as simpl] - [app.util.blob :as blob] - [app.util.objects-map :as omap] [app.util.services :as sv] [app.util.time :as dt] - [clojure.spec.alpha :as s] - [promesa.core :as p])) - -(declare create-file) -(declare retrieve-team-id) - -;; --- Helpers & Specs - -(s/def ::frame-id ::us/uuid) -(s/def ::file-id ::us/uuid) -(s/def ::id ::us/uuid) -(s/def ::name ::us/string) -(s/def ::profile-id ::us/uuid) -(s/def ::project-id ::us/uuid) -(s/def ::url ::us/url) + [clojure.spec.alpha :as s])) ;; --- Mutation: Create File -(s/def ::features ::us/set-of-strings) -(s/def ::is-shared ::us/boolean) -(s/def ::create-file - (s/keys :req-un [::profile-id ::name ::project-id] - :opt-un [::id ::is-shared ::features ::components-v2])) +(s/def ::create-file ::cmd.files.create/create-file) (sv/defmethod ::create-file - {::doc/added "1.0"} - [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id project-id features components-v2] :as params}] (db/with-atomic [conn pool] - (let [team-id (retrieve-team-id conn project-id)] - (proj/check-edition-permissions! conn profile-id project-id) - (with-meta - (create-file conn params) - {::audit/props {:team-id team-id}})))) + (proj/check-edition-permissions! conn profile-id project-id) + (let [team-id (cmd.files/retrieve-team-id conn project-id) + features (cond-> (or features #{}) + ;; BACKWARD COMPATIBILITY with the components-v2 param + components-v2 (conj "components/v2")) + params (assoc params :features features)] + (-> (cmd.files.create/create-file conn params) + (vary-meta assoc ::audit/props {:team-id team-id}))))) -(defn create-file-role - [conn {:keys [file-id profile-id role]}] - (let [params {:file-id file-id - :profile-id profile-id}] - (->> (perms/assign-role-flags params role) - (db/insert! conn :file-profile-rel)))) - -(defn create-file - [conn {:keys [id name project-id is-shared data revn - modified-at deleted-at ignore-sync-until - components-v2 features] - :or {is-shared false revn 0} - :as params}] - (let [id (or id (:id data) (uuid/next)) - - ;; BACKWARD COMPATIBILITY with the components-v2 param - features (cond-> (or features #{}) - components-v2 (conj "components/v2")) - - data (or data - (binding [ffeat/*current* features] - (ctf/make-file-data id))) - - features (db/create-array conn "text" features) - file (db/insert! conn :file - (d/without-nils - {:id id - :project-id project-id - :name name - :revn revn - :is-shared is-shared - :data (blob/encode data) - :features features - :ignore-sync-until ignore-sync-until - :modified-at modified-at - :deleted-at deleted-at}))] - - (->> (assoc params :file-id id :role :owner) - (create-file-role conn)) - - (-> file files/decode-row))) ;; --- Mutation: Rename File -(declare rename-file) - -(s/def ::rename-file - (s/keys :req-un [::profile-id ::name ::id])) +(s/def ::rename-file ::cmd.files/rename-file) (sv/defmethod ::rename-file - {::doc/added "1.0"} + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id id) - (rename-file conn params))) + (cmd.files/check-edition-permissions! conn profile-id id) + (cmd.files/rename-file conn params))) -(defn- rename-file - [conn {:keys [id name] :as params}] - (-> (db/update! conn :file - {:name name - :modified-at (dt/now)} - {:id id}) - (select-keys [:id :name :created-at :modified-at]))) ;; --- Mutation: Set File shared -(declare set-file-shared) -(declare unlink-files) -(declare absorb-library) - -(s/def ::set-file-shared - (s/keys :req-un [::profile-id ::id ::is-shared])) +(s/def ::set-file-shared ::cmd.files/set-file-shared) (sv/defmethod ::set-file-shared - {::doc/added "1.2"} + {::doc/added "1.2" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [id profile-id is-shared] :as params}] (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id id) + (cmd.files/check-edition-permissions! conn profile-id id) (when-not is-shared - (absorb-library conn params) - (unlink-files conn params)) - (-> (set-file-shared conn params) - (update :features db/decode-pgarray #{})))) - -(defn- unlink-files - [conn {:keys [id] :as params}] - (db/delete! conn :file-library-rel {:library-file-id id})) - -(defn- set-file-shared - [conn {:keys [id is-shared] :as params}] - (db/update! conn :file - {:is-shared is-shared} - {:id id})) + (cmd.files/absorb-library conn params) + (cmd.files/unlink-files conn params)) + (cmd.files/set-file-shared conn params))) ;; --- Mutation: Delete File -(declare mark-file-deleted) - -(s/def ::delete-file - (s/keys :req-un [::id ::profile-id])) +(s/def ::delete-file ::cmd.files/delete-file) (sv/defmethod ::delete-file - {::doc/added "1.0"} + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id id) - (absorb-library conn params) - (mark-file-deleted conn params))) - -(defn mark-file-deleted - [conn {:keys [id] :as params}] - (db/update! conn :file - {:deleted-at (dt/now)} - {:id id}) - nil) - -(defn absorb-library - "Find all files using a shared library, and absorb all library assets - into the file local libraries" - [conn {:keys [id] :as params}] - (let [library (db/get-by-id conn :file id)] - (when (:is-shared library) - (let [ldata (-> library files/decode-row pmg/migrate-file :data)] - (->> (db/query conn :file-library-rel {:library-file-id id}) - (keep (fn [{:keys [file-id]}] - (some->> (db/get-by-id conn :file file-id {:check-not-found false}) - (files/decode-row) - (pmg/migrate-file)))) - (run! (fn [{:keys [id data revn] :as file}] - (let [data (ctf/absorb-assets data ldata)] - (db/update! conn :file - {:revn (inc revn) - :data (blob/encode data) - :modified-at (dt/now)} - {:id id}))))))))) + (cmd.files/check-edition-permissions! conn profile-id id) + (cmd.files/absorb-library conn params) + (cmd.files/mark-file-deleted conn params))) ;; --- Mutation: Link file to library -(declare link-file-to-library) - -(s/def ::link-file-to-library - (s/keys :req-un [::profile-id ::file-id ::library-id])) +(s/def ::link-file-to-library ::cmd.files/link-file-to-library) (sv/defmethod ::link-file-to-library + {::doc/added "1.3" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id library-id] :as params}] (when (= file-id library-id) (ex/raise :type :validation :code :invalid-library :hint "A file cannot be linked to itself")) (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id file-id) - (files/check-edition-permissions! conn profile-id library-id) - (link-file-to-library conn params))) - -(def sql:link-file-to-library - "insert into file_library_rel (file_id, library_file_id) - values (?, ?) - on conflict do nothing;") - -(defn- link-file-to-library - [conn {:keys [file-id library-id] :as params}] - (db/exec-one! conn [sql:link-file-to-library file-id library-id])) - + (cmd.files/check-edition-permissions! conn profile-id file-id) + (cmd.files/check-edition-permissions! conn profile-id library-id) + (cmd.files/link-file-to-library conn params))) ;; --- Mutation: Unlink file from library -(declare unlink-file-from-library) - -(s/def ::unlink-file-from-library - (s/keys :req-un [::profile-id ::file-id ::library-id])) +(s/def ::unlink-file-from-library ::cmd.files/unlink-file-from-library) (sv/defmethod ::unlink-file-from-library + {::doc/added "1.3" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id file-id) - (unlink-file-from-library conn params))) - -(defn- unlink-file-from-library - [conn {:keys [file-id library-id] :as params}] - (db/delete! conn :file-library-rel - {:file-id file-id - :library-file-id library-id})) + (cmd.files/check-edition-permissions! conn profile-id file-id) + (cmd.files/unlink-file-from-library conn params))) ;; --- Mutation: Update synchronization status of a link -(declare update-sync) - -(s/def ::update-sync - (s/keys :req-un [::profile-id ::file-id ::library-id])) +(s/def ::update-sync ::cmd.files/update-file-library-sync-status) (sv/defmethod ::update-sync + {::doc/added "1.10" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id file-id) - (update-sync conn params))) + (cmd.files/check-edition-permissions! conn profile-id file-id) + (cmd.files/update-sync conn params))) -(defn- update-sync - [conn {:keys [file-id library-id] :as params}] - (db/update! conn :file-library-rel - {:synced-at (dt/now)} - {:file-id file-id - :library-file-id library-id})) ;; --- Mutation: Ignore updates in linked files (declare ignore-sync) -(s/def ::ignore-sync - (s/keys :req-un [::profile-id ::file-id ::date])) +(s/def ::ignore-sync ::cmd.files/ignore-file-library-sync-status) (sv/defmethod ::ignore-sync + {::doc/added "1.10" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id file-id) - (ignore-sync conn params))) - -(defn- ignore-sync - [conn {:keys [file-id date] :as params}] - (db/update! conn :file - {:ignore-sync-until date} - {:id file-id})) + (cmd.files/check-edition-permissions! conn profile-id file-id) + (cmd.files/ignore-sync conn params))) ;; --- MUTATION: update-file -;; A generic, Changes based (granular) file update method. - -;; File changes that affect to the library, and must be notified -;; to all clients using it. -(defn library-change? - [change] - (or (#{:add-color :mod-color :del-color - :add-media :mod-media :del-media - :add-component :mod-component :del-component - :add-typography :mod-typography :del-typography} (:type change)) - (and (#{:add-obj :mod-obj :del-obj - :reg-objects :mov-objects} (:type change)) - (some? (:component-id change))))) - -(declare insert-change) -(declare retrieve-lagged-changes) -(declare send-notifications) -(declare update-file) - -(s/def ::changes - (s/coll-of map? :kind vector?)) - -(s/def ::hint-origin ::us/keyword) -(s/def ::hint-events - (s/every ::us/keyword :kind vector?)) - -(s/def ::change-with-metadata - (s/keys :req-un [::changes] - :opt-un [::hint-origin - ::hint-events])) - -(s/def ::changes-with-metadata - (s/every ::change-with-metadata :kind vector?)) - -(s/def ::session-id ::us/uuid) -(s/def ::revn ::us/integer) (s/def ::components-v2 ::us/boolean) (s/def ::update-file - (s/and - (s/keys :req-un [::id ::session-id ::profile-id ::revn] - :opt-un [::changes ::changes-with-metadata ::components-v2 ::features]) - (fn [o] - (or (contains? o :changes) - (contains? o :changes-with-metadata))))) - -(def ^:private sql:retrieve-file - "SELECT f.*, p.team_id - FROM file AS f - JOIN project AS p ON (p.id = f.project_id) - WHERE f.id = ? - AND (f.deleted_at IS NULL OR - f.deleted_at > now()) - FOR KEY SHARE") + (s/and ::cmd.files.update/update-file + (s/keys :opt-un [::components-v2]))) (sv/defmethod ::update-file {::climit/queue :update-file - ::climit/key-fn :id} - [{:keys [pool] :as cfg} {:keys [id profile-id components-v2] :as params}] + ::climit/key-fn :id + ::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} {:keys [id profile-id features components-v2] :as params}] (db/with-atomic [conn pool] (db/xact-lock! conn id) - (let [file (db/exec-one! conn [sql:retrieve-file id]) - features' (:features params #{}) - features (db/decode-pgarray (:features file) features') + (cmd.files/check-edition-permissions! conn profile-id id) - ;; BACKWARD COMPATIBILITY with the components-v2 parameter - features (cond-> features + (let [;; BACKWARD COMPATIBILITY with the components-v2 parameter + features (cond-> (or features #{}) components-v2 (conj "components/v2")) + tpoint (dt/tpoint) + params (assoc params :features features) + cfg (assoc cfg :conn conn)] - file (assoc file :features features) - tpoint (dt/tpoint)] - - (when-not file - (ex/raise :type :not-found - :code :object-not-found - :hint (format "file with id '%s' does not exists" id))) - - ;; If features are specified from params and the final feature - ;; set is different than the persisted one, update it on the - ;; database. - (when (not= features features') - (let [features (db/create-array conn "text" features)] - (db/update! conn :file - {:features features} - {:id id}))) - - (binding [ffeat/*current* features - ffeat/*wrap-objects-fn* (if (features "storate/objects-map") - omap/wrap - identity)] - (files/check-edition-permissions! conn profile-id (:id file)) - (with-meta - (update-file (assoc cfg :conn conn) - (assoc params :file file)) - {::audit/props - {:project-id (:project-id file) - :team-id (:team-id file)} - - ::rpc/before-complete - (fn [] - (let [elapsed (tpoint)] - (l/trace :hint "update-file" :time (dt/format-duration elapsed))))}))))) - - -(defn- take-snapshot? - "Defines the rule when file `data` snapshot should be saved." - [{:keys [revn modified-at] :as file}] - (let [freq (or (cf/get :file-change-snapshot-every) 20) - timeout (or (cf/get :file-change-snapshot-timeout) - (dt/duration {:hours 1}))] - (or (= 1 freq) - (zero? (mod revn freq)) - (> (inst-ms (dt/diff modified-at (dt/now))) - (inst-ms timeout))))) - -(defn- delete-from-storage - [{:keys [storage] :as cfg} file] - (p/do - (when-let [backend (simpl/resolve-backend storage (:data-backend file))] - (simpl/del-object backend file)))) - -(defn- update-file - [{:keys [conn metrics] :as cfg} - {:keys [file changes changes-with-metadata session-id profile-id] :as params}] - (when (> (:revn params) - (:revn file)) - - (ex/raise :type :validation - :code :revn-conflict - :hint "The incoming revision number is greater that stored version." - :context {:incoming-revn (:revn params) - :stored-revn (:revn file)})) - - (let [changes (if changes-with-metadata - (mapcat :changes changes-with-metadata) - changes) - - changes (vec changes) - - ;; Trace the number of changes processed - _ (mtx/run! metrics {:id :update-file-changes :inc (count changes)}) - - ts (dt/now) - file (-> file - (update :revn inc) - (update :data (fn [data] - ;; Trace the length of bytes of processed data - (mtx/run! metrics {:id :update-file-bytes-processed :inc (alength data)}) - (cond-> data - :always - (-> (blob/decode) - (assoc :id (:id file)) - (pmg/migrate-data)) - - - (contains? ffeat/*current* "components/v2") - (ctf/migrate-to-components-v2) - - :always - (-> (cp/process-changes changes) - (blob/encode))))))] - ;; Insert change to the xlog - (db/insert! conn :file-change - {:id (uuid/next) - :session-id session-id - :profile-id profile-id - :created-at ts - :file-id (:id file) - :revn (:revn file) - :data (when (take-snapshot? file) - (:data file)) - :changes (blob/encode changes)}) - - ;; Update file - (db/update! conn :file - {:revn (:revn file) - :data (:data file) - :data-backend nil - :modified-at ts - :has-media-trimmed false} - {:id (:id file)}) - - ;; We need to delete the data from external storage backend - (when-not (nil? (:data-backend file)) - @(delete-from-storage cfg file)) - - (db/update! conn :project - {:modified-at ts} - {:id (:project-id file)}) - - (let [params (assoc params :file file :changes changes)] - ;; Send asynchronous notifications - (send-notifications cfg params) - - ;; Retrieve and return lagged data - (retrieve-lagged-changes conn params)))) - -(def ^:private - sql:lagged-changes - "select s.id, s.revn, s.file_id, - s.session_id, s.changes - from file_change as s - where s.file_id = ? - and s.revn > ? - order by s.created_at asc") - -(defn- retrieve-lagged-changes - [conn params] - (->> (db/exec! conn [sql:lagged-changes (:id params) (:revn params)]) - (into [] (comp (map files/decode-row) - (map (fn [row] - (cond-> row - (= (:revn row) (:revn (:file params))) - (assoc :changes [])))))))) - -(defn- send-notifications - [{:keys [conn] :as cfg} {:keys [file changes session-id] :as params}] - (let [lchanges (filter library-change? changes) - msgbus (:msgbus cfg)] - - - ;; Asynchronously publish message to the msgbus - (mbus/pub! msgbus - :topic (:id file) - :message {:type :file-change - :profile-id (:profile-id params) - :file-id (:id file) - :session-id (:session-id params) - :revn (:revn file) - :changes changes}) - - (when (and (:is-shared file) (seq lchanges)) - (let [team-id (or (:team-id file) - (retrieve-team-id conn (:project-id file)))] - ;; Asynchronously publish message to the msgbus - (mbus/pub! msgbus - :topic team-id - :message {:type :library-change - :profile-id (:profile-id params) - :file-id (:id file) - :session-id session-id - :revn (:revn file) - :modified-at (dt/now) - :changes lchanges}))))) - -(defn- retrieve-team-id - [conn project-id] - (:team-id (db/get-by-id conn :project project-id {:columns [:team-id]}))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; TEMPORARY FILES (behaves differently) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(s/def ::create-temp-file ::create-file) - -(sv/defmethod ::create-temp-file - [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] - (db/with-atomic [conn pool] - (proj/check-edition-permissions! conn profile-id project-id) - (create-file conn (assoc params :deleted-at (dt/in-future {:days 1}))))) - -(s/def ::update-temp-file - (s/keys :req-un [::changes ::revn ::session-id ::id])) - -(sv/defmethod ::update-temp-file - {::doc/added "1.7"} - [{:keys [pool] :as cfg} {:keys [profile-id session-id id revn changes] :as params}] - (db/with-atomic [conn pool] - (db/insert! conn :file-change - {:id (uuid/next) - :session-id session-id - :profile-id profile-id - :created-at (dt/now) - :file-id id - :revn revn - :data nil - :changes (blob/encode changes)}) - nil)) - -(s/def ::persist-temp-file - (s/keys :req-un [::id ::profile-id])) - -(sv/defmethod ::persist-temp-file - {::doc/added "1.7"} - [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] - (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id id) - (let [file (db/get-by-id conn :file id) - revs (db/query conn :file-change - {:file-id id} - {:order-by [[:revn :asc]]}) - revn (count revs)] - - (when (nil? (:deleted-at file)) - (ex/raise :type :validation - :code :cant-persist-already-persisted-file)) - - (loop [revs (seq revs) - data (blob/decode (:data file))] - (if-let [rev (first revs)] - (recur (rest revs) - (->> rev :changes blob/decode (cp/process-changes data))) - (db/update! conn :file - {:deleted-at nil - :revn revn - :data (blob/encode data)} - {:id id}))) - nil))) + (-> (cmd.files.update/update-file cfg params) + (vary-meta assoc ::rpc/before-complete + (fn [] + (let [elapsed (tpoint)] + (l/trace :hint "update-file" :time (dt/format-duration elapsed))))))))) ;; --- Mutation: upsert object thumbnail -(def sql:upsert-object-thumbnail - "insert into file_object_thumbnail(file_id, object_id, data) - values (?, ?, ?) - on conflict(file_id, object_id) do - update set data = ?;") - -(s/def ::data (s/nilable ::us/string)) -(s/def ::object-id ::us/string) -(s/def ::upsert-file-object-thumbnail - (s/keys :req-un [::profile-id ::file-id ::object-id ::data])) +(s/def ::upsert-file-object-thumbnail ::cmd.files/upsert-file-object-thumbnail) (sv/defmethod ::upsert-file-object-thumbnail - [{:keys [pool] :as cfg} {:keys [profile-id file-id object-id data]}] + {::doc/added "1.13" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id file-id) - (if data - (db/exec-one! conn [sql:upsert-object-thumbnail file-id object-id data data]) - (db/delete! conn :file-object-thumbnail {:file-id file-id :object-id object-id})) + (cmd.files/check-edition-permissions! conn profile-id file-id) + (cmd.files/upsert-file-object-thumbnail! conn params) nil)) + ;; --- Mutation: upsert file thumbnail -(def sql:upsert-file-thumbnail - "insert into file_thumbnail (file_id, revn, data, props) - values (?, ?, ?, ?::jsonb) - on conflict(file_id, revn) do - update set data = ?, props=?, updated_at=now();") - -(s/def ::revn ::us/integer) -(s/def ::props map?) -(s/def ::upsert-file-thumbnail - (s/keys :req-un [::profile-id ::file-id ::revn ::data ::props])) +(s/def ::upsert-file-thumbnail ::cmd.files/upsert-file-thumbnail) (sv/defmethod ::upsert-file-thumbnail "Creates or updates the file thumbnail. Mainly used for paint the grid thumbnails." - [{:keys [pool] :as cfg} {:keys [profile-id file-id revn data props]}] + {::doc/added "1.13" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] - (files/check-edition-permissions! conn profile-id file-id) - (let [props (db/tjson (or props {}))] - (db/exec-one! conn [sql:upsert-file-thumbnail - file-id revn data props data props]) - nil))) + (cmd.files/check-edition-permissions! conn profile-id file-id) + (cmd.files/upsert-file-thumbnail conn params) + nil)) + + +;; --- MUTATION COMMAND: create-temp-file + +(s/def ::create-temp-file ::cmd.files.temp/create-temp-file) + +(sv/defmethod ::create-temp-file + {::doc/added "1.7" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] + (db/with-atomic [conn pool] + (proj/check-edition-permissions! conn profile-id project-id) + (cmd.files.create/create-file conn (assoc params :deleted-at (dt/in-future {:days 1}))))) + +;; --- MUTATION COMMAND: update-temp-file + +(s/def ::update-temp-file ::cmd.files.temp/update-temp-file) + +(sv/defmethod ::update-temp-file + {::doc/added "1.7" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (cmd.files.temp/update-temp-file conn params) + nil)) + +;; --- MUTATION COMMAND: persist-temp-file + +(s/def ::persist-temp-file ::cmd.files.temp/persist-temp-file) + +(sv/defmethod ::persist-temp-file + {::doc/added "1.7" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + (db/with-atomic [conn pool] + (cmd.files/check-edition-permissions! conn profile-id id) + (cmd.files.temp/persist-temp-file conn params))) diff --git a/backend/src/app/rpc/mutations/share_link.clj b/backend/src/app/rpc/mutations/share_link.clj index ff4148a18e..9e8ab45d61 100644 --- a/backend/src/app/rpc/mutations/share_link.clj +++ b/backend/src/app/rpc/mutations/share_link.clj @@ -10,7 +10,7 @@ [app.common.spec :as us] [app.common.uuid :as uuid] [app.db :as db] - [app.rpc.queries.files :as files] + [app.rpc.commands.files :as files] [app.util.services :as sv] [clojure.spec.alpha :as s])) diff --git a/backend/src/app/rpc/queries/comments.clj b/backend/src/app/rpc/queries/comments.clj index c83f4576a4..e9db1a6c8a 100644 --- a/backend/src/app/rpc/queries/comments.clj +++ b/backend/src/app/rpc/queries/comments.clj @@ -8,8 +8,8 @@ (:require [app.db :as db] [app.rpc.commands.comments :as cmd.comments] + [app.rpc.commands.files :as cmd.files] [app.rpc.doc :as-alias doc] - [app.rpc.queries.files :as files] [app.rpc.queries.teams :as teams] [app.util.services :as sv] [clojure.spec.alpha :as s])) @@ -52,7 +52,7 @@ ::doc/deprecated "1.15"} [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] (with-open [conn (db/open pool)] - (files/check-comment-permissions! conn profile-id file-id share-id) + (cmd.files/check-comment-permissions! conn profile-id file-id share-id) (cmd.comments/get-comment-thread conn params))) ;; --- QUERY: Comments @@ -65,7 +65,7 @@ [{:keys [pool] :as cfg} {:keys [profile-id thread-id share-id] :as params}] (with-open [conn (db/open pool)] (let [thread (db/get-by-id conn :comment-thread thread-id)] - (files/check-comment-permissions! conn profile-id (:file-id thread) share-id)) + (cmd.files/check-comment-permissions! conn profile-id (:file-id thread) share-id)) (cmd.comments/get-comments conn thread-id))) @@ -78,5 +78,5 @@ ::doc/added "1.13"} [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id]}] (with-open [conn (db/open pool)] - (files/check-comment-permissions! conn profile-id file-id share-id) + (cmd.files/check-comment-permissions! conn profile-id file-id share-id) (cmd.comments/get-file-comments-users conn file-id profile-id))) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 349c54bc27..8dedbc916f 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -6,289 +6,56 @@ (ns app.rpc.queries.files (:require - [app.common.data :as d] - [app.common.data.macros :as dm] - [app.common.exceptions :as ex] - [app.common.geom.shapes :as gsh] - [app.common.pages.helpers :as cph] - [app.common.pages.migrations :as pmg] [app.common.spec :as us] - [app.common.types.file :as ctf] - [app.common.types.shape-tree :as ctt] [app.db :as db] - [app.db.sql :as sql] [app.rpc :as-alias rpc] + [app.rpc.commands.files :as cmd.files] + [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rpch] - [app.rpc.permissions :as perms] [app.rpc.queries.projects :as projects] - [app.rpc.queries.share-link :refer [retrieve-share-link]] [app.rpc.queries.teams :as teams] - [app.util.blob :as blob] [app.util.services :as sv] - [clojure.spec.alpha :as s] - [cuerdas.core :as str])) - -(declare decode-row) - -;; --- Helpers & Specs - -(s/def ::frame-id ::us/uuid) -(s/def ::id ::us/uuid) -(s/def ::name ::us/string) -(s/def ::project-id ::us/uuid) -(s/def ::file-id ::us/uuid) -(s/def ::profile-id ::us/uuid) -(s/def ::team-id ::us/uuid) -(s/def ::search-term ::us/string) -(s/def ::components-v2 ::us/boolean) - -;; --- Query: File Permissions - -(def ^:private sql:file-permissions - "select fpr.is_owner, - fpr.is_admin, - fpr.can_edit - from file_profile_rel as fpr - where fpr.file_id = ? - and fpr.profile_id = ? - union all - select tpr.is_owner, - tpr.is_admin, - tpr.can_edit - from team_profile_rel as tpr - inner join project as p on (p.team_id = tpr.team_id) - inner join file as f on (p.id = f.project_id) - where f.id = ? - and tpr.profile_id = ? - union all - select ppr.is_owner, - ppr.is_admin, - ppr.can_edit - from project_profile_rel as ppr - inner join file as f on (f.project_id = ppr.project_id) - where f.id = ? - and ppr.profile_id = ?") - -(defn retrieve-file-permissions - [conn profile-id file-id] - (when (and profile-id file-id) - (db/exec! conn [sql:file-permissions - file-id profile-id - file-id profile-id - file-id profile-id]))) - -(defn get-permissions - ([conn profile-id file-id] - (let [rows (retrieve-file-permissions conn profile-id file-id) - is-owner (boolean (some :is-owner rows)) - is-admin (boolean (some :is-admin rows)) - can-edit (boolean (some :can-edit rows))] - (when (seq rows) - {:type :membership - :is-owner is-owner - :is-admin (or is-owner is-admin) - :can-edit (or is-owner is-admin can-edit) - :can-read true - :is-logged (some? profile-id)}))) - ([conn profile-id file-id share-id] - (let [perms (get-permissions conn profile-id file-id) - ldata (retrieve-share-link conn file-id share-id)] - - ;; NOTE: in a future when share-link becomes more powerful and - ;; will allow us specify which parts of the app is available, we - ;; will probably need to tweak this function in order to expose - ;; this flags to the frontend. - (cond - (some? perms) perms - (some? ldata) {:type :share-link - :can-read true - :is-logged (some? profile-id) - :who-comment (:who-comment ldata) - :who-inspect (:who-inspect ldata)})))) - -(def has-edit-permissions? - (perms/make-edition-predicate-fn get-permissions)) - -(def has-read-permissions? - (perms/make-read-predicate-fn get-permissions)) - -(def has-comment-permissions? - (perms/make-comment-predicate-fn get-permissions)) - -(def check-edition-permissions! - (perms/make-check-fn has-edit-permissions?)) - -(def check-read-permissions! - (perms/make-check-fn has-read-permissions?)) - -;; A user has comment permissions if she has read permissions, or comment permissions -(defn check-comment-permissions! - [conn profile-id file-id share-id] - (let [can-read (has-read-permissions? conn profile-id file-id) - can-comment (has-comment-permissions? conn profile-id file-id share-id)] - (when-not (or can-read can-comment) - (ex/raise :type :not-found - :code :object-not-found - :hint "not found")))) - -;; --- Query: Files search - -;; TODO: this query need to a good refactor - -(def ^:private sql:search-files - "with projects as ( - select p.* - from project as p - inner join team_profile_rel as tpr on (tpr.team_id = p.team_id) - where tpr.profile_id = ? - and p.team_id = ? - and p.deleted_at is null - and (tpr.is_admin = true or - tpr.is_owner = true or - tpr.can_edit = true) - union - select p.* - from project as p - inner join project_profile_rel as ppr on (ppr.project_id = p.id) - where ppr.profile_id = ? - and p.team_id = ? - and p.deleted_at is null - and (ppr.is_admin = true or - ppr.is_owner = true or - ppr.can_edit = true) - ) - select distinct - f.id, - f.project_id, - f.created_at, - f.modified_at, - f.name, - f.is_shared - from file as f - inner join projects as pr on (f.project_id = pr.id) - where f.name ilike ('%' || ? || '%') - and f.deleted_at is null - order by f.created_at asc") - -(s/def ::search-files - (s/keys :req-un [::profile-id ::team-id] - :opt-un [::search-term])) - -(sv/defmethod ::search-files - [{:keys [pool] :as cfg} {:keys [profile-id team-id search-term] :as params}] - (when search-term - (db/exec! pool [sql:search-files - profile-id team-id - profile-id team-id - search-term]))) + [clojure.spec.alpha :as s])) ;; --- Query: Project Files -(def ^:private sql:project-files - "select f.id, - f.project_id, - f.created_at, - f.modified_at, - f.name, - f.revn, - f.is_shared - from file as f - where f.project_id = ? - and f.deleted_at is null - order by f.modified_at desc") - -(s/def ::project-files - (s/keys :req-un [::profile-id ::project-id])) +(s/def ::project-files ::cmd.files/get-project-files) (sv/defmethod ::project-files + {::doc/added "1.1" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] (with-open [conn (db/open pool)] (projects/check-read-permissions! conn profile-id project-id) - (db/exec! conn [sql:project-files project-id]))) + (cmd.files/get-project-files conn project-id))) ;; --- Query: File (By ID) -(defn retrieve-object-thumbnails - ([{:keys [pool]} file-id] - (let [sql (str/concat - "select object_id, data " - " from file_object_thumbnail" - " where file_id=?")] - (->> (db/exec! pool [sql file-id]) - (d/index-by :object-id :data)))) - - ([{:keys [pool]} file-id object-ids] - (with-open [conn (db/open pool)] - (let [sql (str/concat - "select object_id, data " - " from file_object_thumbnail" - " where file_id=? and object_id = ANY(?)") - ids (db/create-array conn "text" (seq object-ids))] - (->> (db/exec! conn [sql file-id ids]) - (d/index-by :object-id :data)))))) - -(defn retrieve-file - [{:keys [pool] :as cfg} id features] - (let [file (->> (db/get-by-id pool :file id) - (decode-row) - (pmg/migrate-file))] - - (if (contains? features "components/v2") - (update file :data ctf/migrate-to-components-v2) - (if (dm/get-in file [:data :options :components-v2]) - (ex/raise :type :restriction - :code :feature-disabled - :hint "tried to open a components/v2 file with feature disabled") - file)))) - -(s/def ::features ::us/set-of-strings) +(s/def ::components-v2 ::us/boolean) (s/def ::file - (s/keys :req-un [::profile-id ::id] - :opt-un [::features ::components-v2])) + (s/and ::cmd.files/get-file + (s/keys :opt-un [::components-v2]))) (sv/defmethod ::file "Retrieve a file by its ID. Only authenticated users." + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id id features components-v2] :as params}] - (let [perms (get-permissions pool profile-id id) + (with-open [conn (db/open pool)] + (let [perms (cmd.files/get-permissions pool profile-id id) + ;; BACKWARD COMPATIBILTY with the components-v2 parameter + features (cond-> (or features #{}) + components-v2 (conj "components/v2"))] - ;; BACKWARD COMPATIBILTY with the components-v2 parameter - features (cond-> (or features #{}) - components-v2 (conj features "components/v2"))] - (check-read-permissions! perms) - (let [file (retrieve-file cfg id features) - thumbs (retrieve-object-thumbnails cfg id)] - (-> file - (assoc :thumbnails thumbs) + (cmd.files/check-read-permissions! perms) + (-> (cmd.files/get-file conn id features) (assoc :permissions perms))))) - ;; --- QUERY: page -(defn- prune-objects - "Given the page data and the object-id returns the page data with all - other not needed objects removed from the `:objects` data - structure." - [{:keys [objects] :as page} object-id] - (let [objects (cph/get-children-with-self objects object-id)] - (assoc page :objects (d/index-by :id objects)))) - -(defn- prune-thumbnails - "Given the page data, removes the `:thumbnail` prop from all - shapes." - [page] - (update page :objects d/update-vals #(dissoc % :thumbnail))) - -(s/def ::page-id ::us/uuid) -(s/def ::object-id ::us/uuid) - (s/def ::page - (s/and - (s/keys :req-un [::profile-id ::file-id] - :opt-un [::page-id ::object-id ::features ::components-v2]) - (fn [obj] - (if (contains? obj :object-id) - (contains? obj :page-id) - true)))) + (s/and ::cmd.files/get-page + (s/keys :opt-un [::components-v2]))) (sv/defmethod ::page "Retrieves the page data from file and returns it. If no page-id is @@ -300,288 +67,100 @@ mandatory. Mainly used for rendering purposes." - [{:keys [pool] :as cfg} {:keys [profile-id file-id page-id object-id features components-v2] :as props}] - (check-read-permissions! pool profile-id file-id) - (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter - features (cond-> (or features #{}) - components-v2 (conj features "components/v2")) + {::doc/added "1.5" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id features components-v2] :as params}] + (with-open [conn (db/open pool)] + (cmd.files/check-read-permissions! conn profile-id file-id) + (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter + features (cond-> (or features #{}) + components-v2 (conj "components/v2")) + params (assoc params :features features)] - file (retrieve-file cfg file-id features) - page-id (or page-id (-> file :data :pages first)) - page (dm/get-in file [:data :pages-index page-id])] - - (cond-> (prune-thumbnails page) - (uuid? object-id) - (prune-objects object-id)))) + (cmd.files/get-page conn params)))) ;; --- QUERY: file-data-for-thumbnail -(defn- get-file-thumbnail-data - [cfg {:keys [data id] :as file}] - (letfn [;; function responsible on finding the frame marked to be - ;; used as thumbnail; the returned frame always have - ;; the :page-id set to the page that it belongs. - (get-thumbnail-frame [data] - (d/seek :use-for-thumbnail? - (for [page (-> data :pages-index vals) - frame (-> page :objects ctt/get-frames)] - (assoc frame :page-id (:id page))))) - - ;; function responsible to filter objects data structure of - ;; all unneeded shapes if a concrete frame is provided. If no - ;; frame, the objects is returned untouched. - (filter-objects [objects frame-id] - (d/index-by :id (cph/get-children-with-self objects frame-id))) - - ;; function responsible of assoc available thumbnails - ;; to frames and remove all children shapes from objects if - ;; thumbnails is available - (assoc-thumbnails [objects page-id thumbnails] - (loop [objects objects - frames (filter cph/frame-shape? (vals objects))] - - (if-let [frame (-> frames first)] - (let [frame-id (:id frame) - object-id (str page-id frame-id) - frame (if-let [thumb (get thumbnails object-id)] - (assoc frame :thumbnail thumb :shapes []) - (dissoc frame :thumbnail)) - - children-ids - (cph/get-children-ids objects frame-id) - - bounds - (when (:show-content frame) - (gsh/selection-rect (concat [frame] (->> children-ids (map (d/getf objects)))))) - - frame - (cond-> frame - (some? bounds) - (assoc :children-bounds bounds))] - - (if (:thumbnail frame) - (recur (-> objects - (assoc frame-id frame) - (d/without-keys children-ids)) - (rest frames)) - (recur (assoc objects frame-id frame) - (rest frames)))) - - objects)))] - - (let [frame (get-thumbnail-frame data) - frame-id (:id frame) - page-id (or (:page-id frame) - (-> data :pages first)) - - page (dm/get-in data [:pages-index page-id]) - frame-ids (if (some? frame) (list frame-id) (map :id (ctt/get-frames (:objects page)))) - - obj-ids (map #(str page-id %) frame-ids) - thumbs (retrieve-object-thumbnails cfg id obj-ids)] - - (cond-> page - ;; If we have frame, we need to specify it on the page level - ;; and remove the all other unrelated objects. - (some? frame-id) - (-> (assoc :thumbnail-frame-id frame-id) - (update :objects filter-objects frame-id)) - - ;; Assoc the available thumbnails and prune not visible shapes - ;; for avoid transfer unnecessary data. - :always - (update :objects assoc-thumbnails page-id thumbs))))) - (s/def ::file-data-for-thumbnail - (s/keys :req-un [::profile-id ::file-id] - :opt-un [::components-v2 ::features])) + (s/and ::cmd.files/get-file-data-for-thumbnail + (s/keys :opt-un [::components-v2]))) (sv/defmethod ::file-data-for-thumbnail "Retrieves the data for generate the thumbnail of the file. Used mainly for render thumbnails on dashboard." + {::doc/added "1.11" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id features components-v2] :as props}] - (check-read-permissions! pool profile-id file-id) - (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter - features (cond-> (or features #{}) - components-v2 (conj features "components/v2")) - file (retrieve-file cfg file-id features)] - {:file-id file-id - :revn (:revn file) - :page (get-file-thumbnail-data cfg file)})) - + (with-open [conn (db/open pool)] + (cmd.files/check-read-permissions! conn profile-id file-id) + (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter + features (cond-> (or features #{}) + components-v2 (conj "components/v2")) + file (cmd.files/retrieve-file conn file-id features)] + {:file-id file-id + :revn (:revn file) + :page (cmd.files/get-file-data-for-thumbnail conn file)}))) ;; --- Query: Shared Library Files -(def ^:private sql:team-shared-files - "select f.id, - f.revn, - f.data, - f.project_id, - f.created_at, - f.modified_at, - f.name, - f.is_shared - from file as f - inner join project as p on (p.id = f.project_id) - where f.is_shared = true - and f.deleted_at is null - and p.deleted_at is null - and p.team_id = ? - order by f.modified_at desc") - -(s/def ::team-shared-files - (s/keys :req-un [::profile-id ::team-id])) +(s/def ::team-shared-files ::cmd.files/get-team-shared-files) (sv/defmethod ::team-shared-files - [{:keys [pool] :as cfg} {:keys [team-id] :as params}] - (let [assets-sample - (fn [assets limit] - (let [sorted-assets (->> (vals assets) - (sort-by #(str/lower (:name %))))] - - {:count (count sorted-assets) - :sample (into [] (take limit sorted-assets))})) - - library-summary - (fn [data] - {:components (assets-sample (:components data) 4) - :colors (assets-sample (:colors data) 3) - :typographies (assets-sample (:typographies data) 3)}) - - xform (comp - (map decode-row) - (map #(assoc % :library-summary (library-summary (:data %)))) - (map #(dissoc % :data)))] - - (into #{} xform (db/exec! pool [sql:team-shared-files team-id])))) + {::doc/added "1.3" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] + (with-open [conn (db/open pool)] + (teams/check-read-permissions! conn profile-id team-id) + (cmd.files/get-team-shared-files conn params))) ;; --- Query: File Libraries used by a File -(def ^:private sql:file-libraries - "WITH RECURSIVE libs AS ( - SELECT fl.*, flr.synced_at - FROM file AS fl - JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id) - WHERE flr.file_id = ?::uuid - UNION - SELECT fl.*, flr.synced_at - FROM file AS fl - JOIN file_library_rel AS flr ON (flr.library_file_id = fl.id) - JOIN libs AS l ON (flr.file_id = l.id) - ) - SELECT l.id, - l.data, - l.project_id, - l.created_at, - l.modified_at, - l.deleted_at, - l.name, - l.revn, - l.synced_at - FROM libs AS l - WHERE l.deleted_at IS NULL OR l.deleted_at > now();") - -(defn retrieve-file-libraries - [{:keys [pool] :as cfg} is-indirect file-id] - (let [xform (comp - (map #(assoc % :is-indirect is-indirect)) - (map decode-row))] - (into #{} xform (db/exec! pool [sql:file-libraries file-id])))) - -(s/def ::file-libraries - (s/keys :req-un [::profile-id ::file-id])) +(s/def ::file-libraries ::cmd.files/get-file-libraries) (sv/defmethod ::file-libraries + {::doc/added "1.3" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] - (check-read-permissions! pool profile-id file-id) - (retrieve-file-libraries cfg false file-id)) + (with-open [conn (db/open pool)] + (cmd.files/check-read-permissions! conn profile-id file-id) + (cmd.files/get-file-libraries conn false file-id))) ;; --- Query: Files that use this File library -(def ^:private sql:library-using-files - "SELECT f.id, - f.name - FROM file_library_rel AS flr - JOIN file AS f ON (f.id = flr.file_id) - WHERE flr.library_file_id = ? - AND (f.deleted_at IS NULL OR f.deleted_at > now())") - -(s/def ::library-using-files - (s/keys :req-un [::profile-id ::file-id])) +(s/def ::library-using-files ::cmd.files/get-library-file-references) (sv/defmethod ::library-using-files + {::doc/added "1.13" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] - (check-read-permissions! pool profile-id file-id) - (db/exec! pool [sql:library-using-files file-id])) + (with-open [conn (db/open pool)] + (cmd.files/check-read-permissions! conn profile-id file-id) + (cmd.files/get-library-file-references conn file-id))) ;; --- QUERY: team-recent-files -(def sql:team-recent-files - "with recent_files as ( - select f.id, - f.revn, - f.project_id, - f.created_at, - f.modified_at, - f.name, - f.is_shared, - row_number() over w as row_num - from file as f - join project as p on (p.id = f.project_id) - where p.team_id = ? - and p.deleted_at is null - and f.deleted_at is null - window w as (partition by f.project_id order by f.modified_at desc) - order by f.modified_at desc - ) - select * from recent_files where row_num <= 10;") - - -(s/def ::team-recent-files - (s/keys :req-un [::profile-id ::team-id])) +(s/def ::team-recent-files ::cmd.files/get-team-recent-files) (sv/defmethod ::team-recent-files + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id team-id]}] - (teams/check-read-permissions! pool profile-id team-id) - (db/exec! pool [sql:team-recent-files team-id])) + (with-open [conn (db/open pool)] + (teams/check-read-permissions! conn profile-id team-id) + (cmd.files/get-team-recent-files conn team-id))) + ;; --- QUERY: get file thumbnail -(s/def ::revn ::us/integer) - -(s/def ::file-thumbnail - (s/keys :req-un [::profile-id ::file-id] - :opt-un [::revn])) +(s/def ::file-thumbnail ::cmd.files/get-file-thumbnail) (sv/defmethod ::file-thumbnail + {::doc/added "1.13" + ::doc/deprecated "1.17"} [{:keys [pool]} {:keys [profile-id file-id revn]}] - (check-read-permissions! pool profile-id file-id) - (let [sql (sql/select :file-thumbnail - (cond-> {:file-id file-id} - revn (assoc :revn revn)) - {:limit 1 - :order-by [[:revn :desc]]}) - - row (db/exec-one! pool sql)] - - (when-not row - (ex/raise :type :not-found - :code :file-thumbnail-not-found)) - - (with-meta {:data (:data row) - :props (some-> (:props row) db/decode-transit-pgobject) - :revn (:revn row) - :file-id (:file-id row)} - {::rpc/transform-response (rpch/http-cache {:max-age (* 1000 60 60)})}))) - -;; --- Helpers - -(defn decode-row - [{:keys [data changes features] :as row}] - (when row - (cond-> row - features (assoc :features (db/decode-pgarray features)) - changes (assoc :changes (blob/decode changes)) - data (assoc :data (blob/decode data))))) + (with-open [conn (db/open pool)] + (cmd.files/check-read-permissions! conn profile-id file-id) + (-> (cmd.files/get-file-thumbnail conn file-id revn) + (with-meta {::rpc/transform-response (rpch/http-cache {:max-age (* 1000 60 60)})})))) diff --git a/backend/src/app/rpc/queries/fonts.clj b/backend/src/app/rpc/queries/fonts.clj index 70b9a7435e..077766cd33 100644 --- a/backend/src/app/rpc/queries/fonts.clj +++ b/backend/src/app/rpc/queries/fonts.clj @@ -8,7 +8,7 @@ (:require [app.common.spec :as us] [app.db :as db] - [app.rpc.queries.files :as files] + [app.rpc.commands.files :as files] [app.rpc.queries.projects :as projects] [app.rpc.queries.teams :as teams] [app.util.services :as sv] diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj index 2e64986f48..5c25315707 100644 --- a/backend/src/app/rpc/queries/viewer.clj +++ b/backend/src/app/rpc/queries/viewer.clj @@ -6,88 +6,26 @@ (ns app.rpc.queries.viewer (:require - [app.common.exceptions :as ex] [app.common.spec :as us] [app.db :as db] - [app.rpc.commands.comments :as comments] - [app.rpc.queries.files :as files] - [app.rpc.queries.share-link :as slnk] + [app.rpc.commands.viewer :as viewer] + [app.rpc.doc :as-alias doc] [app.util.services :as sv] - [clojure.spec.alpha :as s] - [promesa.core :as p])) + [clojure.spec.alpha :as s])) -;; --- Query: View Only Bundle - -(defn- retrieve-project - [pool id] - (db/get-by-id pool :project id {:columns [:id :name :team-id]})) - -(defn- retrieve-bundle - [{:keys [pool] :as cfg} file-id profile-id features] - (p/let [file (files/retrieve-file cfg file-id features) - project (retrieve-project pool (:project-id file)) - libs (files/retrieve-file-libraries cfg false file-id) - users (comments/get-file-comments-users pool file-id profile-id) - - links (->> (db/query pool :share-link {:file-id file-id}) - (mapv slnk/decode-share-link-row)) - - fonts (db/query pool :team-font-variant - {:team-id (:team-id project) - :deleted-at nil})] - {:file file - :users users - :fonts fonts - :project project - :share-links links - :libraries libs})) - -(s/def ::file-id ::us/uuid) -(s/def ::profile-id ::us/uuid) -(s/def ::share-id ::us/uuid) -(s/def ::features ::us/set-of-strings) - -;; TODO: deprecated, should be removed when version >= 1.18 (s/def ::components-v2 ::us/boolean) - (s/def ::view-only-bundle - (s/keys :req-un [::file-id] - :opt-un [::profile-id ::share-id ::features ::components-v2])) + (s/and ::viewer/get-view-only-bundle + (s/keys :opt-un [::components-v2]))) -(sv/defmethod ::view-only-bundle {:auth false} - [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id features components-v2] :as params}] - (p/let [;; BACKWARD COMPATIBILTY with the components-v2 parameter +(sv/defmethod ::view-only-bundle + {:auth false + ::doc/added "1.3" + ::doc/deprecated "1.17"} + [{:keys [pool] :as cfg} {:keys [features components-v2] :as params}] + (with-open [conn (db/open pool)] + (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter features (cond-> (or features #{}) - components-v2 (conj features "components/v2")) - - slink (slnk/retrieve-share-link pool file-id share-id) - perms (files/get-permissions pool profile-id file-id share-id) - thumbs (files/retrieve-object-thumbnails cfg file-id) - bundle (p/-> (retrieve-bundle cfg file-id profile-id features) - (assoc :permissions perms) - (assoc-in [:file :thumbnails] thumbs))] - ;; When we have neither profile nor share, we just return a not - ;; found response to the user. - (do - (when (and (not profile-id) - (not slink)) - (ex/raise :type :not-found - :code :object-not-found)) - - ;; When we have only profile, we need to check read permissions - ;; on file. - (when (and profile-id (not slink)) - (files/check-read-permissions! pool profile-id file-id)) - - (cond-> bundle - (some? slink) - (assoc :share slink) - - (and (some? slink) - (not (contains? (:flags slink) "view-all-pages"))) - (update-in [:file :data] (fn [data] - (let [allowed-pages (:pages slink)] - (-> data - (update :pages (fn [pages] (filterv #(contains? allowed-pages %) pages))) - (update :pages-index (fn [index] (select-keys index allowed-pages))))))))))) - + components-v2 (conj "components/v2")) + params (assoc params :features features)] + (viewer/get-view-only-bundle conn params)))) diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index 01730dbb6c..707242f237 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -135,8 +135,7 @@ (update :features conj "storage/pointer-map")))) (migrate-to-omap [data file-id] - (binding [pmap/*tracked* (atom {}) - pmap/*metadata* {:file-id file-id}] + (binding [pmap/*tracked* (atom {})] (let [data (-> data (update :pages-index update-vals pmap/wrap) (update :components pmap/wrap))] @@ -144,7 +143,6 @@ (db/insert! h/*conn* :file-data-fragment {:id id :file-id file-id - :metadata (-> item meta db/tjson) :content (-> item deref blob/encode)})) data)))] diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj index 530890317e..fa1b68c182 100644 --- a/backend/src/app/tasks/file_gc.clj +++ b/backend/src/app/tasks/file_gc.clj @@ -17,11 +17,12 @@ [app.common.types.shape-tree :as ctt] [app.config :as cf] [app.db :as db] + [app.rpc.commands.files :as files] [app.util.blob :as blob] + [app.util.pointer-map :as pmap] [app.util.time :as dt] [clojure.set :as set] [clojure.spec.alpha :as s] - [cuerdas.core :as str] [integrant.core :as ig])) (declare ^:private retrieve-candidates) @@ -55,7 +56,7 @@ (recur (inc total) (rest files))) (do - (l/info :hint "task finished" :min-age (dt/format-duration min-age) :total total) + (l/info :hint "task finished" :min-age (dt/format-duration min-age) :processed total) ;; Allow optional rollback passed by params (when (:rollback? params) @@ -72,6 +73,7 @@ "select f.id, f.data, f.revn, + f.features, f.modified_at from file as f where f.has_media_trimmed is false @@ -86,17 +88,22 @@ (if id (do (l/warn :hint "explicit file id passed on params" :id id) - (db/query conn :file {:id id})) + (->> (db/query conn :file {:id id}) + (map #(update % :features db/decode-pgarray #{})))) (let [interval (db/interval min-age) get-chunk (fn [cursor] (let [rows (db/exec! conn [sql:retrieve-candidates-chunk interval cursor])] - [(some->> rows peek :modified-at) (seq rows)]))] + [(some->> rows peek :modified-at) + (map #(update % :features db/decode-pgarray #{}) rows)]))] + (d/iteration get-chunk :vf second :kf first :initk (dt/now))))) (defn collect-used-media + "Analyzes the file data and collects all references to external + assets. Returns a set of ids." [data] (let [xform (comp (map :objects) @@ -152,9 +159,8 @@ unused (set/difference stored using)] (when (seq unused) - (let [sql (str/concat - "delete from file_object_thumbnail " - " where file_id=? and object_id=ANY(?)") + (let [sql (str "delete from file_object_thumbnail " + " where file_id=? and object_id=ANY(?)") res (db/exec-one! conn [sql file-id (db/create-array conn "text" unused)])] (l/debug :hint "delete file object thumbnails" :file-id file-id :total (:next.jdbc/update-count res)))))) @@ -233,21 +239,41 @@ {:data new-data} {:id library-id}))))) +(def ^:private sql:get-unused-fragments + "SELECT id FROM file_data_fragment + WHERE file_id = ? AND id != ALL(?::uuid[])") + +(defn- clean-data-fragments! + [conn file-id data] + (let [used (->> (concat (vals data) + (vals (:pages-index data))) + (into #{} (comp (filter pmap/pointer-map?) + (map pmap/get-id))) + (db/create-array conn "uuid")) + rows (db/exec! conn [sql:get-unused-fragments file-id used])] + (doseq [fragment-id (map :id rows)] + (l/trace :hint "remove unused file data fragment" :id (str fragment-id)) + (db/delete! conn :file-data-fragment {:id fragment-id :file-id file-id})))) + (defn- process-file - [{:keys [conn] :as cfg} {:keys [id data revn modified-at] :as file}] + [{:keys [conn] :as cfg} {:keys [id data revn modified-at features] :as file}] (l/debug :hint "processing file" :id id :modified-at modified-at) - (let [data (-> (blob/decode data) - (assoc :id id) - (pmg/migrate-data))] + (binding [pmap/*load-fn* (partial files/load-pointer conn id)] + (let [data (-> (blob/decode data) + (assoc :id id) + (pmg/migrate-data))] - (clean-file-media! conn id data) - (clean-file-frame-thumbnails! conn id data) - (clean-file-thumbnails! conn id revn) - (clean-deleted-components! conn id data) + (clean-file-media! conn id data) + (clean-file-frame-thumbnails! conn id data) + (clean-file-thumbnails! conn id revn) + (clean-deleted-components! conn id data) - ;; Mark file as trimmed - (db/update! conn :file + (when (contains? features "storage/pointer-map") + (clean-data-fragments! conn id data)) + + ;; Mark file as trimmed + (db/update! conn :file {:has-media-trimmed true} {:id id}) - nil)) + nil))) diff --git a/backend/src/app/util/fressian.clj b/backend/src/app/util/fressian.clj index 7d54e32075..335f74b75b 100644 --- a/backend/src/app/util/fressian.clj +++ b/backend/src/app/util/fressian.clj @@ -86,17 +86,25 @@ (write-tag! w tag 1) (write-list! w o)) +(defn begin-closed-list! + [^StreamingWriter w] + (.beginClosedList w)) + +(defn end-list! + [^StreamingWriter w] + (.endList w)) + (defn write-map-like "Writes a map as Fressian with the tag 'map' and all keys cached." [tag ^Writer w m] (write-tag! w tag 1) - (.beginClosedList ^StreamingWriter w) + (begin-closed-list! w) (loop [items (seq m)] (when-let [^clojure.lang.MapEntry item (first items)] (write-object! w (.key item) true) (write-object! w (.val item)) (recur (rest items)))) - (.endList ^StreamingWriter w)) + (end-list! w)) (defn read-map-like [^Reader rdr] diff --git a/backend/test/app/services_files_test.clj b/backend/test/app/services_files_test.clj index 004e29b2f4..913ddba5e9 100644 --- a/backend/test/app/services_files_test.clj +++ b/backend/test/app/services_files_test.clj @@ -496,7 +496,8 @@ (t/is (contains? (:objects result) shape1-id)) (t/is (contains? (:objects result) frame2-id)) (t/is (contains? (:objects result) shape2-id)) - (t/is (contains? (:objects result) uuid/zero))) + (t/is (contains? (:objects result) uuid/zero)) + ) ;; Query :page RPC method with page-id (let [data {::th/type :page @@ -523,6 +524,7 @@ :components-v2 true} {:keys [error result] :as out} (th/query! data)] ;; (th/print-result! out) + (t/is (nil? error)) (t/is (map? result)) (t/is (contains? result :objects)) (t/is (contains? (:objects result) frame1-id)) @@ -542,7 +544,9 @@ (t/is (not (th/success? out))) (let [{:keys [type code]} (-> out :error ex-data)] (t/is (= :validation type)) - (t/is (= :spec-validation code))))) + (t/is (= :spec-validation code)))) + + ) (t/testing "RPC :file-data-for-thumbnail" ;; Insert a thumbnail data for the frame-id diff --git a/backend/test/app/test_helpers.clj b/backend/test/app/test_helpers.clj index fea2b70757..0d9c3ddf53 100644 --- a/backend/test/app/test_helpers.clj +++ b/backend/test/app/test_helpers.clj @@ -18,7 +18,9 @@ [app.media] [app.migrations] [app.rpc.commands.auth :as cmd.auth] - [app.rpc.mutations.files :as files] + [app.rpc.commands.files :as files] + [app.rpc.commands.files.create :as files.create] + [app.rpc.commands.files.update :as files.update] [app.rpc.mutations.profile :as profile] [app.rpc.mutations.projects :as projects] [app.rpc.mutations.teams :as teams] @@ -178,11 +180,11 @@ (us/assert uuid? profile-id) (us/assert uuid? project-id) (with-open [conn (db/open pool)] - (#'files/create-file conn - (merge {:id (mk-uuid "file" i) - :name (str "file" i) - :components-v2 true} - params))))) + (files.create/create-file conn + (merge {:id (mk-uuid "file" i) + :name (str "file" i) + :components-v2 true} + params))))) (defn mark-file-deleted* ([params] (mark-file-deleted* *pool* params)) @@ -259,27 +261,27 @@ ([params] (create-file-role* *pool* params)) ([pool {:keys [file-id profile-id role] :or {role :owner}}] (with-open [conn (db/open pool)] - (#'files/create-file-role conn {:file-id file-id - :profile-id profile-id - :role role})))) + (files.create/create-file-role! conn {:file-id file-id + :profile-id profile-id + :role role})))) (defn update-file* ([params] (update-file* *pool* params)) ([pool {:keys [file-id changes session-id profile-id revn] :or {session-id (uuid/next) revn 0}}] (with-open [conn (db/open pool)] - (let [file (db/get-by-id conn :file file-id) - msgbus (:app.msgbus/msgbus *system*) - metrics (:app.metrics/metrics *system*)] - (#'files/update-file {:conn conn - :msgbus msgbus - :metrics metrics} - {:file file - :revn revn - :components-v2 true - :changes changes - :session-id session-id - :profile-id profile-id}))))) + (let [msgbus (:app.msgbus/msgbus *system*) + metrics (:app.metrics/metrics *system*) + features #{"components/v2"}] + (files.update/update-file {:conn conn + :msgbus msgbus + :metrics metrics} + {:id file-id + :revn revn + :features features + :changes changes + :session-id session-id + :profile-id profile-id}))))) ;; --- RPC HELPERS diff --git a/common/src/app/common/files/features.cljc b/common/src/app/common/files/features.cljc index a51c2344fc..34d40800e8 100644 --- a/common/src/app/common/files/features.cljc +++ b/common/src/app/common/files/features.cljc @@ -6,5 +6,12 @@ (ns app.common.files.features) +;; A set of enabled by default file features. Will be used in feature +;; negotiation on obtaining files from backend. + +(def enabled #{}) + +(def ^:dynamic *previous* #{}) (def ^:dynamic *current* #{}) -(def ^:dynamic *wrap-objects-fn* identity) +(def ^:dynamic *wrap-with-objects-map-fn* identity) +(def ^:dynamic *wrap-with-pointer-map-fn* identity) diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc index 3f485ec7d2..1c08c8594c 100644 --- a/common/src/app/common/transit.cljc +++ b/common/src/app/common/transit.cljc @@ -33,6 +33,10 @@ (def write-handler-map (atom nil)) (def read-handler-map (atom nil)) +;; A generic pointer; mainly used for deserialize backend pointer-map +;; instances that serializes to pointer but may in other ways. +(defrecord Pointer [id]) + ;; --- HELPERS #?(:clj @@ -133,6 +137,11 @@ (.fromMillis ^js lxn/DateTime ms)))) :wfn (comp str inst-ms)} + {:id "penpot/pointer" + :class Pointer + :rfn (fn [[id meta]] + (Pointer. id meta {}))} + #?(:clj {:id "m" :class OffsetDateTime diff --git a/common/src/app/common/types/components_list.cljc b/common/src/app/common/types/components_list.cljc index 95abd7342e..137990c8ae 100644 --- a/common/src/app/common/types/components_list.cljc +++ b/common/src/app/common/types/components_list.cljc @@ -16,8 +16,8 @@ (defn add-component [file-data {:keys [id name path main-instance-id main-instance-page shapes]}] - (let [components-v2 (dm/get-in file-data [:options :components-v2]) - wrap-object-fn feat/*wrap-objects-fn*] + (let [components-v2 (dm/get-in file-data [:options :components-v2]) + wrap-object-fn feat/*wrap-with-objects-map-fn*] (cond-> file-data :always (assoc-in [:components id] @@ -35,7 +35,7 @@ (defn mod-component [file-data {:keys [id name path objects]}] - (let [wrap-objects-fn feat/*wrap-objects-fn*] + (let [wrap-objects-fn feat/*wrap-with-objects-map-fn*] (update-in file-data [:components id] (fn [component] (let [objects (some-> objects wrap-objects-fn)] diff --git a/common/src/app/common/types/page.cljc b/common/src/app/common/types/page.cljc index dd34854815..835df4b37c 100644 --- a/common/src/app/common/types/page.cljc +++ b/common/src/app/common/types/page.cljc @@ -50,11 +50,13 @@ (defn make-empty-page [id name] - (let [wrap-fn ffeat/*wrap-objects-fn*] + (let [wrap-objects-fn ffeat/*wrap-with-objects-map-fn* + wrap-pointer-fn ffeat/*wrap-with-pointer-map-fn*] (-> empty-page-data (assoc :id id) (assoc :name name) - (update :objects wrap-fn)))) + (update :objects wrap-objects-fn) + (wrap-pointer-fn)))) ;; --- Helpers for flow diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 8b3b682de9..595a1029ad 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -110,7 +110,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (:current-team-id state)] - (->> (rp/query :team-members {:team-id team-id}) + (->> (rp/query! :team-members {:team-id team-id}) (rx/map team-members-fetched)))))) ;; --- EVENT: fetch-team-stats @@ -128,7 +128,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (:current-team-id state)] - (->> (rp/query :team-stats {:team-id team-id}) + (->> (rp/query! :team-stats {:team-id team-id}) (rx/map team-stats-fetched)))))) ;; --- EVENT: fetch-team-invitations @@ -146,7 +146,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (:current-team-id state)] - (->> (rp/query :team-invitations {:team-id team-id}) + (->> (rp/query! :team-invitations {:team-id team-id}) (rx/map team-invitations-fetched)))))) ;; --- EVENT: fetch-projects @@ -165,7 +165,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (:current-team-id state)] - (->> (rp/query :projects {:team-id team-id}) + (->> (rp/query! :projects {:team-id team-id}) (rx/map projects-fetched)))))) ;; --- EVENT: search @@ -193,7 +193,7 @@ (watch [_ state _] (let [team-id (:current-team-id state) params (assoc params :team-id team-id)] - (->> (rp/query :search-files params) + (->> (rp/query! :search-files params) (rx/map search-result-fetched)))))) ;; --- EVENT: files @@ -222,7 +222,7 @@ (ptk/reify ::fetch-files ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :project-files {:project-id project-id}) + (->> (rp/cmd! :get-project-files {:project-id project-id}) (rx/map #(files-fetched project-id %)))))) ;; --- EVENT: shared-files @@ -243,7 +243,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (:current-team-id state)] - (->> (rp/query :team-shared-files {:team-id team-id}) + (->> (rp/cmd! :get-team-shared-files {:team-id team-id}) (rx/map shared-files-fetched)))))) ;; --- EVENT: Get files that use this shared-file @@ -269,7 +269,8 @@ ptk/WatchEvent (watch [_ _ _] (->> (rx/from files) - (rx/mapcat (fn [file] (rp/query :library-using-files {:file-id (:id file)}))) + (rx/map :id) + (rx/mapcat #(rp/cmd! :get-library-file-references {:file-id %})) (rx/reduce into []) (rx/map library-using-files-fetched))))) @@ -292,7 +293,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (or team-id (:current-team-id state))] - (->> (rp/query :team-recent-files {:team-id team-id}) + (->> (rp/cmd! :get-team-recent-files {:team-id team-id}) (rx/map recent-files-fetched))))))) @@ -588,7 +589,7 @@ new-name (str name " " (tr "dashboard.copy-suffix"))] - (->> (rp/command! :duplicate-project {:project-id id :name new-name}) + (->> (rp/cmd! :duplicate-project {:project-id id :name new-name}) (rx/tap on-success) (rx/map project-duplicated) (rx/catch on-error)))))) @@ -608,7 +609,7 @@ :or {on-success identity on-error rx/throw}} (meta params)] - (->> (rp/command! :move-project {:project-id id :team-id team-id}) + (->> (rp/cmd! :move-project {:project-id id :team-id team-id}) (rx/tap on-success) (rx/catch on-error)))))) @@ -683,7 +684,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (uuid/uuid (get-in state [:route :path-params :team-id]))] - (->> (rp/mutation :delete-file {:id id}) + (->> (rp/cmd! :delete-file {:id id}) (rx/map #(file-deleted team-id project-id))))))) ;; --- Rename File @@ -706,7 +707,7 @@ ptk/WatchEvent (watch [_ _ _] (let [params (select-keys params [:id :name])] - (->> (rp/mutation :rename-file params) + (->> (rp/cmd! :rename-file params) (rx/ignore)))))) ;; --- Set File shared @@ -731,7 +732,7 @@ ptk/WatchEvent (watch [_ _ _] (let [params {:id id :is-shared is-shared}] - (->> (rp/mutation :set-file-shared params) + (->> (rp/cmd! :set-file-shared params) (rx/ignore)))))) ;; --- EVENT: create-file @@ -774,7 +775,7 @@ (assoc :name name) (assoc :features features))] - (->> (rp/mutation! :create-file params) + (->> (rp/cmd! :create-file params) (rx/tap on-success) (rx/map #(with-meta (file-created %) (meta it))) (rx/catch on-error)))))) @@ -794,7 +795,7 @@ new-name (str name " " (tr "dashboard.copy-suffix"))] - (->> (rp/command! :duplicate-file {:file-id id :name new-name}) + (->> (rp/cmd! :duplicate-file {:file-id id :name new-name}) (rx/tap on-success) (rx/map file-created) (rx/catch on-error)))))) @@ -816,7 +817,7 @@ (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] - (->> (rp/command! :move-files {:ids ids :project-id project-id}) + (->> (rp/cmd! :move-files {:ids ids :project-id project-id}) (rx/tap on-success) (rx/catch on-error)))))) @@ -836,8 +837,7 @@ (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] - - (->> (rp/command! :clone-template {:project-id project-id :template-id template-id}) + (->> (rp/cmd! :clone-template {:project-id project-id :template-id template-id}) (rx/tap on-success) (rx/catch on-error)))))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 6afd2e2138..4b744acc3b 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -357,7 +357,7 @@ ptk/WatchEvent (watch [_ _ _] (let [params {:id id :name name}] - (->> (rp/mutation :rename-file params) + (->> (rp/cmd! :rename-file params) (rx/ignore)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 2572ab354e..98a176d247 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -7,6 +7,7 @@ (ns app.main.data.workspace.libraries (:require [app.common.data :as d] + [app.common.files.features :as ffeat] [app.common.geom.point :as gpt] [app.common.logging :as log] [app.common.pages :as cp] @@ -19,7 +20,7 @@ [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] - [app.common.types.file.media-object :as ctfm] + [app.common.types.file.media-object :as ctfm] [app.common.types.pages-list :as ctpl] [app.common.types.shape-tree :as ctst] [app.common.types.typography :as ctt] @@ -359,7 +360,7 @@ (pcb/with-library-data data) (pcb/update-component id update-fn))] - (rx/of (dch/commit-changes changes)))))) + (rx/of (dch/commit-changes changes)))))) (defn duplicate-component "Create a new component copied from the one with the given id." @@ -428,7 +429,7 @@ component (ctf/get-deleted-component file-data component-id) page (ctpl/get-page file-data (:main-instance-page component)) - ; Make a new main instance, with the same id of the original + ; Make a new main instance, with the same id of the original [_main-instance shapes] (ctn/make-component-instance page component @@ -446,8 +447,8 @@ changes shapes) - ; restore-component change needs to be done after add main instance - ; because when undo changes, the orden is inverse + ; restore-component change needs to be done after add main instance + ; because when undo changes, the orden is inverse changes (pcb/restore-component changes component-id)] (rx/of (dch/commit-changes (assoc changes :file-id library-id))))))) @@ -508,12 +509,12 @@ (cph/clean-loops objects)) changes (reduce - (fn [changes id] - (dwlh/generate-detach-instance changes container id)) - (-> (pcb/empty-changes it) - (pcb/with-container container) - (pcb/with-objects objects)) - selected)] + (fn [changes id] + (dwlh/generate-detach-instance changes container id)) + (-> (pcb/empty-changes it) + (pcb/with-container container) + (pcb/with-objects objects)) + selected)] (rx/of (dch/commit-changes changes)))))) @@ -572,8 +573,8 @@ (log/debug :msg "RESET-COMPONENT finished" :js/rchanges (log-changes (:redo-changes changes) - file)) - (rx/of (dch/commit-changes changes)))))) + file)) + (rx/of (dch/commit-changes changes)))))) (defn update-component "Modify the component linked to the shape with the given id, in the @@ -624,11 +625,11 @@ (log/debug :msg "UPDATE-COMPONENT finished" :js/local-changes (log-changes - (:redo-changes local-changes) - file) + (:redo-changes local-changes) + file) :js/nonlocal-changes (log-changes - (:redo-changes nonlocal-changes) - file)) + (:redo-changes nonlocal-changes) + file)) (rx/of (when (seq (:redo-changes local-changes)) @@ -705,48 +706,48 @@ sync-typographies? (or (nil? asset-type) (= asset-type :typographies)) library-changes (reduce - pcb/concat-changes - (pcb/empty-changes it) - [(when sync-components? - (dwlh/generate-sync-library it file-id :components asset-id library-id state)) - (when sync-colors? - (dwlh/generate-sync-library it file-id :colors asset-id library-id state)) - (when sync-typographies? - (dwlh/generate-sync-library it file-id :typographies asset-id library-id state))]) + pcb/concat-changes + (pcb/empty-changes it) + [(when sync-components? + (dwlh/generate-sync-library it file-id :components asset-id library-id state)) + (when sync-colors? + (dwlh/generate-sync-library it file-id :colors asset-id library-id state)) + (when sync-typographies? + (dwlh/generate-sync-library it file-id :typographies asset-id library-id state))]) file-changes (reduce - pcb/concat-changes - (pcb/empty-changes it) - [(when sync-components? - (dwlh/generate-sync-file it file-id :components asset-id library-id state)) - (when sync-colors? - (dwlh/generate-sync-file it file-id :colors asset-id library-id state)) - (when sync-typographies? - (dwlh/generate-sync-file it file-id :typographies asset-id library-id state))]) + pcb/concat-changes + (pcb/empty-changes it) + [(when sync-components? + (dwlh/generate-sync-file it file-id :components asset-id library-id state)) + (when sync-colors? + (dwlh/generate-sync-file it file-id :colors asset-id library-id state)) + (when sync-typographies? + (dwlh/generate-sync-file it file-id :typographies asset-id library-id state))]) changes (pcb/concat-changes library-changes file-changes)] (log/debug :msg "SYNC-FILE finished" :js/rchanges (log-changes - (:redo-changes changes) - file)) + (:redo-changes changes) + file)) (rx/concat - (rx/of (dm/hide-tag :sync-dialog)) - (when (seq (:redo-changes changes)) - (rx/of (dch/commit-changes (assoc changes ;; TODO a ver qué pasa con esto - :file-id file-id)))) - (when (not= file-id library-id) - ;; When we have just updated the library file, give some time for the - ;; update to finish, before marking this file as synced. - ;; TODO: look for a more precise way of syncing this. - ;; Maybe by using the stream (second argument passed to watch) - ;; to wait for the corresponding changes-committed and then proceed - ;; with the :update-sync mutation. - (rx/concat (rx/timer 3000) - (rp/mutation :update-sync - {:file-id file-id - :library-id library-id}))) - (when (and (seq (:redo-changes library-changes)) - sync-components?) - (rx/of (sync-file-2nd-stage file-id library-id asset-id)))))))))) + (rx/of (dm/hide-tag :sync-dialog)) + (when (seq (:redo-changes changes)) + (rx/of (dch/commit-changes (assoc changes ;; TODO a ver qué pasa con esto + :file-id file-id)))) + (when (not= file-id library-id) + ;; When we have just updated the library file, give some time for the + ;; update to finish, before marking this file as synced. + ;; TODO: look for a more precise way of syncing this. + ;; Maybe by using the stream (second argument passed to watch) + ;; to wait for the corresponding changes-committed and then proceed + ;; with the :update-file-library-sync-status mutation. + (rx/concat (rx/timer 3000) + (rp/cmd! :update-file-library-sync-status + {:file-id file-id + :library-id library-id}))) + (when (and (seq (:redo-changes library-changes)) + sync-components?) + (rx/of (sync-file-2nd-stage file-id library-id asset-id)))))))))) (defn- sync-file-2nd-stage "If some components have been modified, we need to launch another synchronization @@ -775,8 +776,8 @@ (dwlh/generate-sync-library it file-id :components asset-id library-id state)])] (log/debug :msg "SYNC-FILE (2nd stage) finished" :js/rchanges (log-changes - (:redo-changes changes) - file)) + (:redo-changes changes) + file)) (when (seq (:redo-changes changes)) (rx/of (dch/commit-changes (assoc changes :file-id file-id)))))))) @@ -788,9 +789,9 @@ ptk/WatchEvent (watch [_ state _] - (rp/mutation :ignore-sync - {:file-id (get-in state [:workspace-file :id]) - :date (dt/now)})))) + (rp/cmd! :ignore-file-library-sync-status + {:file-id (get-in state [:workspace-file :id]) + :date (dt/now)})))) (defn notify-sync-file [file-id] @@ -852,8 +853,8 @@ (log/info :msg "DETECTED COMPONENTS CHANGED" :ids (map str components-changed)) (run! st/emit! - (map #(update-component-sync % (:id data)) - components-changed)))))] + (map #(update-component-sync % (:id data)) + components-changed)))))] (when components-v2 (->> change-s @@ -880,7 +881,7 @@ ptk/WatchEvent (watch [_ _ _] (let [params {:id id :is-shared is-shared}] - (->> (rp/mutation :set-file-shared params) + (->> (rp/cmd! :set-file-shared params) (rx/ignore)))))) (defn- shared-files-fetched @@ -898,7 +899,7 @@ (ptk/reify ::fetch-shared-files ptk/WatchEvent (watch [_ _ _] - (->> (rp/query :team-shared-files {:team-id team-id}) + (->> (rp/cmd! :get-team-shared-files {:team-id team-id}) (rx/map shared-files-fetched))))) ;; --- Link and unlink Files @@ -908,13 +909,16 @@ (ptk/reify ::attach-library ptk/WatchEvent (watch [_ state _] - (let [components-v2 (features/active-feature? state :components-v2) - fetched #(assoc-in %2 [:workspace-libraries (:id %1)] %1) - params {:file-id file-id - :library-id library-id}] - (->> (rp/mutation :link-file-to-library params) - (rx/mapcat #(rp/query :file {:id library-id :components-v2 components-v2})) - (rx/map #(partial fetched %))))))) + (let [features (cond-> ffeat/enabled + (features/active-feature? state :components-v2) + (conj "components/v2"))] + (rx/concat + (->> (rp/cmd! :link-file-to-library {:file-id file-id :library-id library-id}) + (rx/ignore)) + (->> (rp/cmd! :get-file {:id library-id :features features}) + (rx/map (fn [file] + (fn [state] + (assoc-in state [:workspace-libraries library-id] file)))))))))) (defn unlink-file-from-library [file-id library-id] @@ -927,5 +931,5 @@ (watch [_ _ _] (let [params {:file-id file-id :library-id library-id}] - (->> (rp/mutation :unlink-file-from-library params) + (->> (rp/cmd! :unlink-file-from-library params) (rx/ignore)))))) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 2d4ad1d258..cc148353ce 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.files.features :as ffeat] [app.common.logging :as log] [app.common.pages :as cp] [app.common.pages.changes-spec :as pcs] @@ -18,7 +19,6 @@ [app.config :as cf] [app.main.data.dashboard :as dd] [app.main.data.fonts :as df] - [app.main.data.modal :as modal] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] @@ -26,7 +26,6 @@ [app.main.repo :as rp] [app.main.store :as st] [app.util.http :as http] - [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.time :as dt] [beicon.core :as rx] @@ -138,7 +137,11 @@ (ptk/reify ::persist-changes ptk/WatchEvent (watch [_ state _] - (let [features (cond-> #{} + (let [;; this features set does not includes the ffeat/enabled + ;; because they are already available on the backend and + ;; this request provides a set of features to enable in + ;; this request. + features (cond-> #{} (features/active-feature? state :components-v2) (conj "components/v2")) sid (:session-id state) @@ -150,7 +153,7 @@ :features features}] (when (= file-id (:id params)) - (->> (rp/mutation :update-file params) + (->> (rp/cmd! :update-file params) (rx/mapcat (fn [lagged] (log/debug :hint "changes persisted" :lagged (count lagged)) (let [lagged (cond->> lagged @@ -285,14 +288,13 @@ ptk/WatchEvent (watch [_ state _] (let [share-id (-> state :viewer-local :share-id) - features (cond-> #{} + features (cond-> ffeat/enabled (features/active-feature? state :components-v2) (conj "components/v2"))] - - (->> (rx/zip (rp/query! :file-raw {:id file-id :features features}) + (->> (rx/zip (rp/cmd! :get-raw-file {:id file-id :features features}) (rp/query! :team-users {:file-id file-id}) (rp/query! :project {:id project-id}) - (rp/query! :file-libraries {:file-id file-id}) + (rp/cmd! :get-file-libraries {:file-id file-id}) (rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id})) (rx/take 1) (rx/map (fn [[file-raw users project libraries file-comments-users]] @@ -303,16 +305,7 @@ :file-comments-users file-comments-users})) (rx/mapcat (fn [{:keys [project] :as bundle}] (rx/of (ptk/data-event ::bundle-fetched bundle) - (df/load-team-fonts (:team-id project))))) - (rx/catch (fn [err] - (if (and (= (:type err) :restriction) - (= (:code err) :feature-disabled)) - (let [team-id (:current-team-id state)] - (rx/of (modal/show - {:type :alert - :message (tr "errors.components-v2") - :on-accept #(st/emit! (rt/nav :dashboard-projects {:team-id team-id}))}))) - (rx/throw err))))))))) + (df/load-team-fonts (:team-id project)))))))))) ;; --- Helpers diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 5f894f081e..ce478da9bd 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -81,7 +81,7 @@ (rx/merge ;; Update the local copy of the thumbnails so we don't need to request it again (rx/of #(assoc-in % [:workspace-file :thumbnails object-id] data)) - (->> (rp/mutation! :upsert-file-object-thumbnail params) + (->> (rp/cmd! :upsert-file-object-thumbnail params) (rx/ignore)))) (rx/empty)))))))))) diff --git a/frontend/src/app/main/errors.cljs b/frontend/src/app/main/errors.cljs index f0d724c0e6..fe9eb87444 100644 --- a/frontend/src/app/main/errors.cljs +++ b/frontend/src/app/main/errors.cljs @@ -14,6 +14,7 @@ [app.common.spec :as us] [app.config :as cf] [app.main.data.messages :as msg] + [app.main.data.modal :as modal] [app.main.data.users :as du] [app.main.store :as st] [app.util.globals :as glob] @@ -167,6 +168,26 @@ (ts/schedule #(st/emit! (rt/assign-exception error)))) +(defmethod ptk/handle-error :restriction + [{:keys [code] :as error}] + (cond + (= :feature-mismatch code) + (let [message (tr "errors.feature-mismatch" (:feature error))] + (st/emit! (modal/show + {:type :alert + :message message + :on-accept #(prn "kaka")}))) + + (= :features-not-supported code) + (let [message (tr "errors.feature-not-supported" (:feature error))] + (st/emit! (modal/show + {:type :alert + :message message + :on-accept #(prn "kaka")}))) + + :else + (ptk/handle-error (assoc error :type :server-error)))) + ;; This happens when the backed server fails to process the ;; request. This can be caused by an internal assertion or any other ;; uncontrolled error. diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index c8126335e8..04d77115b4 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -16,7 +16,7 @@ (log/set-level! :debug) -(def features-list #{:auto-layout :components-v2}) +(def available-features #{:auto-layout :components-v2}) (defn- toggle-feature [feature] @@ -38,14 +38,14 @@ (defn toggle-feature! [feature] - (assert (contains? features-list feature) "Not supported feature") + (assert (contains? available-features feature) "Not supported feature") (st/emit! (toggle-feature feature))) (defn active-feature? ([feature] (active-feature? @st/state feature)) ([state feature] - (assert (contains? features-list feature) "Not supported feature") + (assert (contains? available-features feature) "Not supported feature") (contains? (get state :features) feature))) (def features @@ -57,7 +57,7 @@ (defn use-feature [feature] - (assert (contains? features-list feature) "Not supported feature") + (assert (contains? available-features feature) "Not supported feature") (let [active-feature-ref (mf/use-memo (mf/deps feature) #(active-feature feature)) active-feature? (mf/deref active-feature-ref)] active-feature?)) @@ -69,6 +69,6 @@ (when *assert* ;; By default, all features disabled, except in development ;; environment, that are enabled except components-v2 - (doseq [f features-list] + (doseq [f available-features] (when (not= f :components-v2) (toggle-feature! f))))) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index d7dc4727a6..dcac66f926 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -12,6 +12,8 @@ [app.util.http :as http] [beicon.core :as rx])) +(derive :get-file ::query) + (defn handle-response [{:keys [status body] :as response}] (cond @@ -48,7 +50,6 @@ query api." ([id params] (send-query! id params nil)) - ([id params {:keys [raw-transit?]}] (let [decode-transit (if raw-transit? http/conditional-error-decode-transit @@ -74,14 +75,24 @@ (defn- send-command! "A simple helper for a common case of sending and receiving transit data to the penpot mutation api." - [id params {:keys [response-type form-data?]}] - (->> (http/send! {:method :post - :uri (u/join @cf/public-uri "api/rpc/command/" (name id)) - :credentials "include" - :body (if form-data? (http/form-data params) (http/transit-data params)) - :response-type (or response-type :text)}) - (rx/map http/conditional-decode-transit) - (rx/mapcat handle-response))) + [id params {:keys [response-type form-data? raw-transit?]}] + (let [decode-fn (if raw-transit? + http/conditional-error-decode-transit + http/conditional-decode-transit) + method (if (isa? id ::query) :get :post)] + + (->> (http/send! {:method method + :uri (u/join @cf/public-uri "api/rpc/command/" (name id)) + :credentials "include" + :body (when (= method :post) + (if form-data? + (http/form-data params) + (http/transit-data params))) + :query (when (= method :get) + params) + :response-type (or response-type :text)}) + (rx/map decode-fn) + (rx/mapcat handle-response)))) (defn- dispatch [& args] (first args)) @@ -93,9 +104,9 @@ [id params] (send-query! id params)) -(defmethod query :file-raw +(defmethod command :get-raw-file [_id params] - (send-query! :file params {:raw-transit? true})) + (send-command! :get-file params {:raw-transit? true})) (defmethod mutation :default [id params] diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index 0a5c23994f..79e9193f38 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.dashboard.grid (:require [app.common.data.macros :as dm] + [app.common.files.features :as ffeat] [app.common.logging :as log] [app.main.data.dashboard :as dd] [app.main.data.messages :as msg] @@ -34,18 +35,21 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) -(log/set-level! :info) +(log/set-level! :debug) ;; --- Grid Item Thumbnail (defn ask-for-thumbnail "Creates some hooks to handle the files thumbnails cache" [file] - (wrk/ask! {:cmd :thumbnails/generate - :revn (:revn file) - :file-id (:id file) - :file-name (:name file) - :components-v2 (features/active-feature? :components-v2)})) + (let [features (cond-> ffeat/enabled + (features/active-feature? :components-v2) + (conj "components/v2"))] + (wrk/ask! {:cmd :thumbnails/generate + :revn (:revn file) + :file-id (:id file) + :file-name (:name file) + :features features}))) (mf/defc grid-item-thumbnail {::mf/wrap [mf/memo]} @@ -61,10 +65,10 @@ (rx/subscribe-on :af) (rx/subs (fn [{:keys [data fonts] :as params}] (run! fonts/ensure-loaded! fonts) - (log/info :hint "loaded thumbnail" - :file-id (dm/str (:id file)) - :file-name (:name file) - :elapsed (str/ffmt "%ms" (tp))) + (log/debug :hint "loaded thumbnail" + :file-id (dm/str (:id file)) + :file-name (:name file) + :elapsed (str/ffmt "%ms" (tp))) (when-let [node (mf/ref-val container)] (dom/set-html! node data)))))))) diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs index a5660ff1d4..1276b4775a 100644 --- a/frontend/src/app/render.cljs +++ b/frontend/src/app/render.cljs @@ -98,22 +98,23 @@ [{:keys [page-id file-id object-id render-embed?]}] (let [components-v2 (features/use-feature :components-v2) fetch-state (mf/use-fn - (mf/deps file-id page-id object-id) + (mf/deps file-id page-id object-id components-v2) (fn [] - (->> (rx/zip - (repo/query! :font-variants {:file-id file-id}) - (repo/query! :page {:file-id file-id - :page-id page-id - :object-id object-id - :components-v2 components-v2})) - (rx/tap (fn [[fonts]] - (when (seq fonts) - (st/emit! (df/fonts-fetched fonts))))) - (rx/map (comp :objects second)) - (rx/map (fn [objects] - (let [objects (render/adapt-objects-for-shape objects object-id)] - {:objects objects - :object (get objects object-id)})))))) + (let [features (cond-> #{} components-v2 (conj "components/v2"))] + (->> (rx/zip + (repo/query! :font-variants {:file-id file-id}) + (repo/cmd! :page {:file-id file-id + :page-id page-id + :object-id object-id + :features features})) + (rx/tap (fn [[fonts]] + (when (seq fonts) + (st/emit! (df/fonts-fetched fonts))))) + (rx/map (comp :objects second)) + (rx/map (fn [objects] + (let [objects (render/adapt-objects-for-shape objects object-id)] + {:objects objects + :object (get objects object-id)}))))))) {:keys [objects object]} (use-resource fetch-state)] @@ -137,17 +138,18 @@ [{:keys [page-id file-id object-ids render-embed?]}] (let [components-v2 (features/use-feature :components-v2) fetch-state (mf/use-fn - (mf/deps file-id page-id) + (mf/deps file-id page-id components-v2) (fn [] - (->> (rx/zip - (repo/query! :font-variants {:file-id file-id}) - (repo/query! :page {:file-id file-id - :page-id page-id - :components-v2 components-v2})) - (rx/tap (fn [[fonts]] - (when (seq fonts) - (st/emit! (df/fonts-fetched fonts))))) - (rx/map (comp :objects second))))) + (let [features (cond-> #{} components-v2 (conj "components/v2"))] + (->> (rx/zip + (repo/query! :font-variants {:file-id file-id}) + (repo/cmd! :get-page {:file-id file-id + :page-id page-id + :features features})) + (rx/tap (fn [[fonts]] + (when (seq fonts) + (st/emit! (df/fonts-fetched fonts))))) + (rx/map (comp :objects second)))))) objects (use-resource fetch-state)] @@ -204,7 +206,7 @@ [{:keys [file-id embed] :as props}] (let [fetch (mf/use-fn (mf/deps file-id) - (fn [] (repo/query! :file {:id file-id}))) + (fn [] (repo/cmd! :get-file {:id file-id}))) file (use-resource fetch) state (mf/use-state nil)] diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index aed2f35c3f..fcf9f55c31 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -139,6 +139,7 @@ [{:keys [body headers] :as response}] (let [contenttype (get headers "content-type")] (if (and (str/starts-with? contenttype "application/transit+json") + (string? body) (pos? (count body))) (assoc response :body (t/decode-str body)) response))) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index ac3ecfac4b..c40a172b90 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -155,14 +155,15 @@ (->> (r/render-components (:data file) :deleted-components) (rx/map #(vector (str (:id file) "/deleted-components.svg") %)))) -(defn fetch-file-with-libraries [file-id components-v2] - (->> (rx/zip (rp/query :file {:id file-id :components-v2 components-v2}) - (rp/query :file-libraries {:file-id file-id})) - (rx/map - (fn [[file file-libraries]] - (let [libraries-ids (->> file-libraries (map :id) (filterv #(not= (:id file) %)))] - (-> file - (assoc :libraries libraries-ids))))))) +(defn fetch-file-with-libraries + [file-id components-v2] + (let [features (cond-> #{} components-v2 (conj "components/v2"))] + (->> (rx/zip (rp/cmd! :get-file {:id file-id :features features}) + (rp/cmd! :get-file-libraries {:file-id file-id})) + (rx/map + (fn [[file file-libraries]] + (let [libraries-ids (->> file-libraries (map :id) (filterv #(not= (:id file) %)))] + (assoc file :libraries libraries-ids))))))) (defn get-component-ref-file [objects shape] diff --git a/frontend/src/app/worker/impl.cljs b/frontend/src/app/worker/impl.cljs index 4cd27ac2c3..4e19add0e2 100644 --- a/frontend/src/app/worker/impl.cljs +++ b/frontend/src/app/worker/impl.cljs @@ -6,6 +6,7 @@ (ns app.worker.impl (:require + [app.common.data.macros :as dm] [app.common.logging :as log] [app.common.pages.changes :as ch] [app.common.transit :as t] @@ -36,18 +37,16 @@ (let [data (-> (t/decode-str file-raw) :data) message (assoc message :data data)] (reset! state data) - (handler (-> message - (assoc :cmd :selection/initialize-index))) - (handler (-> message - (assoc :cmd :snaps/initialize-index))))) + (handler (assoc message :cmd :selection/initialize-index)) + (handler (assoc message :cmd :snaps/initialize-index)))) (defmethod handler :update-page-indices [{:keys [page-id changes] :as message}] - (let [old-page (get-in @state [:pages-index page-id])] + (let [old-page (dm/get-in @state [:pages-index page-id])] (swap! state ch/process-changes changes false) - (let [new-page (get-in @state [:pages-index page-id]) + (let [new-page (dm/get-in @state [:pages-index page-id]) message (assoc message :old-page old-page :new-page new-page)] diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index f7610e0cd9..a58f4e0545 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -15,7 +15,6 @@ [app.common.logging :as log] [app.common.media :as cm] [app.common.text :as ct] - [app.common.types.file :as ctf] [app.common.uuid :as uuid] [app.main.repo :as rp] [app.util.http :as http] @@ -128,16 +127,15 @@ (defn create-file "Create a new file on the back-end" [context components-v2] - (let [resolve (:resolve context) - file-id (resolve (:file-id context))] - (rp/mutation :create-temp-file - {:id file-id - :name (:name context) - :is-shared (:shared context) - :project-id (:project-id context) - :data (-> ctf/empty-file-data - (assoc :id file-id) - (assoc-in [:options :components-v2] components-v2))}))) + (let [resolve-fn (:resolve context) + file-id (resolve-fn (:file-id context)) + features (cond-> #{} components-v2 (conj "components/v2"))] + (rp/cmd! :create-temp-file + {:id file-id + :name (:name context) + :is-shared (:shared context) + :project-id (:project-id context) + :features features}))) (defn link-file-libraries "Create a new file on the back-end" @@ -147,7 +145,7 @@ libraries (->> context :libraries (mapv resolve))] (->> (rx/from libraries) (rx/map #(hash-map :file-id file-id :library-id %)) - (rx/flat-map (partial rp/mutation :link-file-to-library))))) + (rx/flat-map (partial rp/cmd! :link-file-to-library))))) (defn send-changes "Creates batches of changes to be sent to the backend" @@ -165,17 +163,17 @@ (->> (rx/from (d/enumerate batches)) (rx/merge-map (fn [[i change-batch]] - (->> (rp/mutation :update-temp-file - {:id file-id - :session-id session-id - :revn i - :changes change-batch}) + (->> (rp/cmd! :update-temp-file + {:id file-id + :session-id session-id + :revn i + :changes change-batch}) (rx/tap #(do (swap! processed inc) (progress! context :upload-data @processed total)))))) (rx/map first) (rx/ignore)) - (->> (rp/mutation :persist-temp-file {:id file-id}) + (->> (rp/cmd! :persist-temp-file {:id file-id}) ;; We use merge to keep some information not stored in back-end (rx/map #(merge file %)))))) diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index 6b3c757c3b..4bb06f6c19 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -7,6 +7,7 @@ (ns app.worker.thumbnails (:require ["react-dom/server" :as rds] + [app.common.logging :as log] [app.common.uri :as u] [app.config :as cf] [app.main.fonts :as fonts] @@ -17,6 +18,9 @@ [debug :refer [debug?]] [rumext.v2 :as mf])) +(log/set-level! :trace) + + (defn- handle-response [{:keys [body status] :as response}] (cond @@ -48,30 +52,29 @@ (= :request-body-too-large code))) (defn- request-data-for-thumbnail - [file-id revn components-v2] - (let [path "api/rpc/query/file-data-for-thumbnail" - params {:file-id file-id - :revn revn - :strip-frames-with-thumbnails true - :components-v2 components-v2} - request {:method :get - :uri (u/join @cf/public-uri path) - :credentials "include" - :query params}] + [file-id revn features] + (let [path "api/rpc/command/get-file-data-for-thumbnail" + params {:file-id file-id + :revn revn + :strip-frames-with-thumbnails true + :features features} + request {:method :get + :uri (u/join @cf/public-uri path) + :credentials "include" + :query params}] (->> (http/send! request) (rx/map http/conditional-decode-transit) (rx/mapcat handle-response)))) (defn- request-thumbnail [file-id revn] - (let [path "api/rpc/query/file-thumbnail" + (let [path "api/rpc/command/get-file-thumbnail" params {:file-id file-id :revn revn} request {:method :get :uri (u/join @cf/public-uri path) :credentials "include" :query params}] - (->> (http/send! request) (rx/map http/conditional-decode-transit) (rx/mapcat handle-response)))) @@ -91,7 +94,7 @@ (defn- persist-thumbnail [{:keys [file-id data revn fonts]}] - (let [path "api/rpc/mutation/upsert-file-thumbnail" + (let [path "api/rpc/command/upsert-file-thumbnail" params {:file-id file-id :revn revn :props {:fonts fonts} @@ -108,19 +111,22 @@ (rx/map (constantly params))))) (defmethod impl/handler :thumbnails/generate - [{:keys [file-id revn components-v2] :as message}] + [{:keys [file-id revn features] :as message}] (letfn [(on-result [{:keys [data props]}] {:data data :fonts (:fonts props)}) (on-cache-miss [_] - (->> (request-data-for-thumbnail file-id revn components-v2) + (log/debug :hint "request-thumbnail" :file-id file-id :revn revn :cache "miss") + (->> (request-data-for-thumbnail file-id revn features) (rx/map render-thumbnail) (rx/mapcat persist-thumbnail)))] (if (debug? :disable-thumbnail-cache) - (->> (request-data-for-thumbnail file-id revn components-v2) + (->> (request-data-for-thumbnail file-id revn features) (rx/map render-thumbnail)) (->> (request-thumbnail file-id revn) + (rx/tap (fn [_] + (log/debug :hint "request-thumbnail" :file-id file-id :revn revn :cache "hit"))) (rx/catch not-found? on-cache-miss) (rx/map on-result))))) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 3da66fe2a1..058be9b707 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -710,9 +710,13 @@ msgstr "The fonts %s could not be loaded" msgid "errors.clipboard-not-implemented" msgstr "Your browser cannot do this operation" -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.components-v2" -msgstr "This file has already used with Components V2 enabled." +#: src/app/main/errors.cljs +msgid "errors.feature-not-supported" +msgstr "Feature '%s' is not supported." + +#: src/app/main/errors.cljs +msgid "errors.feature-mismatch" +msgstr "Looks like you are opening a file that has the feature '%s' enabled bug your penpot frontend does not supports it or has it disabled." #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" From 5d9606f4d0db5df8b5d6b18365475f5e922e2cf7 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 1 Nov 2022 09:55:15 +0100 Subject: [PATCH 172/682] :paperclip: Update .gitignore file --- .gitignore | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/.gitignore b/.gitignore index b7f152cd6b..37d2807264 100644 --- a/.gitignore +++ b/.gitignore @@ -1,55 +1,59 @@ *-init.clj *.jar -*.penpot *.orig +*.penpot .calva .clj-kondo .cpcache -.lein-deps-sum -.lein-failures -.lein-plugins/ -.lein-repl-history .lsp .nrepl-port .nyc_output .rebel_readline_history .repl +.shadow-cljs +/*.jpg +/*.md +/*.png +/*.sql +/*.txt +/*.yml +/*.zip /.clj-kondo/.cache /_dump -/backend/- +/backend/*.md +/backend/*.sql +/backend/*.txt /backend/assets/ +/backend/builtin-templates /backend/dist/ /backend/logs/ /backend/resources/public/assets /backend/resources/public/media /backend/target/ -/backend/builtin-templates /bundle* /cd.md /clj-profiler/ -/common/.shadow-cljs /common/coverage /common/target /deploy /docker/images/bundle* -/exporter/.shadow-cljs /exporter/target -/frontend/.shadow-cljs -/frontend/package-lock.json -/frontend/cypress/videos/*/ /frontend/cypress/fixtures/validuser.json +/frontend/cypress/videos/*/ +/frontend/cypress/videos/*/ /frontend/dist/ /frontend/npm-debug.log /frontend/out/ +/frontend/package-lock.json /frontend/resources/fonts/experiments /frontend/resources/public/* /frontend/target/ -/frontend/cypress/videos/*/ -/media +/other/ +/scripts/ /telemetry/ +/tmp/ /vendor/**/target /vendor/svgclean/bundle*.js /web clj-profiler/ -figwheel_server.log node_modules From bfccae237341decf2da9f2ea2e755c383f762888 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 2 Nov 2022 11:12:08 +0100 Subject: [PATCH 173/682] :fire: Remove unused namespace --- backend/src/app/util/transit.clj | 179 ------------------------------- 1 file changed, 179 deletions(-) delete mode 100644 backend/src/app/util/transit.clj diff --git a/backend/src/app/util/transit.clj b/backend/src/app/util/transit.clj deleted file mode 100644 index 5cb9c1865a..0000000000 --- a/backend/src/app/util/transit.clj +++ /dev/null @@ -1,179 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.util.transit - (:require - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] - [cognitect.transit :as t] - [linked.core :as lk]) - (:import - app.common.geom.matrix.Matrix - app.common.geom.point.Point - java.io.ByteArrayInputStream - java.io.ByteArrayOutputStream - java.io.File - java.time.Instant - java.time.OffsetDateTime - linked.set.LinkedSet)) - -;; --- Handlers - -(def ^:private file-write-handler - (t/write-handler - (constantly "file") - (fn [v] (str v)))) - -;; --- GEOM - -(def point-write-handler - (t/write-handler - (constantly "point") - (fn [v] (into {} v)))) - -(def point-read-handler - (t/read-handler gpt/map->Point)) - -(def matrix-write-handler - (t/write-handler - (constantly "matrix") - (fn [v] (into {} v)))) - -(def matrix-read-handler - (t/read-handler gmt/map->Matrix)) - -;; --- Ordered Set - -(def ordered-set-write-handler - (t/write-handler - (constantly "ordered-set") - (fn [v] (vec v)))) - -(def ordered-set-read-handler - (t/read-handler #(into (lk/set) %))) - - -;; --- TIME - -(def ^:private instant-read-handler - (t/read-handler - (fn [v] (-> (Long/parseLong v) - (Instant/ofEpochMilli))))) - -(def ^:private instant-write-handler - (t/write-handler - (constantly "m") - (fn [v] (str (.toEpochMilli ^Instant v))))) - -(def ^:private offset-datetime-write-handler - (t/write-handler - (constantly "m") - (fn [v] (str (.toEpochMilli (.toInstant ^OffsetDateTime v)))))) - -(def +read-handlers+ - {"matrix" matrix-read-handler - "ordered-set" ordered-set-read-handler - "point" point-read-handler - "m" instant-read-handler - "instant" instant-read-handler}) - -(def +write-handlers+ - {File file-write-handler - LinkedSet ordered-set-write-handler - Matrix matrix-write-handler - Point point-write-handler - Instant instant-write-handler - OffsetDateTime offset-datetime-write-handler}) - -;; --- Low-Level Api - -(defn reader - ([istream] - (reader istream nil)) - ([istream {:keys [type] :or {type :json}}] - (t/reader istream type {:handlers +read-handlers+}))) - -(defn read! - "Read value from streamed transit reader." - [reader] - (t/read reader)) - -(defn writer - ([ostream] - (writer ostream nil)) - ([ostream {:keys [type] :or {type :json}}] - (t/writer ostream type {:handlers +write-handlers+}))) - -(defn write! - [writer data] - (t/write writer data)) - -;; --- High-Level Api - -(declare str->bytes) -(declare bytes->str) - -(defn decode-stream - ([input] - (decode-stream input nil)) - ([input opts] - (read! (reader input opts)))) - -(defn decode - ([data] - (decode data nil)) - ([data opts] - (with-open [input (ByteArrayInputStream. ^bytes data)] - (decode-stream input opts)))) - -(defn encode-stream - ([data out] - (encode-stream data out nil)) - ([data out opts] - (let [w (writer out opts)] - (write! w data)))) - -(defn encode - ([data] - (encode data nil)) - ([data opts] - (with-open [out (ByteArrayOutputStream.)] - (encode-stream data out opts) - (.toByteArray out)))) - -(defn decode-str - [message] - (->> (str->bytes message) - (decode))) - -(defn encode-str - ([message] - (->> (encode message) - (bytes->str))) - ([message opts] - (->> (encode message opts) - (bytes->str)))) - -(defn encode-verbose-str - [message] - (->> (encode message {:type :json-verbose}) - (bytes->str))) - -;; --- Helpers - -(defn str->bytes - "Convert string to byte array." - ([^String s] - (str->bytes s "UTF-8")) - ([^String s, ^String encoding] - (.getBytes s encoding))) - -(defn bytes->str - "Convert byte array to String." - ([^bytes data] - (bytes->str data "UTF-8")) - ([^bytes data, ^String encoding] - (String. data encoding))) From 67b4d5a1c702e907f5436763ff0b903c9e4e654e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 2 Nov 2022 13:05:56 +0100 Subject: [PATCH 174/682] :bug: Fix issues when RPC returns nil values --- backend/src/app/rpc.clj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index e7e39fc34a..798b67dca3 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -33,10 +33,9 @@ (defn- handle-response-transformation [response request mdata] - (let [response (if (sv/wrapped? response) @response response)] - (if-let [transform-fn (::transform-response mdata)] - (p/do (transform-fn request response)) - (p/resolved response)))) + (if-let [transform-fn (::transform-response mdata)] + (p/do (transform-fn request response)) + (p/resolved response))) (defn- handle-before-comple-hook [response mdata] @@ -46,7 +45,8 @@ (defn- handle-response [request result] - (let [mdata (meta result)] + (let [mdata (meta result) + result (if (sv/wrapped? result) @result result)] (p/-> (yrs/response 200 result (::http/headers mdata {})) (handle-response-transformation request mdata) (handle-before-comple-hook mdata)))) From 1298956d9295aa8c44d72941fb4d7ff82a496698 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 2 Nov 2022 13:14:42 +0100 Subject: [PATCH 175/682] :sparkles: Improve srepl helpers for activate profile --- backend/src/app/srepl/main.clj | 37 +++++++++++----------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index 707242f237..224a623331 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -65,31 +65,18 @@ (cmd.auth/send-email-verification! pool sprops profile) :email-sent)) -(defn update-profile! - "Update a limited set of profile attrs." - [system & {:keys [email id active? deleted? blocked?]}] - - (us/verify! - :expr (some? system) - :hint "system should be provided") - - (us/verify! - :expr (or (string? email) (uuid? id)) - :hint "email or id should be provided") - - (let [params (cond-> {} - (true? active?) (assoc :is-active true) - (false? active?) (assoc :is-active false) - (true? deleted?) (assoc :deleted-at (dt/now)) - (true? blocked?) (assoc :is-blocked true) - (false? blocked?) (assoc :is-blocked false)) - opts (cond-> {} - (some? email) (assoc :email (str/lower email)) - (some? id) (assoc :id id))] - - (db/with-atomic [conn (:app.db/pool system)] - (some-> (db/update! conn :profile params opts) - (profile/decode-profile-row))))) +(defn mark-profile-as-active! + "Mark the profile blocked and removes all the http sessiones + associated with the profile-id." + [system email] + (db/with-atomic [conn (:app.db/pool system)] + (when-let [profile (db/get-by-params conn :profile + {:email (str/lower email)} + {:columns [:id :email] + :check-not-found false})] + (when-not (:is-blocked profile) + (db/update! conn :profile {:is-active true} {:id (:id profile)}) + :activated)))) (defn mark-profile-as-blocked! "Mark the profile blocked and removes all the http sessiones From fa93e5a1a7a2b06cc7a9d5585b4a70c4d08ed342 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 3 Nov 2022 10:35:19 +0100 Subject: [PATCH 176/682] :recycle: Refactor backend tests directory tree --- .circleci/config.yml | 59 ++++++++---------- backend/dev/user.clj | 2 +- .../test_bounce_handling.clj} | 4 +- .../test_email_sending.clj} | 8 +-- .../{app => app_tests}/test_files/font-1.otf | Bin .../{app => app_tests}/test_files/font-1.ttf | Bin .../{app => app_tests}/test_files/font-1.woff | Bin .../{app => app_tests}/test_files/font-2.otf | Bin .../{app => app_tests}/test_files/font-2.woff | Bin .../{app => app_tests}/test_files/sample.jpg | Bin .../{app => app_tests}/test_files/sample1.svg | 0 .../{app => app_tests}/test_files/sample2.svg | 0 .../test_files/template.penpot | Bin .../test/{app => app_tests}/test_helpers.clj | 4 +- .../test_rpc_file.clj} | 6 +- .../test_rpc_font.clj} | 8 +-- .../test_rpc_management.clj} | 8 +-- .../test_rpc_media.clj} | 8 +-- .../test_rpc_profile.clj} | 6 +- .../test_rpc_project.clj} | 4 +- .../test_rpc_team.clj} | 4 +- .../test_rpc_viewer.clj} | 4 +- .../test_storage.clj} | 12 ++-- .../test_telemetry_task.clj} | 4 +- 24 files changed, 68 insertions(+), 73 deletions(-) rename backend/test/{app/bounce_handling_test.clj => app_tests/test_bounce_handling.clj} (99%) rename backend/test/{app/emails_test.clj => app_tests/test_email_sending.clj} (88%) rename backend/test/{app => app_tests}/test_files/font-1.otf (100%) rename backend/test/{app => app_tests}/test_files/font-1.ttf (100%) rename backend/test/{app => app_tests}/test_files/font-1.woff (100%) rename backend/test/{app => app_tests}/test_files/font-2.otf (100%) rename backend/test/{app => app_tests}/test_files/font-2.woff (100%) rename backend/test/{app => app_tests}/test_files/sample.jpg (100%) rename backend/test/{app => app_tests}/test_files/sample1.svg (100%) rename backend/test/{app => app_tests}/test_files/sample2.svg (100%) rename backend/test/{app => app_tests}/test_files/template.penpot (100%) rename backend/test/{app => app_tests}/test_helpers.clj (99%) rename backend/test/{app/services_files_test.clj => app_tests/test_rpc_file.clj} (99%) rename backend/test/{app/services_fonts_test.clj => app_tests/test_rpc_font.clj} (92%) rename backend/test/{app/services_management_test.clj => app_tests/test_rpc_management.clj} (99%) rename backend/test/{app/services_media_test.clj => app_tests/test_rpc_media.clj} (96%) rename backend/test/{app/services_profile_test.clj => app_tests/test_rpc_profile.clj} (99%) rename backend/test/{app/services_projects_test.clj => app_tests/test_rpc_project.clj} (99%) rename backend/test/{app/services_teams_test.clj => app_tests/test_rpc_team.clj} (99%) rename backend/test/{app/services_viewer_test.clj => app_tests/test_rpc_viewer.clj} (98%) rename backend/test/{app/storage_test.clj => app_tests/test_storage.clj} (97%) rename backend/test/{app/tasks_telemetry_test.clj => app_tests/test_telemetry_task.clj} (96%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 41160d33b1..f044e17803 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,19 +3,19 @@ jobs: build: docker: - image: penpotapp/devenv:latest - - image: cimg/postgres:13.5 + - image: cimg/postgres:14.5 environment: POSTGRES_USER: penpot_test POSTGRES_PASSWORD: penpot_test POSTGRES_DB: penpot_test - - image: cimg/redis:6.2.6 + - image: cimg/redis:7.0.5 working_directory: ~/repo resource_class: large environment: # Customize the JVM maximum heap limit - JVM_OPTS: -Xmx1g + JVM_OPTS: -Xmx4g steps: - checkout @@ -29,6 +29,13 @@ jobs: - run: cd .clj-kondo && cat config.edn + - run: + name: frontend styles prettier + working_directory: "./frontend" + command: | + yarn install + yarn run lint-scss + - run: name: common lint working_directory: "./common" @@ -43,13 +50,6 @@ jobs: clj-kondo --version clj-kondo --parallel --lint src/ - - run: - name: frontend styles prettier - working_directory: "./frontend" - command: | - yarn install - yarn run lint-scss - - run: name: backend lint working_directory: "./backend" @@ -57,16 +57,31 @@ jobs: clj-kondo --version clj-kondo --parallel --lint src/ - # run backend test + - run: + working_directory: "./common" + name: common tests + command: | + yarn install + yarn run compile-test + node target/test.js + clojure -X:dev:test :patterns '["common-tests.test-.*"]' + + environment: + PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin + JVM_OPTS: -Xmx4g + - run: name: backend test working_directory: "./backend" - command: "clojure -X:dev:test" + command: | + clojure -X:dev:test :patterns '["app-tests.test-.*"]' + environment: PENPOT_TEST_DATABASE_URI: "postgresql://localhost/penpot_test" PENPOT_TEST_DATABASE_USERNAME: penpot_test PENPOT_TEST_DATABASE_PASSWORD: penpot_test PENPOT_TEST_REDIS_URI: "redis://localhost/1" + JVM_OPTS: -Xmx4g - run: name: frontend tests @@ -79,26 +94,6 @@ jobs: environment: PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin - - run: - working_directory: "./common" - name: common tests (cljs) - command: | - yarn install - yarn run compile-test - node target/test.js - - environment: - PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin - - - run: - working_directory: "./common" - name: common tests (clj) - command: | - clojure -X:dev:test :patterns '["common-tests.test-.*"]' - - environment: - PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin - - save_cache: paths: - ~/.m2 diff --git a/backend/dev/user.clj b/backend/dev/user.clj index 53a10faaa7..bf41b1dc6c 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -63,7 +63,7 @@ ;; --- Development Stuff (defn- run-tests - ([] (run-tests #"^app.*-test$")) + ([] (run-tests #"^app-tests.test-.*$")) ([o] (repl/refresh) (cond diff --git a/backend/test/app/bounce_handling_test.clj b/backend/test/app_tests/test_bounce_handling.clj similarity index 99% rename from backend/test/app/bounce_handling_test.clj rename to backend/test/app_tests/test_bounce_handling.clj index 2e22c1fa80..dacea8e0cc 100644 --- a/backend/test/app/bounce_handling_test.clj +++ b/backend/test/app_tests/test_bounce_handling.clj @@ -4,12 +4,12 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.bounce-handling-test +(ns app-tests.test-bounce-handling (:require + [app-tests.test-helpers :as th] [app.db :as db] [app.emails :as emails] [app.http.awsns :as awsns] - [app.test-helpers :as th] [app.tokens :as tokens] [app.util.time :as dt] [clojure.pprint :refer [pprint]] diff --git a/backend/test/app/emails_test.clj b/backend/test/app_tests/test_email_sending.clj similarity index 88% rename from backend/test/app/emails_test.clj rename to backend/test/app_tests/test_email_sending.clj index d429065aad..b91cf5780a 100644 --- a/backend/test/app/emails_test.clj +++ b/backend/test/app_tests/test_email_sending.clj @@ -4,13 +4,13 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.emails-test +(ns app-tests.test-email-sending (:require - [clojure.test :as t] - [promesa.core :as p] + [app-tests.test-helpers :as th] [app.db :as db] [app.emails :as emails] - [app.test-helpers :as th])) + [clojure.test :as t] + [promesa.core :as p])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) diff --git a/backend/test/app/test_files/font-1.otf b/backend/test/app_tests/test_files/font-1.otf similarity index 100% rename from backend/test/app/test_files/font-1.otf rename to backend/test/app_tests/test_files/font-1.otf diff --git a/backend/test/app/test_files/font-1.ttf b/backend/test/app_tests/test_files/font-1.ttf similarity index 100% rename from backend/test/app/test_files/font-1.ttf rename to backend/test/app_tests/test_files/font-1.ttf diff --git a/backend/test/app/test_files/font-1.woff b/backend/test/app_tests/test_files/font-1.woff similarity index 100% rename from backend/test/app/test_files/font-1.woff rename to backend/test/app_tests/test_files/font-1.woff diff --git a/backend/test/app/test_files/font-2.otf b/backend/test/app_tests/test_files/font-2.otf similarity index 100% rename from backend/test/app/test_files/font-2.otf rename to backend/test/app_tests/test_files/font-2.otf diff --git a/backend/test/app/test_files/font-2.woff b/backend/test/app_tests/test_files/font-2.woff similarity index 100% rename from backend/test/app/test_files/font-2.woff rename to backend/test/app_tests/test_files/font-2.woff diff --git a/backend/test/app/test_files/sample.jpg b/backend/test/app_tests/test_files/sample.jpg similarity index 100% rename from backend/test/app/test_files/sample.jpg rename to backend/test/app_tests/test_files/sample.jpg diff --git a/backend/test/app/test_files/sample1.svg b/backend/test/app_tests/test_files/sample1.svg similarity index 100% rename from backend/test/app/test_files/sample1.svg rename to backend/test/app_tests/test_files/sample1.svg diff --git a/backend/test/app/test_files/sample2.svg b/backend/test/app_tests/test_files/sample2.svg similarity index 100% rename from backend/test/app/test_files/sample2.svg rename to backend/test/app_tests/test_files/sample2.svg diff --git a/backend/test/app/test_files/template.penpot b/backend/test/app_tests/test_files/template.penpot similarity index 100% rename from backend/test/app/test_files/template.penpot rename to backend/test/app_tests/test_files/template.penpot diff --git a/backend/test/app/test_helpers.clj b/backend/test/app_tests/test_helpers.clj similarity index 99% rename from backend/test/app/test_helpers.clj rename to backend/test/app_tests/test_helpers.clj index 0d9c3ddf53..e5837ebc14 100644 --- a/backend/test/app/test_helpers.clj +++ b/backend/test/app_tests/test_helpers.clj @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.test-helpers +(ns app-tests.test-helpers (:require [app.common.data :as d] [app.common.flags :as flags] @@ -65,7 +65,7 @@ :name "test" :file-uri "test" :thumbnail-uri "test" - :path (-> "app/test_files/template.penpot" io/resource fs/path)}] + :path (-> "app_tests/test_files/template.penpot" io/resource fs/path)}] system (-> (merge main/system-config main/worker-config) (assoc-in [:app.redis/redis :uri] (:redis-uri config)) (assoc-in [:app.db/pool :uri] (:database-uri config)) diff --git a/backend/test/app/services_files_test.clj b/backend/test/app_tests/test_rpc_file.clj similarity index 99% rename from backend/test/app/services_files_test.clj rename to backend/test/app_tests/test_rpc_file.clj index 913ddba5e9..cdaf561713 100644 --- a/backend/test/app/services_files_test.clj +++ b/backend/test/app_tests/test_rpc_file.clj @@ -4,14 +4,14 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.services-files-test +(ns app-tests.test-rpc-file (:require + [app-tests.test-helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.db.sql :as sql] [app.http :as http] [app.storage :as sto] - [app.test-helpers :as th] [app.util.time :as dt] [clojure.test :as t] [datoteka.core :as fs])) @@ -124,7 +124,7 @@ (t/deftest file-gc-task (letfn [(create-file-media-object [{:keys [profile-id file-id]}] (let [mfile {:filename "sample.jpg" - :path (th/tempfile "app/test_files/sample.jpg") + :path (th/tempfile "app_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} params {::th/type :upload-file-media-object diff --git a/backend/test/app/services_fonts_test.clj b/backend/test/app_tests/test_rpc_font.clj similarity index 92% rename from backend/test/app/services_fonts_test.clj rename to backend/test/app_tests/test_rpc_font.clj index 36c6112cf8..40f9b80853 100644 --- a/backend/test/app/services_fonts_test.clj +++ b/backend/test/app_tests/test_rpc_font.clj @@ -4,13 +4,13 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.services-fonts-test +(ns app-tests.test-rpc-font (:require + [app-tests.test-helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] [app.storage :as sto] - [app.test-helpers :as th] [clojure.test :as t] [datoteka.fs :as fs] [datoteka.io :as io])) @@ -24,7 +24,7 @@ proj-id (:default-project-id prof) font-id (uuid/custom 10 1) - ttfdata (-> (io/resource "app/test_files/font-1.ttf") + ttfdata (-> (io/resource "app_tests/test_files/font-1.ttf") io/input-stream io/read-as-bytes) @@ -59,7 +59,7 @@ proj-id (:default-project-id prof) font-id (uuid/custom 10 1) - data (-> (io/resource "app/test_files/font-1.woff") + data (-> (io/resource "app_tests/test_files/font-1.woff") io/input-stream io/read-as-bytes) diff --git a/backend/test/app/services_management_test.clj b/backend/test/app_tests/test_rpc_management.clj similarity index 99% rename from backend/test/app/services_management_test.clj rename to backend/test/app_tests/test_rpc_management.clj index 79a08d4ecf..194508b5e8 100644 --- a/backend/test/app/services_management_test.clj +++ b/backend/test/app_tests/test_rpc_management.clj @@ -4,16 +4,16 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.services-management-test +(ns app-tests.test-rpc-management (:require + [app-tests.test-storage :refer [configure-storage-backend]] + [app-tests.test-helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] [app.storage :as sto] - [app.test-helpers :as th] - [app.storage-test :refer [configure-storage-backend]] - [clojure.test :as t] [buddy.core.bytes :as b] + [clojure.test :as t] [datoteka.core :as fs])) (t/use-fixtures :once th/state-init) diff --git a/backend/test/app/services_media_test.clj b/backend/test/app_tests/test_rpc_media.clj similarity index 96% rename from backend/test/app/services_media_test.clj rename to backend/test/app_tests/test_rpc_media.clj index 8e638f490d..e36159713d 100644 --- a/backend/test/app/services_media_test.clj +++ b/backend/test/app_tests/test_rpc_media.clj @@ -4,12 +4,12 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.services-media-test +(ns app-tests.test-rpc-media (:require + [app-tests.test-helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.storage :as sto] - [app.test-helpers :as th] [clojure.test :as t] [datoteka.core :as fs])) @@ -60,7 +60,7 @@ :project-id (:default-project-id prof) :is-shared false}) mfile {:filename "sample.jpg" - :path (th/tempfile "app/test_files/sample.jpg") + :path (th/tempfile "app_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} @@ -99,7 +99,7 @@ :project-id (:default-project-id prof) :is-shared false}) mfile {:filename "sample.jpg" - :path (th/tempfile "app/test_files/sample.jpg") + :path (th/tempfile "app_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} diff --git a/backend/test/app/services_profile_test.clj b/backend/test/app_tests/test_rpc_profile.clj similarity index 99% rename from backend/test/app/services_profile_test.clj rename to backend/test/app_tests/test_rpc_profile.clj index 884707598a..574d777458 100644 --- a/backend/test/app/services_profile_test.clj +++ b/backend/test/app_tests/test_rpc_profile.clj @@ -4,14 +4,14 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.services-profile-test +(ns app-tests.test-rpc-profile (:require + [app-tests.test-helpers :as th] [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] [app.rpc.commands.auth :as cauth] [app.rpc.mutations.profile :as profile] - [app.test-helpers :as th] [app.tokens :as tokens] [app.util.time :as dt] [clojure.java.io :as io] @@ -110,7 +110,7 @@ :profile-id (:id profile) :file {:filename "sample.jpg" :size 123123 - :path (th/tempfile "app/test_files/sample.jpg") + :path (th/tempfile "app_tests/test_files/sample.jpg") :mtype "image/jpeg"}} out (th/mutation! data)] diff --git a/backend/test/app/services_projects_test.clj b/backend/test/app_tests/test_rpc_project.clj similarity index 99% rename from backend/test/app/services_projects_test.clj rename to backend/test/app_tests/test_rpc_project.clj index 4507f4f656..dd71e9fb9a 100644 --- a/backend/test/app/services_projects_test.clj +++ b/backend/test/app_tests/test_rpc_project.clj @@ -4,12 +4,12 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.services-projects-test +(ns app-tests.test-rpc-project (:require + [app-tests.test-helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] - [app.test-helpers :as th] [app.util.time :as dt] [clojure.test :as t])) diff --git a/backend/test/app/services_teams_test.clj b/backend/test/app_tests/test_rpc_team.clj similarity index 99% rename from backend/test/app/services_teams_test.clj rename to backend/test/app_tests/test_rpc_team.clj index 650fda6b69..930269817b 100644 --- a/backend/test/app/services_teams_test.clj +++ b/backend/test/app_tests/test_rpc_team.clj @@ -4,13 +4,13 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.services-teams-test +(ns app-tests.test-rpc-team (:require + [app-tests.test-helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] [app.storage :as sto] - [app.test-helpers :as th] [app.tokens :as tokens] [app.util.time :as dt] [clojure.test :as t] diff --git a/backend/test/app/services_viewer_test.clj b/backend/test/app_tests/test_rpc_viewer.clj similarity index 98% rename from backend/test/app/services_viewer_test.clj rename to backend/test/app_tests/test_rpc_viewer.clj index 688e09597b..5e8ef519ad 100644 --- a/backend/test/app/services_viewer_test.clj +++ b/backend/test/app_tests/test_rpc_viewer.clj @@ -4,11 +4,11 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.services-viewer-test +(ns app-tests.test-rpc-viewer (:require + [app-tests.test-helpers :as th] [app.common.uuid :as uuid] [app.db :as db] - [app.test-helpers :as th] [clojure.test :as t] [datoteka.core :as fs])) diff --git a/backend/test/app/storage_test.clj b/backend/test/app_tests/test_storage.clj similarity index 97% rename from backend/test/app/storage_test.clj rename to backend/test/app_tests/test_storage.clj index b619cbda3f..3821672da1 100644 --- a/backend/test/app/storage_test.clj +++ b/backend/test/app_tests/test_storage.clj @@ -4,13 +4,13 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.storage-test +(ns app-tests.test-storage (:require + [app-tests.test-helpers :as th] [app.common.exceptions :as ex] [app.common.uuid :as uuid] [app.db :as db] [app.storage :as sto] - [app.test-helpers :as th] [app.util.time :as dt] [clojure.test :as t] [cuerdas.core :as str] @@ -126,7 +126,7 @@ :is-shared false}) mfile {:filename "sample.jpg" - :path (th/tempfile "app/test_files/sample.jpg") + :path (th/tempfile "app_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} @@ -196,12 +196,12 @@ :project-id proj-id :is-shared false}) - ttfdata (-> (io/resource "app/test_files/font-1.ttf") + ttfdata (-> (io/resource "app_tests/test_files/font-1.ttf") io/input-stream io/read-as-bytes) mfile {:filename "sample.jpg" - :path (th/tempfile "app/test_files/sample.jpg") + :path (th/tempfile "app_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} @@ -267,7 +267,7 @@ :project-id (:default-project-id prof) :is-shared false}) mfile {:filename "sample.jpg" - :path (th/tempfile "app/test_files/sample.jpg") + :path (th/tempfile "app_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} diff --git a/backend/test/app/tasks_telemetry_test.clj b/backend/test/app_tests/test_telemetry_task.clj similarity index 96% rename from backend/test/app/tasks_telemetry_test.clj rename to backend/test/app_tests/test_telemetry_task.clj index 89f4007d5d..9a233e1633 100644 --- a/backend/test/app/tasks_telemetry_test.clj +++ b/backend/test/app_tests/test_telemetry_task.clj @@ -4,11 +4,11 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.tasks-telemetry-test +(ns app-tests.test-telemetry-task (:require + [app-tests.test-helpers :as th] [app.db :as db] [app.emails :as emails] - [app.test-helpers :as th] [app.util.time :as dt] [clojure.pprint :refer [pprint]] [clojure.test :as t] From 16afa90b9c61b87ebb1136a21b03b7db827c33f7 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 3 Nov 2022 14:22:18 +0100 Subject: [PATCH 177/682] :sparkles: Improve internal impl of objects-map --- backend/src/app/util/objects_map.clj | 215 ++++++++++++--------------- 1 file changed, 97 insertions(+), 118 deletions(-) diff --git a/backend/src/app/util/objects_map.clj b/backend/src/app/util/objects_map.clj index 31889d158e..e8c794d934 100644 --- a/backend/src/app/util/objects_map.clj +++ b/backend/src/app/util/objects_map.clj @@ -34,9 +34,10 @@ (declare create) (defprotocol IObjectsMap - (-initialize! [_]) - (-compact! [_]) - (-get-byte-array [_]) + (load! [_]) + (modified? [_]) + (compact! [_]) + (clone [_]) (-get-key-hash [_ key]) (-force-modified! [_])) @@ -70,7 +71,7 @@ ^:unsynchronized-mutable blob ^:unsynchronized-mutable header ^:unsynchronized-mutable content - ^:unsynchronized-mutable initialized? + ^:unsynchronized-mutable loaded? ^:unsynchronized-mutable modified?] IHashEq @@ -84,47 +85,54 @@ (.hasheq ^IHashEq this)) IObjectsMap - (-initialize! [_] - (when-not initialized? - ;; (l/trace :fn "-initialize!" :blob blob ::l/async false) - (let [hsize (.getInt ^ByteBuffer blob 0) - header' (.slice ^ByteBuffer blob 4 hsize) - content' (.slice ^ByteBuffer blob - (int (+ 4 hsize)) - (int (- (.remaining ^ByteBuffer blob) - (+ 4 hsize)))) + (modified? [_] modified?) - nitems (long (/ (.remaining ^ByteBuffer header') RECORD-SIZE)) - positions' (reduce (fn [positions i] - (let [hb (.slice ^ByteBuffer header' - (int (* i RECORD-SIZE)) - (int RECORD-SIZE)) - msb (.getLong ^ByteBuffer hb) - lsb (.getLong ^ByteBuffer hb) - size (.getInt ^ByteBuffer hb) - pos (.getInt ^ByteBuffer hb) - key (uuid/custom msb lsb) - val [size pos]] - (assoc! positions key val))) - (transient {}) - (range nitems))] - (set! positions (persistent! positions')) - (if *lazy* - (set! cache {}) - (loop [cache' (transient {}) - entries (seq positions)] - (if-let [[key [size pos]] (first entries)] - (let [tmp (byte-array (- size 4))] - (.get ^ByteBuffer content' (int (+ pos 4)) ^bytes tmp (int 0) (int (- size 4))) - ;; (l/trace :fn "-initialize!" :step "preload" :key key :size size :pos pos ::l/async false) - (recur (assoc! cache' key (fres/decode tmp)) - (rest entries))) + (load! [this] + (let [hsize (.getInt ^ByteBuffer blob 0) + header' (.slice ^ByteBuffer blob 4 hsize) + content' (.slice ^ByteBuffer blob + (int (+ 4 hsize)) + (int (- (.remaining ^ByteBuffer blob) + (+ 4 hsize)))) - (set! cache (persistent! cache'))))) + nitems (long (/ (.remaining ^ByteBuffer header') RECORD-SIZE)) + positions' (reduce (fn [positions i] + (let [hb (.slice ^ByteBuffer header' + (int (* i RECORD-SIZE)) + (int RECORD-SIZE)) + msb (.getLong ^ByteBuffer hb) + lsb (.getLong ^ByteBuffer hb) + size (.getInt ^ByteBuffer hb) + pos (.getInt ^ByteBuffer hb) + key (uuid/custom msb lsb) + val [size pos]] + (assoc! positions key val))) + (transient {}) + (range nitems))] + (set! positions (persistent! positions')) + (if *lazy* + (set! cache {}) + (loop [cache' (transient {}) + entries (seq positions)] + (if-let [[key [size pos]] (first entries)] + (let [tmp (byte-array (- size 4))] + (.get ^ByteBuffer content' (int (+ pos 4)) ^bytes tmp (int 0) (int (- size 4))) + (recur (assoc! cache' key (fres/decode tmp)) + (rest entries))) - (set! header header') - (set! content content') - (set! initialized? true)))) + (set! cache (persistent! cache'))))) + + (set! header header') + (set! content content') + (set! loaded? true)) + this) + + (-get-key-hash [this key] + (when-not loaded? (load! this)) + (if (contains? cache key) + (c/hash (get cache key)) + (let [[_ pos] (get positions key)] + (.getInt ^ByteBuffer content (int pos))))) (-force-modified! [this] (set! modified? true) @@ -133,7 +141,7 @@ (set! positions (assoc positions key nil)) (set! cache (assoc cache key val))))) - (-compact! [_] + (compact! [this] (when modified? (let [[total-items total-size new-items new-hashes] (loop [entries (seq positions) @@ -181,8 +189,6 @@ hval (get new-hashes key) size (+ (alength ^bytes bval) 4)] - ;; (l/trace :fn "-compact!" :cache "miss" :key key :size size :pos position ::l/async false) - (.putInt ^ByteBuffer rbuf (int size)) (.putInt ^ByteBuffer rbuf (int position)) (.rewind ^ByteBuffer rbuf) @@ -199,7 +205,6 @@ (.putInt ^ByteBuffer rbuf (int position)) (.rewind ^ByteBuffer rbuf) - ;; (l/trace :fn "-compact!" :cache "hit" :key key :size size :pos position ::l/async false) (.put ^ByteBuffer header' ^ByteBuffer rbuf) (.put ^ByteBuffer content' ^ByteBuffer cbuf) (recur (long (+ position size)) @@ -212,112 +217,82 @@ (.rewind ^ByteBuffer content') (.rewind ^ByteBuffer blob') - ;; (l/trace :fn "-compact!" :step "end" ::l/async false) - (set! positions positions') (set! modified? false) (set! blob blob') (set! header header') - (set! content content')))) + (set! content content'))) + this) - (-get-byte-array [this] - ;; (l/trace :fn "-get-byte-array" :this (.getHashCode this) :blob blob ::l/async false) - (-compact! this) - (.array ^ByteBuffer blob)) - - (-get-key-hash [this key] - (-initialize! this) - (if (contains? cache key) - (c/hash (get cache key)) - (let [[_ pos] (get positions key)] - (.getInt ^ByteBuffer content (int pos))))) + (clone [_] + (if loaded? + (ObjectsMap. metadata hash positions cache blob header content loaded? modified?) + (ObjectsMap. metadata nil nil nil blob nil nil false false))) clojure.lang.IDeref - (deref [_] - {:positions positions - :cache cache - :blob blob - :header header - :content content - :initialized? initialized? - :modified? modified?}) - - Cloneable - (clone [_] - (if initialized? - (ObjectsMap. metadata hash positions cache blob header content initialized? modified?) - (ObjectsMap. metadata nil nil nil blob nil nil false false))) + (deref [this] + (compact! this) + (.array ^ByteBuffer blob)) IObj (meta [_] metadata) - (withMeta [this meta] - (set! metadata meta) - this) + (withMeta [_ metadata] + (ObjectsMap. metadata hash positions cache blob header content loaded? modified?)) Seqable (seq [this] - (-initialize! this) + (when-not loaded? (load! this)) (RT/chunkIteratorSeq (.iterator ^Iterable this))) IPersistentCollection - (equiv [_ _] - (throw (UnsupportedOperationException. "not implemented"))) + (equiv [this other] + (identical? this other)) IPersistentMap (cons [this o] - (-initialize! this) + (when-not loaded? (load! this)) (if (map-entry? o) - (do - ;; (l/trace :fn "cons" :key (key o)) - (assoc this (key o) (val o))) + (assoc this (key o) (val o)) (if (vector? o) - (do - ;; (l/trace :fn "cons" :key (nth o 0)) - (assoc this (nth o 0) (nth o 1))) + (assoc this (nth o 0) (nth o 1)) (throw (UnsupportedOperationException. "invalid arguments to cons"))))) (empty [_] (create)) (containsKey [this key] - (-initialize! this) + (when-not loaded? (load! this)) (contains? positions key)) (entryAt [this key] - (-initialize! this) + (when-not loaded? (load! this)) (ObjectsMapEntry. this key)) (valAt [this key] - (-initialize! this) - ;; (strace/print-stack-trace (ex-info "" {})) + (when-not loaded? (load! this)) (if (contains? cache key) - (do - ;; (l/trace :fn "valAt" :key key :cache "hit") - (get cache key)) - (do - (if (contains? positions key) - (let [[size pos] (get positions key) - tmp (byte-array (- size 4))] - (.get ^ByteBuffer content (int (+ pos 4)) ^bytes tmp (int 0) (int (- size 4))) - ;; (l/trace :fn "valAt" :key key :cache "miss" :size size :pos pos) - - (let [val (fres/decode tmp)] - (set! cache (assoc cache key val)) - val)) - (do - ;; (l/trace :fn "valAt" :key key :cache "miss" :val nil) - (set! cache (assoc cache key nil)) - nil))))) + (get cache key) + (if (contains? positions key) + (let [[size pos] (get positions key) + tmp (byte-array (- size 4))] + (.get ^ByteBuffer content (int (+ pos 4)) ^bytes tmp (int 0) (int (- size 4))) + (let [val (fres/decode tmp)] + (set! cache (assoc cache key val)) + val)) + (do + (set! cache (assoc cache key nil)) + nil)))) (valAt [this key not-found] - (-initialize! this) + (when-not loaded? (load! this)) (if (.containsKey ^IPersistentMap positions key) (.valAt this key) not-found)) (assoc [this key val] - (-initialize! this) - ;; (l/trace :fn "assoc" :key key ::l/async false) + (when-not loaded? (load! this)) + (when-not (instance? UUID key) + (throw (IllegalArgumentException. "key should be an instance of UUID"))) (ObjectsMap. metadata nil (assoc positions key nil) @@ -325,15 +300,14 @@ blob header content - initialized? + loaded? true)) (assocEx [_ _ _] (throw (UnsupportedOperationException. "method not implemented"))) (without [this key] - (-initialize! this) - ;; (l/trace :fn "without" :key key ::l/async false) + (when-not loaded? (load! this)) (ObjectsMap. metadata nil (dissoc positions key) @@ -341,16 +315,17 @@ blob header content - initialized? + loaded? true)) Counted - (count [_] + (count [this] + (when-not loaded? (load! this)) (count positions)) Iterable (iterator [this] - (-initialize! this) + (when-not loaded? (load! this)) (ObjectsMapIterator. (.iterator ^Iterable positions) this)) ) @@ -376,12 +351,16 @@ objects (into (create) objects))) +(defn objects-map? + [o] + (instance? ObjectsMap o)) + (fres/add-handlers! {:name "penpot/experimental/objects-map" :class ObjectsMap :wfn (fn [n w o] (fres/write-tag! w n) - (fres/write-bytes! w (-get-byte-array o))) + (fres/write-bytes! w (deref o))) :rfn (fn [r] (-> r fres/read-object! create))}) From aa9e125e3118aba9f1c915c00d81181f61d86aba Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 3 Nov 2022 14:22:42 +0100 Subject: [PATCH 178/682] :tada: Add tests for objects map --- .../test/app_tests/test_util_objects_map.clj | 150 ++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 backend/test/app_tests/test_util_objects_map.clj diff --git a/backend/test/app_tests/test_util_objects_map.clj b/backend/test/app_tests/test_util_objects_map.clj new file mode 100644 index 0000000000..24e4b3b08d --- /dev/null +++ b/backend/test/app_tests/test_util_objects_map.clj @@ -0,0 +1,150 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app-tests.test-util-objects-map + (:require + [app-tests.test-helpers :as th] + [app.common.spec :as us] + [app.common.transit :as transit] + [app.common.types.shape :as cts] + [app.common.uuid :as uuid] + [app.util.fressian :as fres] + [app.util.objects-map :as omap] + [clojure.pprint :refer [pprint]] + [clojure.spec.alpha :as s] + [clojure.test :as t] + [clojure.test.check.clojure-test :refer [defspec]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as props])) + +(t/deftest basic-operations + (t/testing "assoc" + (let [id (uuid/custom 0 1) + id' (uuid/custom 0 2) + obj (-> (omap/create) (assoc id {:foo 1}))] + (t/is (not= id id')) + (t/is (not (contains? obj id'))) + (t/is (contains? obj id)))) + + (t/testing "dissoc" + (let [id (uuid/custom 0 1) + obj (-> (omap/create) (assoc id {:foo 1}))] + (t/is (contains? obj id)) + (let [obj (dissoc obj id)] + (t/is (not (contains? obj id)))))) + + (t/testing "dissoc" + (let [id (uuid/custom 0 1) + obj (-> (omap/create) (assoc id {:foo 1}))] + (t/is (contains? obj id)) + (let [obj (dissoc obj id)] + (t/is (not (contains? obj id)))))) + + (t/testing "seq" + (let [id (uuid/custom 0 1) + obj (-> (omap/create) (assoc id 1))] + (t/is (contains? obj id)) + (let [[entry] (seq obj)] + (t/is (map-entry? entry)) + (t/is (= (key entry) id)) + (t/is (= (val entry) 1))))) + + (t/testing "const & count" + (let [obj (into (omap/create) [[uuid/zero 1]])] + (t/is (contains? obj uuid/zero)) + (t/is (= 1 (count obj))) + (t/is (omap/objects-map? obj)))) + + (t/testing "wrap" + (let [obj1 (omap/wrap {}) + tmp (omap/create) + obj2 (omap/wrap tmp)] + (t/is (omap/objects-map? obj1)) + (t/is (omap/objects-map? obj2)) + (t/is (identical? tmp obj2)) + (t/is (= 0 (count obj1))) + (t/is (= 0 (count obj2))))) + + (t/testing "error on non-uuid keys" + (let [obj (omap/wrap {})] + (t/is (thrown? IllegalArgumentException (assoc obj :foo "bar"))))) + + ) + +(t/deftest internal-operation + (t/testing "modified & compact" + (let [id1 (uuid/custom 0 1) + id2 (uuid/custom 0 2) + obj (omap/wrap {id1 1 id2 2})] + (t/is (= 2 (count obj))) + (t/is (omap/modified? obj)) + (omap/compact! obj) + (t/is (not (omap/modified? obj))) + (t/is (bytes? (deref obj))))) + + (t/testing "low-level serialize/deserialize" + (let [id1 (uuid/custom 0 1) + id2 (uuid/custom 0 2) + obj1 (omap/wrap {id1 1 id2 2}) + obj2 (omap/create (deref obj1))] + (t/is (= (get obj1 id1) (get obj2 id1))) + (t/is (= (get obj1 id2) (get obj2 id2))) + (t/is (= (count obj1) (count obj2))) + (t/is (= (hash obj1) (hash obj2))))) + ) + +(defspec internal-encode-decode 25 + (props/for-all + [data (->> (gen/map gen/uuid (s/gen ::cts/shape)) + (gen/not-empty))] + (let [obj1 (omap/wrap data) + obj2 (omap/create (deref obj1)) + obj3 (assoc obj2 uuid/zero 1) + obj4 (omap/create (deref obj3))] + ;; (app.common.pprint/pprint data) + (t/is (= (hash obj1) (hash obj2))) + (t/is (not= (hash obj2) (hash obj3))) + (t/is (bytes? (deref obj3))) + (t/is (pos? (alength (deref obj3)))) + (t/is (= (hash obj3) (hash obj4)))))) + +(defspec fressian-encode-decode 25 + (props/for-all + [data (->> (gen/map gen/uuid (s/gen ::cts/shape)) + (gen/not-empty) + (gen/fmap omap/wrap) + (gen/fmap (fn [o] {:objects o})))] + (let [res (-> data fres/encode fres/decode)] + (t/is (contains? res :objects)) + (t/is (omap/objects-map? (:objects res))) + (t/is (= (count (:objects data)) + (count (:objects res)))) + (t/is (= (hash (:objects data)) + (hash (:objects res))))))) + +(defspec transit-encode-decode 25 + (props/for-all + [data (->> (gen/map gen/uuid (s/gen ::cts/shape)) + (gen/not-empty) + (gen/fmap omap/wrap) + (gen/fmap (fn [o] {:objects o})))] + (let [res (-> data transit/encode transit/decode)] + ;; (app.common.pprint/pprint data) + ;; (app.common.pprint/pprint res) + (doseq [[k v] (:objects res)] + (t/is (= v (get-in data [:objects k])))) + + (t/is (contains? res :objects)) + (t/is (contains? data :objects)) + + (t/is (omap/objects-map? (:objects data))) + (t/is (not (omap/objects-map? (:objects res)))) + + (t/is (= (count (:objects data)) + (count (:objects res))))))) + + + From efb0ec46bf619981054d7b16f4c58d3ceb923cbb Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 3 Nov 2022 14:46:09 +0100 Subject: [PATCH 179/682] :tada: Add tests for pointer map --- .../test/app_tests/test_util_objects_map.clj | 9 +- .../test/app_tests/test_util_pointer_map.clj | 124 ++++++++++++++++++ 2 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 backend/test/app_tests/test_util_pointer_map.clj diff --git a/backend/test/app_tests/test_util_objects_map.clj b/backend/test/app_tests/test_util_objects_map.clj index 24e4b3b08d..19d978ed20 100644 --- a/backend/test/app_tests/test_util_objects_map.clj +++ b/backend/test/app_tests/test_util_objects_map.clj @@ -36,13 +36,6 @@ (let [obj (dissoc obj id)] (t/is (not (contains? obj id)))))) - (t/testing "dissoc" - (let [id (uuid/custom 0 1) - obj (-> (omap/create) (assoc id {:foo 1}))] - (t/is (contains? obj id)) - (let [obj (dissoc obj id)] - (t/is (not (contains? obj id)))))) - (t/testing "seq" (let [id (uuid/custom 0 1) obj (-> (omap/create) (assoc id 1))] @@ -52,7 +45,7 @@ (t/is (= (key entry) id)) (t/is (= (val entry) 1))))) - (t/testing "const & count" + (t/testing "cons & count" (let [obj (into (omap/create) [[uuid/zero 1]])] (t/is (contains? obj uuid/zero)) (t/is (= 1 (count obj))) diff --git a/backend/test/app_tests/test_util_pointer_map.clj b/backend/test/app_tests/test_util_pointer_map.clj new file mode 100644 index 0000000000..1fc2d8423a --- /dev/null +++ b/backend/test/app_tests/test_util_pointer_map.clj @@ -0,0 +1,124 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app-tests.test-util-pointer-map + (:require + [app-tests.test-helpers :as th] + [app.common.spec :as us] + [app.common.transit :as transit] + [app.common.types.shape :as cts] + [app.common.uuid :as uuid] + [app.util.fressian :as fres] + [app.util.pointer-map :as pmap] + [clojure.pprint :refer [pprint]] + [clojure.spec.alpha :as s] + [clojure.test :as t] + [clojure.test.check.clojure-test :refer [defspec]] + [clojure.test.check.generators :as gen] + [clojure.test.check.properties :as props])) + +(t/deftest basic-operations + (t/testing "assoc" + (let [obj (-> (pmap/create) (assoc :a 1))] + (t/is (contains? obj :a)) + (t/is (not (contains? obj :b))) + (t/is (= 1 (count obj))))) + + (t/testing "dissoc" + (let [obj (pmap/wrap {:a 1 :b 2})] + (t/is (contains? obj :a)) + (t/is (contains? obj :b)) + (let [obj (dissoc obj :a)] + (t/is (not (contains? obj :a))) + (t/is (contains? obj :b))))) + + (t/testing "seq" + (let [obj (pmap/wrap {:a 1 :b 2}) + s1 (first obj)] + (t/is (= (key s1) :a)) + (t/is (= (val s1) 1)))) + + (t/testing "cons & count" + (let [obj (into (pmap/create) [[:a 1]])] + (t/is (contains? obj :a)) + (t/is (= 1 (count obj))) + (t/is (pmap/pointer-map? obj)))) + + (t/testing "wrap" + (let [obj1 (pmap/wrap {}) + tmp (pmap/create) + obj2 (pmap/wrap tmp)] + (t/is (pmap/pointer-map? obj1)) + (t/is (pmap/pointer-map? obj2)) + (t/is (identical? tmp obj2)) + (t/is (= 0 (count obj1))) + (t/is (= 0 (count obj2))))) + ) + + +(t/deftest internal-tracking + (t/testing "simple tracking" + (binding [pmap/*tracked* (atom {})] + (let [obj (pmap/create)] + (t/is (pmap/modified? obj)) + (t/is (uuid? (pmap/get-id obj))) + (t/is (contains? @pmap/*tracked* (pmap/get-id obj))) + (t/is (= 1 (count @pmap/*tracked*)))))) + + (t/testing "tracking modifying modified" + (binding [pmap/*tracked* (atom {})] + (let [obj1 (pmap/create) + obj2 (assoc obj1 :a 1)] + (t/is (pmap/modified? obj1)) + (t/is (not= obj1 obj2)) + (t/is (= 0 (count obj1))) + (t/is (= 1 (count obj2))) + (t/is (uuid? (pmap/get-id obj1))) + (t/is (uuid? (pmap/get-id obj2))) + (t/is (= (pmap/get-id obj2) + (pmap/get-id obj1))) + (t/is (= (hash (pmap/get-id obj2)) + (hash (pmap/get-id obj1)))) + (t/is (contains? @pmap/*tracked* (pmap/get-id obj2))) + (t/is (= 1 (count @pmap/*tracked*)))))) + + (t/testing "tracking modifying not modified" + (binding [pmap/*tracked* (atom {}) + pmap/*load-fn* (constantly {})] + (let [obj1 (pmap/create uuid/zero {}) + obj2 (assoc obj1 :a 1)] + (t/is (pmap/modified? obj2)) + (t/is (not (pmap/modified? obj1))) + (t/is (not= obj1 obj2)) + (t/is (= 0 (count obj1))) + (t/is (= 1 (count obj2))) + (t/is (uuid? (pmap/get-id obj1))) + (t/is (uuid? (pmap/get-id obj2))) + (t/is (not= (pmap/get-id obj2) + (pmap/get-id obj1))) + (t/is (not= (hash (pmap/get-id obj2)) + (hash (pmap/get-id obj1)))) + (t/is (contains? @pmap/*tracked* (pmap/get-id obj1))) + (t/is (contains? @pmap/*tracked* (pmap/get-id obj2))) + (t/is (= 2 (count @pmap/*tracked*)))))) + + (t/testing "loading" + (binding [pmap/*tracked* (atom {}) + pmap/*load-fn* (constantly {:a 1})] + (let [obj1 (pmap/create uuid/zero {})] + (t/is (not (pmap/modified? obj1))) + (t/is (= 1 (count obj1))) + (t/is (= uuid/zero (pmap/get-id obj1))) + (t/is (contains? @pmap/*tracked* (pmap/get-id obj1))) + (t/is (= 1 (count @pmap/*tracked*))) + (t/is (contains? obj1 :a)) + (t/is (not (contains? obj1 :b))) + (t/is (= 1 (get obj1 :a))) + (t/is (= nil (get obj1 :b))) + (t/is (= ::empty (get obj1 :b ::empty)))))) + + ) + From 0dc3dba428c3afb08722a4e0854bb54bce373c14 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 3 Nov 2022 15:52:43 +0100 Subject: [PATCH 180/682] :paperclip: Set definitive fressian handler ids for objects and pointer map --- backend/src/app/util/objects_map.clj | 2 +- backend/src/app/util/pointer_map.clj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/app/util/objects_map.clj b/backend/src/app/util/objects_map.clj index e8c794d934..643aa6d5aa 100644 --- a/backend/src/app/util/objects_map.clj +++ b/backend/src/app/util/objects_map.clj @@ -356,7 +356,7 @@ (instance? ObjectsMap o)) (fres/add-handlers! - {:name "penpot/experimental/objects-map" + {:name "penpot/objects-map/v1" :class ObjectsMap :wfn (fn [n w o] (fres/write-tag! w n) diff --git a/backend/src/app/util/pointer_map.clj b/backend/src/app/util/pointer_map.clj index 0942f77e7b..586cce335e 100644 --- a/backend/src/app/util/pointer_map.clj +++ b/backend/src/app/util/pointer_map.clj @@ -192,7 +192,7 @@ (into (create) data))) (fres/add-handlers! - {:name "penpot/experimental/pointer-map/v2" + {:name "penpot/pointer-map/v1" :class PointerMap :wfn (fn [n w o] (fres/write-tag! w n 3) From 12e2d3ad9626b5795d4b37791b8a630af20e9c95 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Nov 2022 17:38:06 +0100 Subject: [PATCH 181/682] :paperclip: Rename app-tests to backend-tests for naming consistency --- backend/dev/user.clj | 2 +- .../test/app_tests/test_files/template.penpot | Bin 8815 -> 0 bytes .../test_bounce_handling.clj | 4 ++-- .../test_email_sending.clj | 4 ++-- .../test_files/font-1.otf | Bin .../test_files/font-1.ttf | Bin .../test_files/font-1.woff | Bin .../test_files/font-2.otf | Bin .../test_files/font-2.woff | Bin .../test_files/sample.jpg | Bin .../test_files/sample1.svg | 0 .../test_files/sample2.svg | 0 .../test_helpers.clj | 2 +- .../test_rpc_file.clj | 4 ++-- .../test_rpc_font.clj | 4 ++-- .../test_rpc_management.clj | 6 +++--- .../test_rpc_media.clj | 4 ++-- .../test_rpc_profile.clj | 4 ++-- .../test_rpc_project.clj | 4 ++-- .../test_rpc_team.clj | 4 ++-- .../test_rpc_viewer.clj | 4 ++-- .../test_storage.clj | 4 ++-- .../test_telemetry_task.clj | 4 ++-- .../test_util_objects_map.clj | 4 ++-- .../test_util_pointer_map.clj | 4 ++-- 25 files changed, 31 insertions(+), 31 deletions(-) delete mode 100644 backend/test/app_tests/test_files/template.penpot rename backend/test/{app_tests => backend_tests}/test_bounce_handling.clj (99%) rename backend/test/{app_tests => backend_tests}/test_email_sending.clj (90%) rename backend/test/{app_tests => backend_tests}/test_files/font-1.otf (100%) rename backend/test/{app_tests => backend_tests}/test_files/font-1.ttf (100%) rename backend/test/{app_tests => backend_tests}/test_files/font-1.woff (100%) rename backend/test/{app_tests => backend_tests}/test_files/font-2.otf (100%) rename backend/test/{app_tests => backend_tests}/test_files/font-2.woff (100%) rename backend/test/{app_tests => backend_tests}/test_files/sample.jpg (100%) rename backend/test/{app_tests => backend_tests}/test_files/sample1.svg (100%) rename backend/test/{app_tests => backend_tests}/test_files/sample2.svg (100%) rename backend/test/{app_tests => backend_tests}/test_helpers.clj (99%) rename backend/test/{app_tests => backend_tests}/test_rpc_file.clj (99%) rename backend/test/{app_tests => backend_tests}/test_rpc_font.clj (97%) rename backend/test/{app_tests => backend_tests}/test_rpc_management.clj (99%) rename backend/test/{app_tests => backend_tests}/test_rpc_media.clj (98%) rename backend/test/{app_tests => backend_tests}/test_rpc_profile.clj (99%) rename backend/test/{app_tests => backend_tests}/test_rpc_project.clj (99%) rename backend/test/{app_tests => backend_tests}/test_rpc_team.clj (99%) rename backend/test/{app_tests => backend_tests}/test_rpc_viewer.clj (98%) rename backend/test/{app_tests => backend_tests}/test_storage.clj (99%) rename backend/test/{app_tests => backend_tests}/test_telemetry_task.clj (96%) rename backend/test/{app_tests => backend_tests}/test_util_objects_map.clj (98%) rename backend/test/{app_tests => backend_tests}/test_util_pointer_map.clj (98%) diff --git a/backend/dev/user.clj b/backend/dev/user.clj index bf41b1dc6c..a0cff02752 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -63,7 +63,7 @@ ;; --- Development Stuff (defn- run-tests - ([] (run-tests #"^app-tests.test-.*$")) + ([] (run-tests #"^backend-tests.test-.*$")) ([o] (repl/refresh) (cond diff --git a/backend/test/app_tests/test_files/template.penpot b/backend/test/app_tests/test_files/template.penpot deleted file mode 100644 index e0c81bb83ef474a9e190d2f99a335e58422ae9e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8815 zcmX9^2RzjO|Nq<_?yTHdNygcfan6=;MxyLJ3g;ZocFvBDtca|JQf8_ziKvWZlaN`7 z>{V7|@Bhc||GE3z`~7;)*X#9uzTWTG`}H^oBX_(hA>XMD`2PnY{!ZoyaL!_dc z=B@PP@AQB@{d{~#6e$mUpvMlv$Iq4IMk12O-GaQmr(B7_L~lR;({>a7cnZN|6yt;U z^dnF1kbK8o@PWh$j5J2tg^c$lc#QktNxowsedI>+CI2R7Gs=C`KuguPZSW)QX=!1yoPq zq@!0O#^21{R)* zE4FmKk?T2X<@QNiQExD|=BQ^hY#--_{6!9(*a16%JX7#wX-|Lm5Rxm!!yM=q^ufFP z`;mMpfrY<8sr-q){(cl0$h^sJ(Y#q)tO*M!gufs8lsPGHKZ4g*l0TkI^qoOW1yaa< zUf{g5p(eW!qBj}jGH<0@JWRG*8XWi1uTa@A?5|L)*`x=N<&!GtDFDCVvIsK0#&JELL4b+`=frz&^%O8sJc4{&eDNf2 zL~0sSd{4o-aWc_6kYzSdVIKD1_$iQPO!Zy7@m@r%iz-&pO+k@JP*G4KDq#q21UZ7T z98ra+pyG;gRhE^gPg~ z1`#O}iuae1mBz>bYMP<`_*16PE<|^due#XZx&|>c$yHs|EWo$Lw&q`164!S#ZtS7s=shaERfr+|UNJxlu zh@7+^*W!RpGZ$>{nKf==z!Ko@<= zJDC{h7epoy)x}PSaMcn+%Mi(=V4|xY+0O?AY9P@PM4P&pxeO>JD6fnL1JM^$8u?T^ zC!>H40D*!F3Wb8dP$&!vqk++Y4=wo8A?QyVoYui$FtCIcK}UO9*bJZ$Pyhk@njf{aM|lL_>}ucH zU?@JH^J6{>l3c$Vu)Oyd0I=OrTS2@hfD8`m^gxKV3j;)aiYiIsmHQS&1$eCjc=q0W zKG=Y5ZhubD?R1Px1Av=O4T@s9ic5JwZ~8Y2)CdSkCb(o#ucR};Pg>}IFECI?e@|Ao zNz%k-_?GZ3nG51R$8iVnxmiDrkd(+>-lCN+`%XVJsFNAbqg%1PRIFI8V^4zKQ9U2q zzn1rbG7`88h?UBsAOL?aFe7V?3P^A*dnhmATE`F1ao#)u=*%}(ZIQn{xYNG;4}ajo5Y*(%~r=nZJE`K)eMeoc30BrH&(f4Ilry!3% z7ZTljG#nPIL3tqyFN=Mkxv26)|OiXUXexWjT%*R*6Z?dF;40e11)Mcdi;Y)mS8=>RJ z{=fl?M#7)p%#lAkKW^cex_CL9cqen_?meAL2gu&spDM@fMYCPjnBL~WWcTt*@%1pL zV6vaODGb|j*`^~@Oogxz=8EHaLieV0$NEmO?MIm8OQ1W-!;8xg_hT&QNv|c)UD0o~ zK2HDDc5mDc2^VcDSyKO2KzbkpbX}0+f6U!}zE5GtU3B9cASX$&x)~A^_&_7%fi9%8 z+;hJ-$m13WxCGxOGR+wTFsOH%Nmr=gWt&fcnYo`GE%GwfWA*3or1@71vC?&w)@ zY2$MA#6MdCP2YL1;wM$+8H<)OLQp0xl)=^fte7CXiO=-u%7L%+d+Mqt-rdD%VG;5? z&QLNvRMU~I#P~>@O(^vQc%S-8cn>+e*(BDPG-xE>pK>>!1I8*UUcI4n=M(2YSY-U< z)9Y6j_1dX0u2xe3b1mlm(E)zhG{;xz+%A2OU{(qXAM&LnT1~+848wF*O52~}+QmmA zedR(WJwmI4KTr#0LcQ0oJgdH^9>5nqt+v$~wv@QhdMC#}{rX`pzFItOr+FetpEgE( zX6r1(xrsMPXY2$piOyUOYsU@o@L9n1YvsQw9i(TVH7UQn*Lvy~>>BSL zbk)B-{P>0M>BIWnBK5JhZ~6(fZ+EhF2Hzezfw)pbfg2nREhB`EmKFkTXXpSLq?|k% zpkX=4XH1(rc**VND@B)Hb+WDM97+ks;xEpFhPJqc>np~|+Jx}cbCdR z@qRnMirTqK{ij&?Joj8jrL~`Y@px{PS0_C^z^q-m^Ca@;o2sXAxA=XP+cF-P3aEay z`xN{5B0Bob)>F!VSP71_K!0rL2R11&jNUV@Sx@5Spv33XACU{q`kEeT2O&n}uIXl#n02X`T3z|Q$l`3Hvnfpt@wVy#d$*MHKCPFddigCp*v*WGywfSBT zQlBR3O)q;-7OnY{2u)B51VUeb=wgwQ4kyub2O*M5BV2~c?K z%E#^AtvPRb!Cpc#X3&nW#yw|rwI|Y|{I#QUrlO}uUe>Yj+!GIpf(m->6Tq%1H}Kih z;*?9EEGoImk1slP;O!4*5kf;Yh6M#_pYxr{EFY^5%Y*7@y>a-n&^Vj0!E1h7b3vU! zUSIR6Q)9~b*Qi2VWkTz@4NxhXK9@+xSCHM=AI9wut`39Bipe!HI+t!Yr}jkA!s>pB zT?(TFI)xzlEr(X(-kVkK$=m>}=+bV;n#*_kLhgGrr{ZDrFGsS; z$U(&80n6K<$h+r$C?#3&n}F+5l$CL&qIql^LArK0Q5C*<0$9yE$0}Zpf5*}hSEp1N zyT!j)f603`)S)s$*~AR;wF(z0e_JNm^f<3;dPwsncK032BGV@+o&DRQ?DNd`m-C$; zj}8o1Yz~OCTt`P<_A?$;;p1E$|H?Lm8~*PDw6Kg3f}q-S{fhdRlooU28uKVP-vVp^jIddG2~8pi1D6=)`-}!W;FL~ zkr!)u5X-ZgAF2zEdf996423&u@V9EPJo_tDO5>;ov^V-ul-YwQ+qqocz_OsPH9xK| zwUWOWwaIo#D6w})7X7l|d7BwvI1kRJfdqXZC@q4P0S<-0&=oYaNOB;65->Hp{kDl! zSXR!QjYCL53-1!0p204N!OCk-;bQ1C!P8@Mm-<3K8JDWp)%d!0WFF%p+XCj9Kf(xhAFqZ>b%&CVH#9dtEo zeCz!0BCOx1X#Zd)AR(c6@YSOZA?KCflZUL}%9S%@3Xc=R)$HRCRJVa+r8h*hK#ro&dgU zY9cGwEUn}Uwu^5lX1*GbfBT7JZAo<4HQcwhhDv5o{zIu6;2ANEI+J?C{BEsHdj#G@ zyxu%LfBomWYwX1V)`5#&RaY{#jtABrR*-{}cIN}0?-!2|7F6}aLo!Q}zyoJ=@vQ>(v;p3GAEtSp6k%^DJ~f=Q zuDKE#?xUK0mrTAJuJbNIO{YXo^uA+jSC^VPIV1bm?iE))uL8&gnSZ}@ojyoU9)Ihq z)_g}^Wm&micl*F*BS@t3s@&3VgUh!BPJnWxpj*+q;=55txoRmN;Caq9cY`l@R{d)k z)H^Q*(Ga#YzV)v^*E?LyfaBlz7O(XxWS!;kT~Nzo&kz1AiOyyhnyLi)UggTze%R{z zE_FoSO1^cy-euy4h-dP(v%hq|7Cez+X&bnFPvSt;@z>ArwuUmt3csa?r4IXZOPt3w z`UcO>R``^Z@eEqkQE%$diXj&mhehPyzaA&wNxps2b16aoo9`q)vdN%TrFom)NU9_x zr@&?M%Si21kAg!rycJ^}aK^tr#^Rdi*n;?@YV7;6>OiAYE}7VKbaG^zvj#>!gnbc} zW_qnT4}pUI-v2Hc5Lq*D%P?INz-Z;;1CUMq6*%YUv~*2SA^97(B_Xeb^X}X<9RN-D ziy~mrJ1uHq&;0TPC|SFbt+}AqCKA{MCZV26a_~O|KKzLH|n-3Vu1e`m0%7Cq}ovkz{fbFJp`!()@MM+sS zo0C@ih=ut%;T@haD27;@P!0JkV0tqXA{V;`&!jQGicg!8gSxhD&=xt%@@QptW3}S5 z-p)aDg@je$LrCY@y@lw{j@3116Cq)u!uVCE-vR#~XT@pzL;4thD;N2j_K8d{J+ni(Y5S!3_#kkLmPcg>Jty&NQcbQ zUHzf@E47_s<~#rB^%(jfhMb$DQ=NN1joFVSY)$}W>JvI4{&X2p0v8~BOmBKT3Q(yG z2GOq-AC<6-aT5YW;b}A>=gtnfI+>+M>RsMy>BAVSn*s(6D{8nnMz(xYsqmKcqI6Bm zB5vVq>McPepnd{;c&VTF#B?oIN>ujlb#o;w%;50fD=&jiO9Iz-6|D0|=1#%8Gus@S z63vNU-p;1$1spK!vrp*~?a2Ct%13dL4E_b!(i&m2oT%ncamr@rtgmN?f7Qf0Y3GRF zEs-{0cjqsElW)wfXZ27xN$%dTP2UR!p$lCZm(O)9U;J$mhq_P9oh<^uhzEcKXsFKU zJiwp75!%Os1N_0D2@ezp01N<(3IYZP@KAR-sNz5=f(ivVfIKijVoC?(IRk(;fIAO> zZD%+$fKC}a$s|D^6`+UFf_HNWfJC8X&GIYO z1hh=?0qOng(M^~soHG)<^UUB#BGe#lv_ve|?cVpy80wErJ_}N6`p2=hnyKl*>-*Pg zaNCU-NI3#0kYH+BD&>~?`beem%V0Upw_?F(fBz89>NE|f=0i1Jq*w6n)xWeqv#|WL zuU!7Is?z3SEYg-Dlsy>fhg%lBiDqJo2u)3|8cR9YfJE5dji1fybyn+s_Iy37ACsDB zxfq^v6w4y14bWx8xc;RuH!$y?0Q8&O2}08Cw9wm#<}B0hMwVsQ0Zx(21Dxi&pb!yC)?8{!?T1Zq<`NkLkqKNiL%}NeTTQXkp|gk z+MTj`yJ6~su|81&bLP2%x%$H|e)nWt-9y0e`cpGYdD0y4gLm;ozj|52{2EDpr z6CoE>zAL2E%&M`Tx|;G9a|~&;aGBXZaaCP$ZT`uh2zX>O@~c{@y4$nY^|fe#ju~)# z;RaE^J)x`g`jWA}hVigojG_SKZl!*BZ$)4zXY_66-Kl%8CVfp@xoY{0XSXhb*HZax z8mw>+>gZYVM9Ui*i26i2GT~dH$3>>s=MhGsE)vU!%i-%(R$B!TXyX9s_vmOz3^yzk zzJ9<@zs^#$n=o)w=Z}WKW-fByBr#H~RU>nn-@xl|3mHYX%1`%r;ropwmiMSw-o)wF zM;_v-dDOM7l2My_o6M%MatZm8b5lH-IojgD|CqKu~CmtSO*{6aX=K_OPxi zjO9PyjNH5z(7Gri9TI%cn>FX^M?MkmMQ#0{kA?q+gqa{=YR^o>61AJnim%#cS~`n| za`gX8-FD%oiKa?SL7D!26;ZzLf=nw+G|5}LAZK(9KJLhY7wFdPSj?S~ZfLyFMy|PD zW`n%aM?ZI(vkot1E)~itfq5YoE;Nobt9&U}EKemLcY#b^1UEJShJetb1k3=mENDde zYw5^<=>ByKuE{xV3S>He%{8NK4W{6|6PH63ZAf~DK66!tE6m=C#*8Um<-Yp6Ir4&j zT;|R5DDMknk5GW3Bx8Ft5}i1$=qpUiVL_4mxl-o-lETJGH6jtBaagEfKs5Em%QVwy zHschFN>$z1%r9$Rl)1F*&)sbvH=reX5TnI~y7AAv&gfVa59&KkMIc`^sCBJe6ts7y zC7H|3Z09@Y(3kXQ-U$z^rD0V{A@YwWE+&^=xqSjWfcM*G3d^*V&>N(4WW;3mg%UG4kc8ndS;g@{vwcw_KO|`y&2_wr`^2^@26EX|#kV4=<@(GT@Bt0K4ok@!_-%`0QA6 z-+fd&Om(;cg1=Wq5&_e`&8^w@~RyZEjTH=0~=%Ord$oeT?ystGt{BHYu{l@B; z;YYfSHilYDKoSy=O^HJ!B3`MgtZr8~V$_%(-FAvTJPO}h`tioGKKwj;Q+B654@-S> zPf{nn;x6yxu-vGuF7oHVSE%>Pm2=&A+Z*N{_RXVyoTH)dN88?yOTAzB`H<}M(ZOf> ze}z>{@Z|hGb;0!uY0~~Dz{3w7V+w}9Y=AD|4~tQ+_j604UMx<$+OKT?vlyG`EU}pG z>mcH)tXsZ}NM$>+70G%86^wX1sP~sO_@7Ka0cH zMw%x-VHkpvqB35^OoigIpv1w}b*f@F4vaxe> zpio=_T-?08{QUfEX9a}?`Gk4+`1!zIa60%I1mX-IJBpp}q!Em11FnE8H)u)VSxX0} zfkDAtKpngr!XPv-W;z=9X+RNvX?C@cumT4cgpevgl<59WyM}rhENkvHYz2m-*Iy9{blCjw|7YvcF zPWd7etid&Xx8YXFg$GJ)7pK#cIx4KnJl6_}3%aE&d*tN5kN+}JZSaN|}V`&m9Xyz<5*DLg0Oy2gWo{{+Q$TT9<_ z@qT}DiDi1LWT*PWhl^5gJT%I@%z8kMDg@fN#>-G_)t~Sjj<4MYN{obpokHNVbSD6N z$i5~yq_&}};;8q-k-ennIgirzzM-K52iqs#Yr0#e7M;SUtyac-w`_;o?gxc_h=@G* zV9|TGbd(&fZ)Gjq)O~p<>Cx2w*?1Xbokhq;D|ch3EN2Q80t|SISk`?sv*M5-Hd!1b zZF&1f+5LI8{Exo)(DsB0Y`GDk_U6kyzS-@Ni2Gj}0(EEqI4f8wWlyMIYROoWe-@b&hUjcc2@E13ppK?! z?#l`N6LAlRY)S}${~8Q;+~p6wBa7$l?iDIKzPXZNy26{zUUi_;r1v(tPUF_Yy1MRu z6;)ffoT|x3l{YYhg`XX1cXO0~C|RdbMdlzywvoKMc9ECE-z#Ze-0TewG->Y`@g+Xm zmy2863==ui{}Xl1q)Lt5OT4afVK1WYV-H*Ab84Vm(Lu}O7ED0tiW0Uv@?7&)xLQVn z$8zHH7bAQO3RyRPZXDK+jl13H9B5R@9*CUk3FS7?_^KA`I@MlNTNMkD5l1o$w*3Cv z+_iY|TSd*DQ*lJ)BjxtsOqP?M(Zb>o+I9?rw{(DxmFgVLIVT<)!)0+-IZjtN4W}D3 zn;yfi3}^eU_~|yj+g_xOS3}8{UHt_OvhcDy-~Er6&+${|8+Mm|`YC_%tD>lJ6EnE!X{8HRC)TY7hkjw6v6n|rYP`NcfpjNG^DgW;mm z8Ewwhbo4I+h`+2ZnY(mlS`(u6=q}KQyN^s5GM_)k+End(dC*t~@={n8O?oB!)Xi_J z!kGOp7pYkJG4C0t`8HY)<+w9UTVDPPbcIS4b|rxfdq&kTyL)kdxI*7GGaEjpq(PH= zIgcNu)i!l12s&PSc~<17Z(3ao_`d}e0?mlQh9mOb^1oFY1ifMgTb^iew*x`QhTLyI zGVSg73!yA&`lQ3P-XSe`BZ~#1u_);S!HA}h*-av72WyZ@p~dnmg7iOMI<;UAjC3X) zZ^XL0vKX(m8+02o#MzTA5!GX9-qB8DA{|9)@g0V%3|}i4l_9SpAAUD{itGM+G4O-4 zkw%#^ozkWI9d|34M<|Wcp2!9%g+8V9#;IsD(_!RqJ z`&rj!8WD0pN}>Q89KlLW`p)toM&v`B3t^rO#Z#6Y?^xcQ(}f=GyYsJ#P4PTh68HR)HGQ7(+w8fIF;6L`oAIuI zhqQkJyYh99`(Sv_D`Frf`ialTH-Op@M? Date: Tue, 8 Nov 2022 10:40:19 +0100 Subject: [PATCH 182/682] :recycle: Refactor tests directory structure --- .circleci/config.yml | 12 +++++----- backend/dev/user.clj | 2 +- ..._handling.clj => bounce_handling_test.clj} | 4 ++-- ...ail_sending.clj => email_sending_test.clj} | 4 ++-- .../{test_helpers.clj => helpers.clj} | 4 ++-- .../{test_rpc_file.clj => rpc_file_test.clj} | 6 ++--- .../{test_rpc_font.clj => rpc_font_test.clj} | 8 +++---- ...management.clj => rpc_management_test.clj} | 6 ++--- ...{test_rpc_media.clj => rpc_media_test.clj} | 8 +++---- ...t_rpc_profile.clj => rpc_profile_test.clj} | 6 ++--- ...t_rpc_project.clj => rpc_project_test.clj} | 4 ++-- .../{test_rpc_team.clj => rpc_team_test.clj} | 4 ++-- ...est_rpc_viewer.clj => rpc_viewer_test.clj} | 4 ++-- .../{test_storage.clj => storage_test.clj} | 12 +++++----- ...etry_task.clj => tasks_telemetry_test.clj} | 4 ++-- .../backend_tests/test_files/template.penpot | Bin 0 -> 8815 bytes ...ects_map.clj => util_objects_map_test.clj} | 4 ++-- ...nter_map.clj => util_pointer_map_test.clj} | 4 ++-- common/shadow-cljs.edn | 2 +- .../{test_data.cljc => data_test.cljc} | 2 +- ...geom_shapes.cljc => geom_shapes_test.cljc} | 2 +- .../{test_geom.cljc => geom_test.cljc} | 2 +- .../{test_helpers => helpers}/components.cljc | 2 +- .../{test_helpers => helpers}/files.cljc | 2 +- ...s_helpers.cljc => pages_helpers_test.cljc} | 2 +- ...ations.cljc => pages_migrations_test.cljc} | 2 +- .../{test_pages.cljc => pages_test.cljc} | 2 +- .../test/common_tests/setup.cljs | 4 ++-- common/test/common_tests/test_setup.cljc | 10 -------- .../{test_text.cljc => text_test.cljc} | 2 +- ...t_types_file.cljc => types_file_test.cljc} | 6 ++--- ...ljc => types_shape_interactions_test.cljc} | 2 +- .../{test_types.cljc => types_test.cljc} | 2 +- .../{test_uuid.cljc => uuid_test.cljc} | 2 +- frontend/shadow-cljs.edn | 2 +- .../helpers}/events.cljs | 14 +++++++---- .../helpers}/libraries.cljs | 16 +++++++++---- .../helpers}/pages.cljs | 20 ++++++++++------ .../helpers_shapes_test.cljs} | 14 +++++++---- frontend/test/frontend_tests/setup.cljs | 22 ++++++++++++++++++ .../state_components_sync_test.cljs} | 16 +++++++++---- .../state_components_test.cljs} | 8 +++---- .../util_range_tree_test.cljs} | 14 +++++++---- .../util_simple_math_test.cljs} | 8 ++++++- .../util_snap_data_test.cljs} | 10 ++++---- 45 files changed, 170 insertions(+), 116 deletions(-) rename backend/test/backend_tests/{test_bounce_handling.clj => bounce_handling_test.clj} (99%) rename backend/test/backend_tests/{test_email_sending.clj => email_sending_test.clj} (90%) rename backend/test/backend_tests/{test_helpers.clj => helpers.clj} (99%) rename backend/test/backend_tests/{test_rpc_file.clj => rpc_file_test.clj} (99%) rename backend/test/backend_tests/{test_rpc_font.clj => rpc_font_test.clj} (92%) rename backend/test/backend_tests/{test_rpc_management.clj => rpc_management_test.clj} (99%) rename backend/test/backend_tests/{test_rpc_media.clj => rpc_media_test.clj} (96%) rename backend/test/backend_tests/{test_rpc_profile.clj => rpc_profile_test.clj} (99%) rename backend/test/backend_tests/{test_rpc_project.clj => rpc_project_test.clj} (99%) rename backend/test/backend_tests/{test_rpc_team.clj => rpc_team_test.clj} (99%) rename backend/test/backend_tests/{test_rpc_viewer.clj => rpc_viewer_test.clj} (98%) rename backend/test/backend_tests/{test_storage.clj => storage_test.clj} (97%) rename backend/test/backend_tests/{test_telemetry_task.clj => tasks_telemetry_test.clj} (96%) create mode 100644 backend/test/backend_tests/test_files/template.penpot rename backend/test/backend_tests/{test_util_objects_map.clj => util_objects_map_test.clj} (98%) rename backend/test/backend_tests/{test_util_pointer_map.clj => util_pointer_map_test.clj} (98%) rename common/test/common_tests/{test_data.cljc => data_test.cljc} (98%) rename common/test/common_tests/{test_geom_shapes.cljc => geom_shapes_test.cljc} (99%) rename common/test/common_tests/{test_geom.cljc => geom_test.cljc} (99%) rename common/test/common_tests/{test_helpers => helpers}/components.cljc (99%) rename common/test/common_tests/{test_helpers => helpers}/files.cljc (99%) rename common/test/common_tests/{test_pages_helpers.cljc => pages_helpers_test.cljc} (97%) rename common/test/common_tests/{test_pages_migrations.cljc => pages_migrations_test.cljc} (98%) rename common/test/common_tests/{test_pages.cljc => pages_test.cljc} (99%) rename frontend/test/app/init_test.cljs => common/test/common_tests/setup.cljs (73%) delete mode 100644 common/test/common_tests/test_setup.cljc rename common/test/common_tests/{test_text.cljc => text_test.cljc} (97%) rename common/test/common_tests/{test_types_file.cljc => types_file_test.cljc} (98%) rename common/test/common_tests/{test_types_shape_interactions.cljc => types_shape_interactions_test.cljc} (99%) rename common/test/common_tests/{test_types.cljc => types_test.cljc} (98%) rename common/test/common_tests/{test_uuid.cljc => uuid_test.cljc} (96%) rename frontend/test/{app/test_helpers => frontend_tests/helpers}/events.cljs (87%) rename frontend/test/{app/test_helpers => frontend_tests/helpers}/libraries.cljs (93%) rename frontend/test/{app/test_helpers => frontend_tests/helpers}/pages.cljs (93%) rename frontend/test/{app/shapes_test.cljs => frontend_tests/helpers_shapes_test.cljs} (77%) create mode 100644 frontend/test/frontend_tests/setup.cljs rename frontend/test/{app/components_sync_test.cljs => frontend_tests/state_components_sync_test.cljs} (99%) rename frontend/test/{app/components_basic_test.cljs => frontend_tests/state_components_test.cljs} (99%) rename frontend/test/{app/util/range_tree_test.cljs => frontend_tests/util_range_tree_test.cljs} (95%) rename frontend/test/{app/util/simple_math_test.cljs => frontend_tests/util_simple_math_test.cljs} (91%) rename frontend/test/{app/util/snap_data_test.cljs => frontend_tests/util_snap_data_test.cljs} (99%) diff --git a/.circleci/config.yml b/.circleci/config.yml index f044e17803..4d219ba911 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -62,19 +62,19 @@ jobs: name: common tests command: | yarn install - yarn run compile-test - node target/test.js - clojure -X:dev:test :patterns '["common-tests.test-.*"]' + yarn test + clojure -X:dev:test :patterns '["common-tests.*-test"]' environment: PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin JVM_OPTS: -Xmx4g + NODE_OPTIONS: --max-old-space-size=4096 - run: name: backend test working_directory: "./backend" command: | - clojure -X:dev:test :patterns '["app-tests.test-.*"]' + clojure -X:dev:test :patterns '["backend-tests.*-test"]' environment: PENPOT_TEST_DATABASE_URI: "postgresql://localhost/penpot_test" @@ -88,11 +88,11 @@ jobs: working_directory: "./frontend" command: | yarn install - clojure -M:dev:shadow-cljs compile test - node target/tests.js + yarn test environment: PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin + NODE_OPTIONS: --max-old-space-size=4096 - save_cache: paths: diff --git a/backend/dev/user.clj b/backend/dev/user.clj index a0cff02752..dce35764b6 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -63,7 +63,7 @@ ;; --- Development Stuff (defn- run-tests - ([] (run-tests #"^backend-tests.test-.*$")) + ([] (run-tests #"^backend-tests.*-test$")) ([o] (repl/refresh) (cond diff --git a/backend/test/backend_tests/test_bounce_handling.clj b/backend/test/backend_tests/bounce_handling_test.clj similarity index 99% rename from backend/test/backend_tests/test_bounce_handling.clj rename to backend/test/backend_tests/bounce_handling_test.clj index a85616ffed..2938ecc9f1 100644 --- a/backend/test/backend_tests/test_bounce_handling.clj +++ b/backend/test/backend_tests/bounce_handling_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-bounce-handling +(ns backend-tests.bounce-handling-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.db :as db] [app.emails :as emails] [app.http.awsns :as awsns] diff --git a/backend/test/backend_tests/test_email_sending.clj b/backend/test/backend_tests/email_sending_test.clj similarity index 90% rename from backend/test/backend_tests/test_email_sending.clj rename to backend/test/backend_tests/email_sending_test.clj index 685d657791..49802dd9e7 100644 --- a/backend/test/backend_tests/test_email_sending.clj +++ b/backend/test/backend_tests/email_sending_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-email-sending +(ns backend-tests.email-sending-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.db :as db] [app.emails :as emails] [clojure.test :as t] diff --git a/backend/test/backend_tests/test_helpers.clj b/backend/test/backend_tests/helpers.clj similarity index 99% rename from backend/test/backend_tests/test_helpers.clj rename to backend/test/backend_tests/helpers.clj index 01948bcb99..75b3a3eb8f 100644 --- a/backend/test/backend_tests/test_helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-helpers +(ns backend-tests.helpers (:require [app.common.data :as d] [app.common.flags :as flags] @@ -65,7 +65,7 @@ :name "test" :file-uri "test" :thumbnail-uri "test" - :path (-> "app_tests/test_files/template.penpot" io/resource fs/path)}] + :path (-> "backend_tests/test_files/template.penpot" io/resource fs/path)}] system (-> (merge main/system-config main/worker-config) (assoc-in [:app.redis/redis :uri] (:redis-uri config)) (assoc-in [:app.db/pool :uri] (:database-uri config)) diff --git a/backend/test/backend_tests/test_rpc_file.clj b/backend/test/backend_tests/rpc_file_test.clj similarity index 99% rename from backend/test/backend_tests/test_rpc_file.clj rename to backend/test/backend_tests/rpc_file_test.clj index 15fc74fc81..6f7562b542 100644 --- a/backend/test/backend_tests/test_rpc_file.clj +++ b/backend/test/backend_tests/rpc_file_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-rpc-file +(ns backend-tests.rpc-file-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.db.sql :as sql] @@ -124,7 +124,7 @@ (t/deftest file-gc-task (letfn [(create-file-media-object [{:keys [profile-id file-id]}] (let [mfile {:filename "sample.jpg" - :path (th/tempfile "app_tests/test_files/sample.jpg") + :path (th/tempfile "backend_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} params {::th/type :upload-file-media-object diff --git a/backend/test/backend_tests/test_rpc_font.clj b/backend/test/backend_tests/rpc_font_test.clj similarity index 92% rename from backend/test/backend_tests/test_rpc_font.clj rename to backend/test/backend_tests/rpc_font_test.clj index f6c6c75d27..6116b8d8aa 100644 --- a/backend/test/backend_tests/test_rpc_font.clj +++ b/backend/test/backend_tests/rpc_font_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-rpc-font +(ns backend-tests.rpc-font-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] @@ -24,7 +24,7 @@ proj-id (:default-project-id prof) font-id (uuid/custom 10 1) - ttfdata (-> (io/resource "app_tests/test_files/font-1.ttf") + ttfdata (-> (io/resource "backend_tests/test_files/font-1.ttf") io/input-stream io/read-as-bytes) @@ -59,7 +59,7 @@ proj-id (:default-project-id prof) font-id (uuid/custom 10 1) - data (-> (io/resource "app_tests/test_files/font-1.woff") + data (-> (io/resource "backend_tests/test_files/font-1.woff") io/input-stream io/read-as-bytes) diff --git a/backend/test/backend_tests/test_rpc_management.clj b/backend/test/backend_tests/rpc_management_test.clj similarity index 99% rename from backend/test/backend_tests/test_rpc_management.clj rename to backend/test/backend_tests/rpc_management_test.clj index 9c68bd94c2..c1f03838d9 100644 --- a/backend/test/backend_tests/test_rpc_management.clj +++ b/backend/test/backend_tests/rpc_management_test.clj @@ -4,10 +4,10 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-rpc-management +(ns backend-tests.rpc-management-test (:require - [backend-tests.test-storage :refer [configure-storage-backend]] - [backend-tests.test-helpers :as th] + [backend-tests.storage-test :refer [configure-storage-backend]] + [backend-tests.helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] diff --git a/backend/test/backend_tests/test_rpc_media.clj b/backend/test/backend_tests/rpc_media_test.clj similarity index 96% rename from backend/test/backend_tests/test_rpc_media.clj rename to backend/test/backend_tests/rpc_media_test.clj index dbe3fe68ec..5fb6f99051 100644 --- a/backend/test/backend_tests/test_rpc_media.clj +++ b/backend/test/backend_tests/rpc_media_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-rpc-media +(ns backend-tests.rpc-media-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.storage :as sto] @@ -60,7 +60,7 @@ :project-id (:default-project-id prof) :is-shared false}) mfile {:filename "sample.jpg" - :path (th/tempfile "app_tests/test_files/sample.jpg") + :path (th/tempfile "backend_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} @@ -99,7 +99,7 @@ :project-id (:default-project-id prof) :is-shared false}) mfile {:filename "sample.jpg" - :path (th/tempfile "app_tests/test_files/sample.jpg") + :path (th/tempfile "backend_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} diff --git a/backend/test/backend_tests/test_rpc_profile.clj b/backend/test/backend_tests/rpc_profile_test.clj similarity index 99% rename from backend/test/backend_tests/test_rpc_profile.clj rename to backend/test/backend_tests/rpc_profile_test.clj index 8e89029925..1c97a904b5 100644 --- a/backend/test/backend_tests/test_rpc_profile.clj +++ b/backend/test/backend_tests/rpc_profile_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-rpc-profile +(ns backend-tests.rpc-profile-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] @@ -110,7 +110,7 @@ :profile-id (:id profile) :file {:filename "sample.jpg" :size 123123 - :path (th/tempfile "app_tests/test_files/sample.jpg") + :path (th/tempfile "backend_tests/test_files/sample.jpg") :mtype "image/jpeg"}} out (th/mutation! data)] diff --git a/backend/test/backend_tests/test_rpc_project.clj b/backend/test/backend_tests/rpc_project_test.clj similarity index 99% rename from backend/test/backend_tests/test_rpc_project.clj rename to backend/test/backend_tests/rpc_project_test.clj index a8e4286d80..24c745a836 100644 --- a/backend/test/backend_tests/test_rpc_project.clj +++ b/backend/test/backend_tests/rpc_project_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-rpc-project +(ns backend-tests.rpc-project-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] diff --git a/backend/test/backend_tests/test_rpc_team.clj b/backend/test/backend_tests/rpc_team_test.clj similarity index 99% rename from backend/test/backend_tests/test_rpc_team.clj rename to backend/test/backend_tests/rpc_team_test.clj index 23dedbe88f..302f80dc50 100644 --- a/backend/test/backend_tests/test_rpc_team.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-rpc-team +(ns backend-tests.rpc-team-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] diff --git a/backend/test/backend_tests/test_rpc_viewer.clj b/backend/test/backend_tests/rpc_viewer_test.clj similarity index 98% rename from backend/test/backend_tests/test_rpc_viewer.clj rename to backend/test/backend_tests/rpc_viewer_test.clj index e09ba7846f..baaa416edb 100644 --- a/backend/test/backend_tests/test_rpc_viewer.clj +++ b/backend/test/backend_tests/rpc_viewer_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-rpc-viewer +(ns backend-tests.rpc-viewer-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.common.uuid :as uuid] [app.db :as db] [clojure.test :as t] diff --git a/backend/test/backend_tests/test_storage.clj b/backend/test/backend_tests/storage_test.clj similarity index 97% rename from backend/test/backend_tests/test_storage.clj rename to backend/test/backend_tests/storage_test.clj index af5cd7f621..b47e98b096 100644 --- a/backend/test/backend_tests/test_storage.clj +++ b/backend/test/backend_tests/storage_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-storage +(ns backend-tests.storage-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.common.exceptions :as ex] [app.common.uuid :as uuid] [app.db :as db] @@ -126,7 +126,7 @@ :is-shared false}) mfile {:filename "sample.jpg" - :path (th/tempfile "app_tests/test_files/sample.jpg") + :path (th/tempfile "backend_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} @@ -196,12 +196,12 @@ :project-id proj-id :is-shared false}) - ttfdata (-> (io/resource "app_tests/test_files/font-1.ttf") + ttfdata (-> (io/resource "backend_tests/test_files/font-1.ttf") io/input-stream io/read-as-bytes) mfile {:filename "sample.jpg" - :path (th/tempfile "app_tests/test_files/sample.jpg") + :path (th/tempfile "backend_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} @@ -267,7 +267,7 @@ :project-id (:default-project-id prof) :is-shared false}) mfile {:filename "sample.jpg" - :path (th/tempfile "app_tests/test_files/sample.jpg") + :path (th/tempfile "backend_tests/test_files/sample.jpg") :mtype "image/jpeg" :size 312043} diff --git a/backend/test/backend_tests/test_telemetry_task.clj b/backend/test/backend_tests/tasks_telemetry_test.clj similarity index 96% rename from backend/test/backend_tests/test_telemetry_task.clj rename to backend/test/backend_tests/tasks_telemetry_test.clj index f1b177467b..7ff727b8bc 100644 --- a/backend/test/backend_tests/test_telemetry_task.clj +++ b/backend/test/backend_tests/tasks_telemetry_test.clj @@ -4,9 +4,9 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns backend-tests.test-telemetry-task +(ns backend-tests.tasks-telemetry-test (:require - [backend-tests.test-helpers :as th] + [backend-tests.helpers :as th] [app.db :as db] [app.emails :as emails] [app.util.time :as dt] diff --git a/backend/test/backend_tests/test_files/template.penpot b/backend/test/backend_tests/test_files/template.penpot new file mode 100644 index 0000000000000000000000000000000000000000..e0c81bb83ef474a9e190d2f99a335e58422ae9e0 GIT binary patch literal 8815 zcmX9^2RzjO|Nq<_?yTHdNygcfan6=;MxyLJ3g;ZocFvBDtca|JQf8_ziKvWZlaN`7 z>{V7|@Bhc||GE3z`~7;)*X#9uzTWTG`}H^oBX_(hA>XMD`2PnY{!ZoyaL!_dc z=B@PP@AQB@{d{~#6e$mUpvMlv$Iq4IMk12O-GaQmr(B7_L~lR;({>a7cnZN|6yt;U z^dnF1kbK8o@PWh$j5J2tg^c$lc#QktNxowsedI>+CI2R7Gs=C`KuguPZSW)QX=!1yoPq zq@!0O#^21{R)* zE4FmKk?T2X<@QNiQExD|=BQ^hY#--_{6!9(*a16%JX7#wX-|Lm5Rxm!!yM=q^ufFP z`;mMpfrY<8sr-q){(cl0$h^sJ(Y#q)tO*M!gufs8lsPGHKZ4g*l0TkI^qoOW1yaa< zUf{g5p(eW!qBj}jGH<0@JWRG*8XWi1uTa@A?5|L)*`x=N<&!GtDFDCVvIsK0#&JELL4b+`=frz&^%O8sJc4{&eDNf2 zL~0sSd{4o-aWc_6kYzSdVIKD1_$iQPO!Zy7@m@r%iz-&pO+k@JP*G4KDq#q21UZ7T z98ra+pyG;gRhE^gPg~ z1`#O}iuae1mBz>bYMP<`_*16PE<|^due#XZx&|>c$yHs|EWo$Lw&q`164!S#ZtS7s=shaERfr+|UNJxlu zh@7+^*W!RpGZ$>{nKf==z!Ko@<= zJDC{h7epoy)x}PSaMcn+%Mi(=V4|xY+0O?AY9P@PM4P&pxeO>JD6fnL1JM^$8u?T^ zC!>H40D*!F3Wb8dP$&!vqk++Y4=wo8A?QyVoYui$FtCIcK}UO9*bJZ$Pyhk@njf{aM|lL_>}ucH zU?@JH^J6{>l3c$Vu)Oyd0I=OrTS2@hfD8`m^gxKV3j;)aiYiIsmHQS&1$eCjc=q0W zKG=Y5ZhubD?R1Px1Av=O4T@s9ic5JwZ~8Y2)CdSkCb(o#ucR};Pg>}IFECI?e@|Ao zNz%k-_?GZ3nG51R$8iVnxmiDrkd(+>-lCN+`%XVJsFNAbqg%1PRIFI8V^4zKQ9U2q zzn1rbG7`88h?UBsAOL?aFe7V?3P^A*dnhmATE`F1ao#)u=*%}(ZIQn{xYNG;4}ajo5Y*(%~r=nZJE`K)eMeoc30BrH&(f4Ilry!3% z7ZTljG#nPIL3tqyFN=Mkxv26)|OiXUXexWjT%*R*6Z?dF;40e11)Mcdi;Y)mS8=>RJ z{=fl?M#7)p%#lAkKW^cex_CL9cqen_?meAL2gu&spDM@fMYCPjnBL~WWcTt*@%1pL zV6vaODGb|j*`^~@Oogxz=8EHaLieV0$NEmO?MIm8OQ1W-!;8xg_hT&QNv|c)UD0o~ zK2HDDc5mDc2^VcDSyKO2KzbkpbX}0+f6U!}zE5GtU3B9cASX$&x)~A^_&_7%fi9%8 z+;hJ-$m13WxCGxOGR+wTFsOH%Nmr=gWt&fcnYo`GE%GwfWA*3or1@71vC?&w)@ zY2$MA#6MdCP2YL1;wM$+8H<)OLQp0xl)=^fte7CXiO=-u%7L%+d+Mqt-rdD%VG;5? z&QLNvRMU~I#P~>@O(^vQc%S-8cn>+e*(BDPG-xE>pK>>!1I8*UUcI4n=M(2YSY-U< z)9Y6j_1dX0u2xe3b1mlm(E)zhG{;xz+%A2OU{(qXAM&LnT1~+848wF*O52~}+QmmA zedR(WJwmI4KTr#0LcQ0oJgdH^9>5nqt+v$~wv@QhdMC#}{rX`pzFItOr+FetpEgE( zX6r1(xrsMPXY2$piOyUOYsU@o@L9n1YvsQw9i(TVH7UQn*Lvy~>>BSL zbk)B-{P>0M>BIWnBK5JhZ~6(fZ+EhF2Hzezfw)pbfg2nREhB`EmKFkTXXpSLq?|k% zpkX=4XH1(rc**VND@B)Hb+WDM97+ks;xEpFhPJqc>np~|+Jx}cbCdR z@qRnMirTqK{ij&?Joj8jrL~`Y@px{PS0_C^z^q-m^Ca@;o2sXAxA=XP+cF-P3aEay z`xN{5B0Bob)>F!VSP71_K!0rL2R11&jNUV@Sx@5Spv33XACU{q`kEeT2O&n}uIXl#n02X`T3z|Q$l`3Hvnfpt@wVy#d$*MHKCPFddigCp*v*WGywfSBT zQlBR3O)q;-7OnY{2u)B51VUeb=wgwQ4kyub2O*M5BV2~c?K z%E#^AtvPRb!Cpc#X3&nW#yw|rwI|Y|{I#QUrlO}uUe>Yj+!GIpf(m->6Tq%1H}Kih z;*?9EEGoImk1slP;O!4*5kf;Yh6M#_pYxr{EFY^5%Y*7@y>a-n&^Vj0!E1h7b3vU! zUSIR6Q)9~b*Qi2VWkTz@4NxhXK9@+xSCHM=AI9wut`39Bipe!HI+t!Yr}jkA!s>pB zT?(TFI)xzlEr(X(-kVkK$=m>}=+bV;n#*_kLhgGrr{ZDrFGsS; z$U(&80n6K<$h+r$C?#3&n}F+5l$CL&qIql^LArK0Q5C*<0$9yE$0}Zpf5*}hSEp1N zyT!j)f603`)S)s$*~AR;wF(z0e_JNm^f<3;dPwsncK032BGV@+o&DRQ?DNd`m-C$; zj}8o1Yz~OCTt`P<_A?$;;p1E$|H?Lm8~*PDw6Kg3f}q-S{fhdRlooU28uKVP-vVp^jIddG2~8pi1D6=)`-}!W;FL~ zkr!)u5X-ZgAF2zEdf996423&u@V9EPJo_tDO5>;ov^V-ul-YwQ+qqocz_OsPH9xK| zwUWOWwaIo#D6w})7X7l|d7BwvI1kRJfdqXZC@q4P0S<-0&=oYaNOB;65->Hp{kDl! zSXR!QjYCL53-1!0p204N!OCk-;bQ1C!P8@Mm-<3K8JDWp)%d!0WFF%p+XCj9Kf(xhAFqZ>b%&CVH#9dtEo zeCz!0BCOx1X#Zd)AR(c6@YSOZA?KCflZUL}%9S%@3Xc=R)$HRCRJVa+r8h*hK#ro&dgU zY9cGwEUn}Uwu^5lX1*GbfBT7JZAo<4HQcwhhDv5o{zIu6;2ANEI+J?C{BEsHdj#G@ zyxu%LfBomWYwX1V)`5#&RaY{#jtABrR*-{}cIN}0?-!2|7F6}aLo!Q}zyoJ=@vQ>(v;p3GAEtSp6k%^DJ~f=Q zuDKE#?xUK0mrTAJuJbNIO{YXo^uA+jSC^VPIV1bm?iE))uL8&gnSZ}@ojyoU9)Ihq z)_g}^Wm&micl*F*BS@t3s@&3VgUh!BPJnWxpj*+q;=55txoRmN;Caq9cY`l@R{d)k z)H^Q*(Ga#YzV)v^*E?LyfaBlz7O(XxWS!;kT~Nzo&kz1AiOyyhnyLi)UggTze%R{z zE_FoSO1^cy-euy4h-dP(v%hq|7Cez+X&bnFPvSt;@z>ArwuUmt3csa?r4IXZOPt3w z`UcO>R``^Z@eEqkQE%$diXj&mhehPyzaA&wNxps2b16aoo9`q)vdN%TrFom)NU9_x zr@&?M%Si21kAg!rycJ^}aK^tr#^Rdi*n;?@YV7;6>OiAYE}7VKbaG^zvj#>!gnbc} zW_qnT4}pUI-v2Hc5Lq*D%P?INz-Z;;1CUMq6*%YUv~*2SA^97(B_Xeb^X}X<9RN-D ziy~mrJ1uHq&;0TPC|SFbt+}AqCKA{MCZV26a_~O|KKzLH|n-3Vu1e`m0%7Cq}ovkz{fbFJp`!()@MM+sS zo0C@ih=ut%;T@haD27;@P!0JkV0tqXA{V;`&!jQGicg!8gSxhD&=xt%@@QptW3}S5 z-p)aDg@je$LrCY@y@lw{j@3116Cq)u!uVCE-vR#~XT@pzL;4thD;N2j_K8d{J+ni(Y5S!3_#kkLmPcg>Jty&NQcbQ zUHzf@E47_s<~#rB^%(jfhMb$DQ=NN1joFVSY)$}W>JvI4{&X2p0v8~BOmBKT3Q(yG z2GOq-AC<6-aT5YW;b}A>=gtnfI+>+M>RsMy>BAVSn*s(6D{8nnMz(xYsqmKcqI6Bm zB5vVq>McPepnd{;c&VTF#B?oIN>ujlb#o;w%;50fD=&jiO9Iz-6|D0|=1#%8Gus@S z63vNU-p;1$1spK!vrp*~?a2Ct%13dL4E_b!(i&m2oT%ncamr@rtgmN?f7Qf0Y3GRF zEs-{0cjqsElW)wfXZ27xN$%dTP2UR!p$lCZm(O)9U;J$mhq_P9oh<^uhzEcKXsFKU zJiwp75!%Os1N_0D2@ezp01N<(3IYZP@KAR-sNz5=f(ivVfIKijVoC?(IRk(;fIAO> zZD%+$fKC}a$s|D^6`+UFf_HNWfJC8X&GIYO z1hh=?0qOng(M^~soHG)<^UUB#BGe#lv_ve|?cVpy80wErJ_}N6`p2=hnyKl*>-*Pg zaNCU-NI3#0kYH+BD&>~?`beem%V0Upw_?F(fBz89>NE|f=0i1Jq*w6n)xWeqv#|WL zuU!7Is?z3SEYg-Dlsy>fhg%lBiDqJo2u)3|8cR9YfJE5dji1fybyn+s_Iy37ACsDB zxfq^v6w4y14bWx8xc;RuH!$y?0Q8&O2}08Cw9wm#<}B0hMwVsQ0Zx(21Dxi&pb!yC)?8{!?T1Zq<`NkLkqKNiL%}NeTTQXkp|gk z+MTj`yJ6~su|81&bLP2%x%$H|e)nWt-9y0e`cpGYdD0y4gLm;ozj|52{2EDpr z6CoE>zAL2E%&M`Tx|;G9a|~&;aGBXZaaCP$ZT`uh2zX>O@~c{@y4$nY^|fe#ju~)# z;RaE^J)x`g`jWA}hVigojG_SKZl!*BZ$)4zXY_66-Kl%8CVfp@xoY{0XSXhb*HZax z8mw>+>gZYVM9Ui*i26i2GT~dH$3>>s=MhGsE)vU!%i-%(R$B!TXyX9s_vmOz3^yzk zzJ9<@zs^#$n=o)w=Z}WKW-fByBr#H~RU>nn-@xl|3mHYX%1`%r;ropwmiMSw-o)wF zM;_v-dDOM7l2My_o6M%MatZm8b5lH-IojgD|CqKu~CmtSO*{6aX=K_OPxi zjO9PyjNH5z(7Gri9TI%cn>FX^M?MkmMQ#0{kA?q+gqa{=YR^o>61AJnim%#cS~`n| za`gX8-FD%oiKa?SL7D!26;ZzLf=nw+G|5}LAZK(9KJLhY7wFdPSj?S~ZfLyFMy|PD zW`n%aM?ZI(vkot1E)~itfq5YoE;Nobt9&U}EKemLcY#b^1UEJShJetb1k3=mENDde zYw5^<=>ByKuE{xV3S>He%{8NK4W{6|6PH63ZAf~DK66!tE6m=C#*8Um<-Yp6Ir4&j zT;|R5DDMknk5GW3Bx8Ft5}i1$=qpUiVL_4mxl-o-lETJGH6jtBaagEfKs5Em%QVwy zHschFN>$z1%r9$Rl)1F*&)sbvH=reX5TnI~y7AAv&gfVa59&KkMIc`^sCBJe6ts7y zC7H|3Z09@Y(3kXQ-U$z^rD0V{A@YwWE+&^=xqSjWfcM*G3d^*V&>N(4WW;3mg%UG4kc8ndS;g@{vwcw_KO|`y&2_wr`^2^@26EX|#kV4=<@(GT@Bt0K4ok@!_-%`0QA6 z-+fd&Om(;cg1=Wq5&_e`&8^w@~RyZEjTH=0~=%Ord$oeT?ystGt{BHYu{l@B; z;YYfSHilYDKoSy=O^HJ!B3`MgtZr8~V$_%(-FAvTJPO}h`tioGKKwj;Q+B654@-S> zPf{nn;x6yxu-vGuF7oHVSE%>Pm2=&A+Z*N{_RXVyoTH)dN88?yOTAzB`H<}M(ZOf> ze}z>{@Z|hGb;0!uY0~~Dz{3w7V+w}9Y=AD|4~tQ+_j604UMx<$+OKT?vlyG`EU}pG z>mcH)tXsZ}NM$>+70G%86^wX1sP~sO_@7Ka0cH zMw%x-VHkpvqB35^OoigIpv1w}b*f@F4vaxe> zpio=_T-?08{QUfEX9a}?`Gk4+`1!zIa60%I1mX-IJBpp}q!Em11FnE8H)u)VSxX0} zfkDAtKpngr!XPv-W;z=9X+RNvX?C@cumT4cgpevgl<59WyM}rhENkvHYz2m-*Iy9{blCjw|7YvcF zPWd7etid&Xx8YXFg$GJ)7pK#cIx4KnJl6_}3%aE&d*tN5kN+}JZSaN|}V`&m9Xyz<5*DLg0Oy2gWo{{+Q$TT9<_ z@qT}DiDi1LWT*PWhl^5gJT%I@%z8kMDg@fN#>-G_)t~Sjj<4MYN{obpokHNVbSD6N z$i5~yq_&}};;8q-k-ennIgirzzM-K52iqs#Yr0#e7M;SUtyac-w`_;o?gxc_h=@G* zV9|TGbd(&fZ)Gjq)O~p<>Cx2w*?1Xbokhq;D|ch3EN2Q80t|SISk`?sv*M5-Hd!1b zZF&1f+5LI8{Exo)(DsB0Y`GDk_U6kyzS-@Ni2Gj}0(EEqI4f8wWlyMIYROoWe-@b&hUjcc2@E13ppK?! z?#l`N6LAlRY)S}${~8Q;+~p6wBa7$l?iDIKzPXZNy26{zUUi_;r1v(tPUF_Yy1MRu z6;)ffoT|x3l{YYhg`XX1cXO0~C|RdbMdlzywvoKMc9ECE-z#Ze-0TewG->Y`@g+Xm zmy2863==ui{}Xl1q)Lt5OT4afVK1WYV-H*Ab84Vm(Lu}O7ED0tiW0Uv@?7&)xLQVn z$8zHH7bAQO3RyRPZXDK+jl13H9B5R@9*CUk3FS7?_^KA`I@MlNTNMkD5l1o$w*3Cv z+_iY|TSd*DQ*lJ)BjxtsOqP?M(Zb>o+I9?rw{(DxmFgVLIVT<)!)0+-IZjtN4W}D3 zn;yfi3}^eU_~|yj+g_xOS3}8{UHt_OvhcDy-~Er6&+${|8+Mm|`YC_%tD>lJ6EnE!X{8HRC)TY7hkjw6v6n|rYP`NcfpjNG^DgW;mm z8Ewwhbo4I+h`+2ZnY(mlS`(u6=q}KQyN^s5GM_)k+End(dC*t~@={n8O?oB!)Xi_J z!kGOp7pYkJG4C0t`8HY)<+w9UTVDPPbcIS4b|rxfdq&kTyL)kdxI*7GGaEjpq(PH= zIgcNu)i!l12s&PSc~<17Z(3ao_`d}e0?mlQh9mOb^1oFY1ifMgTb^iew*x`QhTLyI zGVSg73!yA&`lQ3P-XSe`BZ~#1u_);S!HA}h*-av72WyZ@p~dnmg7iOMI<;UAjC3X) zZ^XL0vKX(m8+02o#MzTA5!GX9-qB8DA{|9)@g0V%3|}i4l_9SpAAUD{itGM+G4O-4 zkw%#^ozkWI9d|34M<|Wcp2!9%g+8V9#;IsD(_!RqJ z`&rj!8WD0pN}>Q89KlLW`p)toM&v`B3t^rO#Z#6Y?^xcQ(}f=GyYsJ#P4PTh68HR)HGQ7(+w8fIF;6L`oAIuI zhqQkJyYh99`(Sv_D`Frf`ialTH-Op@M? Date: Tue, 8 Nov 2022 11:04:40 +0100 Subject: [PATCH 183/682] :books: Add documentation to objects and pointer map namespaces --- backend/src/app/util/objects_map.clj | 10 ++++++++++ backend/src/app/util/pointer_map.clj | 30 ++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/backend/src/app/util/objects_map.clj b/backend/src/app/util/objects_map.clj index 643aa6d5aa..8d053a6938 100644 --- a/backend/src/app/util/objects_map.clj +++ b/backend/src/app/util/objects_map.clj @@ -5,6 +5,16 @@ ;; Copyright (c) KALEIDOS INC (ns app.util.objects-map + "Implements a specialized map-like data structure for store an UUID => + OBJECT mappings. The main purpose of this data structure is be able + to serialize it on fressian as byte-array and have the ability to + decode each field separatelly without the need to decode the whole + map from the byte-array. + + It works transparently, so no aditional dynamic vars are needed. It + only works by reference equality and the hash-code is calculated + properly from each value." + (:require ;; [app.common.logging :as l] [app.common.transit :as t] diff --git a/backend/src/app/util/pointer_map.clj b/backend/src/app/util/pointer_map.clj index 586cce335e..9124ed3c64 100644 --- a/backend/src/app/util/pointer_map.clj +++ b/backend/src/app/util/pointer_map.clj @@ -5,6 +5,36 @@ ;; Copyright (c) KALEIDOS INC (ns app.util.pointer-map + "Implements a map-like data structure that provides an entry point for + store its content in a separated blob storage. + + By default it is not coupled with any storage mode and this is + externalized using specified entry points for inspect the current + available objects and hook the load data function. + + Each object is identified by an UUID and metadata hashmap which can + be used to locate it in the storage layer. The hash code of the + object is always the hash of the UUID. And it always performs + equality by reference (I mean, you can't deep compare two pointers + map without completelly loads its contents and compare the content. + + Each time you pass from not-modified to modified state, the id will + be regenerated, and is resposability of the hashmap user to properly + garbage collect the unused/unreferenced objects. + + For properly work it requires some dynamic vars binded to + appropriate values: + + - *load-fn*: when you instantatiate an object by ID, on first access + to the contents of the hash-map will try to load the real data by + calling the function binded to that dynamic var. + + - *tracked*: when you instantiate an object or modify it, it is updated + on the atom (holding a hashmap) binded to this var; if no binding is + available, no tracking is performed. Tracking is needed for finally + persist to external storage all modified objects. + " + (:require [app.common.logging :as l] [app.common.transit :as t] From 38ed3b076a32f14f76310759ca1192e26a841797 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Nov 2022 11:59:25 +0100 Subject: [PATCH 184/682] :fire: Remove unused config entry --- backend/src/app/config.clj | 2 -- 1 file changed, 2 deletions(-) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 5ce6f52b11..7d8556e0cf 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -151,7 +151,6 @@ (s/def ::http-server-max-multipart-body-size ::us/integer) (s/def ::http-server-io-threads ::us/integer) (s/def ::http-server-worker-threads ::us/integer) -(s/def ::initial-project-skey ::us/string) (s/def ::ldap-attrs-email ::us/string) (s/def ::ldap-attrs-fullname ::us/string) (s/def ::ldap-attrs-username ::us/string) @@ -248,7 +247,6 @@ ::http-server-max-multipart-body-size ::http-server-io-threads ::http-server-worker-threads - ::initial-project-skey ::ldap-attrs-email ::ldap-attrs-fullname ::ldap-attrs-username From 59ba87d9cd8fe7d6788a44ce11388dedff7b2f3a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 8 Nov 2022 11:26:58 +0100 Subject: [PATCH 185/682] :sparkles: Properly report malformed json error --- backend/src/app/http/middleware.clj | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index 9ccdd8370e..ce0471aff3 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -19,6 +19,7 @@ [yetti.request :as yrq] [yetti.response :as yrs]) (:import + com.fasterxml.jackson.core.JsonParseException com.fasterxml.jackson.core.io.JsonEOFException io.undertow.server.RequestTooBigException java.io.OutputStream)) @@ -60,10 +61,13 @@ :code :request-body-too-large :hint (ex-message cause))) - (instance? JsonEOFException cause) + + (or (instance? JsonEOFException cause) + (instance? JsonParseException cause)) (raise (ex/error :type :validation :code :malformed-json - :hint (ex-message cause))) + :hint (ex-message cause) + :cause cause)) :else (raise cause)))] From dfb73192b8b70a02fdbfa69fbbdf6fc4180edf98 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Nov 2022 11:59:41 +0100 Subject: [PATCH 186/682] :paperclip: Change rpc middleware order (minor) --- backend/src/app/rpc.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 798b67dca3..69a63db8b3 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -161,8 +161,8 @@ [cfg f mdata] (let [f (as-> f $ (wrap-dispatch cfg $ mdata) - (wrap-metrics cfg $ mdata) (retry/wrap-retry cfg $ mdata) + (wrap-metrics cfg $ mdata) (climit/wrap cfg $ mdata) (rlimit/wrap cfg $ mdata) (wrap-audit cfg $ mdata)) From b20d2badfebc7128c932cf2298c76eb155381adb Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Nov 2022 12:00:33 +0100 Subject: [PATCH 187/682] :sparkles: Load workspace thumbnails in a separated request --- backend/src/app/rpc/commands/files.clj | 80 ++++++++++--------- backend/src/app/rpc/commands/files/create.clj | 2 +- backend/src/app/rpc/commands/files/update.clj | 2 +- backend/src/app/rpc/commands/viewer.clj | 23 +++--- backend/src/app/rpc/mutations/files.clj | 2 +- backend/src/app/rpc/queries/files.clj | 10 ++- frontend/src/app/main/data/workspace.cljs | 6 +- .../app/main/data/workspace/persistence.cljs | 4 +- .../app/main/data/workspace/thumbnails.cljs | 14 ++-- frontend/src/app/main/refs.cljs | 4 +- frontend/src/app/main/repo.cljs | 2 + 11 files changed, 83 insertions(+), 66 deletions(-) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 82d2dd779b..775221c83b 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -90,7 +90,7 @@ where f.id = ? and ppr.profile_id = ?") -(defn retrieve-file-permissions +(defn get-file-permissions [conn profile-id file-id] (when (and profile-id file-id) (db/exec! conn [sql:file-permissions @@ -100,7 +100,7 @@ (defn get-permissions ([conn profile-id file-id] - (let [rows (retrieve-file-permissions conn profile-id file-id) + (let [rows (get-file-permissions conn profile-id file-id) is-owner (boolean (some :is-owner rows)) is-admin (boolean (some :is-admin rows)) can-edit (boolean (some :can-edit rows))] @@ -154,7 +154,7 @@ ;; --- HELPERS -(defn retrieve-team-id +(defn get-team-id [conn project-id] (:team-id (db/get-by-id conn :project project-id {:columns [:team-id]}))) @@ -209,25 +209,7 @@ ;; --- COMMAND QUERY: get-file (by id) -(defn retrieve-object-thumbnails - ([conn file-id] - (let [sql (str/concat - "select object_id, data " - " from file_object_thumbnail" - " where file_id=?")] - (->> (db/exec! conn [sql file-id]) - (d/index-by :object-id :data)))) - - ([conn file-id object-ids] - (let [sql (str/concat - "select object_id, data " - " from file_object_thumbnail" - " where file_id=? and object_id = ANY(?)") - ids (db/create-array conn "text" (seq object-ids))] - (->> (db/exec! conn [sql file-id ids]) - (d/index-by :object-id :data))))) - -(defn retrieve-file +(defn get-file [conn id client-features] ;; here we check if client requested features are supported (check-features-compatibility! client-features) @@ -255,20 +237,10 @@ file))) -(defn get-file - [conn id features] - (let [file (retrieve-file conn id features) - thumbs (retrieve-object-thumbnails conn id)] - (assoc file :thumbnails thumbs) - #_file)) - (s/def ::get-file (s/keys :req-un [::profile-id ::id] :opt-un [::features])) - -;; TODO: this should be changed probably because thumbnails will not be included - (sv/defmethod ::get-file "Retrieve a file by its ID. Only authenticated users." {::doc/added "1.17"} @@ -280,6 +252,38 @@ (assoc :permissions perms))))) +;; --- COMMAND QUERY: get-file-object-thumbnails + +(defn get-object-thumbnails + ([conn file-id] + (let [sql (str/concat + "select object_id, data " + " from file_object_thumbnail" + " where file_id=?")] + (->> (db/exec! conn [sql file-id]) + (d/index-by :object-id :data)))) + + ([conn file-id object-ids] + (let [sql (str/concat + "select object_id, data " + " from file_object_thumbnail" + " where file_id=? and object_id = ANY(?)") + ids (db/create-array conn "text" (seq object-ids))] + (->> (db/exec! conn [sql file-id ids]) + (d/index-by :object-id :data))))) + +(s/def ::get-file-object-thumbnails + (s/keys :req-un [::profile-id ::file-id])) + +(sv/defmethod ::get-file-object-thumbnails + "Retrieve a file object thumbnails." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id file-id) + (get-object-thumbnails conn file-id))) + + ;; --- COMMAND QUERY: get-project-files (def ^:private sql:project-files @@ -313,7 +317,7 @@ ;; --- COMMAND QUERY: has-file-libraries -(declare retrieve-has-file-libraries) +(declare get-has-file-libraries) (s/def ::file-id ::us/uuid) (s/def ::profile-id ::us/uuid) @@ -327,7 +331,7 @@ [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (with-open [conn (db/open pool)] (check-read-permissions! pool profile-id file-id) - (retrieve-has-file-libraries conn params))) + (get-has-file-libraries conn params))) (def ^:private sql:has-file-libraries "SELECT COUNT(*) > 0 AS has_libraries @@ -337,7 +341,7 @@ AND (fl.deleted_at IS NULL OR fl.deleted_at > now())") -(defn- retrieve-has-file-libraries +(defn- get-has-file-libraries [conn {:keys [file-id]}] (let [row (db/exec-one! conn [sql:has-file-libraries file-id])] (:has-libraries row))) @@ -361,7 +365,7 @@ (defn get-page [conn {:keys [file-id page-id object-id features]}] - (let [file (retrieve-file conn file-id features) + (let [file (get-file conn file-id features) page-id (or page-id (-> file :data :pages first)) page (dm/get-in file [:data :pages-index page-id])] (cond-> (prune-thumbnails page) @@ -656,7 +660,7 @@ frame-ids (if (some? frame) (list frame-id) (map :id (ctt/get-frames (:objects page)))) obj-ids (map #(str page-id %) frame-ids) - thumbs (retrieve-object-thumbnails conn id obj-ids)] + thumbs (get-object-thumbnails conn id obj-ids)] (cond-> page ;; If we have frame, we need to specify it on the page level @@ -681,7 +685,7 @@ [{:keys [pool] :as cfg} {:keys [profile-id file-id features] :as props}] (with-open [conn (db/open pool)] (check-read-permissions! conn profile-id file-id) - (let [file (retrieve-file conn file-id features)] + (let [file (get-file conn file-id features)] {:file-id file-id :revn (:revn file) :page (get-file-data-for-thumbnail conn file)}))) diff --git a/backend/src/app/rpc/commands/files/create.clj b/backend/src/app/rpc/commands/files/create.clj index 0baa572969..4a7c5d6005 100644 --- a/backend/src/app/rpc/commands/files/create.clj +++ b/backend/src/app/rpc/commands/files/create.clj @@ -77,7 +77,7 @@ [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] (db/with-atomic [conn pool] (proj/check-edition-permissions! conn profile-id project-id) - (let [team-id (files/retrieve-team-id conn project-id)] + (let [team-id (files/get-team-id conn project-id)] (-> (create-file conn params) (vary-meta assoc ::audit/props {:team-id team-id}))))) diff --git a/backend/src/app/rpc/commands/files/update.clj b/backend/src/app/rpc/commands/files/update.clj index 9a65686636..4752efb1a7 100644 --- a/backend/src/app/rpc/commands/files/update.clj +++ b/backend/src/app/rpc/commands/files/update.clj @@ -282,7 +282,7 @@ (when (and (:is-shared file) (seq lchanges)) (let [team-id (or (:team-id file) - (files/retrieve-team-id conn (:project-id file)))] + (files/get-team-id conn (:project-id file)))] ;; Asynchronously publish message to the msgbus (mbus/pub! msgbus :topic team-id diff --git a/backend/src/app/rpc/commands/viewer.clj b/backend/src/app/rpc/commands/viewer.clj index 1f757b86b5..2d77a7c222 100644 --- a/backend/src/app/rpc/commands/viewer.clj +++ b/backend/src/app/rpc/commands/viewer.clj @@ -23,18 +23,19 @@ (defn- get-bundle [conn file-id profile-id features] - (let [file (files/get-file conn file-id features) - project (get-project conn (:project-id file)) - libs (files/get-file-libraries conn false file-id) - users (comments/get-file-comments-users conn file-id profile-id) + (let [file (files/get-file conn file-id features) + thumbnails (files/get-object-thumbnails conn file-id) + project (get-project conn (:project-id file)) + libs (files/get-file-libraries conn false file-id) + users (comments/get-file-comments-users conn file-id profile-id) - links (->> (db/query conn :share-link {:file-id file-id}) - (mapv slnk/decode-share-link-row)) + links (->> (db/query conn :share-link {:file-id file-id}) + (mapv slnk/decode-share-link-row)) - fonts (db/query conn :team-font-variant - {:team-id (:team-id project) - :deleted-at nil})] - {:file file + fonts (db/query conn :team-font-variant + {:team-id (:team-id project) + :deleted-at nil})] + {:file (assoc file :thumbnails thumbnails) :users users :fonts fonts :project project @@ -45,7 +46,7 @@ [conn {:keys [profile-id file-id share-id features] :as params}] (let [slink (slnk/retrieve-share-link conn file-id share-id) perms (files/get-permissions conn profile-id file-id share-id) - thumbs (files/retrieve-object-thumbnails conn file-id) + thumbs (files/get-object-thumbnails conn file-id) bundle (-> (get-bundle conn file-id profile-id features) (assoc :permissions perms) (assoc-in [:file :thumbnails] thumbs))] diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 20b3a54482..f419b63756 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -33,7 +33,7 @@ [{:keys [pool] :as cfg} {:keys [profile-id project-id features components-v2] :as params}] (db/with-atomic [conn pool] (proj/check-edition-permissions! conn profile-id project-id) - (let [team-id (cmd.files/retrieve-team-id conn project-id) + (let [team-id (cmd.files/get-team-id conn project-id) features (cond-> (or features #{}) ;; BACKWARD COMPATIBILITY with the components-v2 param components-v2 (conj "components/v2")) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 8dedbc916f..3b7e8052a3 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -36,6 +36,12 @@ (s/and ::cmd.files/get-file (s/keys :opt-un [::components-v2]))) +(defn get-file + [conn id features] + (let [file (cmd.files/get-file conn id features) + thumbs (cmd.files/get-object-thumbnails conn id)] + (assoc file :thumbnails thumbs))) + (sv/defmethod ::file "Retrieve a file by its ID. Only authenticated users." {::doc/added "1.0" @@ -48,7 +54,7 @@ components-v2 (conj "components/v2"))] (cmd.files/check-read-permissions! perms) - (-> (cmd.files/get-file conn id features) + (-> (get-file conn id features) (assoc :permissions perms))))) ;; --- QUERY: page @@ -96,7 +102,7 @@ (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter features (cond-> (or features #{}) components-v2 (conj "components/v2")) - file (cmd.files/retrieve-file conn file-id features)] + file (cmd.files/get-file conn file-id features)] {:file-id file-id :revn (:revn file) :page (cmd.files/get-file-data-for-thumbnail conn file)}))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 4b744acc3b..f4ffb8671e 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -132,7 +132,8 @@ (->> (rx/of bundle) (rx/mapcat (fn [bundle] - (let [bundle (assoc bundle :file (t/decode-str (:file-raw bundle))) + (let [file (-> bundle :file-raw t/decode-str) + bundle (assoc bundle :file file) team-id (dm/get-in bundle [:project :team-id])] (rx/merge (rx/of (dwn/initialize team-id file-id) @@ -148,7 +149,7 @@ (unchecked-set ug/global "name" name))))) (defn- file-initialized - [{:keys [file users project libraries file-comments-users] :as bundle}] + [{:keys [file thumbnails users project libraries file-comments-users] :as bundle}] (ptk/reify ::file-initialized ptk/UpdateEvent (update [_ state] @@ -158,6 +159,7 @@ :workspace-undo {} :workspace-project project :workspace-file (assoc file :initialized true) + :workspace-thumbnails thumbnails :workspace-data (-> (:data file) (ctst/start-object-indices) ;; DEBUG: Uncomment this to try out migrations in local without changing diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index cc148353ce..4a85fcae1a 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -292,13 +292,15 @@ (features/active-feature? state :components-v2) (conj "components/v2"))] (->> (rx/zip (rp/cmd! :get-raw-file {:id file-id :features features}) + (rp/cmd! :get-file-object-thumbnails {:file-id file-id}) (rp/query! :team-users {:file-id file-id}) (rp/query! :project {:id project-id}) (rp/cmd! :get-file-libraries {:file-id file-id}) (rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id})) (rx/take 1) - (rx/map (fn [[file-raw users project libraries file-comments-users]] + (rx/map (fn [[file-raw thumbnails users project libraries file-comments-users]] {:file-raw file-raw + :thumbnails thumbnails :users users :project project :libraries libraries diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index ce478da9bd..2c2a60f274 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -52,7 +52,7 @@ ptk/UpdateEvent (update [_ state] (let [object-id (dm/str page-id frame-id)] - (assoc-in state [:workspace-file :thumbnails object-id] nil))))) + (update state :workspace-thumbnails dissoc object-id))))) (defn update-thumbnail "Updates the thumbnail information for the given frame `id`" @@ -63,8 +63,8 @@ (ptk/reify ::update-thumbnail ptk/WatchEvent (watch [_ state _] - (let [object-id (dm/str page-id frame-id) - file-id (or file-id (:current-file-id state)) + (let [object-id (dm/str page-id frame-id) + file-id (or file-id (:current-file-id state)) blob-result (thumbnail-stream object-id)] (->> blob-result @@ -80,7 +80,7 @@ (let [params {:file-id file-id :object-id object-id :data data}] (rx/merge ;; Update the local copy of the thumbnails so we don't need to request it again - (rx/of #(assoc-in % [:workspace-file :thumbnails object-id] data)) + (rx/of #(update % :workspace-thumbnails assoc object-id data)) (->> (rp/cmd! :upsert-file-object-thumbnail params) (rx/ignore)))) @@ -169,6 +169,6 @@ (ptk/reify ::duplicate-thumbnail ptk/UpdateEvent (update [_ state] - (let [page-id (get state :current-page-id) - old-shape-thumbnail (get-in state [:workspace-file :thumbnails (dm/str page-id old-id)])] - (-> state (assoc-in [:workspace-file :thumbnails (dm/str page-id new-id)] old-shape-thumbnail)))))) + (let [page-id (:current-page-id state) + thumbnail (dm/get-in state [:workspace-thumbnails (dm/str page-id old-id)])] + (update state :workspace-thumbnails assoc (dm/str page-id new-id) thumbnail))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 5717f7c86d..de39b2c043 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -408,7 +408,7 @@ st/state)) (def thumbnail-data - (l/derived #(dm/get-in % [:workspace-file :thumbnails] {}) st/state)) + (l/derived #(get % :workspace-thumbnails {}) st/state)) (defn thumbnail-frame-data [page-id frame-id] @@ -434,7 +434,7 @@ (defn get-flex-child-viewer? [ids page-id] (l/derived - (fn [state] + (fn [state] (let [objects (wsh/lookup-viewer-objects state page-id)] (filterv #(= :flex (:layout (cph/get-parent objects %))) ids))) st/state =)) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index dcac66f926..3e6cba60f5 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -13,6 +13,8 @@ [beicon.core :as rx])) (derive :get-file ::query) +(derive :get-file-object-thumbnails ::query) +(derive :get-file-libraries ::query) (defn handle-response [{:keys [status body] :as response}] From 5192b366694ab50c69cd3217fd97be4a1a0ecf55 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Nov 2022 12:00:54 +0100 Subject: [PATCH 188/682] :lipstick: Add some cosmetic adjustements --- frontend/src/app/main/data/workspace/thumbnails.cljs | 9 ++++----- frontend/src/app/main/ui/workspace/shapes/frame.cljs | 7 ++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 2c2a60f274..27cc69fb7f 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -36,11 +36,10 @@ ;; will be empty on first rendering before drawing the thumbnail and we don't want to store that (let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-empty='false']" object-id))] (if (some? node) - (-> node - (.toBlob (fn [blob] - (rx/push! subs blob) - (rx/end! subs)) - "image/png")) + (.toBlob node (fn [blob] + (rx/push! subs blob) + (rx/end! subs)) + "image/png") ;; If we cannot find the node we send `nil` and the upsert will delete the thumbnail (do (rx/push! subs nil) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index cbde0cc103..1de768035e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -45,9 +45,10 @@ (defn check-props [new-props old-props] - (and - (= (unchecked-get new-props "thumbnail?") (unchecked-get old-props "thumbnail?")) - (= (unchecked-get new-props "shape") (unchecked-get old-props "shape")))) + (and (= (unchecked-get new-props "thumbnail?") + (unchecked-get old-props "thumbnail?")) + (= (unchecked-get new-props "shape") + (unchecked-get old-props "shape")))) (defn nested-frame-wrapper-factory [shape-wrapper] From fde03e21b020480f156fc63a7ce72769703cb51e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Nov 2022 16:56:02 +0100 Subject: [PATCH 189/682] :tada: Add conditional reading to RPC --- backend/src/app/rpc.clj | 50 ++++++++------ backend/src/app/rpc/commands/auth.clj | 9 +-- backend/src/app/rpc/commands/binfile.clj | 11 +-- backend/src/app/rpc/commands/files.clj | 28 ++++++-- backend/src/app/rpc/commands/files/update.clj | 8 +-- backend/src/app/rpc/cond.clj | 67 +++++++++++++++++++ backend/src/app/rpc/helpers.clj | 54 ++++++++++++++- backend/src/app/rpc/mutations/files.clj | 8 +-- backend/src/app/rpc/mutations/teams.clj | 25 ++++--- backend/src/app/util/services.clj | 27 -------- backend/test/backend_tests/helpers.clj | 3 +- .../rpc_cond_middleware_test.clj | 42 ++++++++++++ 12 files changed, 242 insertions(+), 90 deletions(-) create mode 100644 backend/src/app/rpc/cond.clj create mode 100644 backend/test/backend_tests/rpc_cond_middleware_test.clj diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 69a63db8b3..b8a7417d23 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -16,6 +16,8 @@ [app.metrics :as mtx] [app.msgbus :as-alias mbus] [app.rpc.climit :as climit] + [app.rpc.cond :as cond] + [app.rpc.helpers :as rph] [app.rpc.retry :as retry] [app.rpc.rlimit :as rlimit] [app.storage :as-alias sto] @@ -25,6 +27,7 @@ [integrant.core :as ig] [promesa.core :as p] [promesa.exec :as px] + [yetti.request :as yrq] [yetti.response :as yrs])) (defn- default-handler @@ -33,23 +36,29 @@ (defn- handle-response-transformation [response request mdata] - (if-let [transform-fn (::transform-response mdata)] - (p/do (transform-fn request response)) - (p/resolved response))) + (let [transform-fn (reduce (fn [res-fn transform-fn] + (fn [request response] + (p/then (res-fn request response) #(transform-fn request %)))) + (constantly response) + (::response-transform-fns mdata))] + (transform-fn request response))) (defn- handle-before-comple-hook [response mdata] - (when-let [hook-fn (::before-complete mdata)] + (doseq [hook-fn (::before-complete-fns mdata)] (ex/ignoring (hook-fn))) response) (defn- handle-response [request result] - (let [mdata (meta result) - result (if (sv/wrapped? result) @result result)] - (p/-> (yrs/response 200 result (::http/headers mdata {})) - (handle-response-transformation request mdata) - (handle-before-comple-hook mdata)))) + (if (fn? result) + (p/wrap (result request)) + (let [mdata (meta result)] + (p/-> (yrs/response {:status (::http/status mdata 200) + :headers (::http/headers mdata {}) + :body (rph/unwrap result)}) + (handle-response-transformation request mdata) + (handle-before-comple-hook mdata))))) (defn- rpc-query-handler "Ring handler that dispatches query requests and convert between @@ -92,18 +101,20 @@ internal async flow into ring async flow." [methods {:keys [profile-id session-id params] :as request} respond raise] (let [cmd (keyword (:command params)) - data (into {::request request} params) + etag (yrq/get-header request "if-none-match") + data (into {::request request ::cond/key etag} params) data (if profile-id (assoc data :profile-id profile-id ::session-id session-id) (dissoc data :profile-id)) method (get methods cmd default-handler)] - (-> (method data) - (p/then (partial handle-response request)) - (p/then respond) - (p/catch (fn [cause] - (let [context {:profile-id profile-id}] - (raise (ex/wrap-with-context cause context)))))))) + (binding [cond/*enabled* true] + (-> (method data) + (p/then (partial handle-response request)) + (p/then respond) + (p/catch (fn [cause] + (let [context {:profile-id profile-id}] + (raise (ex/wrap-with-context cause context))))))))) (defn- wrap-metrics "Wrap service method with metrics measurement." @@ -125,9 +136,9 @@ [{:keys [executor] :as cfg} f mdata] (with-meta (fn [cfg params] - (-> (px/submit! executor #(f cfg params)) - (p/bind p/wrap) - (p/then' sv/wrap))) + (->> (px/submit! executor (px/wrap-bindings #(f cfg params))) + (p/mapcat p/wrap) + (p/map rph/wrap))) mdata)) (defn- wrap-audit @@ -161,6 +172,7 @@ [cfg f mdata] (let [f (as-> f $ (wrap-dispatch cfg $ mdata) + (cond/wrap cfg $ mdata) (retry/wrap-retry cfg $ mdata) (wrap-metrics cfg $ mdata) (climit/wrap cfg $ mdata) diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index f41f6bf92d..46f2c5d720 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -18,6 +18,7 @@ [app.rpc :as-alias rpc] [app.rpc.climit :as climit] [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] [app.tokens :as tokens] @@ -135,10 +136,10 @@ {:invitation-token (:invitation-token params)} profile)] - (with-meta response - {::rpc/transform-response (session/create-fn session (:id profile)) - ::audit/props (audit/profile->props profile) - ::audit/profile-id (:id profile)}))))) + (-> response + (rph/with-transform (session/create-fn session (:id profile))) + (vary-meta merge {::audit/props (audit/profile->props profile) + ::audit/profile-id (:id profile)})))))) (s/def ::login-with-password (s/keys :req-un [::email ::password] diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 301366d032..0daeb8190b 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -16,7 +16,6 @@ [app.config :as cf] [app.db :as db] [app.media :as media] - [app.rpc :as-alias rpc] [app.rpc.commands.files :as files] [app.rpc.doc :as-alias doc] [app.rpc.queries.projects :as projects] @@ -874,7 +873,7 @@ {::doc/added "1.15"} [{:keys [pool] :as cfg} {:keys [profile-id file-id include-libraries? embed-assets?] :as params}] (files/check-read-permissions! pool profile-id file-id) - (let [resp (reify yrs/StreamableResponseBody + (let [body (reify yrs/StreamableResponseBody (-write-body-to-stream [_ _ output-stream] (-> cfg (assoc ::file-ids [file-id]) @@ -882,12 +881,8 @@ (assoc ::include-libraries? include-libraries?) (export! output-stream))))] - (with-meta (sv/wrap nil) - {::rpc/transform-response - (fn [_ response] - (-> response - (assoc :body resp) - (assoc :headers {"content-type" "application/octet-stream"})))}))) + (fn [_] + (yrs/response 200 body {"content-type" "application/octet-stream"})))) (s/def ::file ::media/upload) (s/def ::import-binfile diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 775221c83b..2d354f63d8 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -19,8 +19,9 @@ [app.db.sql :as sql] [app.rpc :as-alias rpc] [app.rpc.commands.files.thumbnails :as-alias thumbs] + [app.rpc.cond :as-alias cond] [app.rpc.doc :as-alias doc] - [app.rpc.helpers :as rpch] + [app.rpc.helpers :as rph] [app.rpc.permissions :as perms] [app.rpc.queries.projects :as projects] [app.rpc.queries.share-link :refer [retrieve-share-link]] @@ -237,19 +238,30 @@ file))) +(defn- get-minimal-file + [{:keys [pool] :as cfg} id] + (db/get pool :file {:id id} {:columns [:id :modified-at :revn]})) + +(defn- get-file-etag + [{:keys [modified-at revn]}] + (str (dt/format-instant modified-at :iso) "-" revn)) + (s/def ::get-file (s/keys :req-un [::profile-id ::id] :opt-un [::features])) (sv/defmethod ::get-file "Retrieve a file by its ID. Only authenticated users." - {::doc/added "1.17"} + {::doc/added "1.17" + ::cond/get-object #(get-minimal-file %1 (:id %2)) + ::cond/key-fn get-file-etag} [{:keys [pool] :as cfg} {:keys [profile-id id features] :as params}] (with-open [conn (db/open pool)] (let [perms (get-permissions conn profile-id id)] (check-read-permissions! perms) - (-> (get-file conn id features) - (assoc :permissions perms))))) + (let [file (-> (get-file conn id features) + (assoc :permissions perms))] + (vary-meta file assoc ::cond/key (get-file-etag file)))))) ;; --- COMMAND QUERY: get-file-object-thumbnails @@ -277,7 +289,10 @@ (sv/defmethod ::get-file-object-thumbnails "Retrieve a file object thumbnails." - {::doc/added "1.17"} + {::doc/added "1.17" + ::cond/get-object #(get-minimal-file %1 (:file-id %2)) + ::cond/reuse-key? true + ::cond/key-fn get-file-etag} [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (with-open [conn (db/open pool)] (check-read-permissions! conn profile-id file-id) @@ -592,8 +607,7 @@ (with-open [conn (db/open pool)] (check-read-permissions! conn profile-id file-id) (-> (get-file-thumbnail conn file-id revn) - (with-meta {::rpc/transform-response (rpch/http-cache {:max-age (* 1000 60 60)})})))) - + (with-meta {::rpc/transform-response (rph/http-cache {:max-age (* 1000 60 60)})})))) ;; --- COMMAND QUERY: get-file-data-for-thumbnail diff --git a/backend/src/app/rpc/commands/files/update.clj b/backend/src/app/rpc/commands/files/update.clj index 4752efb1a7..a590d63e8e 100644 --- a/backend/src/app/rpc/commands/files/update.clj +++ b/backend/src/app/rpc/commands/files/update.clj @@ -19,10 +19,10 @@ [app.loggers.audit :as audit] [app.metrics :as mtx] [app.msgbus :as mbus] - [app.rpc :as-alias rpc] [app.rpc.climit :as-alias climit] [app.rpc.commands.files :as files] [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.util.blob :as blob] [app.util.objects-map :as omap] [app.util.pointer-map :as pmap] @@ -135,10 +135,8 @@ (let [cfg (assoc cfg :conn conn) tpoint (dt/tpoint)] (-> (update-file cfg params) - (vary-meta assoc ::rpc/before-complete - (fn [] - (let [elapsed (tpoint)] - (l/trace :hint "update-file" :time (dt/format-duration elapsed))))))))) + (rph/with-defer #(let [elapsed (tpoint)] + (l/trace :hint "update-file" :time (dt/format-duration elapsed)))))))) (defn update-file [{:keys [conn metrics] :as cfg} {:keys [id profile-id changes changes-with-metadata] :as params}] diff --git a/backend/src/app/rpc/cond.clj b/backend/src/app/rpc/cond.clj new file mode 100644 index 0000000000..58440ad4b2 --- /dev/null +++ b/backend/src/app/rpc/cond.clj @@ -0,0 +1,67 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.cond + "Conditional loading middleware. + + A middleware consists mainly on wrapping a RPC method with + conditional logic. It expects to to have some metadata set on the RPC + method that will enable this middleware to retrieve the necessary data + for process the conditional logic: + + - `::get-object` => should be a function that retrieves the minimum version + of the object that will be used for calculate the KEY (etags in terms of + the HTTP protocol). + - `::key-fn` a function used to generate a string representation + of the object. This function can be applied to the object returned by the + `get-object` but also to the RPC return value (in case you don't provide + the return value calculated key under `::key` metadata prop. + - `::reuse-key?` enables reusing the key calculated on first time; usefull + when the target object is not retrieved on the RPC (typical on retrieving + dependent objects). + " + (:require + [app.common.logging :as l] + [app.rpc.helpers :as rph] + [app.util.services :as-alias sv] + [promesa.core :as p] + [promesa.exec :as px] + [yetti.response :as yrs])) + +(def + ^{:dynamic true + :doc "Runtime flag for enable/disable conditional processing of RPC methods."} + *enabled* false) + +(defn- fmt-key + [s] + (when s + (str "W/\"" s "\""))) + +(defn wrap + [{:keys [executor]} f {:keys [::get-object ::key-fn ::reuse-key?] :as mdata}] + (if (and (ifn? get-object) (ifn? key-fn)) + (do + (l/debug :hint "instrumenting method" :service (::sv/name mdata)) + (fn [cfg {:keys [::key] :as params}] + (if *enabled* + (->> (if (or key reuse-key?) + (->> (px/submit! executor (partial get-object cfg params)) + (p/map key-fn) + (p/map fmt-key)) + (p/resolved nil)) + (p/mapcat (fn [key'] + (if (and (some? key) + (= key key')) + (p/resolved (fn [_] (yrs/response 304))) + (->> (f cfg params) + (p/map (fn [result] + (->> (or (and reuse-key? key') + (-> result meta ::key fmt-key) + (-> result key-fn fmt-key)) + (rph/with-header result "etag"))))))))) + (f cfg params)))) + f)) diff --git a/backend/src/app/rpc/helpers.clj b/backend/src/app/rpc/helpers.clj index 326f482d81..2dda8203b6 100644 --- a/backend/src/app/rpc/helpers.clj +++ b/backend/src/app/rpc/helpers.clj @@ -6,7 +6,43 @@ (ns app.rpc.helpers "General purpose RPC helpers." - (:require [app.common.data.macros :as dm])) + (:require + [app.common.data.macros :as dm] + [app.http :as-alias http] + [app.rpc :as-alias rpc])) + +;; A utilty wrapper object for wrap service responses that does not +;; implements the IObj interface that make possible attach metadata to +;; it. + +(deftype MetadataWrapper [obj ^:unsynchronized-mutable metadata] + clojure.lang.IDeref + (deref [_] obj) + + clojure.lang.IObj + (withMeta [_ meta] + (MetadataWrapper. obj meta)) + + (meta [_] metadata)) + +(defn wrap + "Conditionally wrap a value into MetadataWrapper instance. If the + object already implements IObj interface it will be returned as is." + ([] (wrap nil)) + ([o] + (if (instance? clojure.lang.IObj o) + o + (MetadataWrapper. o {}))) + ([o m] + (MetadataWrapper. o m))) + +(defn wrapped? + [o] + (instance? MetadataWrapper o)) + +(defn unwrap + [o] + (if (wrapped? o) @o o)) (defn http-cache [{:keys [max-age]}] @@ -14,3 +50,19 @@ (let [exp (if (integer? max-age) max-age (inst-ms max-age)) val (dm/fmt "max-age=%" (int (/ exp 1000.0)))] (update response :headers assoc "cache-control" val)))) + +(defn with-header + "Add a http header to the RPC result." + [mdw key val] + (vary-meta mdw update ::http/headers assoc key val)) + +(defn with-transform + "Adds a http response transform to the RPC result." + [mdw transform-fn] + (vary-meta mdw update ::rpc/response-transform-fns conj transform-fn)) + +(defn with-defer + "Defer execution of the function until request is finished." + [mdw hook-fn] + (vary-meta mdw update ::rpc/before-complete-fns conj hook-fn)) + diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index f419b63756..48dfb39efe 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -11,13 +11,13 @@ [app.common.spec :as us] [app.db :as db] [app.loggers.audit :as audit] - [app.rpc :as-alias rpc] [app.rpc.climit :as-alias climit] [app.rpc.commands.files :as cmd.files] [app.rpc.commands.files.create :as cmd.files.create] [app.rpc.commands.files.temp :as cmd.files.temp] [app.rpc.commands.files.update :as cmd.files.update] [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.rpc.queries.projects :as proj] [app.util.services :as sv] [app.util.time :as dt] @@ -166,10 +166,8 @@ cfg (assoc cfg :conn conn)] (-> (cmd.files.update/update-file cfg params) - (vary-meta assoc ::rpc/before-complete - (fn [] - (let [elapsed (tpoint)] - (l/trace :hint "update-file" :time (dt/format-duration elapsed))))))))) + (rph/with-defer #(let [elapsed (tpoint)] + (l/trace :hint "update-file" :time (dt/format-duration elapsed)))))))) ;; --- Mutation: upsert object thumbnail diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index 288cfdf768..83c68c1e8f 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -16,8 +16,8 @@ [app.emails :as eml] [app.loggers.audit :as audit] [app.media :as media] - [app.rpc :as-alias rpc] [app.rpc.climit :as climit] + [app.rpc.helpers :as rph] [app.rpc.mutations.projects :as projects] [app.rpc.permissions :as perms] [app.rpc.queries.profile :as profile] @@ -487,18 +487,17 @@ :email email :role role))) - (with-meta team - {::audit/props {:invitations (count emails)} - - ::rpc/before-complete - #(audit-fn :cmd :submit - :type "mutation" - :name "invite-team-member" - :profile-id profile-id - :props {:emails emails - :role role - :profile-id profile-id - :invitations (count emails)})})))) + (-> team + (vary-meta assoc ::audit/props {:invitations (count emails)}) + (rph/with-defer + #(audit-fn :cmd :submit + :type "mutation" + :name "invite-team-member" + :profile-id profile-id + :props {:emails emails + :role role + :profile-id profile-id + :invitations (count emails)})))))) ;; --- Mutation: Update invitation role diff --git a/backend/src/app/util/services.clj b/backend/src/app/util/services.clj index 59a048e1e4..66f9fc8db0 100644 --- a/backend/src/app/util/services.clj +++ b/backend/src/app/util/services.clj @@ -11,33 +11,6 @@ [app.common.data :as d] [cuerdas.core :as str])) -;; A utilty wrapper object for wrap service responses that does not -;; implements the IObj interface that make possible attach metadata to -;; it. - -(deftype MetadataWrapper [obj ^:unsynchronized-mutable metadata] - clojure.lang.IDeref - (deref [_] obj) - - clojure.lang.IObj - (withMeta [_ meta] - (MetadataWrapper. obj meta)) - - (meta [_] metadata)) - -(defn wrap - "Conditionally wrap a value into MetadataWrapper instance. If the - object already implements IObj interface it will be returned as is." - ([] (wrap nil)) - ([o] - (if (instance? clojure.lang.IObj o) - o - (MetadataWrapper. o {})))) - -(defn wrapped? - [o] - (instance? MetadataWrapper o)) - (defmacro defmethod [sname & body] (let [[docs body] (if (string? (first body)) diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj index 75b3a3eb8f..3e12312edd 100644 --- a/backend/test/backend_tests/helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -17,6 +17,7 @@ [app.main :as main] [app.media] [app.migrations] + [app.rpc.helpers :as rph] [app.rpc.commands.auth :as cmd.auth] [app.rpc.commands.files :as files] [app.rpc.commands.files.create :as files.create] @@ -295,7 +296,7 @@ [expr] `(try (let [result# (deref ~expr) - result# (cond-> result# (sv/wrapped? result#) deref)] + result# (cond-> result# (rph/wrapped? result#) deref)] {:error nil :result result#}) (catch Exception e# diff --git a/backend/test/backend_tests/rpc_cond_middleware_test.clj b/backend/test/backend_tests/rpc_cond_middleware_test.clj new file mode 100644 index 0000000000..74f95e1964 --- /dev/null +++ b/backend/test/backend_tests/rpc_cond_middleware_test.clj @@ -0,0 +1,42 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns backend-tests.rpc-cond-middleware-test + (:require + [backend-tests.storage-test :refer [configure-storage-backend]] + [backend-tests.helpers :as th] + [app.common.uuid :as uuid] + [app.db :as db] + [app.http :as http] + [app.rpc.cond :as cond] + [clojure.test :as t] + [datoteka.core :as fs])) + +(t/use-fixtures :once th/state-init) +(t/use-fixtures :each th/database-reset) + +(t/deftest conditional-requests + (let [profile (th/create-profile* 1 {:is-active true}) + project (th/create-project* 1 {:team-id (:default-team-id profile) + :profile-id (:id profile)}) + file1 (th/create-file* 1 {:profile-id (:id profile) + :project-id (:id project)}) + params {::th/type :get-file :id (:id file1) :profile-id (:id profile)}] + + (binding [cond/*enabled* true] + (let [{:keys [error result]} (th/command! params)] + (t/is (nil? error)) + (t/is (map? result)) + (t/is (contains? (meta result) :app.http/headers)) + (t/is (contains? (meta result) :app.rpc.cond/key)) + + (let [etag (-> result meta :app.http/headers (get "etag")) + {:keys [error result]} (th/command! (assoc params ::cond/key etag))] + (t/is (nil? error)) + (t/is (fn? result)) + (t/is (= 304 (-> (result nil) :status)))) + )))) + From 8852ed815faa79c48ef3d2878feaefa6988d41ee Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 10 Nov 2022 09:43:13 +0100 Subject: [PATCH 190/682] :bug: Fix unexpected exception on file-gc cron task --- backend/src/app/tasks/file_gc.clj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/app/tasks/file_gc.clj b/backend/src/app/tasks/file_gc.clj index fa1b68c182..81d14eeca1 100644 --- a/backend/src/app/tasks/file_gc.clj +++ b/backend/src/app/tasks/file_gc.clj @@ -44,10 +44,10 @@ (defmethod ig/init-key ::handler [_ {:keys [pool] :as cfg}] - (fn [{:keys [id] :as params}] + (fn [{:keys [file-id] :as params}] (db/with-atomic [conn pool] (let [min-age (or (:min-age params) (:min-age cfg)) - cfg (assoc cfg :min-age min-age :conn conn :id id)] + cfg (assoc cfg :min-age min-age :conn conn :file-id file-id)] (loop [total 0 files (retrieve-candidates cfg)] (if-let [file (first files)] @@ -84,11 +84,11 @@ for update skip locked") (defn- retrieve-candidates - [{:keys [conn min-age id] :as cfg}] - (if id + [{:keys [conn min-age file-id] :as cfg}] + (if (uuid? file-id) (do - (l/warn :hint "explicit file id passed on params" :id id) - (->> (db/query conn :file {:id id}) + (l/warn :hint "explicit file id passed on params" :file-id file-id) + (->> (db/query conn :file {:id file-id}) (map #(update % :features db/decode-pgarray #{})))) (let [interval (db/interval min-age) get-chunk (fn [cursor] From 20738545b8ea162b097f359c5bb02c8c5e4d2fe0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 24 Oct 2022 16:17:27 +0200 Subject: [PATCH 191/682] :tada: Transform graphics into components --- common/src/app/common/types/shape.cljc | 18 +- .../resources/styles/main/partials/modal.scss | 15 ++ frontend/src/app/main/data/workspace.cljs | 188 +++++++++++++- .../src/app/main/data/workspace/media.cljs | 50 +--- .../app/main/data/workspace/svg_upload.cljs | 243 +++++++++++------- frontend/src/app/main/refs.cljs | 5 +- frontend/src/app/main/ui/workspace.cljs | 17 ++ .../app/main/ui/workspace/sidebar/assets.cljs | 32 ++- 8 files changed, 404 insertions(+), 164 deletions(-) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index c994f3c340..9249ac5608 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -388,12 +388,14 @@ (setup-rect-selrect))) (defn- setup-image - [{:keys [metadata] :as shape} props] - (-> (setup-rect shape props) - (assoc - :proportion (/ (:width metadata) - (:height metadata)) - :proportion-lock true))) + [shape props] + (let [metadata (or (:metadata shape) (:metadata props))] + (-> (setup-rect shape props) + (assoc + :metadata metadata + :proportion (/ (:width metadata) + (:height metadata)) + :proportion-lock true)))) (defn setup-shape "A function that initializes the geometric data of @@ -409,7 +411,9 @@ (defn make-shape "Make a non group shape, ready to use." [type geom-props attrs] - (-> (make-minimal-shape type) + (-> (if-not (= type :group) + (make-minimal-shape type) + (make-minimal-group uuid/zero geom-props (:name attrs))) (setup-shape geom-props) (merge attrs))) diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index a9420d84cb..cbfc24e1b8 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -1848,6 +1848,21 @@ } } +.remove-graphics-dialog { + .close { + border: 1px solid $color-gray-30; + background: $color-canvas; + border-radius: 3px; + padding: 0.5rem 1rem; + cursor: pointer; + margin-right: 8px; + + &:hover { + background: $color-gray-20; + } + } +} + //- LOGIN .login-register { background-color: $color-white; diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index f4ffb8671e..81fa94eeb1 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -13,11 +13,15 @@ [app.common.geom.point :as gpt] [app.common.geom.proportions :as gpr] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.rect :as gpsr] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.text :as txt] [app.common.transit :as t] + [app.common.types.container :as ctn] + [app.common.types.file :as ctf] + [app.common.types.pages-list :as ctpl] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] @@ -25,6 +29,7 @@ [app.main.data.comments :as dcm] [app.main.data.events :as ev] [app.main.data.messages :as msg] + [app.main.data.modal :as modal] [app.main.data.users :as du] [app.main.data.workspace.bool :as dwb] [app.main.data.workspace.changes :as dch] @@ -49,17 +54,19 @@ [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] + [app.main.data.workspace.svg-upload :as svg] [app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] [app.main.data.workspace.viewport :as dwv] [app.main.data.workspace.zoom :as dwz] + [app.main.features :as features] [app.main.repo :as rp] [app.main.streams :as ms] [app.util.dom :as dom] [app.util.globals :as ug] [app.util.http :as http] - [app.util.i18n :as i18n] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.timers :as tm] [app.util.webapi :as wapi] @@ -78,6 +85,7 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (declare file-initialized) +(declare remove-graphics) ;; --- Initialize Workspace @@ -170,15 +178,20 @@ :current-file-comments-users (d/index-by :id file-comments-users))) ptk/WatchEvent - (watch [_ _ _] - (let [file-id (:id file) - ignore-until (:ignore-sync-until file) - needs-update? (some #(and (> (:modified-at %) (:synced-at %)) - (or (not ignore-until) - (> (:modified-at %) ignore-until))) - libraries)] + (watch [_ state _] + (let [file-id (:id file) + ignore-until (:ignore-sync-until file) + some-graphics? (some? (-> file :data :media)) + needs-update? (some #(and (> (:modified-at %) (:synced-at %)) + (or (not ignore-until) + (> (:modified-at %) ignore-until))) + libraries) + components-v2 (features/active-feature? state :components-v2)] (rx/merge (rx/of (fbc/fix-bool-contents)) + (if (and some-graphics? components-v2) + (rx/of (remove-graphics (:id file) (:name file))) + (rx/empty)) (if needs-update? (rx/of (dwl/notify-sync-file file-id)) (rx/empty))))))) @@ -1241,7 +1254,7 @@ (catch :default e (let [data (ex-data e)] (if (:not-implemented data) - (rx/of (msg/warn (i18n/tr "errors.clipboard-not-implemented"))) + (rx/of (msg/warn (tr "errors.clipboard-not-implemented"))) (js/console.error "ERROR" e)))))))) (defn paste-from-event @@ -1588,6 +1601,163 @@ (dwu/commit-undo-transaction)))))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Remove graphics +;; TODO: this should be deprecated and removed together with components-v2 +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- initialize-remove-graphics + [total] + (ptk/reify ::initialize-remove-graphics + ptk/UpdateEvent + (update [_ state] + (assoc state :remove-graphics {:total total + :current nil})))) + +(defn- update-remove-graphics + [current] + (ptk/reify ::update-remove-graphics + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:remove-graphics :current] current)))) + +(defn- complete-remove-graphics + [] + (ptk/reify ::complete-remove-graphics + ptk/UpdateEvent + (update [_ state] + (dissoc state :remove-graphics)))) + +(defn- create-shapes-svg + [file-id objects pos media-obj] + (let [path (cfg/resolve-file-media media-obj) + + upload-images + (fn [svg-data] + (->> (svg/upload-images svg-data file-id) + (rx/map #(assoc svg-data :image-data %)))) + + process-svg + (fn [svg-data] + (let [[shape children] + (svg/create-svg-shapes svg-data pos objects uuid/zero #{} false)] + [shape children]))] + + (->> (http/send! {:method :get :uri path :mode :no-cors}) + (rx/map :body) + (rx/map #(vector (:name media-obj) %)) + (rx/merge-map dwm/svg->clj) + (rx/merge-map upload-images) + (rx/map process-svg) + (rx/catch #(js/console.error ; When error downloading media-obj, skip it and continue with next one + (str "Error downloading " (:name media-obj) " " path ":") + (clj->js %)))))) + +(defn- create-shapes-img + [pos media-obj] + (let [{:keys [name width height id mtype]} media-obj + + group-shape (cts/make-shape :group + {:x (:x pos) + :y (:y pos) + :width width + :height height} + {:name name + :frame-id uuid/zero + :parent-id uuid/zero}) + + img-shape (cts/make-shape :image + {:x (:x pos) + :y (:y pos) + :width width + :height height + :metadata {:id id + :width width + :height height + :mtype mtype}} + {:name name + :frame-id uuid/zero + :parent-id (:id group-shape)})] + (rx/of [group-shape [img-shape]]))) + +(defn- remove-graphic + [it file-data page [index [media-obj pos]]] + (let [process-shapes + (fn [[shape children]] + (let [page' (reduce #(ctst/add-shape (:id %2) %2 %1 uuid/zero (:parent-id %2) nil false) + page + (cons shape children)) + + shape' (ctn/get-shape page' (:id shape)) + + path (cph/merge-path-item (tr "workspace.assets.graphics") (:path media-obj)) + + [component-shape component-shapes updated-shapes] + (ctn/make-component-shape shape' (:objects page') (:id file-data) true) + + changes (-> (pcb/empty-changes it) + (pcb/set-save-undo? false) + (pcb/with-page page') + (pcb/with-objects (:objects page')) + (pcb/with-library-data file-data) + (pcb/delete-media (:id media-obj)) + (pcb/add-objects (cons shape children)) + (pcb/add-component (:id component-shape) + path + (:name media-obj) + component-shapes + updated-shapes + (:id shape) + (:id page)))] + + (dch/commit-changes changes))) + + shapes (if (= (:mtype media-obj) "image/svg+xml") + (create-shapes-svg (:id file-data) (:objects page) pos media-obj) + (create-shapes-img pos media-obj))] + + (rx/concat + (rx/of (update-remove-graphics index)) + (rx/map process-shapes shapes)))) + +(defn- remove-graphics + [file-id file-name] + (ptk/reify ::remove-graphics + ptk/WatchEvent + (watch [it state _] + (let [file-data (wsh/get-file state file-id) + + grid-gap 50 + + [file-data' page-id start-pos] + (ctf/get-or-add-library-page file-data grid-gap) + + new-page? (nil? (ctpl/get-page file-data page-id)) + page (ctpl/get-page file-data' page-id) + media (vals (:media file-data')) + + media-points + (map #(assoc % :points (gpsr/rect->points {:x 0 + :y 0 + :width (:width %) + :height (:height %)})) + media) + + shape-grid + (ctst/generate-shape-grid media-points start-pos grid-gap)] + + (rx/concat + (rx/of (modal/show {:type :remove-graphics-dialog :file-name file-name}) + (initialize-remove-graphics (count media))) + (when new-page? + (rx/of (dch/commit-changes (-> (pcb/empty-changes it) + (pcb/set-save-undo? false) + (pcb/add-page (:id page) page))))) + (rx/mapcat (partial remove-graphic it file-data' page) + (rx/from (d/enumerate (d/zip media shape-grid)))) + (rx/of (modal/hide) + (complete-remove-graphics))))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Exports ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index e08c75c8ea..3ff712de57 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -17,8 +17,6 @@ [app.main.store :as st] [app.util.http :as http] [app.util.i18n :refer [tr]] - [app.util.svg :as usvg] - [app.util.webapi :as wapi] [beicon.core :as rx] [cljs.spec.alpha :as s] [cuerdas.core :as str] @@ -35,27 +33,6 @@ (catch :default _err (rx/throw {:type :svg-parser})))) -(defn extract-name [url] - (let [query-idx (str/last-index-of url "?") - url (if (> query-idx 0) (subs url 0 query-idx) url) - filename (->> (str/split url "/") (last)) - ext-idx (str/last-index-of filename ".")] - (if (> ext-idx 0) (subs filename 0 ext-idx) filename))) - -(defn data-uri->blob - [data-uri] - (let [[mtype b64-data] (str/split data-uri ";base64,") - mtype (subs mtype (inc (str/index-of mtype ":"))) - decoded (.atob js/window b64-data) - size (.-length ^js decoded) - content (js/Uint8Array. size)] - - (doseq [i (range 0 size)] - (aset content i (.charCodeAt decoded i))) - - (wapi/create-blob content mtype))) - - ;; TODO: rename to bitmap-image-uploaded (defn image-uploaded [image {:keys [x y]}] @@ -82,26 +59,8 @@ ;; Once the SVG is uploaded, we need to extract all the bitmap ;; images and upload them separately, then proceed to create ;; all shapes. - (->> (rx/from (usvg/collect-images svg-data)) - (rx/map (fn [uri] - (merge - {:file-id file-id - :is-local true} - (if (str/starts-with? uri "data:") - {:name "image" - :content (data-uri->blob uri)} - {:name (extract-name uri) - :url uri})))) - (rx/mapcat (fn [uri-data] - (->> (rp/mutation! (if (contains? uri-data :content) - :upload-file-media-object - :create-file-media-object-from-url) uri-data) - ;; When the image uploaded fail we skip the shape - ;; returning `nil` will afterward not create the shape. - (rx/catch #(rx/of nil)) - (rx/map #(vector (:url uri-data) %))))) - (rx/reduce (fn [acc [url image]] (assoc acc url image)) {}) - (rx/map #(svg/create-svg-shapes (assoc svg-data :image-data %) position)))))) + (->> (svg/upload-images svg-data file-id) + (rx/map #(svg/add-svg-shapes (assoc svg-data :image-data %) position)))))) (defn- process-uris [{:keys [file-id local? name uris mtype on-image on-svg]}] @@ -112,13 +71,13 @@ (prepare [uri] {:file-id file-id :is-local local? - :name (or name (extract-name uri)) + :name (or name (svg/extract-name uri)) :url uri}) (fetch-svg [name uri] (->> (http/send! {:method :get :uri uri :mode :no-cors}) (rx/map #(vector - (or name (extract-name uri)) + (or name (svg/extract-name uri)) (:body %)))))] (rx/merge @@ -234,6 +193,7 @@ (rx/catch handle-error) (rx/finalize #(st/emit! (dm/hide-tag :media-loading))))))))) +;; Deprecated in components-v2 (defn upload-media-asset [params] (let [params (assoc params diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 4424f6ba03..2d6aa420a5 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -20,9 +20,11 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] + [app.main.repo :as rp] [app.util.color :as uc] [app.util.path.parser :as upp] [app.util.svg :as usvg] + [app.util.webapi :as wapi] [beicon.core :as rx] [cuerdas.core :as str] [potok.core :as ptk])) @@ -411,105 +413,170 @@ (mapv #(usvg/inherit-attributes attrs %)))] [shape children])))))) -(defn add-svg-child-changes [page-id objects selected frame-id parent-id svg-data [unames changes] [index data]] - (let [[shape children] (parse-svg-element frame-id svg-data data unames)] - (if (some? shape) - (let [shape-id (:id shape) +(defn create-svg-children + [objects selected frame-id parent-id svg-data [unames children] [_index svg-element]] + (let [[new-shape new-children] (parse-svg-element frame-id svg-data svg-element unames)] + (if (some? new-shape) + (let [shape-id (:id new-shape) - new-shape (dwsh/make-new-shape shape objects selected) - changes (-> changes - (pcb/add-object new-shape) - (pcb/change-parent parent-id [new-shape] index)) + new-shape' (-> (dwsh/make-new-shape new-shape objects selected) + (assoc :parent-id parent-id)) - unames (conj unames (:name new-shape)) + children (conj children new-shape') + unames (conj unames (:name new-shape')) - reducer-fn (partial add-svg-child-changes page-id objects selected frame-id shape-id svg-data)] - (reduce reducer-fn [unames changes] (d/enumerate children))) + reducer-fn (partial create-svg-children objects selected frame-id shape-id svg-data)] - [unames changes]))) + (reduce reducer-fn [unames children] (d/enumerate new-children))) + + [unames children]))) + +(defn data-uri->blob + [data-uri] + (let [[mtype b64-data] (str/split data-uri ";base64,") + mtype (subs mtype (inc (str/index-of mtype ":"))) + decoded (.atob js/window b64-data) + size (.-length ^js decoded) + content (js/Uint8Array. size)] + + (doseq [i (range 0 size)] + (aset content i (.charCodeAt decoded i))) + + (wapi/create-blob content mtype))) + +(defn extract-name [url] + (let [query-idx (str/last-index-of url "?") + url (if (> query-idx 0) (subs url 0 query-idx) url) + filename (->> (str/split url "/") (last)) + ext-idx (str/last-index-of filename ".")] + (if (> ext-idx 0) (subs filename 0 ext-idx) filename))) + +(defn upload-images + "Extract all bitmap images inside the svg data, and upload them, associated to the file. + Return a map { }." + [svg-data file-id] + (->> (rx/from (usvg/collect-images svg-data)) + (rx/map (fn [uri] + (merge + {:file-id file-id + :is-local true} + (if (str/starts-with? uri "data:") + {:name "image" + :content (data-uri->blob uri)} + {:name (extract-name uri) + :url uri})))) + (rx/mapcat (fn [uri-data] + (->> (rp/mutation! (if (contains? uri-data :content) + :upload-file-media-object + :create-file-media-object-from-url) uri-data) + ;; When the image uploaded fail we skip the shape + ;; returning `nil` will afterward not create the shape. + (rx/catch #(rx/of nil)) + (rx/map #(vector (:url uri-data) %))))) + (rx/reduce (fn [acc [url image]] (assoc acc url image)) {}))) (defn create-svg-shapes - [svg-data {:keys [x y] :as position}] - (ptk/reify ::create-svg-shapes + [svg-data {:keys [x y] :as position} objects frame-id selected center?] + (try + (let [[vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) + x (if center? + (- x vb-x (/ vb-width 2)) + x) + y (if center? + (- y vb-y (/ vb-height 2)) + y) + + unames (ctst/retrieve-used-names objects) + + svg-name (->> (str/replace (:name svg-data) ".svg" "") + (ctst/generate-unique-name unames)) + + svg-data (-> svg-data + (assoc :x x + :y y + :offset-x vb-x + :offset-y vb-y + :width vb-width + :height vb-height + :name svg-name)) + + [def-nodes svg-data] (-> svg-data + (usvg/fix-default-values) + (usvg/fix-percents) + (usvg/extract-defs)) + + svg-data (assoc svg-data :defs def-nodes) + + root-shape (create-svg-root frame-id svg-data) + root-id (:id root-shape) + + ;; In penpot groups have the size of their children. To respect the imported svg size and empty space let's create a transparent shape as background to respect the imported size + base-background-shape {:tag :rect + :attrs {:x "0" + :y "0" + :width (str (:width root-shape)) + :height (str (:height root-shape)) + :fill "none" + :id "base-background"} + :hidden true + :content []} + + svg-data (-> svg-data + (assoc :defs def-nodes) + (assoc :content (into [base-background-shape] (:content svg-data)))) + + ;; Create the root shape + new-shape (dwsh/make-new-shape root-shape objects selected) + + root-attrs (-> (:attrs svg-data) + (usvg/format-styles)) + + [_ new-children] + (reduce (partial create-svg-children objects selected frame-id root-id svg-data) + [unames []] + (d/enumerate (->> (:content svg-data) + (mapv #(usvg/inherit-attributes root-attrs %)))))] + + [new-shape new-children]) + + (catch :default e + (.error js/console "Error SVG" e) + (rx/throw {:type :svg-parser + :data e})))) + +(defn add-svg-shapes + [svg-data position] + (ptk/reify ::add-svg-shapes ptk/WatchEvent (watch [it state _] - (try - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - frame-id (ctst/top-nested-frame objects position) - selected (wsh/lookup-selected state) + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + frame-id (ctst/top-nested-frame objects position) + selected (wsh/lookup-selected state) - [vb-x vb-y vb-width vb-height] (svg-dimensions svg-data) - x (- x vb-x (/ vb-width 2)) - y (- y vb-y (/ vb-height 2)) + [new-shape new-children] + (create-svg-shapes svg-data position objects frame-id selected true) - unames (ctst/retrieve-used-names objects) + changes (-> (pcb/empty-changes it page-id) + (pcb/with-objects objects) + (pcb/add-object new-shape)) - svg-name (->> (str/replace (:name svg-data) ".svg" "") - (ctst/generate-unique-name unames)) + changes + (reduce (fn [changes [index new-child]] + (-> changes + (pcb/add-object new-child) + (pcb/change-parent (:parent-id new-child) [new-child] index))) + changes + (d/enumerate new-children)) - svg-data (-> svg-data - (assoc :x x - :y y - :offset-x vb-x - :offset-y vb-y - :width vb-width - :height vb-height - :name svg-name)) + changes (pcb/resize-parents changes + (->> changes + :redo-changes + (filter #(= :add-obj (:type %))) + (map :id) + reverse + vec))] - [def-nodes svg-data] (-> svg-data - (usvg/fix-default-values) - (usvg/fix-percents) - (usvg/extract-defs)) + (rx/of (dch/commit-changes changes) + (dws/select-shapes (d/ordered-set (:id new-shape)))))))) - svg-data (assoc svg-data :defs def-nodes) - - root-shape (create-svg-root frame-id svg-data) - root-id (:id root-shape) - - ;; In penpot groups have the size of their children. To respect the imported svg size and empty space let's create a transparent shape as background to respect the imported size - base-background-shape {:tag :rect - :attrs {:x "0" - :y "0" - :width (str (:width root-shape)) - :height (str (:height root-shape)) - :fill "none" - :id "base-background"} - :hidden true - :content []} - - svg-data (-> svg-data - (assoc :defs def-nodes) - (assoc :content (into [base-background-shape] (:content svg-data)))) - - ;; Creates the root shape - new-shape (dwsh/make-new-shape root-shape objects selected) - - changes (-> (pcb/empty-changes it page-id) - (pcb/with-objects objects) - (pcb/add-object new-shape)) - - root-attrs (-> (:attrs svg-data) - (usvg/format-styles)) - - ;; Reduce the children to create the changes to add the children shapes - [_ changes] - (reduce (partial add-svg-child-changes page-id objects selected frame-id root-id svg-data) - [unames changes] - (d/enumerate (->> (:content svg-data) - (mapv #(usvg/inherit-attributes root-attrs %))))) - changes (pcb/resize-parents changes - (->> changes - :redo-changes - (filter #(= :add-obj (:type %))) - (map :id) - reverse - vec))] - - (rx/of (dch/commit-changes changes) - (dws/select-shapes (d/ordered-set root-id)))) - - (catch :default e - (.error js/console "Error SVG" e) - (rx/throw {:type :svg-parser - :data e})))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index de39b2c043..5c2e6eea14 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -300,7 +300,6 @@ [ids] (l/derived #(select-keys % ids) workspace-modifiers)) - (def workspace-modifiers-with-objects (l/derived (fn [state] @@ -361,6 +360,10 @@ (def workspace-focus-selected (l/derived :workspace-focus-selected st/state)) +;; Remove this when deprecating components-v2 +(def remove-graphics + (l/derived :remove-graphics st/state)) + ;; ---- Viewer refs (defn lookup-viewer-objects-by-id diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index cbb8ba694d..bfcca5b485 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -9,6 +9,7 @@ [app.common.colors :as clr] [app.common.data.macros :as dm] [app.main.data.messages :as msg] + [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.persistence :as dwp] [app.main.features :as features] @@ -168,3 +169,19 @@ :layout layout}] [:& workspace-loader])]]]]]])) +(mf/defc remove-graphics-dialog + {::mf/register modal/components + ::mf/register-as :remove-graphics-dialog} + [{:keys [] :as ctx}] + (let [remove-state (mf/deref refs/remove-graphics) + close #(modal/hide!)] + [:div.modal-overlay + [:div.modal-container.remove-graphics-dialog + [:div.modal-header + [:div.modal-header-title + [:h2 (str "Updating " (:file-name ctx) "...")]] + [:div.modal-close-button + {:on-click close} i/close]] + [:div.modal-content + [:p (str "Converting " (:current remove-state) " / " (:total remove-state))]]]])) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index f1b909d7f2..3a341175e6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -759,9 +759,7 @@ (mf/use-fn (mf/deps object selected-objects item-ref on-drag-start) (fn [event] - (on-asset-drag-start event object selected-objects item-ref :graphics on-drag-start))) - - ] + (on-asset-drag-start event object selected-objects item-ref :graphics on-drag-start)))] [:div {:ref item-ref :class-name (dom/classnames @@ -919,6 +917,8 @@ groups (group-assets objects reverse-sort?) + components-v2 (mf/use-ctx ctx/components-v2) + add-graphic (mf/use-fn (fn [] @@ -1051,12 +1051,13 @@ :open? open?} (when local? [:& asset-section-block {:role :title-button} - [:div.assets-button {:on-click add-graphic} - i/plus - [:& file-uploader {:accept cm/str-image-types - :multi true - :ref input-ref - :on-selected on-file-selected}]]]) + (when-not components-v2 + [:div.assets-button {:on-click add-graphic} + i/plus + [:& file-uploader {:accept cm/str-image-types + :multi true + :ref input-ref + :on-selected on-file-selected}]])]) [:& asset-section-block {:role :content} [:& graphics-group {:file-id file-id @@ -1916,10 +1917,12 @@ selected-assets (mf/deref refs/selected-assets) - selected-count (+ (count (:components selected-assets)) - (count (:graphics selected-assets)) - (count (:colors selected-assets)) - (count (:typographies selected-assets))) + selected-count (+ (count (:components selected-assets)) + (count (:graphics selected-assets)) + (count (:colors selected-assets)) + (count (:typographies selected-assets))) + + components-v2 (mf/use-ctx ctx/components-v2) toggle-open #(st/emit! (dwl/set-assets-box-open (:id file) :library (not open?))) @@ -2053,7 +2056,8 @@ show-graphics? (and (or (= (:box filters) :all) (= (:box filters) :graphics)) (or (> (count media) 0) - (str/empty? (:term filters)))) + (and (str/empty? (:term filters)) + (not components-v2)))) show-colors? (and (or (= (:box filters) :all) (= (:box filters) :colors)) (or (> (count colors) 0) From 6b1ecfd89c53444e773981d24d4e769dca7f0d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 11 Nov 2022 12:07:51 +0100 Subject: [PATCH 192/682] :paperclip: Add some code enhancements --- frontend/src/app/main/data/workspace.cljs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 81fa94eeb1..895c5799ea 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -14,6 +14,7 @@ [app.common.geom.proportions :as gpr] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.rect :as gpsr] + [app.common.logging :as log] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] @@ -1649,15 +1650,15 @@ (rx/merge-map dwm/svg->clj) (rx/merge-map upload-images) (rx/map process-svg) - (rx/catch #(js/console.error ; When error downloading media-obj, skip it and continue with next one - (str "Error downloading " (:name media-obj) " " path ":") - (clj->js %)))))) + (rx/catch ; When error downloading media-obj, skip it and continue with next one + #(log/error :hint "error downloading file" + :path path + :name (:name media-obj) + :cause %))))) (defn- create-shapes-img - [pos media-obj] - (let [{:keys [name width height id mtype]} media-obj - - group-shape (cts/make-shape :group + [pos {:keys [name width height id mtype] :as media-obj}] + (let [group-shape (cts/make-shape :group {:x (:x pos) :y (:y pos) :width width From 1affb53a26c50d67dfb72cfc123137f441aa0576 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Thu, 3 Nov 2022 13:11:17 +0100 Subject: [PATCH 193/682] :sparkles: Better overlays interactions on boards inside boards --- CHANGES.md | 1 + .../app/common/types/shape/interactions.cljc | 70 ++- .../types_shape_interactions_test.cljc | 440 +++++++++++++----- .../app/main/data/workspace/interactions.cljs | 6 +- frontend/src/app/main/refs.cljs | 3 + frontend/src/app/main/ui/viewer/shapes.cljs | 338 +++++++------- .../sidebar/options/menus/interactions.cljs | 37 +- frontend/translations/en.po | 7 + frontend/translations/es.po | 7 + 9 files changed, 595 insertions(+), 314 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 519a5ccdc0..bb9f0aa99e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### :boom: Breaking changes & Deprecations ### :sparkles: New features +- Better overlays interactions on boards inside boards [Taiga #4386](https://tree.taiga.io/project/penpot/us/4386) ### :bug: Bugs fixed - Add title to color bullets [Taiga #4218](https://tree.taiga.io/project/penpot/task/4218) diff --git a/common/src/app/common/types/shape/interactions.cljc b/common/src/app/common/types/shape/interactions.cljc index 806e7494ca..4731a1ef61 100644 --- a/common/src/app/common/types/shape/interactions.cljc +++ b/common/src/app/common/types/shape/interactions.cljc @@ -87,7 +87,7 @@ :close-overlay :prev-screen :open-url}) - +(s/def ::position-relative-to (s/nilable ::us/uuid)) (s/def ::overlay-pos-type #{:manual :center @@ -110,7 +110,7 @@ (defmethod action-opts-spec :navigate [_] (s/keys :opt-un [::destination ::preserve-scroll - ::animation])) + ::animation])) (defmethod action-opts-spec :open-overlay [_] (s/keys :req-un [::overlay-position @@ -118,7 +118,8 @@ :opt-un [::destination ::close-click-outside ::background-overlay - ::animation])) + ::animation + ::position-relative-to])) (defmethod action-opts-spec :toggle-overlay [_] (s/keys :req-un [::overlay-position @@ -126,11 +127,13 @@ :opt-un [::destination ::close-click-outside ::background-overlay - ::animation])) + ::animation + ::position-relative-to])) (defmethod action-opts-spec :close-overlay [_] (s/keys :opt-un [::destination - ::animation])) + ::animation + ::position-relative-to])) (defmethod action-opts-spec :prev-screen [_] (s/keys :req-un [])) @@ -159,6 +162,7 @@ {:event-type :click :action-type :navigate :destination nil + :position-relative-to nil :preserve-scroll false}) (def default-delay 600) @@ -336,6 +340,13 @@ (assert (has-overlay-opts interaction)) (assoc interaction :background-overlay background-overlay)) +(defn set-position-relative-to + [interaction position-relative-to] + (us/verify ::interaction interaction) + (us/verify ::position-relative-to position-relative-to) + (assert (has-overlay-opts interaction)) + (assoc interaction :position-relative-to position-relative-to)) + (defn- calc-overlay-pos-initial [destination shape objects overlay-pos-type] (if (and (= overlay-pos-type :manual) (some? destination)) @@ -350,43 +361,58 @@ (gpt/point 0 0))) (defn calc-overlay-position - [interaction base-frame dest-frame frame-offset] + [interaction ;; interaction data + relative-to-shape ;; the interaction position is realtive to this sape + base-frame ;; the base frame of the current interaction + dest-frame ;; the frame to display with this interaction + frame-offset] ;; if this interaction starts in a frame opened on another interaction, this is the position of that frame + (us/verify ::interaction interaction) (assert (has-overlay-opts interaction)) (if (nil? dest-frame) (gpt/point 0 0) - (let [overlay-size (:selrect dest-frame) - base-frame-size (:selrect base-frame)] + (let [overlay-position (:overlay-position interaction) + overlay-size (:selrect dest-frame) + relative-to-shape-size (:selrect relative-to-shape) + base-frame-size (:selrect base-frame) + relative-to-is-auto? (and (nil? (:position-relative-to interaction)) (not= :manual (:overlay-pos-type interaction))) + base-position (if relative-to-is-auto? + {:x 0 :y 0} + {:x (+ (:x frame-offset) + (- (:x relative-to-shape-size) (:x base-frame-size))) + :y (+ (:y frame-offset) + (- (:y relative-to-shape-size) (:y base-frame-size)))})] (case (:overlay-pos-type interaction) :center - (gpt/point (/ (- (:width base-frame-size) (:width overlay-size)) 2) - (/ (- (:height base-frame-size) (:height overlay-size)) 2)) + (gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2)) + (+ (:y base-position) (/ (- (:height relative-to-shape-size) (:height overlay-size)) 2))) :top-left - (gpt/point 0 0) + (gpt/point (:x base-position) (:y base-position)) :top-right - (gpt/point (- (:width base-frame-size) (:width overlay-size)) - 0) + (gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size))) + (:y base-position)) :top-center - (gpt/point (/ (- (:width base-frame-size) (:width overlay-size)) 2) - 0) + (gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2)) + (:y base-position)) :bottom-left - (gpt/point 0 - (- (:height base-frame-size) (:height overlay-size))) + (gpt/point (:x base-position) + (+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size)))) :bottom-right - (gpt/point (- (:width base-frame-size) (:width overlay-size)) - (- (:height base-frame-size) (:height overlay-size))) + (gpt/point (+ (:x base-position) (- (:width relative-to-shape-size) (:width overlay-size))) + (+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size)))) :bottom-center - (gpt/point (/ (- (:width base-frame-size) (:width overlay-size)) 2) - (- (:height base-frame-size) (:height overlay-size))) + (gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2)) + (+ (:y base-position) (- (:height relative-to-shape-size) (:height overlay-size)))) :manual - (gpt/add (:overlay-position interaction) frame-offset))))) + (gpt/point (+ (:x base-position) (:x overlay-position)) + (+ (:y base-position) (:y overlay-position))))))) (defn has-animation? [interaction] diff --git a/common/test/common_tests/types_shape_interactions_test.cljc b/common/test/common_tests/types_shape_interactions_test.cljc index da5340dd17..922d337d22 100644 --- a/common/test/common_tests/types_shape_interactions_test.cljc +++ b/common/test/common_tests/types_shape_interactions_test.cljc @@ -27,7 +27,7 @@ (t/testing "Set event type changed" (let [new-interaction (ctsi/set-event-type interaction :mouse-press shape)] - (t/is (= :mouse-press (:event-type new-interaction))))) + (t/is (= :mouse-press (:event-type new-interaction))))) (t/testing "Set after delay on non-frame" (let [result (ex/try @@ -91,7 +91,7 @@ (t/testing "Set action type open-overlay with previous data" (let [interaction (assoc interaction :overlay-pos-type :top-left - :overlay-position (gpt/point 100 200)) + :overlay-position (gpt/point 100 200)) new-interaction (ctsi/set-action-type interaction :open-overlay)] (t/is (= :open-overlay (:action-type new-interaction))) @@ -107,7 +107,7 @@ (t/testing "Set action type toggle-overlay with previous data" (let [interaction (assoc interaction :overlay-pos-type :top-left - :overlay-position (gpt/point 100 200)) + :overlay-position (gpt/point 100 200)) new-interaction (ctsi/set-action-type interaction :toggle-overlay)] (t/is (= :toggle-overlay (:action-type new-interaction))) @@ -146,19 +146,18 @@ (t/is (= :open-url (:action-type new-interaction))) (t/is (= "https://example.com" (:url new-interaction))))))) - (t/deftest option-delay (let [frame (cts/make-minimal-shape :frame) i1 ctsi/default-interaction i2 (ctsi/set-event-type i1 :after-delay frame)] - (t/testing "Has delay" - (t/is (not (ctsi/has-delay i1))) - (t/is (ctsi/has-delay i2))) + (t/testing "Has delay" + (t/is (not (ctsi/has-delay i1))) + (t/is (ctsi/has-delay i2))) - (t/testing "Set delay" - (let [new-interaction (ctsi/set-delay i2 1000)] - (t/is (= 1000 (:delay new-interaction))))))) + (t/testing "Set delay" + (let [new-interaction (ctsi/set-delay i2 1000)] + (t/is (= 1000 (:delay new-interaction))))))) (t/deftest option-destination @@ -167,47 +166,47 @@ i2 (ctsi/set-action-type i1 :prev-screen) i3 (ctsi/set-action-type i1 :open-overlay)] - (t/testing "Has destination" - (t/is (ctsi/has-destination i1)) - (t/is (not (ctsi/has-destination i2)))) + (t/testing "Has destination" + (t/is (ctsi/has-destination i1)) + (t/is (not (ctsi/has-destination i2)))) - (t/testing "Set destination" - (let [new-interaction (ctsi/set-destination i1 destination)] - (t/is (= destination (:destination new-interaction))) - (t/is (nil? (:overlay-pos-type new-interaction))) - (t/is (nil? (:overlay-position new-interaction))))) + (t/testing "Set destination" + (let [new-interaction (ctsi/set-destination i1 destination)] + (t/is (= destination (:destination new-interaction))) + (t/is (nil? (:overlay-pos-type new-interaction))) + (t/is (nil? (:overlay-position new-interaction))))) - (t/testing "Set destination of overlay" - (let [new-interaction (ctsi/set-destination i3 destination)] - (t/is (= destination (:destination new-interaction))) - (t/is (= :center (:overlay-pos-type new-interaction))) - (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))))) + (t/testing "Set destination of overlay" + (let [new-interaction (ctsi/set-destination i3 destination)] + (t/is (= destination (:destination new-interaction))) + (t/is (= :center (:overlay-pos-type new-interaction))) + (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))))) (t/deftest option-preserve-scroll (let [i1 ctsi/default-interaction i2 (ctsi/set-action-type i1 :prev-screen)] - (t/testing "Has preserve-scroll" - (t/is (ctsi/has-preserve-scroll i1)) - (t/is (not (ctsi/has-preserve-scroll i2)))) + (t/testing "Has preserve-scroll" + (t/is (ctsi/has-preserve-scroll i1)) + (t/is (not (ctsi/has-preserve-scroll i2)))) - (t/testing "Set preserve-scroll" - (let [new-interaction (ctsi/set-preserve-scroll i1 true)] - (t/is (= true (:preserve-scroll new-interaction))))))) + (t/testing "Set preserve-scroll" + (let [new-interaction (ctsi/set-preserve-scroll i1 true)] + (t/is (= true (:preserve-scroll new-interaction))))))) (t/deftest option-url (let [i1 ctsi/default-interaction i2 (ctsi/set-action-type i1 :open-url)] - (t/testing "Has url" - (t/is (not (ctsi/has-url i1))) - (t/is (ctsi/has-url i2))) + (t/testing "Has url" + (t/is (not (ctsi/has-url i1))) + (t/is (ctsi/has-url i2))) - (t/testing "Set url" - (let [new-interaction (ctsi/set-url i2 "https://example.com")] - (t/is (= "https://example.com" (:url new-interaction))))))) + (t/testing "Set url" + (let [new-interaction (ctsi/set-url i2 "https://example.com")] + (t/is (= "https://example.com" (:url new-interaction))))))) (t/deftest option-overlay-opts @@ -226,50 +225,237 @@ (ctsi/set-action-type :open-overlay) (ctsi/set-destination (:id overlay-frame)))] - (t/testing "Has overlay options" - (t/is (not (ctsi/has-overlay-opts i1))) - (t/is (ctsi/has-overlay-opts i2))) + (t/testing "Has overlay options" + (t/is (not (ctsi/has-overlay-opts i1))) + (t/is (ctsi/has-overlay-opts i2))) - (t/testing "Set overlay-pos-type without destination" - (let [new-interaction (ctsi/set-overlay-pos-type i2 :top-right base-frame objects)] - (t/is (= :top-right (:overlay-pos-type new-interaction))) - (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))) + (t/testing "Set overlay-pos-type without destination" + (let [new-interaction (ctsi/set-overlay-pos-type i2 :top-right base-frame objects)] + (t/is (= :top-right (:overlay-pos-type new-interaction))) + (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))) - (t/testing "Set overlay-pos-type with destination and auto" - (let [new-interaction (ctsi/set-overlay-pos-type i3 :bottom-right base-frame objects)] - (t/is (= :bottom-right (:overlay-pos-type new-interaction))) - (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))) + (t/testing "Set overlay-pos-type with destination and auto" + (let [new-interaction (ctsi/set-overlay-pos-type i3 :bottom-right base-frame objects)] + (t/is (= :bottom-right (:overlay-pos-type new-interaction))) + (t/is (= (gpt/point 0 0) (:overlay-position new-interaction))))) - (t/testing "Set overlay-pos-type with destination and manual" - (let [new-interaction (ctsi/set-overlay-pos-type i3 :manual base-frame objects)] - (t/is (= :manual (:overlay-pos-type new-interaction))) - (t/is (= (gpt/point 35 40) (:overlay-position new-interaction))))) + (t/testing "Set overlay-pos-type with destination and manual" + (let [new-interaction (ctsi/set-overlay-pos-type i3 :manual base-frame objects)] + (t/is (= :manual (:overlay-pos-type new-interaction))) + (t/is (= (gpt/point 35 40) (:overlay-position new-interaction))))) - (t/testing "Toggle overlay-pos-type" - (let [new-interaction (ctsi/toggle-overlay-pos-type i3 :center base-frame objects) - new-interaction-2 (ctsi/toggle-overlay-pos-type new-interaction :center base-frame objects) - new-interaction-3 (ctsi/toggle-overlay-pos-type new-interaction-2 :top-right base-frame objects)] - (t/is (= :manual (:overlay-pos-type new-interaction))) - (t/is (= (gpt/point 35 40) (:overlay-position new-interaction))) - (t/is (= :center (:overlay-pos-type new-interaction-2))) - (t/is (= (gpt/point 0 0) (:overlay-position new-interaction-2))) - (t/is (= :top-right (:overlay-pos-type new-interaction-3))) - (t/is (= (gpt/point 0 0) (:overlay-position new-interaction-3))))) + (t/testing "Toggle overlay-pos-type" + (let [new-interaction (ctsi/toggle-overlay-pos-type i3 :center base-frame objects) + new-interaction-2 (ctsi/toggle-overlay-pos-type new-interaction :center base-frame objects) + new-interaction-3 (ctsi/toggle-overlay-pos-type new-interaction-2 :top-right base-frame objects)] + (t/is (= :manual (:overlay-pos-type new-interaction))) + (t/is (= (gpt/point 35 40) (:overlay-position new-interaction))) + (t/is (= :center (:overlay-pos-type new-interaction-2))) + (t/is (= (gpt/point 0 0) (:overlay-position new-interaction-2))) + (t/is (= :top-right (:overlay-pos-type new-interaction-3))) + (t/is (= (gpt/point 0 0) (:overlay-position new-interaction-3))))) - (t/testing "Set overlay-position" - (let [new-interaction (ctsi/set-overlay-position i3 (gpt/point 50 60))] - (t/is (= :manual (:overlay-pos-type new-interaction))) - (t/is (= (gpt/point 50 60) (:overlay-position new-interaction))))) + (t/testing "Set overlay-position" + (let [new-interaction (ctsi/set-overlay-position i3 (gpt/point 50 60))] + (t/is (= :manual (:overlay-pos-type new-interaction))) + (t/is (= (gpt/point 50 60) (:overlay-position new-interaction))))) - (t/testing "Set close-click-outside" - (let [new-interaction (ctsi/set-close-click-outside i3 true)] - (t/is (not (:close-click-outside i3))) - (t/is (:close-click-outside new-interaction)))) + (t/testing "Set close-click-outside" + (let [new-interaction (ctsi/set-close-click-outside i3 true)] + (t/is (not (:close-click-outside i3))) + (t/is (:close-click-outside new-interaction)))) - (t/testing "Set background-overlay" - (let [new-interaction (ctsi/set-background-overlay i3 true)] - (t/is (not (:background-overlay i3))) - (t/is (:background-overlay new-interaction)))))) + (t/testing "Set background-overlay" + (let [new-interaction (ctsi/set-background-overlay i3 true)] + (t/is (not (:background-overlay i3))) + (t/is (:background-overlay new-interaction)))) + + (t/testing "Set relative-to" + (let [relative-to-id (uuid/random) + new-interaction (ctsi/set-position-relative-to i3 relative-to-id)] + (t/is (= relative-to-id (:position-relative-to new-interaction))))))) + + +(t/deftest calc-overlay-position + (let [base-frame (-> (cts/make-minimal-shape :frame) + (assoc-in [:selrect :width] 100) + (assoc-in [:selrect :height] 100)) + popup (-> (cts/make-minimal-shape :frame) + (assoc-in [:selrect :width] 50) + (assoc-in [:selrect :height] 50) + (assoc-in [:selrect :x] 10) + (assoc-in [:selrect :y] 10)) + overlay-frame (-> (cts/make-minimal-shape :frame) + (assoc-in [:selrect :width] 30) + (assoc-in [:selrect :height] 20)) + + objects {(:id base-frame) base-frame + (:id popup) popup + (:id overlay-frame) overlay-frame} + + frame-offset (gpt/point 5 5) + + interaction (-> ctsi/default-interaction + (ctsi/set-action-type :open-overlay) + (ctsi/set-destination (:id overlay-frame))) + interaction-auto (ctsi/set-position-relative-to interaction nil) + interaction-base-frame (ctsi/set-position-relative-to interaction (:id base-frame)) + interaction-popup (ctsi/set-position-relative-to interaction (:id popup))] + (t/testing "Overlay top-left relative to auto" + (let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-left base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 0)) + (t/is (= (:y overlay-pos) 0)))) + + (t/testing "Overlay top-center relative to auto" + (let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 35)) + (t/is (= (:y overlay-pos) 0)))) + + (t/testing "Overlay top-right relative to auto" + (let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-right base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 70)) + (t/is (= (:y overlay-pos) 0)))) + + (t/testing "Overlay bottom-left relative to auto" + (let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-left base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 0)) + (t/is (= (:y overlay-pos) 80)))) + + (t/testing "Overlay bottom-center relative to auto" + (let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 35)) + (t/is (= (:y overlay-pos) 80)))) + + (t/testing "Overlay bottom-right relative to auto" + (let [i2 (ctsi/set-overlay-pos-type interaction-auto :bottom-right base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 70)) + (t/is (= (:y overlay-pos) 80)))) + + (t/testing "Overlay center relative to auto" + (let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 35)) + (t/is (= (:y overlay-pos) 40)))) + + (t/testing "Overlay manual relative to auto" + (let [i2 (ctsi/set-overlay-pos-type interaction-auto :center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 35)) + (t/is (= (:y overlay-pos) 40)))) + + (t/testing "Overlay manual relative to auto" + (let [i2 (-> interaction-auto + (ctsi/set-overlay-pos-type :manual base-frame objects) + (ctsi/set-overlay-position (gpt/point 12 62))) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 17)) + (t/is (= (:y overlay-pos) 67)))) + + (t/testing "Overlay top-left relative to base-frame" + (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-left base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 5)) + (t/is (= (:y overlay-pos) 5)))) + + (t/testing "Overlay top-center relative to base-frame" + (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 40)) + (t/is (= (:y overlay-pos) 5)))) + + (t/testing "Overlay top-right relative to base-frame" + (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :top-right base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 75)) + (t/is (= (:y overlay-pos) 5)))) + + (t/testing "Overlay bottom-left relative to base-frame" + (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-left base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 5)) + (t/is (= (:y overlay-pos) 85)))) + + (t/testing "Overlay bottom-center relative to base-frame" + (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 40)) + (t/is (= (:y overlay-pos) 85)))) + + (t/testing "Overlay bottom-right relative to base-frame" + (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :bottom-right base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 75)) + (t/is (= (:y overlay-pos) 85)))) + + (t/testing "Overlay center relative to base-frame" + (let [i2 (ctsi/set-overlay-pos-type interaction-base-frame :center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 40)) + (t/is (= (:y overlay-pos) 45)))) + + (t/testing "Overlay manual relative to base-frame" + (let [i2 (-> interaction-base-frame + (ctsi/set-overlay-pos-type :manual base-frame objects) + (ctsi/set-overlay-position (gpt/point 12 62))) + overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 17)) + (t/is (= (:y overlay-pos) 67)))) + + (t/testing "Overlay top-left relative to popup" + (let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 15)) + (t/is (= (:y overlay-pos) 15)))) + + (t/testing "Overlay top-center relative to popup" + (let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 25)) + (t/is (= (:y overlay-pos) 15)))) + + (t/testing "Overlay top-right relative to popup" + (let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-right base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 35)) + (t/is (= (:y overlay-pos) 15)))) + + (t/testing "Overlay bottom-left relative to popup" + (let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-left base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 15)) + (t/is (= (:y overlay-pos) 45)))) + + (t/testing "Overlay bottom-center relative to popup" + (let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 25)) + (t/is (= (:y overlay-pos) 45)))) + + (t/testing "Overlay bottom-right relative to popup" + (let [i2 (ctsi/set-overlay-pos-type interaction-popup :bottom-right base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 35)) + (t/is (= (:y overlay-pos) 45)))) + + (t/testing "Overlay center relative to popup" + (let [i2 (ctsi/set-overlay-pos-type interaction-popup :center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 25)) + (t/is (= (:y overlay-pos) 30)))) + + (t/testing "Overlay manual relative to popup" + (let [i2 (-> interaction-popup + (ctsi/set-overlay-pos-type :manual base-frame objects) + (ctsi/set-overlay-position (gpt/point 12 62))) + overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 27)) + (t/is (= (:y overlay-pos) 77)))))) (t/deftest animation-checks @@ -409,11 +595,11 @@ :easing :ease-out :direction :left})] (t/is (not (ctsi/allowed-animation? (:action-type bad-interaction-1) - (-> bad-interaction-1 :animation :animation-type)))) + (-> bad-interaction-1 :animation :animation-type)))) (t/is (not (ctsi/allowed-animation? (:action-type bad-interaction-2) - (-> bad-interaction-1 :animation :animation-type)))) + (-> bad-interaction-1 :animation :animation-type)))) (t/is (not (ctsi/allowed-animation? (:action-type bad-interaction-3) - (-> bad-interaction-1 :animation :animation-type)))))) + (-> bad-interaction-1 :animation :animation-type)))))) (t/testing "Remove animation if moving to an forbidden state" (let [interaction (ctsi/set-animation-type ctsi/default-interaction :push) @@ -425,26 +611,26 @@ (let [i1 ctsi/default-interaction i2 (ctsi/set-animation-type ctsi/default-interaction :dissolve)] - (t/testing "Has duration?" - (t/is (not (ctsi/has-duration? i1))) - (t/is (ctsi/has-duration? i2))) + (t/testing "Has duration?" + (t/is (not (ctsi/has-duration? i1))) + (t/is (ctsi/has-duration? i2))) - (t/testing "Set duration" - (let [new-interaction (ctsi/set-duration i2 1000)] - (t/is (= 1000 (-> new-interaction :animation :duration))))))) + (t/testing "Set duration" + (let [new-interaction (ctsi/set-duration i2 1000)] + (t/is (= 1000 (-> new-interaction :animation :duration))))))) (t/deftest option-easing (let [i1 ctsi/default-interaction i2 (ctsi/set-animation-type ctsi/default-interaction :dissolve)] - (t/testing "Has easing?" - (t/is (not (ctsi/has-easing? i1))) - (t/is (ctsi/has-easing? i2))) + (t/testing "Has easing?" + (t/is (not (ctsi/has-easing? i1))) + (t/is (ctsi/has-easing? i2))) - (t/testing "Set easing" - (let [new-interaction (ctsi/set-easing i2 :ease-in)] - (t/is (= :ease-in (-> new-interaction :animation :easing))))))) + (t/testing "Set easing" + (let [new-interaction (ctsi/set-easing i2 :ease-in)] + (t/is (= :ease-in (-> new-interaction :animation :easing))))))) (t/deftest option-way @@ -452,15 +638,15 @@ i2 (ctsi/set-animation-type ctsi/default-interaction :slide) i3 (ctsi/set-action-type i2 :open-overlay)] - (t/testing "Has way?" - (t/is (not (ctsi/has-way? i1))) - (t/is (ctsi/has-way? i2)) - (t/is (not (ctsi/has-way? i3))) - (t/is (some? (-> i3 :animation :way)))) ; <- it exists but is ignored + (t/testing "Has way?" + (t/is (not (ctsi/has-way? i1))) + (t/is (ctsi/has-way? i2)) + (t/is (not (ctsi/has-way? i3))) + (t/is (some? (-> i3 :animation :way)))) ; <- it exists but is ignored - (t/testing "Set way" - (let [new-interaction (ctsi/set-way i2 :out)] - (t/is (= :out (-> new-interaction :animation :way))))))) + (t/testing "Set way" + (let [new-interaction (ctsi/set-way i2 :out)] + (t/is (= :out (-> new-interaction :animation :way))))))) (t/deftest option-direction @@ -468,49 +654,49 @@ i2 (ctsi/set-animation-type ctsi/default-interaction :push) i3 (ctsi/set-animation-type ctsi/default-interaction :dissolve)] - (t/testing "Has direction?" - (t/is (not (ctsi/has-direction? i1))) - (t/is (ctsi/has-direction? i2))) + (t/testing "Has direction?" + (t/is (not (ctsi/has-direction? i1))) + (t/is (ctsi/has-direction? i2))) - (t/testing "Set direction" - (let [new-interaction (ctsi/set-direction i2 :left)] - (t/is (= :left (-> new-interaction :animation :direction))))) + (t/testing "Set direction" + (let [new-interaction (ctsi/set-direction i2 :left)] + (t/is (= :left (-> new-interaction :animation :direction))))) - (t/testing "Invert direction" - (let [a-none (:animation i3) - a-right (:animation i2) - a-left (assoc a-right :direction :left) - a-up (assoc a-right :direction :up) - a-down (assoc a-right :direction :down) + (t/testing "Invert direction" + (let [a-none (:animation i3) + a-right (:animation i2) + a-left (assoc a-right :direction :left) + a-up (assoc a-right :direction :up) + a-down (assoc a-right :direction :down) - a-nil' (ctsi/invert-direction nil) - a-none' (ctsi/invert-direction a-none) - a-right' (ctsi/invert-direction a-right) - a-left' (ctsi/invert-direction a-left) - a-up' (ctsi/invert-direction a-up) - a-down' (ctsi/invert-direction a-down)] + a-nil' (ctsi/invert-direction nil) + a-none' (ctsi/invert-direction a-none) + a-right' (ctsi/invert-direction a-right) + a-left' (ctsi/invert-direction a-left) + a-up' (ctsi/invert-direction a-up) + a-down' (ctsi/invert-direction a-down)] - (t/is (nil? a-nil')) - (t/is (nil? (:direction a-none'))) - (t/is (= :left (:direction a-right'))) - (t/is (= :right (:direction a-left'))) - (t/is (= :down (:direction a-up'))) - (t/is (= :up (:direction a-down'))))))) + (t/is (nil? a-nil')) + (t/is (nil? (:direction a-none'))) + (t/is (= :left (:direction a-right'))) + (t/is (= :right (:direction a-left'))) + (t/is (= :down (:direction a-up'))) + (t/is (= :up (:direction a-down'))))))) (t/deftest option-offset-effect (let [i1 ctsi/default-interaction i2 (ctsi/set-animation-type ctsi/default-interaction :slide) i3 (ctsi/set-action-type i2 :open-overlay)] - (t/testing "Has offset-effect" - (t/is (not (ctsi/has-offset-effect? i1))) - (t/is (ctsi/has-offset-effect? i2)) - (t/is (not (ctsi/has-offset-effect? i3))) - (t/is (some? (-> i3 :animation :offset-effect)))) ; <- it exists but is ignored + (t/testing "Has offset-effect" + (t/is (not (ctsi/has-offset-effect? i1))) + (t/is (ctsi/has-offset-effect? i2)) + (t/is (not (ctsi/has-offset-effect? i3))) + (t/is (some? (-> i3 :animation :offset-effect)))) ; <- it exists but is ignored - (t/testing "Set offset-effect" - (let [new-interaction (ctsi/set-offset-effect i2 true)] - (t/is (= true (-> new-interaction :animation :offset-effect))))))) + (t/testing "Set offset-effect" + (let [new-interaction (ctsi/set-offset-effect i2 true)] + (t/is (= true (-> new-interaction :animation :offset-effect))))))) (t/deftest modify-interactions diff --git a/frontend/src/app/main/data/workspace/interactions.cljs b/frontend/src/app/main/data/workspace/interactions.cljs index c26d6ee964..ca33c05e80 100644 --- a/frontend/src/app/main/data/workspace/interactions.cljs +++ b/frontend/src/app/main/data/workspace/interactions.cljs @@ -121,9 +121,9 @@ (rx/concat (rx/of (dch/update-shapes [(:id shape)] (fn [shape] - (let [new-interaction (ctsi/set-destination - ctsi/default-interaction - destination)] + (let [new-interaction (-> ctsi/default-interaction + (ctsi/set-destination destination) + (assoc :position-relative-to (:id shape)))] (update shape :interactions ctsi/add-interaction new-interaction))))) (when (and (not (connected-frame? objects (:id frame))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 5c2e6eea14..fb66b3b6b4 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -388,6 +388,9 @@ (def viewer-local (l/derived :viewer-local st/state)) +(def viewer-overlays + (l/derived :viewer-overlays st/state)) + (def comment-threads (l/derived :comment-threads st/state)) diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index 5a0de8f95f..b884653baa 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -36,8 +36,16 @@ (def viewer-interactions-show? (l/derived :interactions-show? refs/viewer-local)) +(defn- find-relative-to-base-frame + [shape objects overlays-ids base-frame] + (if (or (empty? overlays-ids) (nil? shape) (cph/root? shape)) + base-frame + (if (contains? overlays-ids (:id shape)) + shape + (find-relative-to-base-frame (cph/get-parent objects (:id shape)) objects overlays-ids base-frame)))) + (defn- activate-interaction - [interaction shape base-frame frame-offset objects] + [interaction shape base-frame frame-offset objects overlays] (case (:action-type interaction) :navigate (when-let [frame-id (:destination interaction)] @@ -49,15 +57,21 @@ (dv/go-to-frame frame-id (:animation interaction))))) :open-overlay - (let [dest-frame-id (:destination interaction) - close-click-outside (:close-click-outside interaction) - background-overlay (:background-overlay interaction) - - dest-frame (get objects dest-frame-id) - position (ctsi/calc-overlay-position interaction - base-frame - dest-frame - frame-offset)] + (let [dest-frame-id (:destination interaction) + dest-frame (get objects dest-frame-id) + relative-to-id (if (= :manual (:overlay-pos-type interaction)) + (:id shape) ;; manual interactions are allways from "self" + (:position-relative-to interaction)) + relative-to-shape (or (get objects relative-to-id) base-frame) + close-click-outside (:close-click-outside interaction) + background-overlay (:background-overlay interaction) + overlays-ids (set (map :id overlays)) + relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame) + position (ctsi/calc-overlay-position interaction + relative-to-shape + relative-to-base-frame + dest-frame + frame-offset)] (when dest-frame-id (st/emit! (dv/open-overlay dest-frame-id position @@ -66,14 +80,22 @@ (:animation interaction))))) :toggle-overlay - (let [frame-id (:destination interaction) - dest-frame (get objects frame-id) - position (ctsi/calc-overlay-position interaction - base-frame - dest-frame - frame-offset) - close-click-outside (:close-click-outside interaction) - background-overlay (:background-overlay interaction)] + (let [frame-id (:destination interaction) + dest-frame (get objects frame-id) + relative-to-id (if (= :manual (:overlay-pos-type interaction)) + (:id shape) ;; manual interactions are allways from "self" + (:position-relative-to interaction)) + relative-to-shape (or (get objects relative-to-id) base-frame) + overlays-ids (set (map :id overlays)) + relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame) + position (ctsi/calc-overlay-position interaction + relative-to-shape + relative-to-base-frame + dest-frame + frame-offset) + + close-click-outside (:close-click-outside interaction) + background-overlay (:background-overlay interaction)] (when frame-id (st/emit! (dv/toggle-overlay frame-id position @@ -98,7 +120,7 @@ ;; Perform the opposite action of an interaction, if possible (defn- deactivate-interaction - [interaction shape base-frame frame-offset objects] + [interaction shape base-frame frame-offset objects overlays] (case (:action-type interaction) :open-overlay (let [frame-id (or (:destination interaction) @@ -120,15 +142,21 @@ (:animation interaction))))) :close-overlay - (let [dest-frame-id (:destination interaction) - close-click-outside (:close-click-outside interaction) - background-overlay (:background-overlay interaction) - - dest-frame (get objects dest-frame-id) - position (ctsi/calc-overlay-position interaction - base-frame - dest-frame - frame-offset)] + (let [dest-frame-id (:destination interaction) + dest-frame (get objects dest-frame-id) + relative-to-id (if (= :manual (:overlay-pos-type interaction)) + (:id shape) ;; manual interactions are allways from "self" + (:position-relative-to interaction)) + relative-to-shape (or (get objects relative-to-id) base-frame) + close-click-outside (:close-click-outside interaction) + background-overlay (:background-overlay interaction) + overlays-ids (set (map :id overlays)) + relative-to-base-frame (find-relative-to-base-frame relative-to-shape objects overlays-ids base-frame) + position (ctsi/calc-overlay-position interaction + relative-to-shape + relative-to-base-frame + dest-frame + frame-offset)] (when dest-frame-id (st/emit! (dv/open-overlay dest-frame-id position @@ -138,36 +166,36 @@ nil)) (defn- on-mouse-down - [event shape base-frame frame-offset objects] + [event shape base-frame frame-offset objects overlays] (let [interactions (->> (:interactions shape) (filter #(or (= (:event-type %) :click) (= (:event-type %) :mouse-press))))] (when (seq interactions) (dom/stop-propagation event) (doseq [interaction interactions] - (activate-interaction interaction shape base-frame frame-offset objects))))) + (activate-interaction interaction shape base-frame frame-offset objects overlays))))) (defn- on-mouse-up - [event shape base-frame frame-offset objects] + [event shape base-frame frame-offset objects overlays] (let [interactions (->> (:interactions shape) (filter #(= (:event-type %) :mouse-press)))] (when (seq interactions) (dom/stop-propagation event) (doseq [interaction interactions] - (deactivate-interaction interaction shape base-frame frame-offset objects))))) + (deactivate-interaction interaction shape base-frame frame-offset objects overlays))))) (defn- on-mouse-enter - [event shape base-frame frame-offset objects] + [event shape base-frame frame-offset objects overlays] (let [interactions (->> (:interactions shape) (filter #(or (= (:event-type %) :mouse-enter) (= (:event-type %) :mouse-over))))] (when (seq interactions) (dom/stop-propagation event) (doseq [interaction interactions] - (activate-interaction interaction shape base-frame frame-offset objects))))) + (activate-interaction interaction shape base-frame frame-offset objects overlays))))) (defn- on-mouse-leave - [event shape base-frame frame-offset objects] + [event shape base-frame frame-offset objects overlays] (let [interactions (->> (:interactions shape) (filter #(= (:event-type %) :mouse-leave))) interactions-inv (->> (:interactions shape) @@ -175,19 +203,19 @@ (when (or (seq interactions) (seq interactions-inv)) (dom/stop-propagation event) (doseq [interaction interactions] - (activate-interaction interaction shape base-frame frame-offset objects)) + (activate-interaction interaction shape base-frame frame-offset objects overlays)) (doseq [interaction interactions-inv] - (deactivate-interaction interaction shape base-frame frame-offset objects))))) + (deactivate-interaction interaction shape base-frame frame-offset objects overlays))))) (defn- on-load - [shape base-frame frame-offset objects] + [shape base-frame frame-offset objects overlays] (let [interactions (->> (:interactions shape) (filter #(= (:event-type %) :after-delay)))] (loop [interactions (seq interactions) sems []] (if-let [interaction (first interactions)] (let [sem (tm/schedule (:delay interaction) - #(activate-interaction interaction shape base-frame frame-offset objects))] + #(activate-interaction interaction shape base-frame frame-offset objects overlays))] (recur (next interactions) (conj sems sem))) sems)))) @@ -209,73 +237,71 @@ :transform (gsh/transform-str shape)}]))) + ;; TODO: use-memo use-fn (defn generic-wrapper-factory "Wrap some svg shape and add interaction controls" [component] (mf/fnc generic-wrapper - {::mf/wrap-props false} - [props] - (let [shape (unchecked-get props "shape") - childs (unchecked-get props "childs") - frame (unchecked-get props "frame") - objects (unchecked-get props "objects") - base-frame (mf/use-ctx base-frame-ctx) - frame-offset (mf/use-ctx frame-offset-ctx) + {::mf/wrap-props false} + [props] + (let [shape (unchecked-get props "shape") + childs (unchecked-get props "childs") + frame (unchecked-get props "frame") + objects (unchecked-get props "objects") + base-frame (mf/use-ctx base-frame-ctx) + frame-offset (mf/use-ctx frame-offset-ctx) + interactions-show? (mf/deref viewer-interactions-show?) + overlays (mf/deref refs/viewer-overlays) + interactions (:interactions shape) + svg-element? (and (= :svg-raw (:type shape)) + (not= :svg (get-in shape [:content :tag]))) - interactions-show? (mf/deref viewer-interactions-show?) + on-mouse-down + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-down % shape base-frame frame-offset objects overlays)) - interactions (:interactions shape) + on-mouse-up + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-up % shape base-frame frame-offset objects overlays)) - svg-element? (and (= :svg-raw (:type shape)) - (not= :svg (get-in shape [:content :tag]))) + on-mouse-enter + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-enter % shape base-frame frame-offset objects overlays)) + + on-mouse-leave + (mf/use-fn (mf/deps shape base-frame frame-offset objects) + #(on-mouse-leave % shape base-frame frame-offset objects overlays))] - on-mouse-down - (mf/use-fn (mf/deps shape base-frame frame-offset objects) - #(on-mouse-down % shape base-frame frame-offset objects)) + (mf/with-effect [] + (let [sems (on-load shape base-frame frame-offset objects overlays)] + (partial run! tm/dispose! sems))) - on-mouse-up - (mf/use-fn (mf/deps shape base-frame frame-offset objects) - #(on-mouse-up % shape base-frame frame-offset objects)) + (if-not svg-element? + [:> shape-container {:shape shape + :cursor (when (ctsi/actionable? interactions) "pointer") + :on-mouse-down on-mouse-down + :on-mouse-up on-mouse-up + :on-mouse-enter on-mouse-enter + :on-mouse-leave on-mouse-leave} - on-mouse-enter - (mf/use-fn (mf/deps shape base-frame frame-offset objects) - #(on-mouse-enter % shape base-frame frame-offset objects)) + [:& component {:shape shape + :frame frame + :childs childs + :is-child-selected? true + :objects objects}] - on-mouse-leave - (mf/use-fn (mf/deps shape base-frame frame-offset objects) - #(on-mouse-leave % shape base-frame frame-offset objects))] - - - (mf/with-effect [] - (let [sems (on-load shape base-frame frame-offset objects)] - (partial run! tm/dispose! sems))) - - (if-not svg-element? - [:> shape-container {:shape shape - :cursor (when (ctsi/actionable? interactions) "pointer") - :on-mouse-down on-mouse-down - :on-mouse-up on-mouse-up - :on-mouse-enter on-mouse-enter - :on-mouse-leave on-mouse-leave} - - [:& component {:shape shape - :frame frame - :childs childs - :is-child-selected? true - :objects objects}] - - [:& interaction {:shape shape - :interactions interactions - :interactions-show? interactions-show?}]] + [:& interaction {:shape shape + :interactions interactions + :interactions-show? interactions-show?}]] ;; Don't wrap svg elements inside a otherwise some can break - [:& component {:shape shape - :frame frame - :childs childs - :objects objects}])))) + [:& component {:shape shape + :frame frame + :childs childs + :objects objects}])))) (defn frame-wrapper [shape-container] @@ -320,58 +346,58 @@ (let [shape-container (shape-container-factory objects) frame-wrapper (frame-wrapper shape-container)] (mf/fnc frame-container - {::mf/wrap-props false} - [props] - (let [shape (obj/get props "shape") - childs (mapv #(get objects %) (:shapes shape)) - shape (gsh/transform-shape shape) - props (obj/merge! #js {} props - #js {:shape shape - :childs childs - :objects objects})] + {::mf/wrap-props false} + [props] + (let [shape (obj/get props "shape") + childs (mapv #(get objects %) (:shapes shape)) + shape (gsh/transform-shape shape) + props (obj/merge! #js {} props + #js {:shape shape + :childs childs + :objects objects})] - [:> frame-wrapper props])))) + [:> frame-wrapper props])))) (defn group-container-factory [objects] (let [shape-container (shape-container-factory objects) group-wrapper (group-wrapper shape-container)] (mf/fnc group-container - {::mf/wrap-props false} - [props] - (let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape"))) - props (obj/merge! #js {} props - #js {:childs childs - :objects objects})] - (when (not-empty childs) - [:> group-wrapper props]))))) + {::mf/wrap-props false} + [props] + (let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape"))) + props (obj/merge! #js {} props + #js {:childs childs + :objects objects})] + (when (not-empty childs) + [:> group-wrapper props]))))) (defn bool-container-factory [objects] (let [shape-container (shape-container-factory objects) bool-wrapper (bool-wrapper shape-container)] (mf/fnc bool-container - {::mf/wrap-props false} - [props] - (let [childs (->> (cph/get-children-ids objects (:id (unchecked-get props "shape"))) - (select-keys objects)) - props (obj/merge! #js {} props - #js {:childs childs - :objects objects})] - [:> bool-wrapper props])))) + {::mf/wrap-props false} + [props] + (let [childs (->> (cph/get-children-ids objects (:id (unchecked-get props "shape"))) + (select-keys objects)) + props (obj/merge! #js {} props + #js {:childs childs + :objects objects})] + [:> bool-wrapper props])))) (defn svg-raw-container-factory [objects] (let [shape-container (shape-container-factory objects) svg-raw-wrapper (svg-raw-wrapper shape-container)] (mf/fnc svg-raw-container - {::mf/wrap-props false} - [props] - (let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape"))) - props (obj/merge! #js {} props - #js {:childs childs - :objects objects})] - [:> svg-raw-wrapper props])))) + {::mf/wrap-props false} + [props] + (let [childs (mapv #(get objects %) (:shapes (unchecked-get props "shape"))) + props (obj/merge! #js {} props + #js {:childs childs + :objects objects})] + [:> svg-raw-wrapper props])))) (defn shape-container-factory [objects] @@ -381,42 +407,40 @@ image-wrapper (image-wrapper) circle-wrapper (circle-wrapper)] (mf/fnc shape-container - {::mf/wrap-props false - ::mf/wrap [mf/memo]} - [props] - (let [shape (unchecked-get props "shape") - frame (unchecked-get props "frame") + {::mf/wrap-props false + ::mf/wrap [mf/memo]} + [props] + (let [shape (unchecked-get props "shape") + frame (unchecked-get props "frame") - group-container - (mf/with-memo [objects] - (group-container-factory objects)) + group-container + (mf/with-memo [objects] + (group-container-factory objects)) - frame-container - (mf/with-memo [objects] - (frame-container-factory objects)) + frame-container + (mf/with-memo [objects] + (frame-container-factory objects)) - bool-container - (mf/with-memo [objects] - (bool-container-factory objects)) + bool-container + (mf/with-memo [objects] + (bool-container-factory objects)) - svg-raw-container - (mf/with-memo [objects] - (svg-raw-container-factory objects)) + svg-raw-container + (mf/with-memo [objects] + (svg-raw-container-factory objects))] + (when (and shape (not (:hidden shape))) + (let [shape (-> (gsh/transform-shape shape) + (gsh/translate-to-frame frame)) - ] - (when (and shape (not (:hidden shape))) - (let [shape (-> (gsh/transform-shape shape) - (gsh/translate-to-frame frame)) - - opts #js {:shape shape - :objects objects}] - (case (:type shape) - :frame [:> frame-container opts] - :text [:> text-wrapper opts] - :rect [:> rect-wrapper opts] - :path [:> path-wrapper opts] - :image [:> image-wrapper opts] - :circle [:> circle-wrapper opts] - :group [:> group-container {:shape shape :frame frame :objects objects}] - :bool [:> bool-container {:shape shape :frame frame :objects objects}] - :svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}]))))))) + opts #js {:shape shape + :objects objects}] + (case (:type shape) + :frame [:> frame-container opts] + :text [:> text-wrapper opts] + :rect [:> rect-wrapper opts] + :path [:> path-wrapper opts] + :image [:> image-wrapper opts] + :circle [:> circle-wrapper opts] + :group [:> group-container {:shape shape :frame frame :objects objects}] + :bool [:> bool-container {:shape shape :frame frame :objects objects}] + :svg-raw [:> svg-raw-container {:shape shape :frame frame :objects objects}]))))))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs index eac18cdb16..655d1873c5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs @@ -184,6 +184,8 @@ destination (get objects (:destination interaction)) frames (mf/with-memo [objects] (ctt/get-viewer-frames objects {:all-frames? true})) + shape-parent-ids (mf/with-memo [objects] (cph/get-parent-ids objects (:id shape))) + shape-parents (mf/with-memo [frames shape] (filter (comp (set shape-parent-ids) :id) frames)) overlay-pos-type (:overlay-pos-type interaction) close-click-outside? (:close-click-outside interaction false) @@ -220,6 +222,14 @@ value (when (not= value "") (uuid/uuid value))] (update-interaction index #(ctsi/set-destination % value)))) + change-position-relative-to + (fn [event] + (let [value (-> event + dom/get-target + dom/get-value + uuid/uuid)] + (update-interaction index #(ctsi/set-position-relative-to % value)))) + change-preserve-scroll (fn [event] (let [value (-> event dom/get-target dom/checked?)] @@ -243,9 +253,12 @@ (dom/add-class! target "error")))) change-overlay-pos-type - (fn [event] + (fn [shape-id event] (let [value (-> event dom/get-target dom/get-value d/read-string)] - (update-interaction index #(ctsi/set-overlay-pos-type % value shape objects)))) + (update-interaction index #(ctsi/set-overlay-pos-type % value shape objects)) + (when (= value :manual) + (update-interaction index #(ctsi/set-position-relative-to % shape-id))))) + toggle-overlay-pos-type (fn [pos-type] @@ -287,8 +300,7 @@ change-offset-effect (fn [event] (let [value (-> event dom/get-target dom/checked?)] - (update-interaction index #(ctsi/set-offset-effect % value)))) - ] + (update-interaction index #(ctsi/set-offset-effect % value))))] [:* [:div.element-set-options-group {:class (dom/classnames @@ -378,12 +390,27 @@ (when (ctsi/has-overlay-opts interaction) [:* + ; Overlay position relative-to (select) + [:div.interactions-element + [:span.element-set-subtitle.wide (tr "workspace.options.interaction-relative-to")] + [:select.input-select + {:value (str (:position-relative-to interaction)) + :on-change change-position-relative-to} + (when (not= (:overlay-pos-type interaction) :manual) + [:* + [:option {:value ""} (tr "workspace.options.interaction-auto")] + (for [frame shape-parents] + [:option {:key (dm/str "position-relative-to-" (:id frame)) + :value (str (:id frame))} (:name frame)])]) + [:option {:key (dm/str "position-relative-to-" (:id shape)) + :value (str (:id shape))} (:name shape) " (" (tr "workspace.options.interaction-self") ")"]]] + ; Overlay position (select) [:div.interactions-element [:span.element-set-subtitle.wide (tr "workspace.options.interaction-position")] [:select.input-select {:value (str (:overlay-pos-type interaction)) - :on-change change-overlay-pos-type} + :on-change (partial change-overlay-pos-type (:id shape))} (for [[value name] (overlay-pos-type-names)] [:option {:value (str value)} name])]] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 058be9b707..3bccfefadb 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3222,6 +3222,9 @@ msgstr "Push" msgid "workspace.options.interaction-animation-slide" msgstr "Slide" +msgid "workspace.options.interaction-auto" +msgstr "auto" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-background" msgstr "Add background overlay" @@ -3362,6 +3365,10 @@ msgstr "Top right" msgid "workspace.options.interaction-position" msgstr "Position" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-relative-to" +msgstr "Relative to" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-preserve-scroll" msgstr "Preserve scroll position" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index e37b25d2bf..efed7f4802 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -3621,6 +3621,9 @@ msgstr "Empujar" msgid "workspace.options.interaction-animation-slide" msgstr "Deslizar" +msgid "workspace.options.interaction-auto" +msgstr "automático" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-background" msgstr "Añadir sombreado de fondo" @@ -3761,6 +3764,10 @@ msgstr "Arriba derecha" msgid "workspace.options.interaction-position" msgstr "Posición" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.interaction-relative-to" +msgstr "Relativo a" + #: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs msgid "workspace.options.interaction-preserve-scroll" msgstr "Conservar posición de desplazamiento" From 3c424786a7992ac6bed0cc214f21ecdbb78f67e9 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 15 Nov 2022 16:32:03 +0100 Subject: [PATCH 194/682] :sparkles: Show board miniature in manual overlay setting --- CHANGES.md | 1 + .../app/common/types/shape/interactions.cljc | 16 +++-- .../types_shape_interactions_test.cljc | 62 ++++++++++++++++++- .../ui/workspace/viewport/interactions.cljs | 9 ++- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bb9f0aa99e..5238ce23a0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ ### :boom: Breaking changes & Deprecations ### :sparkles: New features - Better overlays interactions on boards inside boards [Taiga #4386](https://tree.taiga.io/project/penpot/us/4386) +- Show board miniature in manual overlay setting [Taiga #4475](https://tree.taiga.io/project/penpot/issue/4475) ### :bug: Bugs fixed - Add title to color bullets [Taiga #4218](https://tree.taiga.io/project/penpot/task/4218) diff --git a/common/src/app/common/types/shape/interactions.cljc b/common/src/app/common/types/shape/interactions.cljc index 4731a1ef61..048e963d98 100644 --- a/common/src/app/common/types/shape/interactions.cljc +++ b/common/src/app/common/types/shape/interactions.cljc @@ -371,17 +371,23 @@ (assert (has-overlay-opts interaction)) (if (nil? dest-frame) (gpt/point 0 0) - (let [overlay-position (:overlay-position interaction) - overlay-size (:selrect dest-frame) - relative-to-shape-size (:selrect relative-to-shape) + (let [overlay-size (:selrect dest-frame) base-frame-size (:selrect base-frame) + relative-to-shape-size (:selrect relative-to-shape) + relative-to-adjusted-to-base-frame {:x (- (:x relative-to-shape-size) (:x base-frame-size)) + :y (- (:y relative-to-shape-size) (:y base-frame-size))} relative-to-is-auto? (and (nil? (:position-relative-to interaction)) (not= :manual (:overlay-pos-type interaction))) base-position (if relative-to-is-auto? {:x 0 :y 0} {:x (+ (:x frame-offset) - (- (:x relative-to-shape-size) (:x base-frame-size))) + (:x relative-to-adjusted-to-base-frame)) :y (+ (:y frame-offset) - (- (:y relative-to-shape-size) (:y base-frame-size)))})] + (:y relative-to-adjusted-to-base-frame))}) + overlay-position (:overlay-position interaction) + overlay-position (if (= (:type relative-to-shape) :frame) + overlay-position + {:x (- (:x overlay-position) (:x relative-to-adjusted-to-base-frame)) + :y (- (:y overlay-position) (:y relative-to-adjusted-to-base-frame))})] (case (:overlay-pos-type interaction) :center (gpt/point (+ (:x base-position) (/ (- (:width relative-to-shape-size) (:width overlay-size)) 2)) diff --git a/common/test/common_tests/types_shape_interactions_test.cljc b/common/test/common_tests/types_shape_interactions_test.cljc index 922d337d22..aa77c913a9 100644 --- a/common/test/common_tests/types_shape_interactions_test.cljc +++ b/common/test/common_tests/types_shape_interactions_test.cljc @@ -285,6 +285,13 @@ (assoc-in [:selrect :height] 50) (assoc-in [:selrect :x] 10) (assoc-in [:selrect :y] 10)) + + rect (-> (cts/make-minimal-shape :rect) + (assoc-in [:selrect :width] 50) + (assoc-in [:selrect :height] 50) + (assoc-in [:selrect :x] 10) + (assoc-in [:selrect :y] 10)) + overlay-frame (-> (cts/make-minimal-shape :frame) (assoc-in [:selrect :width] 30) (assoc-in [:selrect :height] 20)) @@ -300,7 +307,8 @@ (ctsi/set-destination (:id overlay-frame))) interaction-auto (ctsi/set-position-relative-to interaction nil) interaction-base-frame (ctsi/set-position-relative-to interaction (:id base-frame)) - interaction-popup (ctsi/set-position-relative-to interaction (:id popup))] + interaction-popup (ctsi/set-position-relative-to interaction (:id popup)) + interaction-rect (ctsi/set-position-relative-to interaction (:id rect))] (t/testing "Overlay top-left relative to auto" (let [i2 (ctsi/set-overlay-pos-type interaction-auto :top-left base-frame objects) overlay-pos (ctsi/calc-overlay-position i2 base-frame base-frame overlay-frame frame-offset)] @@ -455,7 +463,57 @@ (ctsi/set-overlay-position (gpt/point 12 62))) overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] (t/is (= (:x overlay-pos) 27)) - (t/is (= (:y overlay-pos) 77)))))) + (t/is (= (:y overlay-pos) 77)))) + + (t/testing "Overlay top-left relative to popup" + (let [i2 (ctsi/set-overlay-pos-type interaction-popup :top-left base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 popup base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 15)) + (t/is (= (:y overlay-pos) 15)))) + + (t/testing "Overlay top-center relative to rect" + (let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 rect base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 25)) + (t/is (= (:y overlay-pos) 15)))) + + (t/testing "Overlay top-right relative to rect" + (let [i2 (ctsi/set-overlay-pos-type interaction-rect :top-right base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 rect base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 35)) + (t/is (= (:y overlay-pos) 15)))) + + (t/testing "Overlay bottom-left relative to rect" + (let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-left base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 rect base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 15)) + (t/is (= (:y overlay-pos) 45)))) + + (t/testing "Overlay bottom-center relative to rect" + (let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 rect base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 25)) + (t/is (= (:y overlay-pos) 45)))) + + (t/testing "Overlay bottom-right relative to rect" + (let [i2 (ctsi/set-overlay-pos-type interaction-rect :bottom-right base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 rect base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 35)) + (t/is (= (:y overlay-pos) 45)))) + + (t/testing "Overlay center relative to rect" + (let [i2 (ctsi/set-overlay-pos-type interaction-rect :center base-frame objects) + overlay-pos (ctsi/calc-overlay-position i2 rect base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 25)) + (t/is (= (:y overlay-pos) 30)))) + + (t/testing "Overlay manual relative to rect" + (let [i2 (-> interaction-rect + (ctsi/set-overlay-pos-type :manual base-frame objects) + (ctsi/set-overlay-position (gpt/point 12 62))) + overlay-pos (ctsi/calc-overlay-position i2 rect base-frame overlay-frame frame-offset)] + (t/is (= (:x overlay-pos) 17)) + (t/is (= (:y overlay-pos) 67)))))) (t/deftest animation-checks diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs index 447974d319..4ba7555ba2 100644 --- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -225,13 +225,18 @@ marker-x (+ (:x orig-frame) (:x position)) marker-y (+ (:y orig-frame) (:y position)) width (:width dest-shape) - height (:height dest-shape)] + height (:height dest-shape) + dest-x (:x dest-shape) + dest-y (:y dest-shape)] [:g {:on-mouse-down start-move-position :on-mouse-enter #(reset! hover-disabled? true) :on-mouse-leave #(reset! hover-disabled? false)} + [:use {:href (str "#shape-" (:id dest-shape)) + :x (- marker-x dest-x) + :y (- marker-y dest-y)}] [:path {:stroke "var(--color-primary)" :fill "var(--color-black)" - :fill-opacity 0.3 + :fill-opacity 0.5 :stroke-width 1 :d (dm/str "M" marker-x " " marker-y " " "h " width " " From 5050c352573274fe760e6519bd5d3405e6e12a77 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 6 Sep 2022 13:19:19 +0200 Subject: [PATCH 195/682] :sparkles: Adds layout items options --- common/src/app/common/pages/common.cljc | 134 +++++++++- .../sidebar/options/menus/layout_item.cljs | 3 +- .../sidebar/options/shapes/multiple.cljs | 230 ++++++++++-------- 3 files changed, 248 insertions(+), 119 deletions(-) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index 181cf32cf4..3a47fdcd58 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -71,7 +71,26 @@ :constraints-h :constraints-group :constraints-v :constraints-group :fixed-scroll :constraints-group - :exports :exports-group}) + :exports :exports-group + + :layout :layout-container + :layout-dir :layout-container + :layout-gap :layout-container + :layout-type :layout-container + :layout-wrap-type :layout-container + :layout-padding-type :layout-container + :layout-padding :layout-container + :layout-h-orientation :layout-container + :layout-v-orientation :layout-container + + :layout-margin :layout-item + :layout-margin-type :layout-item + :layout-h-behavior :layout-item + :layout-v-behavior :layout-item + :layout-max-h :layout-item + :layout-min-h :layout-item + :layout-max-w :layout-item + :layout-min-w :layout-item}) ;; Attributes that may directly be edited by the user with forms (def editable-attrs @@ -111,10 +130,29 @@ :stroke-cap-start :stroke-cap-end - :exports} + :exports - :group #{:proportion-lock - :width :height + :layout + :layout-dir + :layout-gap + :layout-type + :layout-wrap-type + :layout-padding-type + :layout-padding + :layout-h-orientation + :layout-v-orientation + + :layout-margin + :layout-margin-type + :layout-h-behavior + :layout-v-behavior + :layout-max-h + :layout-min-h + :layout-max-w + :layout-min-w} + + :group #{:proportion-lock + :width :height :x :y :rotation :selrect @@ -135,7 +173,16 @@ :blur - :exports} + :exports + + :layout-margin + :layout-margin-type + :layout-h-behavior + :layout-v-behavior + :layout-max-h + :layout-min-h + :layout-max-w + :layout-min-w} :rect #{:proportion-lock :width :height @@ -180,7 +227,16 @@ :blur - :exports} + :exports + + :layout-margin + :layout-margin-type + :layout-h-behavior + :layout-v-behavior + :layout-max-h + :layout-min-h + :layout-max-w + :layout-min-w} :circle #{:proportion-lock :width :height @@ -223,7 +279,16 @@ :blur - :exports} + :exports + + :layout-margin + :layout-margin-type + :layout-h-behavior + :layout-v-behavior + :layout-max-h + :layout-min-h + :layout-max-w + :layout-min-w} :path #{:proportion-lock :width :height @@ -266,7 +331,16 @@ :blur - :exports} + :exports + + :layout-margin + :layout-margin-type + :layout-h-behavior + :layout-v-behavior + :layout-max-h + :layout-min-h + :layout-max-w + :layout-min-w} :text #{:proportion-lock :width :height @@ -332,7 +406,16 @@ :grow-type - :exports} + :exports + + :layout-margin + :layout-margin-type + :layout-h-behavior + :layout-v-behavior + :layout-max-h + :layout-min-h + :layout-max-w + :layout-min-w} :image #{:proportion-lock :width :height @@ -358,7 +441,16 @@ :blur - :exports} + :exports + + :layout-margin + :layout-margin-type + :layout-h-behavior + :layout-v-behavior + :layout-max-h + :layout-min-h + :layout-max-w + :layout-min-w} :svg-raw #{:proportion-lock :width :height @@ -403,7 +495,16 @@ :blur - :exports} + :exports + + :layout-margin + :layout-margin-type + :layout-h-behavior + :layout-v-behavior + :layout-max-h + :layout-min-h + :layout-max-w + :layout-min-w} :bool #{:proportion-lock :width :height @@ -446,5 +547,14 @@ :blur - :exports}}) + :exports + + :layout-margin + :layout-margin-type + :layout-h-behavior + :layout-v-behavior + :layout-max-h + :layout-min-h + :layout-max-w + :layout-min-w}}) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index 78b9ccbd40..d27cb93a57 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -136,7 +136,8 @@ (mf/defc layout-item-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type"]))]} - [{:keys [ids _type values is-layout-child? is-layout-container?] :as props}] + [{:keys [ids values is-layout-child? is-layout-container?] :as props}] + (let [open? (mf/use-state false) toggle-open (fn [] (swap! open? not)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 09a34eb25c..17f5f70514 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -34,116 +34,134 @@ ;; - text: read it from all the content nodes, and then merging it. (def type->read-mode {:frame - {:measure :shape - :layer :shape - :constraint :shape - :fill :shape - :shadow :children - :blur :children - :stroke :shape - :text :children - :exports :shape} + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :children + :blur :children + :stroke :shape + :text :children + :exports :shape + :layout-container :shape + :layout-item :shape} :group - {:measure :shape - :layer :shape - :constraint :shape - :fill :children - :shadow :shape - :blur :shape - :stroke :children - :text :children - :exports :shape} + {:measure :shape + :layer :shape + :constraint :shape + :fill :children + :shadow :shape + :blur :shape + :stroke :children + :text :children + :exports :shape + :layout-container :ignore + :layout-item :shape} :path - {:measure :shape - :layer :shape - :constraint :shape - :fill :shape - :shadow :shape - :blur :shape - :stroke :shape - :text :ignore - :exports :shape} + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :shape + :blur :shape + :stroke :shape + :text :ignore + :exports :shape + :layout-container :ignore + :layout-item :shape} :text - {:measure :shape - :layer :shape - :constraint :shape - :fill :text - :shadow :shape - :blur :shape - :stroke :shape - :text :text - :exports :shape} + {:measure :shape + :layer :shape + :constraint :shape + :fill :text + :shadow :shape + :blur :shape + :stroke :shape + :text :text + :exports :shape + :layout-container :ignore + :layout-item :shape} :image - {:measure :shape - :layer :shape - :constraint :shape - :fill :ignore - :shadow :shape - :blur :shape - :stroke :ignore - :text :ignore - :exports :shape} + {:measure :shape + :layer :shape + :constraint :shape + :fill :ignore + :shadow :shape + :blur :shape + :stroke :ignore + :text :ignore + :exports :shape + :layout-container :ignore + :layout-item :shape} :rect - {:measure :shape - :layer :shape - :constraint :shape - :fill :shape - :shadow :shape - :blur :shape - :stroke :shape - :text :ignore - :exports :shape} + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :shape + :blur :shape + :stroke :shape + :text :ignore + :exports :shape + :layout-container :ignore + :layout-item :shape} :circle - {:measure :shape - :layer :shape - :constraint :shape - :fill :shape - :shadow :shape - :blur :shape - :stroke :shape - :text :ignore - :exports :shape} + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :shape + :blur :shape + :stroke :shape + :text :ignore + :exports :shape + :layout-container :ignore + :layout-item :shape} :svg-raw - {:measure :shape - :layer :shape - :constraint :shape - :fill :shape - :shadow :shape - :blur :shape - :stroke :shape - :text :ignore - :exports :shape} + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :shape + :blur :shape + :stroke :shape + :text :ignore + :exports :shape + :layout-container :ignore + :layout-item :shape} :bool - {:measure :shape - :layer :shape - :constraint :shape - :fill :shape - :shadow :shape - :blur :shape - :stroke :shape - :text :ignore - :exports :shape}}) + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :shape + :blur :shape + :stroke :shape + :text :ignore + :exports :shape + :layout-container :ignore + :layout-item :shape}}) (def group->attrs - {:measure measure-attrs - :layer layer-attrs - :constraint constraint-attrs - :fill fill-attrs - :shadow shadow-attrs - :blur blur-attrs - :stroke stroke-attrs - :text ot/attrs - :exports exports-attrs - :layout layout-container-flex-attrs - :layout-item layout-item-attrs}) + {:measure measure-attrs + :layer layer-attrs + :constraint constraint-attrs + :fill fill-attrs + :shadow shadow-attrs + :blur blur-attrs + :stroke stroke-attrs + :text ot/attrs + :exports exports-attrs + :layout-container layout-container-flex-attrs + :layout-item layout-item-attrs}) (def shadow-keys [:style :color :offset-x :offset-y :blur :spread]) @@ -254,20 +272,20 @@ is-layout-child? (mf/deref is-layout-child-ref) has-text? (contains? all-types :text) - + [measure-ids measure-values] (get-attrs shapes objects :measure) - [layer-ids layer-values - constraint-ids constraint-values - fill-ids fill-values - shadow-ids shadow-values - blur-ids blur-values - stroke-ids stroke-values - text-ids text-values - exports-ids exports-values - layout-ids layout-container-values - layout-item-ids layout-item-values] + [layer-ids layer-values + constraint-ids constraint-values + fill-ids fill-values + shadow-ids shadow-values + blur-ids blur-values + stroke-ids stroke-values + text-ids text-values + exports-ids exports-values + layout-container-ids layout-container-values + layout-item-ids layout-item-values] (mf/use-memo (mf/deps objects-no-measures) (fn [] @@ -282,7 +300,7 @@ (get-attrs shapes objects-no-measures :stroke) (get-attrs shapes objects-no-measures :text) (get-attrs shapes objects-no-measures :exports) - (get-attrs shapes objects-no-measures :layout) + (get-attrs shapes objects-no-measures :layout-container) (get-attrs shapes objects-no-measures :layout-item) ])))] @@ -291,8 +309,8 @@ [:& measures-menu {:type type :all-types all-types :ids measure-ids :values measure-values :shape shapes}]) (when (:layout layout-container-values) - [:& layout-container-menu {:type type :ids layout-ids :values layout-container-values}]) - + [:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values}]) + (when is-layout-child? [:& layout-item-menu {:type type From c01c46041d4a6e0304f20477d8b7ae1ac3b8e27f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 29 Sep 2022 13:37:54 +0200 Subject: [PATCH 196/682] :sparkles: Adds autolayout positions calculations --- common/src/app/common/geom/matrix.cljc | 11 + common/src/app/common/geom/shapes.cljc | 10 +- .../app/common/geom/shapes/constraints.cljc | 312 ++++++++++------ .../src/app/common/geom/shapes/intersect.cljc | 22 ++ common/src/app/common/geom/shapes/layout.cljc | 76 +++- .../src/app/common/geom/shapes/modifiers.cljc | 164 +++++---- .../app/common/geom/shapes/transforms.cljc | 343 ++++++------------ common/src/app/common/pages/migrations.cljc | 8 +- common/src/app/common/types/modifiers.cljc | 256 +++++++++++++ .../src/app/main/data/workspace/comments.cljs | 4 +- .../app/main/data/workspace/drawing/box.cljs | 5 +- .../main/data/workspace/drawing/common.cljs | 9 +- .../src/app/main/data/workspace/guides.cljs | 3 +- .../app/main/data/workspace/shape_layout.cljs | 2 +- .../main/data/workspace/state_helpers.cljs | 7 +- .../src/app/main/data/workspace/texts.cljs | 23 +- .../app/main/data/workspace/transforms.cljs | 81 +++-- frontend/src/app/main/render.cljs | 44 +-- frontend/src/app/main/ui/shapes/bool.cljs | 4 +- frontend/src/app/main/ui/shapes/mask.cljs | 7 +- .../app/main/ui/viewer/handoff/render.cljs | 5 +- .../src/app/main/ui/viewer/interactions.cljs | 9 +- frontend/src/app/main/ui/viewer/shapes.cljs | 5 +- .../shapes/frame/dynamic_modifiers.cljs | 11 +- .../app/main/ui/workspace/shapes/text.cljs | 2 +- .../shapes/text/viewport_texts_html.cljs | 6 +- .../main/ui/workspace/sidebar/options.cljs | 4 +- .../src/app/main/ui/workspace/viewport.cljs | 17 +- .../main/ui/workspace/viewport/drawarea.cljs | 5 +- .../main/ui/workspace/viewport/guides.cljs | 2 +- .../ui/workspace/viewport/interactions.cljs | 7 +- .../main/ui/workspace/viewport/outline.cljs | 2 +- .../main/ui/workspace/viewport/selection.cljs | 21 +- .../ui/workspace/viewport/snap_points.cljs | 23 +- .../app/main/ui/workspace/viewport/utils.cljs | 4 +- .../main/ui/workspace/viewport/widgets.cljs | 11 +- frontend/src/app/util/geom/snap_points.cljs | 7 +- 37 files changed, 938 insertions(+), 594 deletions(-) create mode 100644 common/src/app/common/types/modifiers.cljc diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index e4448acb78..53f3d17489 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -252,3 +252,14 @@ (update :d mth/precision 4) (update :e mth/precision 4) (update :f mth/precision 4))) + +(defn transform-point-center + "Transform a point around the shape center" + [point center matrix] + (if (and (some? point) (some? matrix) (some? center)) + (gpt/transform + point + (multiply (translate-matrix center) + matrix + (translate-matrix (gpt/negate center)))) + point)) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index d0d05d47fd..a217d5322d 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -28,7 +28,7 @@ rotation of each shape. Mainly used for multiple selection." [shapes] (->> shapes - (map (comp gpr/points->selrect :points gtr/transform-shape)) + (map (comp gpr/points->selrect :points)) (gpr/join-selrects))) (defn translate-to-frame @@ -166,23 +166,17 @@ (dm/export gtr/transform-matrix) (dm/export gtr/transform-str) (dm/export gtr/inverse-transform-matrix) -(dm/export gtr/transform-point-center) (dm/export gtr/transform-rect) (dm/export gtr/calculate-adjust-matrix) (dm/export gtr/update-group-selrect) (dm/export gtr/update-mask-selrect) -(dm/export gtr/resize-modifiers) -(dm/export gtr/change-orientation-modifiers) -(dm/export gtr/rotation-modifiers) -(dm/export gtr/merge-modifiers) (dm/export gtr/transform-shape) (dm/export gtr/transform-selrect) (dm/export gtr/transform-selrect-matrix) (dm/export gtr/transform-bounds) -(dm/export gtr/modifiers->transform) -(dm/export gtr/empty-modifiers?) (dm/export gtr/move-position-data) (dm/export gtr/apply-transform) +(dm/export gtr/apply-objects-modifiers) ;; Constratins (dm/export gct/calc-child-modifiers) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 79821b364c..49db8a69dd 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -6,14 +6,19 @@ (ns app.common.geom.shapes.constraints (:require - [app.common.geom.matrix :as gmt] + [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] + [app.common.geom.shapes.intersect :as gsi] [app.common.geom.shapes.rect :as gre] + [app.common.geom.shapes.transforms :as gst] [app.common.math :as mth] [app.common.uuid :as uuid])) ;; Auxiliary methods to work in an specifica axis +(defn other-axis [axis] + (if (= :x axis) :y :x)) + (defn get-delta-start [axis rect tr-rect] (if (= :x axis) (- (:x1 tr-rect) (:x1 rect)) @@ -29,6 +34,11 @@ (- (:width tr-rect) (:width rect)) (- (:height tr-rect) (:height rect)))) +(defn get-delta-scale [axis rect tr-rect] + (if (= :x axis) + (/ (:width tr-rect) (:width rect)) + (/ (:height tr-rect) (:height rect)))) + (defn get-delta-center [axis center tr-center] (if (= :x axis) (- (:x tr-center) (:x center)) @@ -53,78 +63,138 @@ (:width rect) (:height rect))) +(defn right-vector + [child-points parent-points] + (let [[p0 p1 p2 _] parent-points + [_c0 c1 _ _] child-points + dir-v (gpt/to-vec p0 p1) + cp (gsi/line-line-intersect c1 (gpt/add c1 dir-v) p1 p2)] + (gpt/to-vec c1 cp))) + +(defn left-vector + [child-points parent-points] + + (let [[p0 p1 _ p3] parent-points + [_ _ _ c3] child-points + dir-v (gpt/to-vec p0 p1) + cp (gsi/line-line-intersect c3 (gpt/add c3 dir-v) p0 p3)] + (gpt/to-vec c3 cp))) + + +(defn top-vector + [child-points parent-points] + + (let [[p0 p1 _ p3] parent-points + [c0 _ _ _] child-points + dir-v (gpt/to-vec p0 p3) + cp (gsi/line-line-intersect c0 (gpt/add c0 dir-v) p0 p1)] + (gpt/to-vec c0 cp))) + +(defn bottom-vector + [child-points parent-points] + + (let [[p0 _ p2 p3] parent-points + [_ _ c2 _] child-points + dir-v (gpt/to-vec p0 p3) + cp (gsi/line-line-intersect c2 (gpt/add c2 dir-v) p2 p3)] + (gpt/to-vec c2 cp))) + +(defn center-horizontal-vector + [child-points parent-points] + + (let [[p0 p1 _ p3] parent-points + [_ c1 _ _] child-points + + dir-v (gpt/to-vec p0 p1) + + p1c (gpt/add p0 (gpt/scale dir-v 0.5)) + p2c (gpt/add p3 (gpt/scale dir-v 0.5)) + + cp (gsi/line-line-intersect c1 (gpt/add c1 dir-v) p1c p2c)] + + (gpt/to-vec c1 cp))) + +(defn center-vertical-vector + [child-points parent-points] + (let [[p0 p1 p2 _] parent-points + [_ c1 _ _] child-points + + dir-v (gpt/to-vec p1 p2) + + p3c (gpt/add p0 (gpt/scale dir-v 0.5)) + p2c (gpt/add p1 (gpt/scale dir-v 0.5)) + + cp (gsi/line-line-intersect c1 (gpt/add c1 dir-v) p3c p2c)] + + (gpt/to-vec c1 cp))) + +(defn start-vector + [axis child-points parent-points] + ((if (= :x axis) left-vector top-vector) child-points parent-points)) + +(defn end-vector + [axis child-points parent-points] + ((if (= :x axis) right-vector bottom-vector) child-points parent-points)) + +(defn center-vector + [axis child-points parent-points] + ((if (= :x axis) center-horizontal-vector center-vertical-vector) child-points parent-points)) + + ;; Constraint function definitions (defmulti constraint-modifier (fn [type & _] type)) -(defmethod constraint-modifier :start - [_ axis parent _ _ transformed-parent-rect] - - (let [parent-rect (:selrect parent) - delta-start (get-delta-start axis parent-rect transformed-parent-rect)] - (if-not (mth/almost-zero? delta-start) - {:displacement (get-displacement axis delta-start)} - {}))) - (defmethod constraint-modifier :end - [_ axis parent _ _ transformed-parent-rect] - (let [parent-rect (:selrect parent) - delta-end (get-delta-end axis parent-rect transformed-parent-rect)] - (if-not (mth/almost-zero? delta-end) - {:displacement (get-displacement axis delta-end)} - {}))) + [_ axis child-points-before parent-points-before child-points-after parent-points-after] + (let [end-before (end-vector axis child-points-before parent-points-before) + end-after (end-vector axis child-points-after parent-points-after) + end-angl (gpt/angle-with-other end-before end-after) + target-end (if (mth/close? end-angl 180) (- (gpt/length end-before)) (gpt/length end-before)) + disp-vector-end (gpt/subtract end-after (gpt/scale (gpt/unit end-after) target-end))] + [{:type :move + :vector disp-vector-end}])) (defmethod constraint-modifier :fixed - [_ axis parent child _ transformed-parent-rect] - (let [parent-rect (:selrect parent) - child-rect (gre/points->rect (:points child)) + [_ axis child-points-before parent-points-before child-points-after parent-points-after transformed-parent] + (let [[c0 c1 _ c4] child-points-after - delta-start (get-delta-start axis parent-rect transformed-parent-rect) - delta-size (get-delta-size axis parent-rect transformed-parent-rect) - child-size (get-size axis child-rect)] - (if (or (not (mth/almost-zero? delta-start)) - (not (mth/almost-zero? delta-size))) + ;; Same as constraint end + end-before (end-vector axis child-points-before parent-points-before) + end-after (end-vector axis child-points-after parent-points-after) + end-angl (gpt/angle-with-other end-before end-after) + target-end (if (mth/close? end-angl 180) (- (gpt/length end-before)) (gpt/length end-before)) + disp-vector-end (gpt/subtract end-after (gpt/scale (gpt/unit end-after) target-end)) - {:displacement (get-displacement axis delta-start) - :resize-origin (get-displacement axis delta-start (:x child-rect) (:y child-rect)) - :resize-vector (get-scale axis (/ (+ child-size delta-size) child-size))} - {}))) + before-vec (if (= axis :x) (gpt/to-vec c0 c1) (gpt/to-vec c0 c4)) + after-vec (if (= axis :x) + (gpt/to-vec c0 (gpt/add c1 disp-vector-end)) + (gpt/to-vec c0 (gpt/add c4 disp-vector-end)) + ) + + resize-angl (gpt/angle-with-other before-vec after-vec) + resize-sign (if (mth/close? resize-angl 180) -1 1) + + scale (* resize-sign (/ (gpt/length after-vec) (gpt/length before-vec))) + ] + [{:type :resize + :vector (get-scale axis scale) + :origin c0 + :transform (:transform transformed-parent) + :transform-inverse (:transform-inverse transformed-parent)}])) (defmethod constraint-modifier :center - [_ axis parent _ _ transformed-parent-rect] - (let [parent-rect (:selrect parent) - parent-center (gco/center-rect parent-rect) - transformed-parent-center (gco/center-rect transformed-parent-rect) - delta-center (get-delta-center axis parent-center transformed-parent-center)] - (if-not (mth/almost-zero? delta-center) - {:displacement (get-displacement axis delta-center)} - {}))) - -(defmethod constraint-modifier :scale - [_ axis _ _ modifiers _] - (let [{:keys [resize-vector resize-vector-2 displacement]} modifiers] - (cond-> {} - (and (some? resize-vector) - (not= (axis resize-vector) 1)) - (assoc :resize-origin (:resize-origin modifiers) - :resize-vector (if (= :x axis) - (gpt/point (:x resize-vector) 1) - (gpt/point 1 (:y resize-vector)))) - - (and (= :y axis) (some? resize-vector-2) - (not (mth/close? (:y resize-vector-2) 1))) - (assoc :resize-origin (:resize-origin-2 modifiers) - :resize-vector (gpt/point 1 (:y resize-vector-2))) - - (some? displacement) - (assoc :displacement - (get-displacement axis (-> (gpt/point 0 0) - (gpt/transform displacement) - (gpt/transform (:resize-transform-inverse modifiers (gmt/matrix))) - axis)))))) + [_ axis child-points-before parent-points-before child-points-after parent-points-after] + (let [center-before (center-vector axis child-points-before parent-points-before) + center-after (center-vector axis child-points-after parent-points-after) + center-angl (gpt/angle-with-other center-before center-after) + target-center (if (mth/close? center-angl 180) (- (gpt/length center-before)) (gpt/length center-before)) + disp-vector-center (gpt/subtract center-after (gpt/scale (gpt/unit center-after) target-center))] + [{:type :move + :vector disp-vector-center}])) (defmethod constraint-modifier :default [_ _ _ _ _] - {}) + []) (def const->type+axis {:left :start @@ -152,7 +222,7 @@ :top :scale))) -(defn clean-modifiers +#_(defn clean-modifiers "Remove redundant modifiers" [{:keys [displacement resize-vector resize-vector-2] :as modifiers}] @@ -174,55 +244,91 @@ (mth/almost-zero? (- 1.0 (:y resize-vector-2)))) (dissoc :resize-origin-2 :resize-vector-2))) + +(defn bounding-box-parent-transform + "Returns a bounding box for the child in the same coordinate system + as the parent. + Returns a points array" + [child parent] + (-> child + :points + (gco/transform-points (:transform-inverse parent)) + (gre/points->rect) + (gre/rect->points) ;; Restore to points so we can transform them + (gco/transform-points (:transform parent)))) + +(defn normalize-modifiers + "Before aplying constraints we need to remove the deformation caused by the resizing of the parent" + [constraints-h constraints-v modifiers child parent transformed-child transformed-parent] + + (let [child-bb-before + (-> child + :points + (gco/transform-points (:transform-inverse parent)) + (gre/points->rect)) + + child-bb-after + (-> transformed-child + :points + (gco/transform-points (:transform-inverse transformed-parent)) + (gre/points->rect)) + + scale-x (/ (:width child-bb-before) (:width child-bb-after)) + scale-y (/ (:height child-bb-before) (:height child-bb-after))] + + (-> modifiers + (update :v2 #(cond-> % + (not= :scale constraints-h) + (conj + ;; This resize will leave the shape in its original position relative to the parent + {:type :resize + :transform (:transform transformed-parent) + :transform-inverse (:transform-inverse transformed-parent) + :origin (-> transformed-parent :points (nth 0)) + :vector (gpt/point scale-x 1)}) + + (not= :scale constraints-v) + (conj + {:type :resize + :transform (:transform transformed-parent) + :transform-inverse (:transform-inverse transformed-parent) + :origin (-> transformed-parent :points (nth 0)) + :vector (gpt/point 1 scale-y)})))))) + (defn calc-child-modifiers - [parent child modifiers ignore-constraints transformed-parent-rect] + [parent child modifiers ignore-constraints transformed-parent] - (if (and (nil? (:resize-vector modifiers)) - (nil? (:resize-vector-2 modifiers))) - ;; If we don't have a resize modifier we return the same modifiers - modifiers - (let [constraints-h - (if-not ignore-constraints - (:constraints-h child (default-constraints-h child)) - :scale) + (let [constraints-h + (if-not ignore-constraints + (:constraints-h child (default-constraints-h child)) + :scale) - constraints-v - (if-not ignore-constraints - (:constraints-v child (default-constraints-v child)) - :scale) + constraints-v + (if-not ignore-constraints + (:constraints-v child (default-constraints-v child)) + :scale)] - modifiers-h (constraint-modifier (constraints-h const->type+axis) :x parent child modifiers transformed-parent-rect) - modifiers-v (constraint-modifier (constraints-v const->type+axis) :y parent child modifiers transformed-parent-rect)] + (if (and (= :scale constraints-h) (= :scale constraints-v)) + modifiers - ;; Build final child modifiers. Apply transform again to the result, to get the - ;; real modifiers that need to be applied to the child, including rotation as needed. - (cond-> {} - (some? (:displacement-after modifiers)) - (assoc :displacement-after (:displacement-after modifiers)) + (let [transformed-child (gst/transform-shape child modifiers) + modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child transformed-parent) - (or (contains? modifiers-h :displacement) - (contains? modifiers-v :displacement)) - (assoc :displacement (cond-> (gpt/point (get-in modifiers-h [:displacement :x] 0) - (get-in modifiers-v [:displacement :y] 0)) - (some? (:resize-transform modifiers)) - (gpt/transform (:resize-transform modifiers)) + tranformed-child-2 (gst/transform-shape child modifiers) + parent-points-before (:points parent) + child-points-before (bounding-box-parent-transform child parent) - :always - (gmt/translate-matrix))) + parent-points-after (:points transformed-parent) + child-points-after (bounding-box-parent-transform tranformed-child-2 transformed-parent) - (:resize-vector modifiers-h) - (assoc :resize-origin (:resize-origin modifiers-h) - :resize-vector (gpt/point (get-in modifiers-h [:resize-vector :x] 1) - (get-in modifiers-h [:resize-vector :y] 1))) + modifiers-h (constraint-modifier (constraints-h const->type+axis) :x + child-points-before parent-points-before + child-points-after parent-points-after + transformed-parent) - (:resize-vector modifiers-v) - (assoc :resize-origin-2 (:resize-origin modifiers-v) - :resize-vector-2 (gpt/point (get-in modifiers-v [:resize-vector :x] 1) - (get-in modifiers-v [:resize-vector :y] 1))) + modifiers-v (constraint-modifier (constraints-v const->type+axis) :y + child-points-before parent-points-before + child-points-after parent-points-after + transformed-parent)] - (:resize-transform modifiers) - (assoc :resize-transform (:resize-transform modifiers) - :resize-transform-inverse (:resize-transform-inverse modifiers)) - - :always - (clean-modifiers))))) + (update modifiers :v2 d/concat-vec modifiers-h modifiers-v))))) diff --git a/common/src/app/common/geom/shapes/intersect.cljc b/common/src/app/common/geom/shapes/intersect.cljc index 34b5ec7f64..72d5bf47a1 100644 --- a/common/src/app/common/geom/shapes/intersect.cljc +++ b/common/src/app/common/geom/shapes/intersect.cljc @@ -348,3 +348,25 @@ :points (every? (partial has-point-rect? rect)))) + +(defn line-line-intersect + "Calculates the interesection point for two lines given by the points a-b and b-c" + [a b c d] + + (let [;; Line equation representation: ax + by + c = 0 + a1 (- (:y b) (:y a)) + b1 (- (:x a) (:x b)) + c1 (+ (* a1 (:x a)) (* b1 (:y a))) + + a2 (- (:y d) (:y c)) + b2 (- (:x c) (:x d)) + c2 (+ (* a2 (:x c)) (* b2 (:y c))) + + ;; Cramer's rule + det (- (* a1 b2) (* a2 b1))] + + ;; If almost zero the lines are parallel + (when (not (mth/almost-zero? det)) + (let [x (/ (- (* b2 c1) (* b1 c2)) det) + y (/ (- (* c2 a1) (* c1 a2)) det)] + (gpt/point x y))))) diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index dc27fb429f..e6f7cc70e5 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -6,7 +6,6 @@ (ns app.common.geom.shapes.layout (:require - [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.rect :as gre])) @@ -71,24 +70,39 @@ (let [wrap? (= layout-wrap-type :wrap) reduce-fn - (fn [[{:keys [line-width line-height num-children] :as line-data} result] child] + (fn [[{:keys [line-width line-height num-children child-fill? num-child-fill] :as line-data} result] child] (let [child-bounds (-> child :points gre/points->rect) - next-width (-> child-bounds :width) - next-height (-> child-bounds :height)] + + cur-child-fill? + (or (and (col? shape) (= :fill (:layout-h-behavior child))) + (and (row? shape) (= :fill (:layout-v-behavior child)))) + + next-width (if cur-child-fill? + 0 + (-> child-bounds :width)) + + next-height (if cur-child-fill? + 0 + (-> child-bounds :height))] (if (and (some? line-data) (or (not wrap?) (and (col? shape) (<= (+ line-width next-width (* layout-gap num-children)) width)) (and (row? shape) (<= (+ line-height next-height (* layout-gap num-children)) height)))) - [{:line-width (if (col? shape) (+ line-width next-width) (max line-width next-width)) - :line-height (if (row? shape) (+ line-height next-height) (max line-height next-height)) - :num-children (inc num-children)} + ;; Si autofill añadimos el minwidth que por defecto es 0 + [{:line-width (if (col? shape) (+ line-width next-width) (max line-width next-width)) + :line-height (if (row? shape) (+ line-height next-height) (max line-height next-height)) + :num-children (inc num-children) + :child-fill? (or cur-child-fill? child-fill?) + :num-child-fill (cond-> num-child-fill cur-child-fill? inc)} result] - [{:line-width next-width - :line-height next-height - :num-children 1} + [{:line-width next-width + :line-height next-height + :num-children 1 + :child-fill? child-fill? + :num-child-fill (if child-fill? 1 0)} (cond-> result (some? line-data) (conj line-data))]))) [line-data layout-lines] (reduce reduce-fn [nil []] children)] @@ -124,13 +138,14 @@ [base-x base-y])) (get-start-line - [{:keys [line-width line-height num-children]} base-x base-y] + [{:keys [line-width line-height num-children child-fill?]} base-x base-y] (let [children-gap (* layout-gap (dec num-children)) start-x (cond - (or (and (col? shape) (= :space-between layout-type)) + (or (and (col? shape) child-fill?) + (and (col? shape) (= :space-between layout-type)) (and (col? shape) (= :space-around layout-type))) x @@ -154,7 +169,8 @@ start-y (cond - (or (and (row? shape) (= :space-between layout-type)) + (or (and (row? shape) child-fill?) + (and (row? shape) (= :space-between layout-type)) (and (row? shape) (= :space-around layout-type))) y @@ -240,6 +256,7 @@ 0)] (assoc line-data + :layout-bounds layout-bounds :layout-gap layout-gap :margin-x margin-x :margin-y margin-y))) @@ -314,14 +331,39 @@ "Calculates the modifiers for the layout" [parent transform child layout-data] - (let [bounds (-> child :points gre/points->selrect) + (let [child-bounds (-> child :points gre/points->selrect) - [corner-p layout-data] (next-p parent bounds layout-data) + fill-space (- (-> layout-data :layout-bounds :width) (:line-width layout-data)) + + fill-width (- (/ fill-space (:num-child-fill layout-data)) + (* 2 (:layout-gap layout-data))) + + fill-scale (/ fill-width (:width child-bounds)) + + child-bounds + (cond-> child-bounds + (and (col? parent) (= :fill (:layout-h-behavior child))) + (assoc :width fill-width)) + + [corner-p layout-data] (next-p parent child-bounds layout-data) delta-p (-> corner-p - (gpt/subtract (gpt/point bounds)) + (gpt/subtract (gpt/point child-bounds)) (cond-> (some? transform) (gpt/transform transform))) - modifiers {:displacement-after (gmt/translate-matrix delta-p)}] + modifiers [] + + modifiers + (cond-> modifiers + (and (col? parent) (= :fill (:layout-h-behavior child))) + (conj {:type :resize + :from :layout + :origin (gpt/point child-bounds) + :vector (gpt/point fill-scale 1)})) + + modifiers + (conj modifiers {:type :move + :from :layout + :vector delta-p})] [modifiers layout-data])) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index f93929abec..4645a6582e 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -15,8 +15,10 @@ [app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth] + [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) +;; TODO LAYOUT: ADAPT TO NEW MODIFIERS (defn set-pixel-precision "Adjust modifiers so they adjust to the pixel grid" [modifiers shape] @@ -106,44 +108,65 @@ (defn set-children-modifiers - [modif-tree shape objects ignore-constraints snap-pixel?] - (letfn [(set-child [transformed-rect snap-pixel? modif-tree child] + [modif-tree objects shape ignore-constraints snap-pixel?] + ;; TODO LAYOUT: SNAP PIXEL! + (letfn [(set-child [transformed-parent _snap-pixel? modif-tree child] (let [modifiers (get-in modif-tree [(:id shape) :modifiers]) - child-modifiers (gct/calc-child-modifiers shape child modifiers ignore-constraints transformed-rect) - child-modifiers (cond-> child-modifiers snap-pixel? (set-pixel-precision child))] - (cond-> modif-tree - (not (gtr/empty-modifiers? child-modifiers)) - (update-in [(:id child) :modifiers] #(merge child-modifiers %)))))] + + child-modifiers (gct/calc-child-modifiers shape child modifiers ignore-constraints transformed-parent) + + ;;_ (.log js/console (:name child) (clj->js child-modifiers)) + + ;;child-modifiers (cond-> child-modifiers snap-pixel? (set-pixel-precision child)) + + result + (cond-> modif-tree + (not (ctm/empty-modifiers? child-modifiers)) + (update-in [(:id child) :modifiers :v2] #(d/concat-vec % (:v2 child-modifiers))) + #_(update-in [(:id child) :modifiers] #(merge-mod2 child-modifiers %)) + #_(update-in [(:id child) :modifiers] #(merge child-modifiers %))) + + ;;_ (.log js/console ">>>" (:name child)) + ;;_ (.log js/console " >" (clj->js child-modifiers)) + ;;_ (.log js/console " >" (clj->js (get-in modif-tree [(:id child) :modifiers]))) + ;;_ (.log js/console " >" (clj->js (get-in result [(:id child) :modifiers]))) + ] + result + )) + ] (let [children (map (d/getf objects) (:shapes shape)) modifiers (get-in modif-tree [(:id shape) :modifiers]) - transformed-rect (gtr/transform-selrect (:selrect shape) modifiers) + ;; transformed-rect (gtr/transform-selrect (:selrect shape) modifiers) + ;; transformed-rect (-> shape (merge {:modifiers modifiers}) gtr/transform-shape :selrect) + transformed-parent (-> shape (merge {:modifiers modifiers}) gtr/transform-shape) + resize-modif? (or (:resize-vector modifiers) (:resize-vector-2 modifiers))] - (reduce (partial set-child transformed-rect (and snap-pixel? resize-modif?)) modif-tree children)))) + (reduce (partial set-child transformed-parent (and snap-pixel? resize-modif?)) modif-tree children)))) (defn group? [shape] (or (= :group (:type shape)) (= :bool (:type shape)))) -(defn merge-modifiers - [modif-tree ids modifiers] - (reduce - (fn [modif-tree id] - (update-in modif-tree [id :modifiers] #(merge % modifiers))) - modif-tree - ids)) +(defn frame? [shape] + (= :frame (:type shape))) + +(defn layout? [shape] + (and (frame? shape) + (:layout shape))) (defn set-layout-modifiers - [modif-tree objects id] + ;; TODO LAYOUT: SNAP PIXEL! + [modif-tree objects parent _snap-pixel?] - (letfn [(transform-child [parent child] + (letfn [(transform-child [child] (let [modifiers (get modif-tree (:id child)) child (cond-> child - (not (group? child)) + (some? modifiers) (-> (merge modifiers) gtr/transform-shape) - (group? child) + (and (nil? modifiers) (group? child)) (gtr/apply-group-modifiers objects modif-tree)) child @@ -158,22 +181,17 @@ modif-tree (cond-> modif-tree - (not (gtr/empty-modifiers? modifiers)) - (merge-modifiers [(:id child)] modifiers) - - (and (not (gtr/empty-modifiers? modifiers)) (group? child)) - (merge-modifiers (:shapes child) modifiers))] + (d/not-empty? modifiers) + (update-in [(:id child) :modifiers :v2] d/concat-vec modifiers) + #_(merge-modifiers [(:id child)] modifiers))] [layout-data modif-tree]))] - (let [modifiers (get modif-tree id) - - shape (-> (get objects id) (merge modifiers) gtr/transform-shape) - - + (let [modifiers (get modif-tree (:id parent)) + shape (-> parent (merge modifiers) gtr/transform-shape) children (->> (:shapes shape) (map (d/getf objects)) - (map (partial transform-child shape))) + (map transform-child)) center (gco/center-shape shape) {:keys [transform transform-inverse]} shape @@ -230,6 +248,17 @@ :else (recur (:id parent) result))))) +(defn resolve-tree-sequence + ;; TODO LAYOUT: Esta ahora puesto al zero pero tiene que mirar todas las raices + "Given the ids that have changed search for layout roots to recalculate" + [_ids objects] + (->> (tree-seq + #(d/not-empty? (get-in objects [% :shapes])) + #(get-in objects [% :shapes]) + uuid/zero) + + (map #(get objects %)))) + (defn resolve-layout-ids "Given a list of ids, resolve the parent layouts that will need to update. This will go upwards in the tree while a layout is found" @@ -239,6 +268,28 @@ (map #(get-first-layout % objects)) ids)) +(defn inside-layout? + [objects shape] + + (loop [current-id (:id shape)] + (let [current (get objects current-id)] + (cond + (or (nil? current) (= current-id (:parent-id current))) + false + + (= :frame (:type current)) + (:layout current) + + :else + (recur (:parent-id current)))))) + +#_(defn modif->js + [modif-tree objects] + (clj->js (into {} + (map (fn [[k v]] + [(get-in objects [k :name]) v])) + modif-tree))) + (defn set-objects-modifiers [ids objects get-modifier ignore-constraints snap-pixel?] @@ -251,40 +302,27 @@ modif-tree (reduce set-modifiers {} ids) - ids (resolve-layout-ids ids objects) + shapes-tree (resolve-tree-sequence ids objects) - ;; First: Calculate children modifiers (constraints, etc) - [modif-tree touched-layouts] - (loop [current (first ids) - pending (rest ids) - modif-tree modif-tree - touched-layouts (d/ordered-set)] - (if (some? current) - (let [shape (get objects current) - pending (concat pending (:shapes shape)) - - touched-layouts - (cond-> touched-layouts - (:layout shape) - (conj (:id shape))) - - modif-tree - (-> modif-tree - (set-children-modifiers shape objects ignore-constraints snap-pixel?))] - - (recur (first pending) (rest pending) modif-tree touched-layouts)) - - [modif-tree touched-layouts])) - - ;; Second: Calculate layout positioning modif-tree - (loop [current (first touched-layouts) - pending (rest touched-layouts) - modif-tree modif-tree] + (->> shapes-tree + (reduce + (fn [modif-tree shape] + (let [has-modifiers? (some? (get-in modif-tree [(:id shape) :modifiers])) + is-layout? (layout? shape) + is-parent? (or (group? shape) (and (frame? shape) (not (layout? shape)))) - (if (some? current) - (let [modif-tree (set-layout-modifiers modif-tree objects current)] - (recur (first pending) (rest pending) modif-tree)) - modif-tree))] + ;; If the current child is inside the layout we ignore the constraints + is-inside-layout? (inside-layout? objects shape)] + (cond-> modif-tree + is-layout? + (set-layout-modifiers objects shape snap-pixel?) + + (and has-modifiers? is-parent?) + (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?)))) + + modif-tree))] + + ;;(.log js/console ">result" (modif->js modif-tree objects)) modif-tree)) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index be27921094..8b1dfaa366 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -14,8 +14,8 @@ [app.common.geom.shapes.path :as gpa] [app.common.geom.shapes.rect :as gpr] [app.common.math :as mth] - [app.common.spec :as us] - [app.common.text :as txt])) + [app.common.text :as txt] + [app.common.types.modifiers :as ctm])) (def ^:dynamic *skip-adjust* false) @@ -76,14 +76,6 @@ ; ---- Geometric operations -(defn- normalize-scale - "We normalize the scale so it's not too close to 0" - [scale] - (cond - (and (< scale 0) (> scale -0.01)) -0.01 - (and (>= scale 0) (< scale 0.01)) 0.01 - :else scale)) - (defn- calculate-skew-angle "Calculates the skew angle of the parallelogram given by the points" [[p1 _ p3 p4]] @@ -182,17 +174,6 @@ (gmt/multiply (:transform-inverse shape (gmt/matrix))) (gmt/translate (gpt/negate center))))) -(defn transform-point-center - "Transform a point around the shape center" - [point center matrix] - (if (and (some? point) (some? matrix) (some? center)) - (gpt/transform - point - (gmt/multiply (gmt/translate-matrix center) - matrix - (gmt/translate-matrix (gpt/negate center)))) - point)) - (defn transform-rect "Transform a rectangles and changes its attributes" [rect matrix] @@ -273,6 +254,30 @@ (if transform (gmt/multiply transform matrix) matrix) (if transform-inverse (gmt/multiply matrix-inverse transform-inverse) matrix-inverse)])) +(defn- adjust-shape-flips + "After some tranformations the flip-x/flip-y flags can change we need + to check this before adjusting the selrect" + [shape points] + + (let [points' (:points shape) + + xv1 (gpt/to-vec (nth points' 0) (nth points' 1)) + xv2 (gpt/to-vec (nth points 0) (nth points 1)) + dot-x (gpt/dot xv1 xv2) + + yv1 (gpt/to-vec (nth points' 0) (nth points' 3)) + yv2 (gpt/to-vec (nth points 0) (nth points 3)) + dot-y (gpt/dot yv1 yv2)] + + (cond-> shape + (neg? dot-x) + (-> (update :flip-x not) + (update :rotation -)) + + (neg? dot-y) + (-> (update :flip-y not) + (update :rotation -))))) + (defn apply-transform "Given a new set of points transformed, set up the rectangle so it keeps its properties. We adjust de x,y,width,height and create a custom transform" @@ -280,6 +285,7 @@ (let [points' (:points shape) points (gco/transform-points points' transform-mtx) + shape (-> shape (adjust-shape-flips points)) bool? (= (:type shape) :bool) path? (= (:type shape) :path) @@ -289,7 +295,6 @@ base-rotation (or (:rotation shape) 0) modif-rotation (or (get-in shape [:modifiers :rotation]) 0) rotation (mod (+ base-rotation modif-rotation) 360)] - (-> shape (cond-> bool? (update :bool-content gpa/transform-content transform-mtx)) @@ -368,173 +373,8 @@ (assoc :flip-x (-> mask :flip-x)) (assoc :flip-y (-> mask :flip-y))))) -;; --- Modifiers -;; The `modifiers` structure contains a list of transformations to -;; do make to a shape, in this order: -;; -;; - resize-origin (gpt/point) + resize-vector (gpt/point) -;; apply a scale vector to all points of the shapes, starting -;; from the origin point. -;; -;; - resize-origin-2 + resize-vector-2 -;; same as the previous one, for cases in that we need to make -;; two vectors from different origin points. -;; -;; - displacement (gmt/matrix) -;; apply a translation matrix to the shape -;; -;; - rotation (gmt/matrix) -;; apply a rotation matrix to the shape -;; -;; - resize-transform (gmt/matrix) + resize-transform-inverse (gmt/matrix) -;; a copy of the rotation matrix currently applied to the shape; -;; this is needed temporarily to apply the resize vectors. -;; -;; - resize-scale-text (bool) -;; tells if the resize vectors must be applied to text shapes -;; or not. - -(defn empty-modifiers? [modifiers] - (empty? (dissoc modifiers :ignore-geometry?))) - -(defn resize-modifiers - [shape attr value] - (us/assert map? shape) - (us/assert #{:width :height} attr) - (us/assert number? value) - (let [{:keys [proportion proportion-lock]} shape - size (select-keys (:selrect shape) [:width :height]) - new-size (if-not proportion-lock - (assoc size attr value) - (if (= attr :width) - (-> size - (assoc :width value) - (assoc :height (/ value proportion))) - (-> size - (assoc :height value) - (assoc :width (* value proportion))))) - width (:width new-size) - height (:height new-size) - - shape-transform (:transform shape) - shape-transform-inv (:transform-inverse shape) - shape-center (gco/center-shape shape) - {sr-width :width sr-height :height} (:selrect shape) - - origin (cond-> (gpt/point (:selrect shape)) - (some? shape-transform) - (transform-point-center shape-center shape-transform)) - - scalev (gpt/divide (gpt/point width height) - (gpt/point sr-width sr-height))] - {:resize-vector scalev - :resize-origin origin - :resize-transform shape-transform - :resize-transform-inverse shape-transform-inv})) - -(defn change-orientation-modifiers - [shape orientation] - (us/assert map? shape) - (us/verify #{:horiz :vert} orientation) - (let [width (:width shape) - height (:height shape) - new-width (if (= orientation :horiz) (max width height) (min width height)) - new-height (if (= orientation :horiz) (min width height) (max width height)) - - shape-transform (:transform shape) - shape-transform-inv (:transform-inverse shape) - shape-center (gco/center-shape shape) - {sr-width :width sr-height :height} (:selrect shape) - - origin (cond-> (gpt/point (:selrect shape)) - (some? shape-transform) - (transform-point-center shape-center shape-transform)) - - scalev (gpt/divide (gpt/point new-width new-height) - (gpt/point sr-width sr-height))] - {:resize-vector scalev - :resize-origin origin - :resize-transform shape-transform - :resize-transform-inverse shape-transform-inv})) - -(defn rotation-modifiers - [shape center angle] - (let [displacement (let [shape-center (gco/center-shape shape)] - (-> (gmt/matrix) - (gmt/rotate angle center) - (gmt/rotate (- angle) shape-center)))] - {:rotation angle - :displacement displacement})) - -(defn merge-modifiers - [objects modifiers] - - (let [set-modifier - (fn [objects [id modifiers]] - (-> objects - (d/update-when id merge modifiers)))] - (->> modifiers - (reduce set-modifier objects)))) - -(defn modifiers->transform - ([modifiers] - (modifiers->transform nil modifiers)) - - ([center modifiers] - (let [displacement (:displacement modifiers) - displacement-after (:displacement-after modifiers) - resize-v1 (:resize-vector modifiers) - resize-v2 (:resize-vector-2 modifiers) - origin-1 (:resize-origin modifiers (gpt/point)) - origin-2 (:resize-origin-2 modifiers (gpt/point)) - - ;; Normalize x/y vector coordinates because scale by 0 is infinite - resize-1 (when (some? resize-v1) - (gpt/point (normalize-scale (:x resize-v1)) - (normalize-scale (:y resize-v1)))) - - resize-2 (when (some? resize-v2) - (gpt/point (normalize-scale (:x resize-v2)) - (normalize-scale (:y resize-v2)))) - - - resize-transform (:resize-transform modifiers) - resize-transform-inverse (:resize-transform-inverse modifiers) - - rt-modif (:rotation modifiers)] - - (cond-> (gmt/matrix) - (some? displacement-after) - (gmt/multiply displacement-after) - - (some? resize-1) - (-> (gmt/translate origin-1) - (cond-> (some? resize-transform) - (gmt/multiply resize-transform)) - (gmt/scale resize-1) - (cond-> (some? resize-transform-inverse) - (gmt/multiply resize-transform-inverse)) - (gmt/translate (gpt/negate origin-1))) - - (some? resize-2) - (-> (gmt/translate origin-2) - (cond-> (some? resize-transform) - (gmt/multiply resize-transform)) - (gmt/scale resize-2) - (cond-> (some? resize-transform-inverse) - (gmt/multiply resize-transform-inverse)) - (gmt/translate (gpt/negate origin-2))) - - (some? displacement) - (gmt/multiply displacement) - - (some? rt-modif) - (-> (gmt/translate center) - (gmt/multiply (gmt/rotate-matrix rt-modif)) - (gmt/translate (gpt/negate center))))))) - -(defn- set-flip [shape modifiers] +#_(defn- set-flip [shape modifiers] (let [rv1x (or (get-in modifiers [:resize-vector :x]) 1) rv1y (or (get-in modifiers [:resize-vector :y]) 1) rv2x (or (get-in modifiers [:resize-vector-2 :x]) 1) @@ -547,7 +387,25 @@ (-> (update :flip-y not) (update :rotation -))))) -(defn- apply-displacement [shape] +#_(defn- set-flip-2 [shape transform] + (let [pt-a (gpt/point (:selrect shape)) + pt-b (gpt/point (-> shape :selrect :x2) (-> shape :selrect :y2)) + + shape-transform (:transform shape (gmt/matrix)) + pt-a' (gpt/transform pt-a (gmt/multiply shape-transform transform )) + pt-b' (gpt/transform pt-b (gmt/multiply shape-transform transform )) + + {:keys [x y]} (gpt/to-vec pt-a' pt-b')] + + (cond-> shape + (neg? x) + (-> (update :flip-x not) + (update :rotation -)) + (neg? y) + (-> (update :flip-y not) + (update :rotation -))))) + +#_(defn- apply-displacement [shape] (let [modifiers (:modifiers shape)] (if (contains? modifiers :displacement) (let [mov-vec (-> (gpt/point 0 0) @@ -580,64 +438,87 @@ (defn apply-modifiers [shape modifiers] (let [center (gco/center-shape shape) - transform (modifiers->transform center modifiers)] - (apply-transform shape transform))) + transform (ctm/modifiers->transform center modifiers)] + (-> shape + #_(set-flip-2 transform) + (apply-transform transform)))) + +(defn apply-objects-modifiers + [objects modifiers] + (letfn [(process-shape [objects [id modifier]] + (update objects id apply-modifiers (:modifiers modifier)))] + (reduce process-shape objects modifiers))) (defn transform-shape - [shape] - (let [modifiers (:modifiers shape)] - (cond - (nil? modifiers) - shape + ([shape] + (let [modifiers (:modifiers shape)] + (-> shape + (dissoc :modifiers) + (transform-shape modifiers)))) - (empty-modifiers? modifiers) - (dissoc shape :modifiers) + ([shape modifiers] + (cond-> shape + (and (some? modifiers) (not (ctm/empty-modifiers? modifiers))) + (-> (apply-modifiers modifiers) + (apply-text-resize modifiers))))) - :else - (let [shape (apply-displacement shape) - modifiers (:modifiers shape)] - (cond-> shape - (not (empty-modifiers? modifiers)) - (-> (set-flip modifiers) - (apply-modifiers modifiers) - (apply-text-resize modifiers)) +(defn transform-bounds-v2 + [points center modifiers] + (let [transform (ctm/modifiers->transform center {:v2 modifiers}) + result (gco/transform-points points center transform)] - :always - (dissoc :modifiers)))))) + ;;(.log js/console "??" (str transform) (clj->js result)) + result) + + #_(letfn [(apply-modifier [points {:keys [type vector origin]}] + (case type + :move + (let [displacement (gmt/translate-matrix vector)] + (gco/transform-points points displacement)) + + :resize + (gco/transform-points points origin (gmt/scale-matrix vector)) + + points))] + (->> modifiers + (reduce apply-modifier points)))) (defn transform-bounds - [points center {:keys [displacement displacement-after resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}] + [points center {:keys [v2 displacement displacement-after resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}] + ;; FIXME: Improve Performance - (let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix)) + (if (some? v2) + (transform-bounds-v2 points center v2) + (let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix)) - displacement - (when (some? displacement) - (gmt/multiply resize-transform-inverse displacement)) + displacement + (when (some? displacement) + (gmt/multiply resize-transform-inverse displacement)) - resize-origin - (when (some? resize-origin) - (transform-point-center resize-origin center resize-transform-inverse)) + resize-origin + (when (some? resize-origin) + (gmt/transform-point-center resize-origin center resize-transform-inverse)) - resize-origin-2 - (when (some? resize-origin-2) - (transform-point-center resize-origin-2 center resize-transform-inverse)) - ] + resize-origin-2 + (when (some? resize-origin-2) + (gmt/transform-point-center resize-origin-2 center resize-transform-inverse)) + ] - (if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2) (nil? displacement-after)) - points + (if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2) (nil? displacement-after)) + points - (cond-> points - (some? displacement) - (gco/transform-points displacement) + (cond-> points + (some? displacement) + (gco/transform-points displacement) - (some? resize-origin) - (gco/transform-points resize-origin (gmt/scale-matrix resize-vector)) + (some? resize-origin) + (gco/transform-points resize-origin (gmt/scale-matrix resize-vector)) - (some? resize-origin-2) - (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2)) + (some? resize-origin-2) + (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2)) - (some? displacement-after) - (gco/transform-points displacement-after))))) + (some? displacement-after) + (gco/transform-points displacement-after)))))) (defn transform-selrect [selrect modifiers] diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index 79c9de1c81..6ff873c8e9 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -102,13 +102,7 @@ (fix-frames-selrects) (and (empty? (:points object)) (not= (:id object) uuid/zero)) - (fix-empty-points) - - ;; Setup an empty transformation to re-calculate selrects - ;; and points data - :always - (-> (assoc :modifiers {:displacement (gmt/matrix)}) - (gsh/transform-shape)))) + (fix-empty-points))) (update-page [page] (update page :objects d/update-vals update-object))] diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc new file mode 100644 index 0000000000..963bcd74f6 --- /dev/null +++ b/common/src/app/common/types/modifiers.cljc @@ -0,0 +1,256 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.types.modifiers + (:require + [app.common.data :as d] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] + [app.common.geom.shapes.common :as gco] + [app.common.spec :as us])) + +;; --- Modifiers + +;; The `modifiers` structure contains a list of transformations to +;; do make to a shape, in this order: +;; +;; - resize-origin (gpt/point) + resize-vector (gpt/point)q +;; apply a scale vector to all points of the shapes, starting +;; from the origin point. +;; +;; - resize-origin-2 + resize-vector-2 +;; same as the previous one, for cases in that we need to make +;; two vectors from different origin points. +;; +;; - displacement (gmt/matrix) +;; apply a translation matrix to the shape +;; +;; - rotation (gmt/matrix) +;; apply a rotation matrix to the shape +;; +;; - resize-transform (gmt/matrix) + resize-transform-inverse (gmt/matrix) +;; a copy of the rotation matrix currently applied to the shape; +;; this is needed temporarily to apply the resize vectors. +;; +;; - resize-scale-text (bool) +;; tells if the resize vectors must be applied to text shapes +;; or not. + +(defn move + ([x y] + (move (gpt/point x y))) + + ([vector] + {:v2 [{:type :move :vector vector}]})) + +(defn resize + [vector origin] + {:v2 [{:type :resize :vector vector :origin origin}]}) + +(defn add-move + ([object x y] + (add-move object (gpt/point x y))) + + ([object vector] + (assoc-in + object + [:modifiers :displacement] + (gmt/translate-matrix (:x vector) (:y vector))))) + +(defn add-resize + [object vector origin] + (-> object + (assoc-in [:modifiers :resize-vector] vector) + (assoc-in [:modifiers :resize-origin] origin))) + +(defn empty-modifiers? [modifiers] + (empty? (dissoc modifiers :ignore-geometry?))) + +(defn resize-modifiers + [shape attr value] + (us/assert map? shape) + (us/assert #{:width :height} attr) + (us/assert number? value) + (let [{:keys [proportion proportion-lock]} shape + size (select-keys (:selrect shape) [:width :height]) + new-size (if-not proportion-lock + (assoc size attr value) + (if (= attr :width) + (-> size + (assoc :width value) + (assoc :height (/ value proportion))) + (-> size + (assoc :height value) + (assoc :width (* value proportion))))) + width (:width new-size) + height (:height new-size) + + shape-transform (:transform shape) + shape-transform-inv (:transform-inverse shape) + shape-center (gco/center-shape shape) + {sr-width :width sr-height :height} (:selrect shape) + + origin (cond-> (gpt/point (:selrect shape)) + (some? shape-transform) + (gmt/transform-point-center shape-center shape-transform)) + + scalev (gpt/divide (gpt/point width height) + (gpt/point sr-width sr-height))] + {:resize-vector scalev + :resize-origin origin + :resize-transform shape-transform + :resize-transform-inverse shape-transform-inv})) + +(defn change-orientation-modifiers + [shape orientation] + (us/assert map? shape) + (us/verify #{:horiz :vert} orientation) + (let [width (:width shape) + height (:height shape) + new-width (if (= orientation :horiz) (max width height) (min width height)) + new-height (if (= orientation :horiz) (min width height) (max width height)) + + shape-transform (:transform shape) + shape-transform-inv (:transform-inverse shape) + shape-center (gco/center-shape shape) + {sr-width :width sr-height :height} (:selrect shape) + + origin (cond-> (gpt/point (:selrect shape)) + (some? shape-transform) + (gmt/transform-point-center shape-center shape-transform)) + + scalev (gpt/divide (gpt/point new-width new-height) + (gpt/point sr-width sr-height))] + {:resize-vector scalev + :resize-origin origin + :resize-transform shape-transform + :resize-transform-inverse shape-transform-inv})) + +(defn rotation-modifiers + [shape center angle] + (let [shape-center (gco/center-shape shape) + rotation (-> (gmt/matrix) + (gmt/rotate angle center) + (gmt/rotate (- angle) shape-center))] + + {:v2 [{:type :rotation + :center shape-center + :rotation angle} + + {:type :move + :vector (gpt/transform (gpt/point 1 1) rotation)}]} + #_{:rotation angle + :displacement displacement})) + +(defn merge-modifiers + [objects modifiers] + + (let [set-modifier + (fn [objects [id modifiers]] + (-> objects + (d/update-when id merge modifiers)))] + (->> modifiers + (reduce set-modifier objects)))) + +(defn modifiers-v2->transform + [modifiers] + (letfn [(apply-modifier [matrix {:keys [type vector rotation center origin transform transform-inverse] :as modifier}] + (case type + :move + (gmt/multiply (gmt/translate-matrix vector) matrix) + + ;;:transform + ;;(gmt/multiply transform matrix) + + :resize + (gmt/multiply + (-> (gmt/matrix) + (gmt/translate origin) + (cond-> (some? transform) + (gmt/multiply transform)) + (gmt/scale vector) + (cond-> (some? transform-inverse) + (gmt/multiply transform-inverse)) + (gmt/translate (gpt/negate origin))) + matrix) + + :rotation + ;; TODO LAYOUT: Comprobar que pasa si no hay centro + (gmt/multiply + (-> (gmt/matrix) + (gmt/translate center) + (gmt/multiply (gmt/rotate-matrix rotation)) + (gmt/translate (gpt/negate center))) + matrix)))] + (->> modifiers + (reduce apply-modifier (gmt/matrix))))) + +(defn- normalize-scale + "We normalize the scale so it's not too close to 0" + [scale] + (cond + (and (< scale 0) (> scale -0.01)) -0.01 + (and (>= scale 0) (< scale 0.01)) 0.01 + :else scale)) + +(defn modifiers->transform + ([modifiers] + (modifiers->transform nil modifiers)) + + ([center modifiers] + (if (some? (:v2 modifiers)) + (modifiers-v2->transform (:v2 modifiers)) + (let [displacement (:displacement modifiers) + displacement-after (:displacement-after modifiers) + resize-v1 (:resize-vector modifiers) + resize-v2 (:resize-vector-2 modifiers) + origin-1 (:resize-origin modifiers (gpt/point)) + origin-2 (:resize-origin-2 modifiers (gpt/point)) + + ;; Normalize x/y vector coordinates because scale by 0 is infinite + resize-1 (when (some? resize-v1) + (gpt/point (normalize-scale (:x resize-v1)) + (normalize-scale (:y resize-v1)))) + + resize-2 (when (some? resize-v2) + (gpt/point (normalize-scale (:x resize-v2)) + (normalize-scale (:y resize-v2)))) + + resize-transform (:resize-transform modifiers) + resize-transform-inverse (:resize-transform-inverse modifiers) + + rt-modif (:rotation modifiers)] + + (cond-> (gmt/matrix) + (some? displacement-after) + (gmt/multiply displacement-after) + + (some? resize-1) + (-> (gmt/translate origin-1) + (cond-> (some? resize-transform) + (gmt/multiply resize-transform)) + (gmt/scale resize-1) + (cond-> (some? resize-transform-inverse) + (gmt/multiply resize-transform-inverse)) + (gmt/translate (gpt/negate origin-1))) + + (some? resize-2) + (-> (gmt/translate origin-2) + (cond-> (some? resize-transform) + (gmt/multiply resize-transform)) + (gmt/scale resize-2) + (cond-> (some? resize-transform-inverse) + (gmt/multiply resize-transform-inverse)) + (gmt/translate (gpt/negate origin-2))) + + (some? displacement) + (gmt/multiply displacement) + + (some? rt-modif) + (-> (gmt/translate center) + (gmt/multiply (gmt/rotate-matrix rt-modif)) + (gmt/translate (gpt/negate center)))))) + )) diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs index c87150b311..7fddf1c78a 100644 --- a/frontend/src/app/main/data/workspace/comments.cljs +++ b/frontend/src/app/main/data/workspace/comments.cljs @@ -159,8 +159,8 @@ build-move-event (fn [comment-thread] (let [frame (get objects (:frame-id comment-thread)) - frame' (-> (merge frame (get object-modifiers (:frame-id comment-thread))) - (gsh/transform-shape)) + modifiers (get object-modifiers (:frame-id comment-thread)) + frame' (gsh/transform-shape frame modifiers) moved (gpt/to-vec (gpt/point (:x frame) (:y frame)) (gpt/point (:x frame') (:y frame'))) position (get-in threads-position-map [(:id comment-thread) :position]) diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index de2a290acf..dc9e867626 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -10,6 +10,7 @@ [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] @@ -37,9 +38,7 @@ scalev)] (-> shape (assoc :click-draw? false) - (assoc-in [:modifiers :resize-vector] scalev) - (assoc-in [:modifiers :resize-origin] (gpt/point x y)) - (assoc-in [:modifiers :resize-rotation] 0)))) + (gsh/transform-shape (ctm/resize scalev (gpt/point x y)))))) (defn update-drawing [state point lock?] (update-in state [:workspace-drawing :object] resize-shape point lock?)) diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index 1742bfd058..c13f76e707 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -6,10 +6,10 @@ (ns app.main.data.workspace.drawing.common (:require - [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm] [app.common.types.shape :as cts] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] @@ -51,8 +51,8 @@ (and click-draw? (not text?)) (-> (assoc :width min-side :height min-side) - (assoc-in [:modifiers :displacement] - (gmt/translate-matrix (- (/ min-side 2)) (- (/ min-side 2))))) + (gsh/transform-shape (ctm/move (- (/ min-side 2)) (- (/ min-side 2)))) + #_(ctm/add-move (- (/ min-side 2)) (- (/ min-side 2)))) (and click-draw? text?) (assoc :height 17 :width 4 :grow-type :auto-width) @@ -61,8 +61,7 @@ (cts/setup-rect-selrect) :always - (-> (gsh/transform-shape) - (dissoc :initialized? :click-draw?)))] + (dissoc :initialized? :click-draw?))] ;; Add & select the created shape to the workspace (rx/concat (if (= :text (:type shape)) diff --git a/frontend/src/app/main/data/workspace/guides.cljs b/frontend/src/app/main/data/workspace/guides.cljs index 35bae09d38..0ef6e5a0b9 100644 --- a/frontend/src/app/main/data/workspace/guides.cljs +++ b/frontend/src/app/main/data/workspace/guides.cljs @@ -79,8 +79,7 @@ build-move-event (fn [guide] (let [frame (get objects (:frame-id guide)) - frame' (-> (merge frame (get object-modifiers (:frame-id guide))) - (gsh/transform-shape)) + frame' (gsh/transform-shape (get object-modifiers (:frame-id guide))) moved (gpt/to-vec (gpt/point (:x frame) (:y frame)) (gpt/point (:x frame') (:y frame'))) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 710995c9bc..cffd33827b 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -55,7 +55,7 @@ (dwt/apply-modifiers)) (rx/empty)))))) -;; TODO: Remove constraints from children +;; TODO LAYOUT: Remove constraints from children (defn create-layout [ids type] (ptk/reify ::create-layout diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index c2f68bd627..c56931cf19 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -8,9 +8,9 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] - [app.common.path.commands :as upc])) + [app.common.path.commands :as upc] + [app.common.types.modifiers :as ctm])) (defn lookup-page ([state] @@ -137,5 +137,6 @@ children (select-keys objects children-ids)] (as-> children $ - (gsh/merge-modifiers $ modifiers) + ;; TODO LAYOUT: REVIEW THIS + (ctm/merge-modifiers $ modifiers) (d/mapm (set-content-modifiers state) $)))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 653b03abc5..1c9f5f147c 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -13,6 +13,7 @@ [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.text :as txt] + [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] @@ -319,17 +320,16 @@ (letfn [(update-fn [shape] (let [{:keys [selrect grow-type]} shape {shape-width :width shape-height :height} selrect - modifier-width (gsh/resize-modifiers shape :width new-width) - modifier-height (gsh/resize-modifiers shape :height new-height)] + modifier-width (ctm/resize-modifiers shape :width new-width) + modifier-height (ctm/resize-modifiers shape :height new-height)] + ;; TODO LAYOUT: MEZCLAR ESTOS EN UN UNICO MODIFIER (cond-> shape (and (not-changed? shape-width new-width) (= grow-type :auto-width)) - (-> (assoc :modifiers modifier-width) - (gsh/transform-shape)) + (gsh/transform-shape modifier-width) (and (not-changed? shape-height new-height) (or (= grow-type :auto-height) (= grow-type :auto-width))) - (-> (assoc :modifiers modifier-height) - (gsh/transform-shape)))))] + (gsh/transform-shape modifier-height))))] (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false})))))) @@ -346,18 +346,17 @@ (defn apply-text-modifier [shape {:keys [width height position-data]}] - (let [modifier-width (when width (gsh/resize-modifiers shape :width width)) - modifier-height (when height (gsh/resize-modifiers shape :height height)) + (let [modifier-width (when width (ctm/resize-modifiers shape :width width)) + modifier-height (when height (ctm/resize-modifiers shape :height height)) + ;; TODO LAYOUT: MEZCLAR LOS DOS EN UN UNICO MODIFIER new-shape (cond-> shape (some? modifier-width) - (-> (assoc :modifiers modifier-width) - (gsh/transform-shape)) + (gsh/transform-shape modifier-width) (some? modifier-height) - (-> (assoc :modifiers modifier-height) - (gsh/transform-shape)) + (gsh/transform-shape modifier-height) (some? position-data) (assoc :position-data position-data)) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 0842f18859..9d8c186d54 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -16,6 +16,7 @@ [app.common.pages.common :as cpc] [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.types.modifiers :as ctm] [app.common.types.shape-tree :as ctst] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] @@ -154,14 +155,14 @@ ids (->> shapes (remove #(get % :blocked false)) - (mapcat #(cph/get-children objects (:id %))) - (concat shapes) + #_(mapcat #(cph/get-children objects (:id %))) + #_(concat shapes) (filter #((cpc/editable-attrs (:type %)) :rotation)) (map :id)) get-modifier (fn [shape] - (gsh/rotation-modifiers shape center angle)) + (ctm/rotation-modifiers shape center angle)) modif-tree (gsh/set-objects-modifiers ids objects get-modifier false false)] @@ -193,7 +194,7 @@ (let [objects (wsh/lookup-page-objects state) object-modifiers (get state :workspace-modifiers) - ids (keys object-modifiers) + ids (or (keys object-modifiers) []) ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids) shapes (map (d/getf objects) ids) @@ -209,11 +210,10 @@ (dch/update-shapes ids (fn [shape] - (let [modif (get object-modifiers (:id shape)) + (let [modif (get-in object-modifiers [(:id shape) :modifiers]) text-shape? (cph/text-shape? shape)] (-> shape - (merge modif) - (gsh/transform-shape) + (gsh/transform-shape modif) (cond-> text-shape? (update-grow-type shape))))) {:reg-objects? true @@ -295,7 +295,7 @@ (let [children (map (d/getf objects) (:shapes shape)) shape-id (:id shape) - transformed-shape (gsh/transform-shape (merge shape (get modif-tree shape-id))) + transformed-shape (gsh/transform-shape shape (get modif-tree shape-id)) [root transformed-root ignore-geometry?] (check-delta shape root transformed-shape transformed-root objects modif-tree) @@ -331,10 +331,10 @@ rotation (or rotation 0) - initial (gsh/transform-point-center initial shape-center shape-transform-inverse) + initial (gmt/transform-point-center initial shape-center shape-transform-inverse) initial (fix-init-point initial handler shape) - point (gsh/transform-point-center (if (= rotation 0) point-snap point) + point (gmt/transform-point-center (if (= rotation 0) point-snap point) shape-center shape-transform-inverse) shapev (-> (gpt/point width height)) @@ -381,14 +381,28 @@ (gpt/transform shape-transform))) resize-origin - (cond-> (gsh/transform-point-center handler-origin shape-center shape-transform) + (cond-> (gmt/transform-point-center handler-origin shape-center shape-transform) (some? displacement) - (gpt/add displacement)) - - displacement (when (some? displacement) - (gmt/translate-matrix displacement))] + (gpt/add displacement))] (rx/of (set-modifiers ids + {:v2 (-> [] + (cond-> displacement + (conj {:type :move + :vector displacement})) + (conj {:type :resize + :vector scalev + :origin resize-origin + :transform shape-transform + :transform-inverse shape-transform-inverse})) + ;;:displacement displacement + ;;:resize-vector scalev + ;;:resize-origin resize-origin + ;;:resize-transform shape-transform + ;;:resize-scale-text scale-text + ;;:resize-transform-inverse shape-transform-inverse + })) + #_(rx/of (set-modifiers ids {:displacement displacement :resize-vector scalev :resize-origin resize-origin @@ -444,7 +458,7 @@ snap-pixel? (and (contains? (:workspace-layout state) :snap-pixel-grid) (int? value)) get-modifier - (fn [shape] (gsh/resize-modifiers shape attr value)) + (fn [shape] (ctm/resize-modifiers shape attr value)) modif-tree (gsh/set-objects-modifiers ids objects get-modifier false snap-pixel?)] @@ -468,7 +482,7 @@ snap-pixel? (contains? (get state :workspace-layout) :snap-pixel-grid) get-modifier - (fn [shape] (gsh/change-orientation-modifiers shape orientation)) + (fn [shape] (ctm/change-orientation-modifiers shape orientation)) modif-tree (gsh/set-objects-modifiers ids objects get-modifier false snap-pixel?)] @@ -655,7 +669,10 @@ (rx/with-latest vector snap-delta) ;; We try to use the previous snap so we don't have to wait for the result of the new (rx/map snap/correct-snap-point) - (rx/map #(hash-map :displacement (gmt/translate-matrix %))) + + #_(rx/map #(hash-map :displacement (gmt/translate-matrix %))) + (rx/map #(array-map :v2 [{:type :move :vector %}])) + (rx/map (partial set-modifiers ids)) (rx/take-until stopper)) @@ -704,7 +721,7 @@ (rx/merge (->> move-events (rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0)) - (rx/map #(hash-map :displacement (gmt/translate-matrix %))) + (rx/map #(ctm/move %)) (rx/map (partial set-modifiers selected)) (rx/take-until stopper)) (rx/of (move-selected direction shift?))) @@ -735,11 +752,11 @@ cpos (gpt/point (:x bbox) (:y bbox)) pos (gpt/point (or (:x position) (:x bbox)) (or (:y position) (:y bbox))) - delta (gpt/subtract pos cpos) - displ (gmt/translate-matrix delta)] + delta (gpt/subtract pos cpos)] - (rx/of (set-modifiers [id] {:displacement displ} false true) - (apply-modifiers [id])))))) + (rx/of + (set-modifiers [id] (ctm/move delta)) + (apply-modifiers [id])))))) (defn- calculate-frame-for-move [ids] @@ -787,11 +804,16 @@ (let [objects (wsh/lookup-page-objects state) selected (wsh/lookup-selected state {:omit-blocked? true}) shapes (map #(get objects %) selected) - selrect (gsh/selection-rect (->> shapes (map gsh/transform-shape))) + selrect (gsh/selection-rect shapes) origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2)))] (rx/of (set-modifiers selected - {:resize-vector (gpt/point -1.0 1.0) + {:v2 [{:type :resize + :vector (gpt/point -1.0 1.0) + :origin origin} + {:type :move + :vector (gpt/point (:width selrect) 0)}]} + #_{:resize-vector (gpt/point -1.0 1.0) :resize-origin origin :displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))} true) @@ -804,11 +826,16 @@ (let [objects (wsh/lookup-page-objects state) selected (wsh/lookup-selected state {:omit-blocked? true}) shapes (map #(get objects %) selected) - selrect (gsh/selection-rect (->> shapes (map gsh/transform-shape))) + selrect (gsh/selection-rect shapes) origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect))] (rx/of (set-modifiers selected - {:resize-vector (gpt/point 1.0 -1.0) + {:v2 [{:type :resize + :vector (gpt/point 1.0 -1.0) + :origin origin} + {:type :move + :vector (gpt/point 0 (:height selrect))}]} + #_{:resize-vector (gpt/point 1.0 -1.0) :resize-origin origin :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))} true) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index c750538dca..77fa6f5a1e 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -15,12 +15,12 @@ ["react-dom/server" :as rds] [app.common.colors :as clr] [app.common.data.macros :as dm] - [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.bounds :as gsb] [app.common.math :as mth] [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm] [app.common.types.shape-tree :as ctst] [app.config :as cfg] [app.main.fonts :as fonts] @@ -82,7 +82,8 @@ (let [render-thumbnails? (mf/use-ctx muc/render-thumbnails) childs (mapv #(get objects %) (:shapes shape)) - shape (gsh/transform-shape shape)] + ;;shape (gsh/transform-shape shape) + ] (if (and render-thumbnails? (some? (:thumbnail shape))) [:& frame/frame-thumbnail {:shape shape :bounds (:children-bounds shape)}] [:& frame-shape {:shape shape :childs childs}]))))) @@ -135,7 +136,7 @@ bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects)) frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))] (when (and shape (not (:hidden shape))) - (let [shape (gsh/transform-shape shape) + (let [;;shape (gsh/transform-shape shape) opts #js {:shape shape} svg-raw? (= :svg-raw (:type shape))] (if-not svg-raw? @@ -167,7 +168,8 @@ (let [shapes (cph/get-immediate-children objects) srect (gsh/selection-rect shapes) object (merge object (select-keys srect [:x :y :width :height])) - object (gsh/transform-shape object)] + ;; object (gsh/transform-shape object) + ] (assoc object :fill-color "#f0f0f0"))) (defn adapt-objects-for-shape @@ -180,14 +182,12 @@ ;; Replace the previous object with the new one objects (assoc objects object-id object) - modifier (-> (gpt/point (:x object) (:y object)) - (gpt/negate) - (gmt/translate-matrix)) + vector (-> (gpt/point (:x object) (:y object)) + (gpt/negate)) mod-ids (cons object-id (cph/get-children-ids objects object-id)) - updt-fn #(-> %1 - (assoc-in [%2 :modifiers :displacement] modifier) - (update %2 gsh/transform-shape))] + + updt-fn #(update %1 %2 gsh/transform-shape (ctm/move vector))] (reduce updt-fn objects mod-ids))) @@ -247,24 +247,21 @@ bounds2 (gsb/get-object-bounds objects (dissoc frame :shadow :blur)) delta-bounds (gpt/point (:x bounds) (:y bounds)) - - modifier (gmt/translate-matrix (gpt/negate delta-bounds)) + vector (gpt/negate delta-bounds) children-ids (cph/get-children-ids objects frame-id) objects - (mf/with-memo [frame-id objects modifier] - (let [update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)] + (mf/with-memo [frame-id objects vector] + (let [update-fn #(update-in %1 %2 ctm/add-move vector)] (->> children-ids (into [frame-id]) (reduce update-fn objects)))) frame - (mf/with-memo [modifier] - (-> frame - (assoc-in [:modifiers :displacement] modifier) - (gsh/transform-shape))) + (mf/with-memo [vector] + (gsh/transform-shape frame (ctm/move vector))) frame (cond-> frame @@ -305,22 +302,21 @@ (let [group-id (:id group) include-metadata? (mf/use-ctx export/include-metadata-ctx) - modifier + vector (mf/use-memo (mf/deps (:x group) (:y group)) (fn [] (-> (gpt/point (:x group) (:y group)) - (gpt/negate) - (gmt/translate-matrix)))) + (gpt/negate)))) objects (mf/use-memo - (mf/deps modifier objects group-id) + (mf/deps vector objects group-id) (fn [] (let [modifier-ids (cons group-id (cph/get-children-ids objects group-id)) - update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) + update-fn #(update %1 %2 ctm/add-move vector) modifiers (reduce update-fn {} modifier-ids)] - (gsh/merge-modifiers objects modifiers)))) + (ctm/merge-modifiers objects modifiers)))) group (get objects group-id) width (* (:width group) zoom) diff --git a/frontend/src/app/main/ui/shapes/bool.cljs b/frontend/src/app/main/ui/shapes/bool.cljs index 94a76d893a..2c00a9684e 100644 --- a/frontend/src/app/main/ui/shapes/bool.cljs +++ b/frontend/src/app/main/ui/shapes/bool.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.shapes.bool (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] [app.main.ui.hooks :refer [use-equal-memo]] @@ -15,6 +14,7 @@ [app.util.object :as obj] [rumext.v2 :as mf])) +;; TODO LAYOUT: REVIEW DYNAMIC CHANGES IN BOOLEANS (defn bool-shape [shape-wrapper] (mf/fnc bool-shape @@ -35,7 +35,7 @@ (some? childs) (->> childs - (d/mapm #(gsh/transform-shape %2)) + #_(d/mapm #(gsh/transform-shape %2)) (gsh/calc-bool-content shape)))))] [:* diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs index 493c43691d..d0685a5f5e 100644 --- a/frontend/src/app/main/ui/shapes/mask.cljs +++ b/frontend/src/app/main/ui/shapes/mask.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.shapes.mask (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] [app.main.ui.context :as muc] [cuerdas.core :as str] @@ -50,9 +51,7 @@ render-id (mf/use-ctx muc/render-id) svg-text? (and (= :text (:type mask)) (some? (:position-data mask))) - mask-bb (-> (gsh/transform-shape mask) - (:points)) - + mask-bb (:points mask) mask-bb-rect (gsh/points->rect mask-bb)] [:defs [:filter {:id (filter-id render-id mask)} @@ -68,7 +67,7 @@ [:clipPath {:class "mask-clip-path" :id (clip-id render-id mask)} [:polyline {:points (->> mask-bb - (map #(str (:x %) "," (:y %))) + (map #(dm/str (:x %) "," (:y %))) (str/join " "))}]] ;; When te shape is a text we pass to the shape the info and disable the filter. diff --git a/frontend/src/app/main/ui/viewer/handoff/render.cljs b/frontend/src/app/main/ui/viewer/handoff/render.cljs index 0567bf6547..0fc20987ee 100644 --- a/frontend/src/app/main/ui/viewer/handoff/render.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/render.cljs @@ -88,7 +88,7 @@ [props] (let [shape (unchecked-get props "shape") childs (mapv #(get objects %) (:shapes shape)) - shape (gsh/transform-shape shape) + ;;shape (gsh/transform-shape shape) props (-> (obj/create) (obj/merge! props) @@ -171,8 +171,7 @@ (mf/use-memo (mf/deps objects) #(svg-raw-container-factory objects))] (when (and shape (not (:hidden shape))) - (let [shape (-> (gsh/transform-shape shape) - (gsh/translate-to-frame frame)) + (let [shape (gsh/translate-to-frame shape frame) opts #js {:shape shape :frame frame}] (case (:type shape) diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index fafb5cf53d..72d8228282 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -8,9 +8,9 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm] [app.common.types.page :as ctp] [app.common.uuid :as uuid] [app.main.data.comments :as dcm] @@ -31,11 +31,10 @@ [frame size objects] (let [ frame-id (:id frame) - modifier (-> (gpt/point (:x size) (:y size)) - (gpt/negate) - (gmt/translate-matrix)) + vector (-> (gpt/point (:x size) (:y size)) + (gpt/negate)) - update-fn #(d/update-when %1 %2 assoc-in [:modifiers :displacement] modifier)] + update-fn #(d/update-when %1 %2 ctm/add-move vector)] (->> (cph/get-children-ids objects frame-id) (into [frame-id]) diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index b884653baa..2b25220786 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -350,7 +350,7 @@ [props] (let [shape (obj/get props "shape") childs (mapv #(get objects %) (:shapes shape)) - shape (gsh/transform-shape shape) + ;;shape (gsh/transform-shape shape) props (obj/merge! #js {} props #js {:shape shape :childs childs @@ -429,7 +429,8 @@ (mf/with-memo [objects] (svg-raw-container-factory objects))] (when (and shape (not (:hidden shape))) - (let [shape (-> (gsh/transform-shape shape) + (let [shape (-> shape + #_(gsh/transform-shape) (gsh/translate-to-frame frame)) opts #js {:shape shape diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 2f4207e140..565cca9d5d 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -11,6 +11,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.types.modifiers :as ctm] [app.main.store :as st] [app.main.ui.workspace.viewport.utils :as vwu] [app.util.dom :as dom] @@ -184,6 +185,7 @@ text? (= type :text) transform-text? (and text? (and (nil? (:resize-vector modifiers)) (nil? (:resize-vector-2 modifiers))))] + ;; TODO LAYOUT: Adapt to new modifiers (doseq [node nodes] (cond ;; Text shapes need special treatment because their resize only change @@ -197,7 +199,7 @@ (dom/class? node "text-container") (let [modifiers (dissoc modifiers :displacement :rotation)] - (when (not (gsh/empty-modifiers? modifiers)) + (when (not (ctm/empty-modifiers? modifiers)) (let [mtx (-> shape (assoc :modifiers modifiers) (gsh/transform-shape) @@ -260,12 +262,15 @@ (d/mapm (fn [id {modifiers :modifiers}] (let [shape (get objects id) center (gsh/center-shape shape) + + ;; TODO LAYOUT: Adapt to new modifiers modifiers (cond-> modifiers ;; For texts we only use the displacement because ;; resize needs to recalculate the text layout (= :text (:type shape)) - (select-keys [:displacement :rotation]))] - (gsh/modifiers->transform center modifiers))) + (select-keys [:displacement :rotation])) + ] + (ctm/modifiers->transform center modifiers))) modifiers)))) shapes diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index 53ed5d9ac3..b51432b5b8 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -58,7 +58,7 @@ :height height :style {:fill "none" :stroke "red"}}] - ;; Text baselineazo + ;; Text baseline [:line {:x1 (mth/round x) :y1 (mth/round (- (:y data) (:height data))) :x2 (mth/round (+ x width)) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 6ee6b3ddcd..e104b64fa0 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -13,6 +13,7 @@ [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.text :as txt] + [app.common.types.modifiers :as ctm] [app.main.data.workspace.texts :as dwt] [app.main.fonts :as fonts] [app.main.refs :as refs] @@ -33,6 +34,7 @@ (with-meta (meta (:position-data shape)))) (dissoc :position-data :transform :transform-inverse))) +;; TODO LAYOUT: Adapt to new modifiers (defn strip-modifier [modifier] (if (or (some? (dm/get-in modifier [:modifiers :resize-vector])) @@ -43,9 +45,9 @@ (defn process-shape [modifiers {:keys [id] :as shape}] (let [modifier (-> (get modifiers id) strip-modifier) shape (cond-> shape - (not (gsh/empty-modifiers? (:modifiers modifier))) + (not (ctm/empty-modifiers? (:modifiers modifier))) (-> (assoc :grow-type :fixed) - (merge modifier) gsh/transform-shape))] + (gsh/transform-shape modifier)))] (-> shape (cond-> (nil? (:position-data shape)) (assoc :migrate true)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 50ea9fa1ec..99f65f0fe3 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -7,7 +7,7 @@ (ns app.main.ui.workspace.sidebar.options (:require [app.common.data :as d] - [app.common.geom.shapes :as gsh] + [app.common.types.modifiers :as ctm] [app.main.data.workspace :as udw] [app.main.refs :as refs] [app.main.store :as st] @@ -64,7 +64,7 @@ shared-libs (mf/deref refs/workspace-libraries) modifiers (mf/deref refs/workspace-modifiers) objects-modified (mf/with-memo [base-objects modifiers] - (gsh/merge-modifiers base-objects modifiers)) + (ctm/merge-modifiers base-objects modifiers)) selected-shapes (into [] (keep (d/getf objects-modified)) selected)] [:div.tool-window [:div.tool-window-content diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 3cf07759b0..f5044bd0de 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -79,8 +79,7 @@ modifiers (mf/deref refs/workspace-modifiers) objects-modified (mf/with-memo [base-objects modifiers] - (gsh/merge-modifiers base-objects modifiers)) - + (gsh/apply-objects-modifiers base-objects modifiers)) background (get options :background clr/canvas) ;; STATE @@ -203,7 +202,7 @@ {:key (dm/str "texts-" page-id) :page-id page-id :objects objects - :modifiers modifiers + ;;:modifiers modifiers :edition edition}]]]] (when show-comments? @@ -336,10 +335,9 @@ (when show-prototypes? [:& widgets/frame-flows {:flows (:flows options) - :objects base-objects + :objects objects-modified :selected selected :zoom zoom - :modifiers modifiers :on-frame-enter on-frame-enter :on-frame-leave on-frame-leave :on-frame-select on-frame-select}]) @@ -348,8 +346,7 @@ [:& drawarea/draw-area {:shape drawing-obj :zoom zoom - :tool drawing-tool - :modifiers modifiers}]) + :tool drawing-tool}]) (when show-grids? [:& frame-grid/frame-grid @@ -371,9 +368,8 @@ :zoom zoom :page-id page-id :selected selected - :objects base-objects - :focus focus - :modifiers modifiers}]) + :objects objects-modified + :focus focus}]) (when show-snap-distance? [:& snap-distances/snap-distances @@ -416,7 +412,6 @@ {:zoom zoom :vbox vbox :hover-frame frame-parent - :modifiers modifiers :disabled-guides? disabled-guides?}]) (when show-selection-handlers? diff --git a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs index 8d31ae5ac4..356af918e1 100644 --- a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.viewport.drawarea "Drawing components." (:require - [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.main.ui.shapes.path :refer [path-shape]] [app.main.ui.workspace.shapes :as shapes] @@ -22,7 +21,7 @@ [:g.draw-area [:g {:style {:pointer-events "none"}} - [:& shapes/shape-wrapper {:shape (gsh/transform-shape shape)}]] + [:& shapes/shape-wrapper {:shape shape}]] (case tool :path [:& path-editor {:shape shape :zoom zoom}] @@ -31,7 +30,7 @@ (mf/defc generic-draw-area [{:keys [shape zoom]}] - (let [{:keys [x y width height]} (:selrect (gsh/transform-shape shape))] + (let [{:keys [x y width height]} (:selrect shape)] (when (and x y (not (mth/nan? x)) (not (mth/nan? y))) diff --git a/frontend/src/app/main/ui/workspace/viewport/guides.cljs b/frontend/src/app/main/ui/workspace/viewport/guides.cljs index efefcaf7a3..5d158ee22f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/guides.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/guides.cljs @@ -278,7 +278,7 @@ frame]} (use-guide handle-change-position get-hover-frame zoom guide) base-frame (or frame hover-frame) - frame (gsh/transform-shape (merge base-frame frame-modifier)) + frame (gsh/transform-shape base-frame frame-modifier) move-vec (gpt/to-vec (gpt/point (:x base-frame) (:y base-frame)) (gpt/point (:x frame) (:y frame))) diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs index 4ba7555ba2..7ecc5989a2 100644 --- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -9,7 +9,6 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.common.types.shape.interactions :as ctsi] [app.main.data.workspace :as dw] @@ -254,13 +253,11 @@ (mf/defc interactions [{:keys [current-transform objects zoom selected hover-disabled?] :as props}] (let [active-shapes (into [] - (comp (filter #(seq (:interactions %))) - (map gsh/transform-shape)) + (comp (filter #(seq (:interactions %)))) (vals objects)) selected-shapes (into [] - (comp (map (d/getf objects)) - (map gsh/transform-shape)) + (map (d/getf objects)) selected) {:keys [editing-interaction-index diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs index a8fd6c2df8..416f71625f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs @@ -72,7 +72,7 @@ "var(--color-primary)" "var(--color-component-highlight)")] (for [shape shapes] [:& outline {:key (str "outline-" (:id shape)) - :shape (gsh/transform-shape shape) + :shape shape :zoom zoom :color color}]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 65d1958e4a..b42fbbc368 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -341,7 +341,7 @@ (let [shape (mf/use-memo (mf/deps shapes) #(->> shapes - (map gsh/transform-shape) + #_(map gsh/transform-shape) (gsh/selection-rect) (cts/setup-shape))) on-resize @@ -369,7 +369,7 @@ (let [shape (mf/use-memo (mf/deps shapes) #(->> shapes - (map gsh/transform-shape) + #_(map gsh/transform-shape) (gsh/selection-rect) (cts/setup-shape)))] @@ -384,7 +384,7 @@ (mf/defc single-handlers [{:keys [shape zoom color disable-handlers] :as props}] (let [shape-id (:id shape) - shape (gsh/transform-shape shape) + ;;shape (gsh/transform-shape shape) on-resize (fn [current-position _initial-position event] @@ -408,14 +408,13 @@ (mf/defc single-selection [{:keys [shape zoom color disable-handlers on-move-selected on-context-menu] :as props}] - (let [shape (gsh/transform-shape shape)] - [:& controls-selection - {:shape shape - :zoom zoom - :color color - :disable-handlers disable-handlers - :on-move-selected on-move-selected - :on-context-menu on-context-menu}])) + [:& controls-selection + {:shape shape + :zoom zoom + :color color + :disable-handlers disable-handlers + :on-move-selected on-move-selected + :on-context-menu on-context-menu}]) (mf/defc selection-area {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs index 1ef1bf79c4..8b8892ea99 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs @@ -50,20 +50,14 @@ :opacity line-opacity}]) (defn get-snap - [coord {:keys [shapes page-id remove-snap? zoom modifiers]}] - (let [shapes-sr - (->> shapes - ;; Merge modifiers into shapes - (map #(merge % (get modifiers (:id %)))) - ;; Create the bounding rectangle for the shapes - (gsh/selection-rect)) + [coord {:keys [shapes page-id remove-snap? zoom]}] + (let [bounds (gsh/selection-rect shapes) + frame-id (snap/snap-frame-id shapes)] - frame-id (snap/snap-frame-id shapes)] - - (->> (rx/of shapes-sr) + (->> (rx/of bounds) (rx/flat-map - (fn [selrect] - (->> (sp/selrect-snap-points selrect) + (fn [bounds] + (->> (sp/selrect-snap-points bounds) (map #(vector frame-id %))))) (rx/flat-map @@ -159,7 +153,7 @@ (mf/defc snap-points {::mf/wrap [mf/memo]} - [{:keys [layout zoom objects selected page-id drawing modifiers focus] :as props}] + [{:keys [layout zoom objects selected page-id drawing focus] :as props}] (us/assert set? selected) (let [shapes (into [] (keep (d/getf objects)) selected) @@ -182,6 +176,5 @@ [:& snap-feedback {:shapes shapes :page-id page-id :remove-snap? remove-snap? - :zoom zoom - :modifiers modifiers}])) + :zoom zoom}])) diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 3c36704cc0..40b54f43cc 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -53,9 +53,7 @@ (defn text-transform [{:keys [x y]} zoom] (let [inv-zoom (/ 1 zoom)] - (str - "scale(" inv-zoom ", " inv-zoom ") " - "translate(" (* zoom x) ", " (* zoom y) ")"))) + (dm/fmt "scale(%, %) translate(%, %)" inv-zoom inv-zoom (* zoom x) (* zoom y)))) (defn title-transform [frame zoom] (let [frame-transform (gsh/transform-str frame {:no-flip true}) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 42596402ea..9da2e1673a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -9,7 +9,6 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.point :as gpt] - [app.common.geom.shapes :as gsh] [app.common.types.shape-tree :as ctt] [app.common.uuid :as uuid] [app.main.data.workspace :as dw] @@ -182,8 +181,8 @@ :on-frame-select on-frame-select}]))])) (mf/defc frame-flow - [{:keys [flow frame modifiers selected? zoom on-frame-enter on-frame-leave on-frame-select]}] - (let [{:keys [x y]} (gsh/transform-shape frame) + [{:keys [flow frame selected? zoom on-frame-enter on-frame-leave on-frame-select]}] + (let [{:keys [x y]} frame flow-pos (gpt/point x (- y (/ 35 zoom))) on-mouse-down @@ -217,9 +216,7 @@ :y -15 :width 100000 :height 24 - :transform (str (when (and selected? modifiers) - (str (:displacement modifiers) " " )) - (vwu/text-transform flow-pos zoom))} + :transform (vwu/text-transform flow-pos zoom)} [:div.flow-badge {:class (dom/classnames :selected selected?)} [:div.content {:on-mouse-down on-mouse-down :on-double-click on-double-click @@ -234,7 +231,6 @@ (let [flows (unchecked-get props "flows") objects (unchecked-get props "objects") zoom (unchecked-get props "zoom") - modifiers (unchecked-get props "modifiers") selected (or (unchecked-get props "selected") #{}) on-frame-enter (unchecked-get props "on-frame-enter") @@ -248,7 +244,6 @@ :frame frame :selected? (contains? selected (:id frame)) :zoom zoom - :modifiers modifiers :on-frame-enter on-frame-enter :on-frame-leave on-frame-leave :on-frame-select on-frame-select}]))])) diff --git a/frontend/src/app/util/geom/snap_points.cljs b/frontend/src/app/util/geom/snap_points.cljs index ea2084c87b..a6120d50fd 100644 --- a/frontend/src/app/util/geom/snap_points.cljs +++ b/frontend/src/app/util/geom/snap_points.cljs @@ -29,10 +29,9 @@ (defn shape-snap-points [{:keys [hidden blocked] :as shape}] (when (and (not blocked) (not hidden)) - (let [shape (gsh/transform-shape shape)] - (case (:type shape) - :frame (-> shape :points gsh/points->selrect frame-snap-points) - (into #{(gsh/center-shape shape)} (:points shape)))))) + (case (:type shape) + :frame (-> shape :points gsh/points->selrect frame-snap-points) + (into #{(gsh/center-shape shape)} (:points shape))))) (defn guide-snap-points [guide frame] From 8d9ed4f8af8bba6b04d18266409158326002e184 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 30 Sep 2022 15:36:30 +0200 Subject: [PATCH 197/682] :sparkles: Fill elements in auto-layout --- common/src/app/common/geom/shapes/layout.cljc | 150 +++++++++++++----- .../app/common/geom/shapes/transforms.cljc | 1 + common/src/app/common/types/modifiers.cljc | 5 + .../shapes/frame/dynamic_modifiers.cljs | 50 +++--- .../shapes/text/viewport_texts_html.cljs | 27 ++-- .../sidebar/options/menus/constraints.cljs | 2 +- .../sidebar/options/menus/measures.cljs | 2 +- .../src/app/main/ui/workspace/viewport.cljs | 2 +- .../ui/workspace/viewport/frame_grid.cljs | 3 +- 9 files changed, 162 insertions(+), 80 deletions(-) diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index e6f7cc70e5..658b09fc78 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -6,6 +6,7 @@ (ns app.common.geom.shapes.layout (:require + [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.rect :as gre])) @@ -70,18 +71,25 @@ (let [wrap? (= layout-wrap-type :wrap) reduce-fn - (fn [[{:keys [line-width line-height num-children child-fill? num-child-fill] :as line-data} result] child] + (fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child] (let [child-bounds (-> child :points gre/points->rect) cur-child-fill? (or (and (col? shape) (= :fill (:layout-h-behavior child))) (and (row? shape) (= :fill (:layout-v-behavior child)))) - next-width (if cur-child-fill? + cur-line-fill? + (or (and (row? shape) (= :fill (:layout-h-behavior child))) + (and (col? shape) (= :fill (:layout-v-behavior child)))) + + ;; TODO LAYOUT: ADD MINWIDTH/HEIGHT + next-width (if (or (and (col? shape) cur-child-fill?) + (and (row? shape) cur-line-fill?)) 0 (-> child-bounds :width)) - next-height (if cur-child-fill? + next-height (if (or (and (row? shape) cur-child-fill?) + (and (col? shape) cur-line-fill?)) 0 (-> child-bounds :height))] @@ -90,19 +98,21 @@ (and (col? shape) (<= (+ line-width next-width (* layout-gap num-children)) width)) (and (row? shape) (<= (+ line-height next-height (* layout-gap num-children)) height)))) - ;; Si autofill añadimos el minwidth que por defecto es 0 + ;; When :fill we add min width (0 by default) [{:line-width (if (col? shape) (+ line-width next-width) (max line-width next-width)) :line-height (if (row? shape) (+ line-height next-height) (max line-height next-height)) :num-children (inc num-children) :child-fill? (or cur-child-fill? child-fill?) + :line-fill? (or cur-line-fill? line-fill?) :num-child-fill (cond-> num-child-fill cur-child-fill? inc)} result] [{:line-width next-width :line-height next-height :num-children 1 - :child-fill? child-fill? - :num-child-fill (if child-fill? 1 0)} + :child-fill? cur-child-fill? + :line-fill? cur-line-fill? + :num-child-fill (if cur-child-fill? 1 0)} (cond-> result (some? line-data) (conj line-data))]))) [line-data layout-lines] (reduce reduce-fn [nil []] children)] @@ -142,10 +152,16 @@ (let [children-gap (* layout-gap (dec num-children)) + line-width (if (and (col? shape) child-fill?) (- width (* layout-gap num-children)) line-width) + line-height (if (and (row? shape) child-fill?) (- height (* layout-gap num-children)) line-height) + start-x (cond - (or (and (col? shape) child-fill?) - (and (col? shape) (= :space-between layout-type)) + ;;(and (col? shape) child-fill?) + ;;;; TODO LAYOUT: Start has to take into account max-width + ;;x + + (or (and (col? shape) (= :space-between layout-type)) (and (col? shape) (= :space-around layout-type))) x @@ -169,8 +185,11 @@ start-y (cond - (or (and (row? shape) child-fill?) - (and (row? shape) (= :space-between layout-type)) + ;;(and (row? shape) child-fill?) + ;;;; TODO LAYOUT: Start has to take into account max-width + ;;y + + (or (and (row? shape) (= :space-between layout-type)) (and (row? shape) (= :space-around layout-type))) y @@ -191,6 +210,7 @@ :else y)] + [start-x start-y])) (get-next-line @@ -213,12 +233,27 @@ next-x next-y]))] - (let [[total-width total-height] - (->> layout-lines (reduce add-lines [0 0])) + (let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0])) total-width (+ total-width (* layout-gap (dec (count layout-lines)))) total-height (+ total-height (* layout-gap (dec (count layout-lines)))) + vertical-fill-space (- height total-height) + horizontal-fill-space (- width total-width) + num-line-fill (count (->> layout-lines (filter :line-fill?))) + + layout-lines + (->> layout-lines + (mapv #(cond-> % + (and (col? shape) (:line-fill? %)) + (update :line-height + (/ vertical-fill-space num-line-fill)) + + (and (row? shape) (:line-fill? %)) + (update :line-width + (/ horizontal-fill-space num-line-fill))))) + + total-height (if (and (col? shape) (> num-line-fill 0)) height total-height) + total-width (if (and (row? shape) (> num-line-fill 0)) width total-width) + [base-x base-y] (get-base-line total-width total-height) @@ -305,6 +340,7 @@ :else start-y) + pos-x (cond-> pos-x (some? margin-x) (+ margin-x)) pos-y (cond-> pos-y (some? margin-y) (+ margin-y)) @@ -323,47 +359,83 @@ next-x (cond-> next-x (some? margin-x) (+ margin-x)) next-y (cond-> next-y (some? margin-y) (+ margin-y)) + layout-data (assoc layout-data :start-x next-x :start-y next-y)] [corner-p layout-data])) +(defn calc-fill-width-data + [child-bounds + {:keys [layout-gap] :as parent} + {:keys [layout-h-behavior] :as child} + {:keys [num-children line-width layout-bounds line-fill? child-fill?] :as layout-data}] + + (cond + (and (col? parent) (= :fill layout-h-behavior) child-fill?) + (let [fill-space (- (:width layout-bounds) line-width (* layout-gap num-children)) + fill-width (/ fill-space (:num-child-fill layout-data)) + fill-scale (/ fill-width (:width child-bounds))] + {:bounds {:width fill-width} + :modifiers [{:type :resize + :origin (gpt/point child-bounds) + :vector (gpt/point fill-scale 1)}]}) + + (and (row? parent) (= :fill layout-h-behavior) line-fill?) + (let [fill-scale (/ line-width (:width child-bounds))] + {:bounds {:width line-width} + :modifiers [{:type :resize + :origin (gpt/point child-bounds) + :vector (gpt/point fill-scale 1)}]}) + )) + +(defn calc-fill-height-data + [child-bounds + {:keys [layout-gap] :as parent} + {:keys [layout-v-behavior] :as child} + {:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}] + + (cond + (and (row? parent) (= :fill layout-v-behavior) child-fill?) + (let [fill-space (- (:height layout-bounds) line-height (* layout-gap num-children)) + fill-height (/ fill-space (:num-child-fill layout-data)) + fill-scale (/ fill-height (:height child-bounds))] + {:bounds {:height fill-height} + :modifiers [{:type :resize + :origin (gpt/point child-bounds) + :vector (gpt/point 1 fill-scale)}]}) + + (and (col? parent) (= :fill layout-v-behavior) line-fill?) + (let [fill-scale (/ line-height (:height child-bounds))] + {:bounds {:height line-height} + :modifiers [{:type :resize + :origin (gpt/point child-bounds) + :vector (gpt/point 1 fill-scale)}]}) + )) + (defn calc-layout-modifiers "Calculates the modifiers for the layout" [parent transform child layout-data] - (let [child-bounds (-> child :points gre/points->selrect) - fill-space (- (-> layout-data :layout-bounds :width) (:line-width layout-data)) + fill-width (calc-fill-width-data child-bounds parent child layout-data) + fill-height (calc-fill-height-data child-bounds parent child layout-data) - fill-width (- (/ fill-space (:num-child-fill layout-data)) - (* 2 (:layout-gap layout-data))) - - fill-scale (/ fill-width (:width child-bounds)) - - child-bounds - (cond-> child-bounds - (and (col? parent) (= :fill (:layout-h-behavior child))) - (assoc :width fill-width)) + child-bounds (cond-> child-bounds + fill-width (merge (:bounds fill-width)) + fill-height (merge (:bounds fill-height))) [corner-p layout-data] (next-p parent child-bounds layout-data) - delta-p (-> corner-p - (gpt/subtract (gpt/point child-bounds)) - (cond-> (some? transform) (gpt/transform transform))) - - modifiers [] + delta-p + (-> corner-p + (gpt/subtract (gpt/point child-bounds)) + (cond-> (some? transform) (gpt/transform transform))) modifiers - (cond-> modifiers - (and (col? parent) (= :fill (:layout-h-behavior child))) - (conj {:type :resize - :from :layout - :origin (gpt/point child-bounds) - :vector (gpt/point fill-scale 1)})) - - modifiers - (conj modifiers {:type :move - :from :layout - :vector delta-p})] + (-> [] + (cond-> fill-width (d/concat-vec (:modifiers fill-width))) + (cond-> fill-height (d/concat-vec (:modifiers fill-height))) + (conj {:type :move :vector delta-p}))] [modifiers layout-data])) + diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 8b1dfaa366..5f1d160b65 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -91,6 +91,7 @@ (defn- calculate-height "Calculates the height of a parallelogram given by the points" [[p1 _ _ p4]] + (-> (gpt/to-vec p4 p1) (gpt/length))) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 963bcd74f6..7c8658cfba 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -254,3 +254,8 @@ (gmt/multiply (gmt/rotate-matrix rt-modif)) (gmt/translate (gpt/negate center)))))) )) + +(defn only-move? + [modifier] + (and (= 1 (-> modifier :v2 count)) + (= :move (-> modifier :v2 first :type)))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 565cca9d5d..0ea026601e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -111,8 +111,7 @@ (dom/query-all shape-defs ".svg-mask-wrapper"))) text? - [shape-node - (dom/query shape-node ".text-container")] + [shape-node] :else [shape-node])))) @@ -178,12 +177,10 @@ (defn update-transform! [base-node shapes transforms modifiers] - (doseq [{:keys [id type] :as shape} shapes] + (doseq [{:keys [id _type] :as shape} shapes] (when-let [nodes (get-nodes base-node shape)] (let [transform (get transforms id) - modifiers (get-in modifiers [id :modifiers]) - text? (= type :text) - transform-text? (and text? (and (nil? (:resize-vector modifiers)) (nil? (:resize-vector-2 modifiers))))] + modifiers (get-in modifiers [id :modifiers])] ;; TODO LAYOUT: Adapt to new modifiers (doseq [node nodes] @@ -197,19 +194,10 @@ (dom/class? node "frame-children") (set-transform-att! node "transform" (gmt/inverse transform)) - (dom/class? node "text-container") - (let [modifiers (dissoc modifiers :displacement :rotation)] - (when (not (ctm/empty-modifiers? modifiers)) - (let [mtx (-> shape - (assoc :modifiers modifiers) - (gsh/transform-shape) - (gsh/transform-matrix {:no-flip true}))] - (override-transform-att! node "transform" mtx)))) - (dom/class? node "frame-title") - (let [shape (-> shape (assoc :modifiers modifiers) gsh/transform-shape) - zoom (get-in @st/state [:workspace-local :zoom] 1) - mtx (vwu/title-transform shape zoom)] + (let [shape (gsh/transform-shape shape modifiers) + zoom (get-in @st/state [:workspace-local :zoom] 1) + mtx (vwu/title-transform shape zoom)] (override-transform-att! node "transform" mtx)) (or (= (dom/get-tag-name node) "mask") @@ -223,7 +211,7 @@ (= (dom/get-tag-name node) "pattern") (set-transform-att! node "patternTransform" transform) - (and (some? transform) (some? node) (or (not text?) transform-text?)) + (and (some? transform) (some? node)) (set-transform-att! node "transform" transform))))))) (defn remove-transform! @@ -251,6 +239,20 @@ (dom/remove-attribute! node "data-old-transform") (dom/remove-attribute! node "transform"))))))))) +(defn adapt-text-modifiers + [modifiers shape] + (let [shape' (gsh/transform-shape shape modifiers) + scalev + (gpt/point (/ (:width shape) (:width shape')) + (/ (:height shape) (:height shape')))] + ;; Reverse the change in size so we can recalculate the layout + (-> modifiers + (update :v2 conj {:type :resize + :vector scalev + :transform (:transform shape') + :transform-inverse (:transform-inverse shape') + :origin (-> shape' :points first)})))) + (defn use-dynamic-modifiers [objects node modifiers] @@ -262,14 +264,8 @@ (d/mapm (fn [id {modifiers :modifiers}] (let [shape (get objects id) center (gsh/center-shape shape) - - ;; TODO LAYOUT: Adapt to new modifiers - modifiers (cond-> modifiers - ;; For texts we only use the displacement because - ;; resize needs to recalculate the text layout - (= :text (:type shape)) - (select-keys [:displacement :rotation])) - ] + text? (= :text (:type shape)) + modifiers (cond-> modifiers text? (adapt-text-modifiers shape))] (ctm/modifiers->transform center modifiers))) modifiers)))) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index e104b64fa0..74e8057e08 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.text :as gsht] [app.common.math :as mth] @@ -34,21 +35,29 @@ (with-meta (meta (:position-data shape)))) (dissoc :position-data :transform :transform-inverse))) -;; TODO LAYOUT: Adapt to new modifiers -(defn strip-modifier +#_(defn strip-modifier [modifier] (if (or (some? (dm/get-in modifier [:modifiers :resize-vector])) (some? (dm/get-in modifier [:modifiers :resize-vector-2]))) modifier (d/update-when modifier :modifiers dissoc :displacement :rotation))) +(defn fix-position [shape modifier] + (let [shape' (-> shape + (assoc :grow-type :fixed) + (gsh/transform-shape modifier)) + + deltav (gpt/to-vec (gpt/point (:selrect shape')) + (gpt/point (:selrect shape)))] + (gsh/transform-shape shape' (ctm/move deltav)))) + (defn process-shape [modifiers {:keys [id] :as shape}] - (let [modifier (-> (get modifiers id) strip-modifier) - shape (cond-> shape - (not (ctm/empty-modifiers? (:modifiers modifier))) - (-> (assoc :grow-type :fixed) - (gsh/transform-shape modifier)))] + (let [modifier (dm/get-in modifiers [id :modifiers])] (-> shape + (cond-> (and (some? modifier) + (not (ctm/only-move? modifier))) + (fix-position modifier)) + (cond-> (nil? (:position-data shape)) (assoc :migrate true)) strip-position-data))) @@ -138,8 +147,8 @@ (fn [id] (let [new-shape (get text-shapes id) old-shape (get prev-text-shapes id) - old-modifiers (-> (get prev-modifiers id) strip-modifier) - new-modifiers (-> (get modifiers id) strip-modifier) + old-modifiers (get prev-modifiers id) + new-modifiers (get modifiers id) remote? (some? (-> new-shape meta :session-id)) ] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs index d7ea7c7158..4d22ffa548 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs @@ -30,7 +30,7 @@ frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes) shapes (as-> old-shapes $ - (map gsh/transform-shape $) + #_(map gsh/transform-shape $) (map gsh/translate-to-frame $ frames)) values (let [{:keys [x y]} (-> shapes first :points gsh/points->selrect)] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 307b19164f..8a9e73f3c9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -83,7 +83,7 @@ ;; the shape with the mouse, generate a copy of the shapes applying ;; the transient transformations. shapes (as-> old-shapes $ - (map gsh/transform-shape $) + #_(map gsh/transform-shape $) (map gsh/translate-to-frame $ frames)) ;; For rotated or stretched shapes, the origin point we show in the menu diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index f5044bd0de..5a4be44caa 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -202,7 +202,7 @@ {:key (dm/str "texts-" page-id) :page-id page-id :objects objects - ;;:modifiers modifiers + :modifiers modifiers :edition edition}]]]] (when show-comments? diff --git a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs index a26d7c6abf..c7b4032862 100644 --- a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.viewport.frame-grid (:require [app.common.data :as d] - [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] @@ -138,4 +137,4 @@ (or (empty? focus) (contains? focus (:id frame)))) [:& grid-display-frame {:key (str "grid-" (:id frame)) :zoom zoom - :frame (gsh/transform-shape frame)}]))])) + :frame frame}]))])) From bc890a0b33bb1bbb58ba25ee3a9cfc81ff1881e3 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 4 Oct 2022 18:12:08 +0200 Subject: [PATCH 198/682] :sparkles: Refactor frames --- common/src/app/common/geom/shapes.cljc | 3 + .../app/common/geom/shapes/constraints.cljc | 16 +- common/src/app/common/geom/shapes/layout.cljc | 7 +- .../app/common/geom/shapes/layout_new.cljc | 606 ++++++++++++++++++ .../src/app/common/geom/shapes/modifiers.cljc | 115 ++-- .../app/common/geom/shapes/transforms.cljc | 16 + .../app/main/ui/workspace/shapes/frame.cljs | 5 +- 7 files changed, 716 insertions(+), 52 deletions(-) create mode 100644 common/src/app/common/geom/shapes/layout_new.cljc diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index a217d5322d..5364b9daad 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -177,6 +177,8 @@ (dm/export gtr/move-position-data) (dm/export gtr/apply-transform) (dm/export gtr/apply-objects-modifiers) +(dm/export gtr/parent-coords-rect) +(dm/export gtr/parent-coords-points) ;; Constratins (dm/export gct/calc-child-modifiers) @@ -210,3 +212,4 @@ ;; Modifiers (dm/export gsm/set-objects-modifiers) + diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 49db8a69dd..0c898348f6 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -261,18 +261,8 @@ "Before aplying constraints we need to remove the deformation caused by the resizing of the parent" [constraints-h constraints-v modifiers child parent transformed-child transformed-parent] - (let [child-bb-before - (-> child - :points - (gco/transform-points (:transform-inverse parent)) - (gre/points->rect)) - - child-bb-after - (-> transformed-child - :points - (gco/transform-points (:transform-inverse transformed-parent)) - (gre/points->rect)) - + (let [child-bb-before (gst/parent-coords-rect child parent) + child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) scale-x (/ (:width child-bb-before) (:width child-bb-after)) scale-y (/ (:height child-bb-before) (:height child-bb-after))] @@ -332,3 +322,5 @@ transformed-parent)] (update modifiers :v2 d/concat-vec modifiers-h modifiers-v))))) + + diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index 658b09fc78..73c08864a6 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -120,7 +120,7 @@ (cond-> layout-lines (some? line-data) (conj line-data)))) (defn calc-layout-lines-position - [{:keys [layout-gap layout-type] :as shape} {:keys [x y width height]} layout-lines] + [{:keys [layout-gap layout-type] :as shape} {:keys [x y width height] :as layout-bounds} layout-lines] (letfn [(get-base-line [total-width total-height] @@ -414,8 +414,9 @@ (defn calc-layout-modifiers "Calculates the modifiers for the layout" - [parent transform child layout-data] - (let [child-bounds (-> child :points gre/points->selrect) + [parent child layout-data] + (let [transform (:transform parent) + child-bounds (-> child :points gre/points->selrect) fill-width (calc-fill-width-data child-bounds parent child layout-data) fill-height (calc-fill-height-data child-bounds parent child layout-data) diff --git a/common/src/app/common/geom/shapes/layout_new.cljc b/common/src/app/common/geom/shapes/layout_new.cljc new file mode 100644 index 0000000000..a15d038702 --- /dev/null +++ b/common/src/app/common/geom/shapes/layout_new.cljc @@ -0,0 +1,606 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) UXBOX Labs SL + +(ns app.common.geom.shapes.layout-new + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes.rect :as gre] + [app.common.geom.shapes.transforms :as gst] + )) + +;; :layout ;; true if active, false if not +;; :layout-dir ;; :right, :left, :top, :bottom +;; :layout-gap ;; number could be negative +;; :layout-type ;; :packed, :space-between, :space-around +;; :layout-wrap-type ;; :wrap, :no-wrap +;; :layout-padding-type ;; :simple, :multiple +;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative +;; :layout-h-orientation ;; :top, :center, :bottom +;; :layout-v-orientation ;; :left, :center, :right + +(defn col? + [{:keys [layout-dir]}] + (or (= :right layout-dir) (= :left layout-dir))) + +(defn row? + [{:keys [layout-dir]}] + (or (= :top layout-dir) (= :bottom layout-dir))) + +(defn h-start? + [{:keys [layout-h-orientation]}] + (= layout-h-orientation :left)) + +(defn h-center? + [{:keys [layout-h-orientation]}] + (= layout-h-orientation :center)) + +(defn h-end? + [{:keys [layout-h-orientation]}] + (= layout-h-orientation :right)) + +(defn v-start? + [{:keys [layout-v-orientation]}] + (= layout-v-orientation :top)) + +(defn v-center? + [{:keys [layout-v-orientation]}] + (= layout-v-orientation :center)) + +(defn v-end? + [{:keys [layout-v-orientation]}] + (= layout-v-orientation :bottom)) + +(defn add-padding [transformed-rect {:keys [layout-padding-type layout-padding]}] + (let [{:keys [p1 p2 p3 p4]} layout-padding + [p1 p2 p3 p4] + (if (= layout-padding-type :multiple) + [p1 p2 p3 p4] + [p1 p1 p1 p1])] + + (-> transformed-rect + (update :y + p1) + (update :width - p2 p3) + (update :x + p3) + (update :height - p1 p4)))) + +;; FUNCTIONS TO WORK WITH POINTS SQUARES + +(defn origin + [points] + (nth points 0)) + +(defn start-hv + "Horizontal vector from the origin with a magnitude `val`" + [[p0 p1 p2 p3] val] + (-> (gpt/to-vec p0 p1) + (gpt/unit) + (gpt/scale val))) + +(defn end-hv + "Horizontal vector from the oposite to the origin in the x axis with a magnitude `val`" + [[p0 p1 p2 p3] val] + (-> (gpt/to-vec p1 p0) + (gpt/unit) + (gpt/scale val))) + +(defn start-vv + "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" + [[p0 p1 p2 p3] val] + (-> (gpt/to-vec p0 p3) + (gpt/unit) + (gpt/scale val))) + +(defn end-vv + "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" + [[p0 p1 p2 p3] val] + (-> (gpt/to-vec p3 p0) + (gpt/unit) + (gpt/scale val))) + +;;(defn start-hp +;; [[p0 _ _ _ :as points] val] +;; (gpt/add p0 (start-hv points val))) +;; +;;(defn end-hp +;; "Horizontal Vector from the oposite to the origin in the x axis with a magnitude `val`" +;; [[_ p1 _ _ :as points] val] +;; (gpt/add p1 (end-hv points val))) +;; +;;(defn start-vp +;; "Vertical Vector from the oposite to the origin in the x axis with a magnitude `val`" +;; [[p0 _ _ _ :as points] val] +;; (gpt/add p0 (start-vv points val))) +;; +;;(defn end-vp +;; "Vertical Vector from the oposite to the origin in the x axis with a magnitude `val`" +;; [[_ _ p3 _ :as points] val] +;; (gpt/add p3 (end-vv points val))) + +(defn width-points + [[p0 p1 p2 p3]] + (gpt/length (gpt/to-vec p0 p1))) + +(defn height-points + [[p0 p1 p2 p3]] + (gpt/length (gpt/to-vec p0 p3))) + +(defn pad-points + [[p0 p1 p2 p3 :as points] pad-top pad-right pad-bottom pad-left] + (let [top-v (start-vv points pad-top) + right-v (end-hv points pad-right) + bottom-v (end-vv points pad-bottom) + left-v (start-hv points pad-left)] + + [(-> p0 (gpt/add left-v) (gpt/add top-v)) + (-> p1 (gpt/add right-v) (gpt/add top-v)) + (-> p2 (gpt/add right-v) (gpt/add bottom-v)) + (-> p3 (gpt/add left-v) (gpt/add bottom-v))])) + +;;;; + + +(defn calc-layout-lines + [{:keys [layout-gap layout-wrap-type] :as parent} children layout-bounds] + + (let [wrap? (= layout-wrap-type :wrap) + layout-width (width-points layout-bounds) + layout-height (height-points layout-bounds) + + reduce-fn + (fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child] + (let [child-bounds (gst/parent-coords-points child parent) + child-width (width-points child-bounds) + child-height (height-points child-bounds) + + col? (col? parent) + row? (row? parent) + + cur-child-fill? + (or (and col? (= :fill (:layout-h-behavior child))) + (and row? (= :fill (:layout-v-behavior child)))) + + cur-line-fill? + (or (and row? (= :fill (:layout-h-behavior child))) + (and col? (= :fill (:layout-v-behavior child)))) + + ;; TODO LAYOUT: ADD MINWIDTH/HEIGHT + next-width (if (or (and col? cur-child-fill?) + (and row? cur-line-fill?)) + 0 + child-width) + + next-height (if (or (and row? cur-child-fill?) + (and col? cur-line-fill?)) + 0 + child-height) + + next-total-width (+ line-width next-width (* layout-gap num-children)) + next-total-height (+ line-height next-height (* layout-gap num-children))] + + (if (and (some? line-data) + (or (not wrap?) + (and col? (<= next-total-width layout-width)) + (and row? (<= next-total-height layout-height)))) + + ;; When :fill we add min width (0 by default) + [{:line-width (if col? (+ line-width next-width) (max line-width next-width)) + :line-height (if row? (+ line-height next-height) (max line-height next-height)) + :num-children (inc num-children) + :child-fill? (or cur-child-fill? child-fill?) + :line-fill? (or cur-line-fill? line-fill?) + :num-child-fill (cond-> num-child-fill cur-child-fill? inc)} + result] + + [{:line-width next-width + :line-height next-height + :num-children 1 + :child-fill? cur-child-fill? + :line-fill? cur-line-fill? + :num-child-fill (if cur-child-fill? 1 0)} + (cond-> result (some? line-data) (conj line-data))]))) + + [line-data layout-lines] (reduce reduce-fn [nil []] children)] + + (cond-> layout-lines (some? line-data) (conj line-data)))) + +(defn calc-layout-lines-position + [{:keys [layout-gap layout-type] :as parent} layout-bounds layout-lines] + + (let [layout-width (width-points layout-bounds) + layout-height (height-points layout-bounds) + row? (row? parent) + col? (col? parent) + h-center? (h-center? parent) + h-end? (h-end? parent) + v-center? (v-center? parent) + v-end? (v-end? parent) + space-between? (= :space-between layout-type) + space-around? (= :space-around layout-type)] + + (letfn [(get-base-line + [total-width total-height] + + (cond-> (origin layout-bounds) + (and row? h-center?) + (gpt/add (start-hv layout-bounds (/ (- layout-width total-width) 2))) + + (and row? h-end?) + (gpt/add (start-hv layout-bounds (- layout-width total-width))) + + (and col? v-center?) + (gpt/add (start-vv layout-bounds (/ (- layout-height total-height) 2))) + + (and col? v-end?) + (gpt/add (start-vv layout-bounds (- layout-height total-height))) + )) + + (get-start-line + [{:keys [line-width line-height num-children child-fill?]} base-p] + + (let [children-gap (* layout-gap (dec num-children)) + + ;;line-width (if (and col? child-fill?) + ;; (- layout-width (* layout-gap num-children)) + ;; line-width) + ;; + ;;line-height (if (and row? child-fill?) + ;; (- layout-height (* layout-gap num-children)) + ;; line-height) + + + start-p + (cond-> base-p + ;; X AXIS + (and col? h-center? (not space-between?) (not space-around?)) + (-> (gpt/add (start-hv layout-bounds (/ layout-width 2))) + (gpt/subtract (start-hv layout-bounds (/ (+ line-width children-gap) 2)))) + + (and col? h-end? (not space-between?) (not space-around?)) + (-> (gpt/add (start-hv layout-bounds layout-width)) + (gpt/subtract (start-hv layout-bounds (+ line-width children-gap)))) + + (and row? h-center? (not space-between?) (not space-around?)) + (gpt/add (start-hv layout-bounds (/ line-width 2))) + + (and row? h-end? (not space-between?) (not space-around?)) + (gpt/add (start-hv layout-bounds line-width)) + + ;; Y AXIS + (and row? v-center? (not space-between?) (not space-around?)) + (-> (gpt/add (start-vv layout-bounds (/ layout-height 2))) + (gpt/subtract (start-vv layout-bounds (/ (+ line-height children-gap) 2)))) + + (and row? v-end? (not space-between?) (not space-around?)) + (-> (gpt/add (start-vv layout-bounds layout-height)) + (gpt/subtract (start-vv layout-bounds (+ line-height children-gap)))) + + (and col? v-center? (not space-between?) (not space-around?)) + (gpt/add (start-vv layout-bounds (/ line-height 2))) + + (and col? v-end? (not space-between?) (not space-around?)) + (gpt/add (start-vv layout-bounds line-height)) + + ) + + + ;;start-x + ;;(cond + ;; ;;(and (col? shape) child-fill?) + ;; ;; TODO LAYOUT: Start has to take into account max-width + ;; ;;x + ;; + ;; (or (and col? space-between?) (and col? space-around?)) + ;; x + ;; + ;; (and col? h-center?) + ;; (- (+ x (/ width 2)) (/ (+ line-width children-gap) 2)) + ;; + ;; (and col? h-end?) + ;; (- (+ x width) (+ line-width children-gap)) + ;; + ;; (and row? h-center?) + ;; (+ base-x (/ line-width 2)) + ;; + ;; (and row? h-end?) + ;; (+ base-x line-width) + ;; + ;; row? + ;; base-x + ;; + ;; :else + ;; x) + + ;;start-y + ;;(cond + ;; ;; (and (row? shape) child-fill?) + ;; ;; TODO LAYOUT: Start has to take into account max-width + ;; ;; y + ;; + ;; (or (and (row? shape) (= :space-between layout-type)) + ;; (and (row? shape) (= :space-around layout-type))) + ;; y + ;; + ;; (and (row? shape) (v-center? shape)) + ;; (- (+ y (/ height 2)) (/ (+ line-height children-gap) 2)) + ;; + ;; (and (row? shape) (v-end? shape)) + ;; (- (+ y height) (+ line-height children-gap)) + ;; + ;; (and (col? shape) (v-center? shape)) + ;; (+ base-y (/ line-height 2)) + ;; + ;; (and (col? shape) (v-end? shape)) + ;; (+ base-y line-height) + ;; + ;; (col? shape) + ;; base-y + ;; + ;; :else + ;; y) + ] + + start-p)) + + (get-next-line + [{:keys [line-width line-height]} base-p] + + (cond-> base-p + col? + (gpt/add (start-hv layout-bounds (+ line-width layout-gap))) + + row? + (gpt/add (start-vv layout-bounds (+ line-height layout-gap))) + ) + + #_(let [next-x (if col? base-x (+ base-x line-width layout-gap)) + next-y (if row? base-y (+ base-y line-height layout-gap))] + [next-x next-y])) + + (add-lines [[total-width total-height] {:keys [line-width line-height]}] + [(+ total-width line-width) + (+ total-height line-height)]) + + (add-starts [[result base-p] layout-line] + (let [start-p (get-start-line layout-line base-p) + next-p (get-next-line layout-line base-p)] + [(conj result + (assoc layout-line :start-p start-p)) + next-p]))] + + (let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0])) + + total-width (+ total-width (* layout-gap (dec (count layout-lines)))) + total-height (+ total-height (* layout-gap (dec (count layout-lines)))) + + vertical-fill-space (- layout-height total-height) + horizontal-fill-space (- layout-width total-width) + num-line-fill (count (->> layout-lines (filter :line-fill?))) + + layout-lines + (->> layout-lines + (mapv #(cond-> % + (and col? (:line-fill? %)) + (update :line-height + (/ vertical-fill-space num-line-fill)) + + (and row? (:line-fill? %)) + (update :line-width + (/ horizontal-fill-space num-line-fill))))) + + total-height (if (and col? (> num-line-fill 0)) layout-height total-height) + total-width (if (and row? (> num-line-fill 0)) layout-width total-width) + + base-p (get-base-line total-width total-height) + + [layout-lines _ _ _ _] + (reduce add-starts [[] base-p] layout-lines)] + layout-lines)))) + +(defn calc-layout-line-data + [{:keys [layout-type layout-gap] :as shape} + {:keys [width height] :as layout-bounds} + {:keys [num-children line-width line-height] :as line-data}] + + (let [layout-gap + (cond + (= :packed layout-type) + layout-gap + + (= :space-around layout-type) + 0 + + (and (col? shape) (= :space-between layout-type)) + (/ (- width line-width) (dec num-children)) + + (and (row? shape) (= :space-between layout-type)) + (/ (- height line-height) (dec num-children))) + + margin-x + (if (and (col? shape) (= :space-around layout-type)) + (/ (- width line-width) (inc num-children) ) + 0) + + margin-y + (if (and (row? shape) (= :space-around layout-type)) + (/ (- height line-height) (inc num-children)) + 0)] + + (assoc line-data + :layout-bounds layout-bounds + :layout-gap layout-gap + :margin-x margin-x + :margin-y margin-y))) + +(defn next-p + "Calculates the position for the current shape given the layout-data context" + [parent + child-bounds + {:keys [start-p layout-gap margin-x margin-y] :as layout-data}] + + (let [width (width-points child-bounds) + height (height-points child-bounds) + + row? (row? parent) + col? (col? parent) + h-center? (h-center? parent) + h-end? (h-end? parent) + v-center? (v-center? parent) + v-end? (v-end? parent) + points (:points parent) + + corner-p + (cond-> start-p + (and row? h-center?) + (gpt/add (start-hv points (- (/ width 2)))) + + (and row? h-end?) + (gpt/add (start-hv points (- width))) + + (and col? v-center?) + (gpt/add (start-vv points (- (/ height 2)))) + + (and col? v-end?) + (gpt/add (start-vv points (- height))) + + (some? margin-x) + (gpt/add (start-hv points margin-x)) + + (some? margin-y) + (gpt/add (start-vv points margin-y))) + + next-p + (cond-> start-p + col? + (gpt/add (start-hv points (+ width layout-gap))) + + row? + (gpt/add (start-vv points (+ height layout-gap))) + + (some? margin-x) + (gpt/add (start-hv points margin-x)) + + (some? margin-y) + (gpt/add (start-vv points margin-y))) + + layout-data + (assoc layout-data :start-p next-p)] + + [corner-p layout-data])) + +(defn calc-fill-width-data + [child-bounds + {:keys [layout-gap] :as parent} + {:keys [layout-h-behavior] :as child} + {:keys [num-children line-width layout-bounds line-fill? child-fill?] :as layout-data}] + + (cond + (and (col? parent) (= :fill layout-h-behavior) child-fill?) + (let [fill-space (- (:width layout-bounds) line-width (* layout-gap num-children)) + fill-width (/ fill-space (:num-child-fill layout-data)) + fill-scale (/ fill-width (:width child-bounds))] + {:bounds {:width fill-width} + :modifiers [{:type :resize + :origin (gpt/point child-bounds) + :vector (gpt/point fill-scale 1)}]}) + + (and (row? parent) (= :fill layout-h-behavior) line-fill?) + (let [fill-scale (/ line-width (:width child-bounds))] + {:bounds {:width line-width} + :modifiers [{:type :resize + :origin (gpt/point child-bounds) + :vector (gpt/point fill-scale 1)}]}) + )) + +(defn calc-fill-height-data + [child-bounds + {:keys [layout-gap] :as parent} + {:keys [layout-v-behavior] :as child} + {:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}] + + (cond + (and (row? parent) (= :fill layout-v-behavior) child-fill?) + (let [fill-space (- (:height layout-bounds) line-height (* layout-gap num-children)) + fill-height (/ fill-space (:num-child-fill layout-data)) + fill-scale (/ fill-height (:height child-bounds))] + {:bounds {:height fill-height} + :modifiers [{:type :resize + :origin (gpt/point child-bounds) + :vector (gpt/point 1 fill-scale)}]}) + + (and (col? parent) (= :fill layout-v-behavior) line-fill?) + (let [fill-scale (/ line-height (:height child-bounds))] + {:bounds {:height line-height} + :modifiers [{:type :resize + :origin (gpt/point child-bounds) + :vector (gpt/point 1 fill-scale)}]}) + )) + +(defn normalize-child-modifiers + "Apply the modifiers and then normalized them against the parent coordinates" + [parent child modifiers transformed-parent] + + (let [transformed-child (gst/transform-shape child modifiers) + child-bb-before (gst/parent-coords-rect child parent) + child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) + scale-x (/ (:width child-bb-before) (:width child-bb-after)) + scale-y (/ (:height child-bb-before) (:height child-bb-after))] + (-> modifiers + (update :v2 #(conj % + {:type :resize + :transform (:transform transformed-parent) + :transform-inverse (:transform-inverse transformed-parent) + :origin (-> transformed-parent :points (nth 0)) + :vector (gpt/point scale-x scale-y)}))))) + +(defn calc-layout-data + "Digest the layout data to pass it to the constrains" + [{:keys [layout-dir points layout-padding layout-padding-type] :as parent} children] + + (let [;; Add padding to the bounds + {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding + [pad-top pad-right pad-bottom pad-left] + (if (= layout-padding-type :multiple) + [pad-top pad-right pad-bottom pad-left] + [pad-top pad-top pad-top pad-top]) + layout-bounds (-> points (pad-points pad-top pad-right pad-bottom pad-left)) + + ;; Reverse + reverse? (or (= :left layout-dir) (= :bottom layout-dir)) + children (cond->> children reverse? reverse) + + ;; Creates the layout lines information + layout-lines + (->> (calc-layout-lines parent children layout-bounds) + (calc-layout-lines-position parent layout-bounds) + (map (partial calc-layout-line-data parent layout-bounds)))] + + {:layout-lines layout-lines + :reverse? reverse?})) + +(defn calc-layout-modifiers + "Calculates the modifiers for the layout" + [parent child layout-line] + (let [child-bounds (gst/parent-coords-points child parent) + + ;;fill-width (calc-fill-width-data child-bounds parent child layout-line) + ;;fill-height (calc-fill-height-data child-bounds parent child layout-line) + + ;;child-bounds (cond-> child-bounds + ;; fill-width (merge (:bounds fill-width)) + ;; fill-height (merge (:bounds fill-height))) + + + [corner-p layout-line] (next-p parent child-bounds layout-line) + + move-vec (gpt/to-vec (origin child-bounds) corner-p) + + modifiers + (-> [] + #_(cond-> fill-width (d/concat-vec (:modifiers fill-width))) + #_(cond-> fill-height (d/concat-vec (:modifiers fill-height))) + (conj {:type :move :vector move-vec}))] + + [modifiers layout-line])) + diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 4645a6582e..fc987dfc9a 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -11,7 +11,8 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.constraints :as gct] - [app.common.geom.shapes.layout :as gcl] + [app.common.geom.shapes.layout :as gclo] + [app.common.geom.shapes.layout-new :as gcln] [app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth] @@ -108,40 +109,21 @@ (defn set-children-modifiers - [modif-tree objects shape ignore-constraints snap-pixel?] + [modif-tree objects parent ignore-constraints snap-pixel?] ;; TODO LAYOUT: SNAP PIXEL! (letfn [(set-child [transformed-parent _snap-pixel? modif-tree child] - (let [modifiers (get-in modif-tree [(:id shape) :modifiers]) - - child-modifiers (gct/calc-child-modifiers shape child modifiers ignore-constraints transformed-parent) - - ;;_ (.log js/console (:name child) (clj->js child-modifiers)) - + (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) + child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints transformed-parent) ;;child-modifiers (cond-> child-modifiers snap-pixel? (set-pixel-precision child)) - - result - (cond-> modif-tree - (not (ctm/empty-modifiers? child-modifiers)) - (update-in [(:id child) :modifiers :v2] #(d/concat-vec % (:v2 child-modifiers))) - #_(update-in [(:id child) :modifiers] #(merge-mod2 child-modifiers %)) - #_(update-in [(:id child) :modifiers] #(merge child-modifiers %))) - - ;;_ (.log js/console ">>>" (:name child)) - ;;_ (.log js/console " >" (clj->js child-modifiers)) - ;;_ (.log js/console " >" (clj->js (get-in modif-tree [(:id child) :modifiers]))) - ;;_ (.log js/console " >" (clj->js (get-in result [(:id child) :modifiers]))) ] - result - )) - ] - (let [children (map (d/getf objects) (:shapes shape)) - modifiers (get-in modif-tree [(:id shape) :modifiers]) - ;; transformed-rect (gtr/transform-selrect (:selrect shape) modifiers) - ;; transformed-rect (-> shape (merge {:modifiers modifiers}) gtr/transform-shape :selrect) - transformed-parent (-> shape (merge {:modifiers modifiers}) gtr/transform-shape) + (cond-> modif-tree + (not (ctm/empty-modifiers? child-modifiers)) + (update-in [(:id child) :modifiers :v2] d/concat-vec (:v2 child-modifiers)))))] - resize-modif? (or (:resize-vector modifiers) (:resize-vector-2 modifiers))] - (reduce (partial set-child transformed-parent (and snap-pixel? resize-modif?)) modif-tree children)))) + (let [children (map (d/getf objects) (:shapes parent)) + modifiers (get-in modif-tree [(:id parent) :modifiers]) + transformed-parent (gtr/transform-shape parent modifiers)] + (reduce (partial set-child transformed-parent snap-pixel?) modif-tree children)))) (defn group? [shape] (or (= :group (:type shape)) @@ -158,6 +140,68 @@ ;; TODO LAYOUT: SNAP PIXEL! [modif-tree objects parent _snap-pixel?] + (letfn [(normalize-child [transformed-parent _snap-pixel? modif-tree child] + (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) + child-modifiers (gcln/normalize-child-modifiers parent child modifiers transformed-parent)] + (cond-> modif-tree + (not (ctm/empty-modifiers? child-modifiers)) + (update-in [(:id child) :modifiers :v2] d/concat-vec (:v2 child-modifiers))))) + + (apply-modifiers [modif-tree child] + (let [modifiers (get-in modif-tree [(:id child) :modifiers])] + (cond-> child + (some? modifiers) + (gtr/transform-shape modifiers) + + (and (nil? modifiers) (group? child)) + (gtr/apply-group-modifiers objects modif-tree)))) + + (set-layout-modifiers [parent [layout-line modif-tree] child] + (let [[modifiers layout-line] + (gcln/calc-layout-modifiers parent child layout-line) + + modif-tree + (cond-> modif-tree + (d/not-empty? modifiers) + (update-in [(:id child) :modifiers :v2] d/concat-vec modifiers))] + + [layout-line modif-tree]))] + + (let [children (map (d/getf objects) (:shapes parent)) + modifiers (get-in modif-tree [(:id parent) :modifiers]) + transformed-parent (gtr/transform-shape parent modifiers) + + modif-tree (reduce (partial normalize-child transformed-parent _snap-pixel?) modif-tree children) + + children (->> children (map (partial apply-modifiers modif-tree))) + + layout-data (gcln/calc-layout-data transformed-parent children) + + children (into [] (cond-> children (:reverse? layout-data) reverse)) + + max-idx (dec (count children)) + layout-lines (:layout-lines layout-data)] + + (loop [modif-tree modif-tree + layout-line (first layout-lines) + pending (rest layout-lines) + from-idx 0] + + + (if (and (some? layout-line) (<= from-idx max-idx)) + (let [to-idx (+ from-idx (:num-children layout-line)) + children (subvec children from-idx to-idx) + + [_ modif-tree] + (reduce (partial set-layout-modifiers transformed-parent) [layout-line modif-tree] children)] + (recur modif-tree (first pending) (rest pending) to-idx)) + + modif-tree))))) + +#_(defn set-layout-modifiers' + ;; TODO LAYOUT: SNAP PIXEL! + [modif-tree objects parent _snap-pixel?] + (letfn [(transform-child [child] (let [modifiers (get modif-tree (:id child)) @@ -182,8 +226,7 @@ modif-tree (cond-> modif-tree (d/not-empty? modifiers) - (update-in [(:id child) :modifiers :v2] d/concat-vec modifiers) - #_(merge-modifiers [(:id child)] modifiers))] + (update-in [(:id child) :modifiers :v2] d/concat-vec modifiers))] [layout-data modif-tree]))] @@ -218,7 +261,6 @@ [_ modif-tree] (reduce (partial set-layout-modifiers shape transform) [layout-line modif-tree] children)] - (recur modif-tree (first pending) (rest pending) to-idx)) modif-tree))))) @@ -316,11 +358,12 @@ is-inside-layout? (inside-layout? objects shape)] (cond-> modif-tree + (and has-modifiers? is-parent?) + (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?) + is-layout? (set-layout-modifiers objects shape snap-pixel?) - - (and has-modifiers? is-parent?) - (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?)))) + ))) modif-tree))] diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 5f1d160b65..6c19575192 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -558,3 +558,19 @@ (apply-group-modifiers shape objects modif-tree) shape)))))] (update-group-selrect group children))) + +(defn parent-coords-rect + [child parent] + (-> child + :points + (gco/transform-points (:transform-inverse parent)) + (gpr/points->rect))) + +(defn parent-coords-points + [child parent] + (-> child + :points + (gco/transform-points (:transform-inverse parent)) + (gpr/points->rect) + (gpr/rect->points) + (gco/transform-points (:transform parent)))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 1de768035e..07b57d1564 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -24,7 +24,10 @@ [app.main.ui.workspace.shapes.frame.node-store :as fns] [app.main.ui.workspace.shapes.frame.thumbnail-render :as ftr] [beicon.core :as rx] - [rumext.v2 :as mf])) + [rumext.v2 :as mf] + + [app.common.geom.shapes.layout :as gsl] + [app.common.geom.point :as gpt])) (defn frame-shape-factory [shape-wrapper] From 8bcb9e19762363a33b46b1e1798cb6391905f375 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 5 Oct 2022 22:16:48 +0200 Subject: [PATCH 199/682] :sparkles: Autofill vectors calculation --- common/src/app/common/geom/shapes/layout.cljc | 565 +++++++++------- .../app/common/geom/shapes/layout_new.cljc | 606 ------------------ .../src/app/common/geom/shapes/modifiers.cljc | 27 +- .../app/main/ui/workspace/shapes/frame.cljs | 5 +- 4 files changed, 363 insertions(+), 840 deletions(-) delete mode 100644 common/src/app/common/geom/shapes/layout_new.cljc diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index 73c08864a6..d0c616e85b 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -8,10 +8,10 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.common.geom.shapes.rect :as gre])) + [app.common.geom.shapes.transforms :as gst])) ;; :layout ;; true if active, false if not -;; :layout-flex-dir ;; :row, :column, :reverse-row, :reverse-column +;; :layout-dir ;; :right, :left, :top, :bottom ;; :layout-gap ;; number could be negative ;; :layout-type ;; :packed, :space-between, :space-around ;; :layout-wrap-type ;; :wrap, :no-wrap @@ -21,12 +21,12 @@ ;; :layout-v-orientation ;; :left, :center, :right (defn col? - [{:keys [layout-flex-dir]}] - (or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir))) + [{:keys [layout-dir]}] + (or (= :right layout-dir) (= :left layout-dir))) (defn row? - [{:keys [layout-flex-dir]}] - (or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir))) + [{:keys [layout-dir]}] + (or (= :top layout-dir) (= :bottom layout-dir))) (defn h-start? [{:keys [layout-h-orientation]}] @@ -65,42 +65,128 @@ (update :x + p3) (update :height - p1 p4)))) +;; FUNCTIONS TO WORK WITH POINTS SQUARES + +(defn origin + [points] + (nth points 0)) + +(defn start-hv + "Horizontal vector from the origin with a magnitude `val`" + [[p0 p1 _ _] val] + (-> (gpt/to-vec p0 p1) + (gpt/unit) + (gpt/scale val))) + +(defn end-hv + "Horizontal vector from the oposite to the origin in the x axis with a magnitude `val`" + [[p0 p1 _ _] val] + (-> (gpt/to-vec p1 p0) + (gpt/unit) + (gpt/scale val))) + +(defn start-vv + "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" + [[p0 _ _ p3] val] + (-> (gpt/to-vec p0 p3) + (gpt/unit) + (gpt/scale val))) + +(defn end-vv + "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" + [[p0 _ _ p3] val] + (-> (gpt/to-vec p3 p0) + (gpt/unit) + (gpt/scale val))) + +;;(defn start-hp +;; [[p0 _ _ _ :as points] val] +;; (gpt/add p0 (start-hv points val))) +;; +;;(defn end-hp +;; "Horizontal Vector from the oposite to the origin in the x axis with a magnitude `val`" +;; [[_ p1 _ _ :as points] val] +;; (gpt/add p1 (end-hv points val))) +;; +;;(defn start-vp +;; "Vertical Vector from the oposite to the origin in the x axis with a magnitude `val`" +;; [[p0 _ _ _ :as points] val] +;; (gpt/add p0 (start-vv points val))) +;; +;;(defn end-vp +;; "Vertical Vector from the oposite to the origin in the x axis with a magnitude `val`" +;; [[_ _ p3 _ :as points] val] +;; (gpt/add p3 (end-vv points val))) + +(defn width-points + [[p0 p1 _ _]] + (gpt/length (gpt/to-vec p0 p1))) + +(defn height-points + [[p0 _ _ p3]] + (gpt/length (gpt/to-vec p0 p3))) + +(defn pad-points + [[p0 p1 p2 p3 :as points] pad-top pad-right pad-bottom pad-left] + (let [top-v (start-vv points pad-top) + right-v (end-hv points pad-right) + bottom-v (end-vv points pad-bottom) + left-v (start-hv points pad-left)] + + [(-> p0 (gpt/add left-v) (gpt/add top-v)) + (-> p1 (gpt/add right-v) (gpt/add top-v)) + (-> p2 (gpt/add right-v) (gpt/add bottom-v)) + (-> p3 (gpt/add left-v) (gpt/add bottom-v))])) + +;;;; + + (defn calc-layout-lines - [{:keys [layout-gap layout-wrap-type] :as shape} children {:keys [width height] :as layout-bounds}] + [{:keys [layout-gap layout-wrap-type] :as parent} children layout-bounds] (let [wrap? (= layout-wrap-type :wrap) + layout-width (width-points layout-bounds) + layout-height (height-points layout-bounds) reduce-fn (fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child] - (let [child-bounds (-> child :points gre/points->rect) + (let [child-bounds (gst/parent-coords-points child parent) + child-width (width-points child-bounds) + child-height (height-points child-bounds) + + col? (col? parent) + row? (row? parent) cur-child-fill? - (or (and (col? shape) (= :fill (:layout-h-behavior child))) - (and (row? shape) (= :fill (:layout-v-behavior child)))) + (or (and col? (= :fill (:layout-h-behavior child))) + (and row? (= :fill (:layout-v-behavior child)))) cur-line-fill? - (or (and (row? shape) (= :fill (:layout-h-behavior child))) - (and (col? shape) (= :fill (:layout-v-behavior child)))) + (or (and row? (= :fill (:layout-h-behavior child))) + (and col? (= :fill (:layout-v-behavior child)))) ;; TODO LAYOUT: ADD MINWIDTH/HEIGHT - next-width (if (or (and (col? shape) cur-child-fill?) - (and (row? shape) cur-line-fill?)) + next-width (if (or (and col? cur-child-fill?) + (and row? cur-line-fill?)) 0 - (-> child-bounds :width)) + child-width) - next-height (if (or (and (row? shape) cur-child-fill?) - (and (col? shape) cur-line-fill?)) + next-height (if (or (and row? cur-child-fill?) + (and col? cur-line-fill?)) 0 - (-> child-bounds :height))] + child-height) + + next-total-width (+ line-width next-width (* layout-gap (dec num-children))) + next-total-height (+ line-height next-height (* layout-gap (dec num-children)))] (if (and (some? line-data) (or (not wrap?) - (and (col? shape) (<= (+ line-width next-width (* layout-gap num-children)) width)) - (and (row? shape) (<= (+ line-height next-height (* layout-gap num-children)) height)))) + (and col? (<= next-total-width layout-width)) + (and row? (<= next-total-height layout-height)))) ;; When :fill we add min width (0 by default) - [{:line-width (if (col? shape) (+ line-width next-width) (max line-width next-width)) - :line-height (if (row? shape) (+ line-height next-height) (max line-height next-height)) + [{:line-width (if col? (+ line-width next-width) (max line-width next-width)) + :line-height (if row? (+ line-height next-height) (max line-height next-height)) :num-children (inc num-children) :child-fill? (or cur-child-fill? child-fill?) :line-fill? (or cur-line-fill? line-fill?) @@ -120,155 +206,148 @@ (cond-> layout-lines (some? line-data) (conj line-data)))) (defn calc-layout-lines-position - [{:keys [layout-gap layout-type] :as shape} {:keys [x y width height] :as layout-bounds} layout-lines] + [{:keys [layout-gap] :as parent} layout-bounds layout-lines] - (letfn [(get-base-line - [total-width total-height] + (let [layout-width (width-points layout-bounds) + layout-height (height-points layout-bounds) + row? (row? parent) + col? (col? parent) + h-center? (h-center? parent) + h-end? (h-end? parent) + v-center? (v-center? parent) + v-end? (v-end? parent)] - (let [base-x - (cond - (and (row? shape) (h-center? shape)) - (+ x (/ (- width total-width) 2)) + (letfn [;; short version to not repeat always with all arguments + (xv [val] + (start-hv layout-bounds val)) - (and (row? shape) (h-end? shape)) - (+ x width (- total-width)) + ;; short version to not repeat always with all arguments + (yv [val] + (start-vv layout-bounds val)) - :else x) + (get-base-line + [total-width total-height] - base-y - (cond - (and (col? shape) (v-center? shape)) - (+ y (/ (- height total-height) 2)) + (cond-> (origin layout-bounds) + (and row? h-center?) + (gpt/add (xv (/ (- layout-width total-width) 2))) - (and (col? shape) (v-end? shape)) - (+ y height (- total-height)) + (and row? h-end?) + (gpt/add (xv (- layout-width total-width))) - :else y)] + (and col? v-center?) + (gpt/add (yv (/ (- layout-height total-height) 2))) - [base-x base-y])) + (and col? v-end?) + (gpt/add (yv (- layout-height total-height))))) - (get-start-line - [{:keys [line-width line-height num-children child-fill?]} base-x base-y] + (get-start-line + [{:keys [line-width line-height num-children child-fill?]} base-p] - (let [children-gap (* layout-gap (dec num-children)) + (let [children-gap (* layout-gap (dec num-children)) - line-width (if (and (col? shape) child-fill?) (- width (* layout-gap num-children)) line-width) - line-height (if (and (row? shape) child-fill?) (- height (* layout-gap num-children)) line-height) + line-width (if (and col? child-fill?) + (- layout-width (* layout-gap (dec num-children))) + line-width) - start-x - (cond - ;;(and (col? shape) child-fill?) - ;;;; TODO LAYOUT: Start has to take into account max-width - ;;x + line-height (if (and row? child-fill?) + (- layout-height (* layout-gap (dec num-children))) + line-height) - (or (and (col? shape) (= :space-between layout-type)) - (and (col? shape) (= :space-around layout-type))) - x + start-p + (cond-> base-p + ;; X AXIS + (and col? h-center?) + (-> (gpt/add (xv (/ layout-width 2))) + (gpt/subtract (xv (/ (+ line-width children-gap) 2)))) - (and (col? shape) (h-center? shape)) - (- (+ x (/ width 2)) (/ (+ line-width children-gap) 2)) + (and col? h-end?) + (-> (gpt/add (xv layout-width)) + (gpt/subtract (xv (+ line-width children-gap)))) - (and (col? shape) (h-end? shape)) - (- (+ x width) (+ line-width children-gap)) + (and row? h-center?) + (gpt/add (xv (/ line-width 2))) - (and (row? shape) (h-center? shape)) - (+ base-x (/ line-width 2)) + (and row? h-end?) + (gpt/add (xv line-width)) - (and (row? shape) (h-end? shape)) - (+ base-x line-width) + ;; Y AXIS + (and row? v-center?) + (-> (gpt/add (yv (/ layout-height 2))) + (gpt/subtract (yv (/ (+ line-height children-gap) 2)))) - (row? shape) - base-x + (and row? v-end?) + (-> (gpt/add (yv layout-height)) + (gpt/subtract (yv (+ line-height children-gap)))) - :else - x) + (and col? v-center?) + (gpt/add (yv (/ line-height 2))) - start-y - (cond - ;;(and (row? shape) child-fill?) - ;;;; TODO LAYOUT: Start has to take into account max-width - ;;y - - (or (and (row? shape) (= :space-between layout-type)) - (and (row? shape) (= :space-around layout-type))) - y + (and col? v-end?) + (gpt/add (yv line-height)))] - (and (row? shape) (v-center? shape)) - (- (+ y (/ height 2)) (/ (+ line-height children-gap) 2)) + start-p)) - (and (row? shape) (v-end? shape)) - (- (+ y height) (+ line-height children-gap)) + (get-next-line + [{:keys [line-width line-height]} base-p] - (and (col? shape) (v-center? shape)) - (+ base-y (/ line-height 2)) + (cond-> base-p + row? + (gpt/add (xv (+ line-width layout-gap))) - (and (col? shape) (v-end? shape)) - (+ base-y line-height) + col? + (gpt/add (yv (+ line-height layout-gap))))) - (col? shape) - base-y + (add-lines [[total-width total-height] {:keys [line-width line-height]}] + [(+ total-width line-width) + (+ total-height line-height)]) - :else - y)] + (add-starts [[result base-p] layout-line] + (let [start-p (get-start-line layout-line base-p) + next-p (get-next-line layout-line base-p)] + [(conj result + (assoc layout-line :start-p start-p)) + next-p]))] - [start-x start-y])) + (let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0])) - (get-next-line - [{:keys [line-width line-height]} base-x base-y] - (let [next-x (if (col? shape) base-x (+ base-x line-width layout-gap)) - next-y (if (row? shape) base-y (+ base-y line-height layout-gap))] - [next-x next-y])) + total-width (+ total-width (* layout-gap (dec (count layout-lines)))) + total-height (+ total-height (* layout-gap (dec (count layout-lines)))) - (add-lines [[total-width total-height] {:keys [line-width line-height]}] - [(+ total-width line-width) - (+ total-height line-height)]) + vertical-fill-space (- layout-height total-height) + horizontal-fill-space (- layout-width total-width) + num-line-fill (count (->> layout-lines (filter :line-fill?))) - (add-starts [[result base-x base-y] layout-line] - (let [[start-x start-y] (get-start-line layout-line base-x base-y) - [next-x next-y] (get-next-line layout-line base-x base-y)] - [(conj result - (assoc layout-line - :start-x start-x - :start-y start-y)) - next-x - next-y]))] + layout-lines + (->> layout-lines + (mapv #(cond-> % + (and col? (:line-fill? %)) + (update :line-height + (/ vertical-fill-space num-line-fill)) - (let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0])) + (and row? (:line-fill? %)) + (update :line-width + (/ horizontal-fill-space num-line-fill))))) - total-width (+ total-width (* layout-gap (dec (count layout-lines)))) - total-height (+ total-height (* layout-gap (dec (count layout-lines)))) + total-height (if (and col? (> num-line-fill 0)) layout-height total-height) + total-width (if (and row? (> num-line-fill 0)) layout-width total-width) - vertical-fill-space (- height total-height) - horizontal-fill-space (- width total-width) - num-line-fill (count (->> layout-lines (filter :line-fill?))) + base-p (get-base-line total-width total-height) - layout-lines - (->> layout-lines - (mapv #(cond-> % - (and (col? shape) (:line-fill? %)) - (update :line-height + (/ vertical-fill-space num-line-fill)) - - (and (row? shape) (:line-fill? %)) - (update :line-width + (/ horizontal-fill-space num-line-fill))))) - - total-height (if (and (col? shape) (> num-line-fill 0)) height total-height) - total-width (if (and (row? shape) (> num-line-fill 0)) width total-width) - - [base-x base-y] - (get-base-line total-width total-height) - - [layout-lines _ _ _ _] - (reduce add-starts [[] base-x base-y] layout-lines)] - layout-lines))) + [layout-lines _ _ _ _] + (reduce add-starts [[] base-p] layout-lines)] + layout-lines)))) (defn calc-layout-line-data + "Calculates the baseline for a flex layout" [{:keys [layout-type layout-gap] :as shape} - {:keys [width height] :as layout-bounds} - {:keys [num-children line-width line-height] :as line-data}] + layout-bounds + {:keys [num-children line-width line-height child-fill?] :as line-data}] - (let [layout-gap + (let [width (width-points layout-bounds) + height (height-points layout-bounds) + + layout-gap (cond - (= :packed layout-type) + (or (= :packed layout-type) child-fill?) layout-gap (= :space-around layout-type) @@ -282,7 +361,7 @@ margin-x (if (and (col? shape) (= :space-around layout-type)) - (/ (- width line-width) (inc num-children) ) + (/ (- width line-width) (inc num-children)) 0) margin-y @@ -296,147 +375,199 @@ :margin-x margin-x :margin-y margin-y))) - -(defn calc-layout-data - "Digest the layout data to pass it to the constrains" - [{:keys [layout-flex-dir] :as shape} children layout-bounds] - - (let [reverse? (or (= :reverse-row layout-flex-dir) (= :reverse-column layout-flex-dir)) - layout-bounds (-> layout-bounds (add-padding shape)) - children (cond->> children reverse? reverse) - layout-lines - (->> (calc-layout-lines shape children layout-bounds) - (calc-layout-lines-position shape layout-bounds) - (map (partial calc-layout-line-data shape layout-bounds)))] - - {:layout-lines layout-lines - :reverse? reverse?})) - (defn next-p "Calculates the position for the current shape given the layout-data context" - [shape - {:keys [width height]} - {:keys [start-x start-y layout-gap margin-x margin-y] :as layout-data}] + [parent + child-width child-height + {:keys [start-p layout-gap margin-x margin-y] :as layout-data}] - (let [pos-x - (cond - (and (row? shape) (h-center? shape)) - (- start-x (/ width 2)) + (let [row? (row? parent) + col? (col? parent) - (and (row? shape) (h-end? shape)) - (- start-x width) + layout-type (:layout-type parent) + space-around? (= :space-around layout-type) + space-between? (= :space-between layout-type) - :else - start-x) + stretch-h? (and row? (or space-around? space-between?)) + stretch-v? (and col? (or space-around? space-between?)) - pos-y - (cond - (and (col? shape) (v-center? shape)) - (- start-y (/ height 2)) + h-center? (and (h-center? parent) (not stretch-h?)) + h-end? (and (h-end? parent) (not stretch-h?)) + v-center? (and (v-center? parent) (not stretch-v?)) + v-end? (and (v-end? parent) (not stretch-v?)) + points (:points parent) - (and (col? shape) (v-end? shape)) - (- start-y height) + xv (partial start-hv points) + yv (partial start-vv points) - :else - start-y) + corner-p + (cond-> start-p + (and row? h-center?) + (gpt/add (xv (- (/ child-width 2)))) + (and row? h-end?) + (gpt/add (xv (- child-width))) - pos-x (cond-> pos-x (some? margin-x) (+ margin-x)) - pos-y (cond-> pos-y (some? margin-y) (+ margin-y)) + (and col? v-center? (not space-around?)) + (gpt/add (yv (- (/ child-height 2)))) - corner-p (gpt/point pos-x pos-y) + (and col? v-end? (not space-around?)) + (gpt/add (yv (- child-height))) - next-x - (if (col? shape) - (+ start-x width layout-gap) - start-x) + (some? margin-x) + (gpt/add (xv margin-x)) - next-y - (if (row? shape) - (+ start-y height layout-gap) - start-y) + (some? margin-y) + (gpt/add (yv margin-y))) - next-x (cond-> next-x (some? margin-x) (+ margin-x)) - next-y (cond-> next-y (some? margin-y) (+ margin-y)) + next-p + (cond-> start-p + col? + (gpt/add (xv (+ child-width layout-gap))) + row? + (gpt/add (yv (+ child-height layout-gap))) + + (some? margin-x) + (gpt/add (xv margin-x)) + + (some? margin-y) + (gpt/add (yv margin-y))) layout-data - (assoc layout-data :start-x next-x :start-y next-y)] + (assoc layout-data :start-p next-p)] + [corner-p layout-data])) (defn calc-fill-width-data - [child-bounds - {:keys [layout-gap] :as parent} + "Calculates the size and modifiers for the width of an auto-fill child" + [{:keys [layout-gap transform transform-inverse] :as parent} {:keys [layout-h-behavior] :as child} - {:keys [num-children line-width layout-bounds line-fill? child-fill?] :as layout-data}] + child-origin child-width + {:keys [num-children line-width line-fill? child-fill? layout-bounds] :as layout-data}] (cond (and (col? parent) (= :fill layout-h-behavior) child-fill?) - (let [fill-space (- (:width layout-bounds) line-width (* layout-gap num-children)) + (let [layout-width (width-points layout-bounds) + fill-space (- layout-width line-width (* layout-gap (dec num-children))) fill-width (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-width (:width child-bounds))] - {:bounds {:width fill-width} + fill-scale (/ fill-width child-width)] + {:width fill-width :modifiers [{:type :resize - :origin (gpt/point child-bounds) + :origin child-origin + :transform transform + :transform-inverse transform-inverse :vector (gpt/point fill-scale 1)}]}) (and (row? parent) (= :fill layout-h-behavior) line-fill?) - (let [fill-scale (/ line-width (:width child-bounds))] - {:bounds {:width line-width} + (let [fill-scale (/ line-width child-width)] + {:width line-width :modifiers [{:type :resize - :origin (gpt/point child-bounds) + :origin child-origin + :transform transform + :transform-inverse transform-inverse :vector (gpt/point fill-scale 1)}]}) )) (defn calc-fill-height-data - [child-bounds - {:keys [layout-gap] :as parent} + "Calculates the size and modifiers for the height of an auto-fill child" + [{:keys [layout-gap transform transform-inverse] :as parent} {:keys [layout-v-behavior] :as child} + child-origin child-height {:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}] (cond (and (row? parent) (= :fill layout-v-behavior) child-fill?) - (let [fill-space (- (:height layout-bounds) line-height (* layout-gap num-children)) + (let [layout-height (height-points layout-bounds) + fill-space (- layout-height line-height (* layout-gap (dec num-children))) fill-height (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-height (:height child-bounds))] - {:bounds {:height fill-height} + fill-scale (/ fill-height child-height)] + {:height fill-height :modifiers [{:type :resize - :origin (gpt/point child-bounds) + :origin child-origin + :transform transform + :transform-inverse transform-inverse :vector (gpt/point 1 fill-scale)}]}) (and (col? parent) (= :fill layout-v-behavior) line-fill?) - (let [fill-scale (/ line-height (:height child-bounds))] - {:bounds {:height line-height} + (let [fill-scale (/ line-height child-height)] + {:height line-height :modifiers [{:type :resize - :origin (gpt/point child-bounds) + :origin child-origin + :transform transform + :transform-inverse transform-inverse :vector (gpt/point 1 fill-scale)}]}) )) +(defn normalize-child-modifiers + "Apply the modifiers and then normalized them against the parent coordinates" + [parent child modifiers transformed-parent] + + (let [transformed-child (gst/transform-shape child modifiers) + child-bb-before (gst/parent-coords-rect child parent) + child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) + scale-x (/ (:width child-bb-before) (:width child-bb-after)) + scale-y (/ (:height child-bb-before) (:height child-bb-after))] + (-> modifiers + (update :v2 #(conj % + {:type :resize + :transform (:transform transformed-parent) + :transform-inverse (:transform-inverse transformed-parent) + :origin (-> transformed-parent :points (nth 0)) + :vector (gpt/point scale-x scale-y)}))))) + +(defn calc-layout-data + "Digest the layout data to pass it to the constrains" + [{:keys [layout-dir layout-padding layout-padding-type] :as parent} children] + + (let [;; Add padding to the bounds + {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding + [pad-top pad-right pad-bottom pad-left] + (if (= layout-padding-type :multiple) + [pad-top pad-right pad-bottom pad-left] + [pad-top pad-top pad-top pad-top]) + + ;; Normalize the points to remove flips + points (gst/parent-coords-points parent parent) + + layout-bounds (pad-points points pad-top pad-right pad-bottom pad-left) + + ;; Reverse + reverse? (or (= :left layout-dir) (= :bottom layout-dir)) + children (cond->> children reverse? reverse) + + ;; Creates the layout lines information + layout-lines + (->> (calc-layout-lines parent children layout-bounds) + (calc-layout-lines-position parent layout-bounds) + (map (partial calc-layout-line-data parent layout-bounds)))] + + {:layout-lines layout-lines + :reverse? reverse?})) + (defn calc-layout-modifiers "Calculates the modifiers for the layout" - [parent child layout-data] - (let [transform (:transform parent) - child-bounds (-> child :points gre/points->selrect) + [parent child layout-line] + (let [child-bounds (gst/parent-coords-points child parent) - fill-width (calc-fill-width-data child-bounds parent child layout-data) - fill-height (calc-fill-height-data child-bounds parent child layout-data) + child-origin (origin child-bounds) + child-width (width-points child-bounds) + child-height (height-points child-bounds) - child-bounds (cond-> child-bounds - fill-width (merge (:bounds fill-width)) - fill-height (merge (:bounds fill-height))) + fill-width (calc-fill-width-data parent child child-origin child-width layout-line) + fill-height (calc-fill-height-data parent child child-origin child-height layout-line) - [corner-p layout-data] (next-p parent child-bounds layout-data) + child-width (or (:width fill-width) child-width) + child-height (or (:height fill-height) child-height) - delta-p - (-> corner-p - (gpt/subtract (gpt/point child-bounds)) - (cond-> (some? transform) (gpt/transform transform))) + [corner-p layout-line] (next-p parent child-width child-height layout-line) + + move-vec (gpt/to-vec child-origin corner-p) modifiers (-> [] (cond-> fill-width (d/concat-vec (:modifiers fill-width))) (cond-> fill-height (d/concat-vec (:modifiers fill-height))) - (conj {:type :move :vector delta-p}))] - - [modifiers layout-data])) + (conj {:type :move :vector move-vec}))] + [modifiers layout-line])) diff --git a/common/src/app/common/geom/shapes/layout_new.cljc b/common/src/app/common/geom/shapes/layout_new.cljc deleted file mode 100644 index a15d038702..0000000000 --- a/common/src/app/common/geom/shapes/layout_new.cljc +++ /dev/null @@ -1,606 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) UXBOX Labs SL - -(ns app.common.geom.shapes.layout-new - (:require - [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.common.geom.shapes.rect :as gre] - [app.common.geom.shapes.transforms :as gst] - )) - -;; :layout ;; true if active, false if not -;; :layout-dir ;; :right, :left, :top, :bottom -;; :layout-gap ;; number could be negative -;; :layout-type ;; :packed, :space-between, :space-around -;; :layout-wrap-type ;; :wrap, :no-wrap -;; :layout-padding-type ;; :simple, :multiple -;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative -;; :layout-h-orientation ;; :top, :center, :bottom -;; :layout-v-orientation ;; :left, :center, :right - -(defn col? - [{:keys [layout-dir]}] - (or (= :right layout-dir) (= :left layout-dir))) - -(defn row? - [{:keys [layout-dir]}] - (or (= :top layout-dir) (= :bottom layout-dir))) - -(defn h-start? - [{:keys [layout-h-orientation]}] - (= layout-h-orientation :left)) - -(defn h-center? - [{:keys [layout-h-orientation]}] - (= layout-h-orientation :center)) - -(defn h-end? - [{:keys [layout-h-orientation]}] - (= layout-h-orientation :right)) - -(defn v-start? - [{:keys [layout-v-orientation]}] - (= layout-v-orientation :top)) - -(defn v-center? - [{:keys [layout-v-orientation]}] - (= layout-v-orientation :center)) - -(defn v-end? - [{:keys [layout-v-orientation]}] - (= layout-v-orientation :bottom)) - -(defn add-padding [transformed-rect {:keys [layout-padding-type layout-padding]}] - (let [{:keys [p1 p2 p3 p4]} layout-padding - [p1 p2 p3 p4] - (if (= layout-padding-type :multiple) - [p1 p2 p3 p4] - [p1 p1 p1 p1])] - - (-> transformed-rect - (update :y + p1) - (update :width - p2 p3) - (update :x + p3) - (update :height - p1 p4)))) - -;; FUNCTIONS TO WORK WITH POINTS SQUARES - -(defn origin - [points] - (nth points 0)) - -(defn start-hv - "Horizontal vector from the origin with a magnitude `val`" - [[p0 p1 p2 p3] val] - (-> (gpt/to-vec p0 p1) - (gpt/unit) - (gpt/scale val))) - -(defn end-hv - "Horizontal vector from the oposite to the origin in the x axis with a magnitude `val`" - [[p0 p1 p2 p3] val] - (-> (gpt/to-vec p1 p0) - (gpt/unit) - (gpt/scale val))) - -(defn start-vv - "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" - [[p0 p1 p2 p3] val] - (-> (gpt/to-vec p0 p3) - (gpt/unit) - (gpt/scale val))) - -(defn end-vv - "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" - [[p0 p1 p2 p3] val] - (-> (gpt/to-vec p3 p0) - (gpt/unit) - (gpt/scale val))) - -;;(defn start-hp -;; [[p0 _ _ _ :as points] val] -;; (gpt/add p0 (start-hv points val))) -;; -;;(defn end-hp -;; "Horizontal Vector from the oposite to the origin in the x axis with a magnitude `val`" -;; [[_ p1 _ _ :as points] val] -;; (gpt/add p1 (end-hv points val))) -;; -;;(defn start-vp -;; "Vertical Vector from the oposite to the origin in the x axis with a magnitude `val`" -;; [[p0 _ _ _ :as points] val] -;; (gpt/add p0 (start-vv points val))) -;; -;;(defn end-vp -;; "Vertical Vector from the oposite to the origin in the x axis with a magnitude `val`" -;; [[_ _ p3 _ :as points] val] -;; (gpt/add p3 (end-vv points val))) - -(defn width-points - [[p0 p1 p2 p3]] - (gpt/length (gpt/to-vec p0 p1))) - -(defn height-points - [[p0 p1 p2 p3]] - (gpt/length (gpt/to-vec p0 p3))) - -(defn pad-points - [[p0 p1 p2 p3 :as points] pad-top pad-right pad-bottom pad-left] - (let [top-v (start-vv points pad-top) - right-v (end-hv points pad-right) - bottom-v (end-vv points pad-bottom) - left-v (start-hv points pad-left)] - - [(-> p0 (gpt/add left-v) (gpt/add top-v)) - (-> p1 (gpt/add right-v) (gpt/add top-v)) - (-> p2 (gpt/add right-v) (gpt/add bottom-v)) - (-> p3 (gpt/add left-v) (gpt/add bottom-v))])) - -;;;; - - -(defn calc-layout-lines - [{:keys [layout-gap layout-wrap-type] :as parent} children layout-bounds] - - (let [wrap? (= layout-wrap-type :wrap) - layout-width (width-points layout-bounds) - layout-height (height-points layout-bounds) - - reduce-fn - (fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child] - (let [child-bounds (gst/parent-coords-points child parent) - child-width (width-points child-bounds) - child-height (height-points child-bounds) - - col? (col? parent) - row? (row? parent) - - cur-child-fill? - (or (and col? (= :fill (:layout-h-behavior child))) - (and row? (= :fill (:layout-v-behavior child)))) - - cur-line-fill? - (or (and row? (= :fill (:layout-h-behavior child))) - (and col? (= :fill (:layout-v-behavior child)))) - - ;; TODO LAYOUT: ADD MINWIDTH/HEIGHT - next-width (if (or (and col? cur-child-fill?) - (and row? cur-line-fill?)) - 0 - child-width) - - next-height (if (or (and row? cur-child-fill?) - (and col? cur-line-fill?)) - 0 - child-height) - - next-total-width (+ line-width next-width (* layout-gap num-children)) - next-total-height (+ line-height next-height (* layout-gap num-children))] - - (if (and (some? line-data) - (or (not wrap?) - (and col? (<= next-total-width layout-width)) - (and row? (<= next-total-height layout-height)))) - - ;; When :fill we add min width (0 by default) - [{:line-width (if col? (+ line-width next-width) (max line-width next-width)) - :line-height (if row? (+ line-height next-height) (max line-height next-height)) - :num-children (inc num-children) - :child-fill? (or cur-child-fill? child-fill?) - :line-fill? (or cur-line-fill? line-fill?) - :num-child-fill (cond-> num-child-fill cur-child-fill? inc)} - result] - - [{:line-width next-width - :line-height next-height - :num-children 1 - :child-fill? cur-child-fill? - :line-fill? cur-line-fill? - :num-child-fill (if cur-child-fill? 1 0)} - (cond-> result (some? line-data) (conj line-data))]))) - - [line-data layout-lines] (reduce reduce-fn [nil []] children)] - - (cond-> layout-lines (some? line-data) (conj line-data)))) - -(defn calc-layout-lines-position - [{:keys [layout-gap layout-type] :as parent} layout-bounds layout-lines] - - (let [layout-width (width-points layout-bounds) - layout-height (height-points layout-bounds) - row? (row? parent) - col? (col? parent) - h-center? (h-center? parent) - h-end? (h-end? parent) - v-center? (v-center? parent) - v-end? (v-end? parent) - space-between? (= :space-between layout-type) - space-around? (= :space-around layout-type)] - - (letfn [(get-base-line - [total-width total-height] - - (cond-> (origin layout-bounds) - (and row? h-center?) - (gpt/add (start-hv layout-bounds (/ (- layout-width total-width) 2))) - - (and row? h-end?) - (gpt/add (start-hv layout-bounds (- layout-width total-width))) - - (and col? v-center?) - (gpt/add (start-vv layout-bounds (/ (- layout-height total-height) 2))) - - (and col? v-end?) - (gpt/add (start-vv layout-bounds (- layout-height total-height))) - )) - - (get-start-line - [{:keys [line-width line-height num-children child-fill?]} base-p] - - (let [children-gap (* layout-gap (dec num-children)) - - ;;line-width (if (and col? child-fill?) - ;; (- layout-width (* layout-gap num-children)) - ;; line-width) - ;; - ;;line-height (if (and row? child-fill?) - ;; (- layout-height (* layout-gap num-children)) - ;; line-height) - - - start-p - (cond-> base-p - ;; X AXIS - (and col? h-center? (not space-between?) (not space-around?)) - (-> (gpt/add (start-hv layout-bounds (/ layout-width 2))) - (gpt/subtract (start-hv layout-bounds (/ (+ line-width children-gap) 2)))) - - (and col? h-end? (not space-between?) (not space-around?)) - (-> (gpt/add (start-hv layout-bounds layout-width)) - (gpt/subtract (start-hv layout-bounds (+ line-width children-gap)))) - - (and row? h-center? (not space-between?) (not space-around?)) - (gpt/add (start-hv layout-bounds (/ line-width 2))) - - (and row? h-end? (not space-between?) (not space-around?)) - (gpt/add (start-hv layout-bounds line-width)) - - ;; Y AXIS - (and row? v-center? (not space-between?) (not space-around?)) - (-> (gpt/add (start-vv layout-bounds (/ layout-height 2))) - (gpt/subtract (start-vv layout-bounds (/ (+ line-height children-gap) 2)))) - - (and row? v-end? (not space-between?) (not space-around?)) - (-> (gpt/add (start-vv layout-bounds layout-height)) - (gpt/subtract (start-vv layout-bounds (+ line-height children-gap)))) - - (and col? v-center? (not space-between?) (not space-around?)) - (gpt/add (start-vv layout-bounds (/ line-height 2))) - - (and col? v-end? (not space-between?) (not space-around?)) - (gpt/add (start-vv layout-bounds line-height)) - - ) - - - ;;start-x - ;;(cond - ;; ;;(and (col? shape) child-fill?) - ;; ;; TODO LAYOUT: Start has to take into account max-width - ;; ;;x - ;; - ;; (or (and col? space-between?) (and col? space-around?)) - ;; x - ;; - ;; (and col? h-center?) - ;; (- (+ x (/ width 2)) (/ (+ line-width children-gap) 2)) - ;; - ;; (and col? h-end?) - ;; (- (+ x width) (+ line-width children-gap)) - ;; - ;; (and row? h-center?) - ;; (+ base-x (/ line-width 2)) - ;; - ;; (and row? h-end?) - ;; (+ base-x line-width) - ;; - ;; row? - ;; base-x - ;; - ;; :else - ;; x) - - ;;start-y - ;;(cond - ;; ;; (and (row? shape) child-fill?) - ;; ;; TODO LAYOUT: Start has to take into account max-width - ;; ;; y - ;; - ;; (or (and (row? shape) (= :space-between layout-type)) - ;; (and (row? shape) (= :space-around layout-type))) - ;; y - ;; - ;; (and (row? shape) (v-center? shape)) - ;; (- (+ y (/ height 2)) (/ (+ line-height children-gap) 2)) - ;; - ;; (and (row? shape) (v-end? shape)) - ;; (- (+ y height) (+ line-height children-gap)) - ;; - ;; (and (col? shape) (v-center? shape)) - ;; (+ base-y (/ line-height 2)) - ;; - ;; (and (col? shape) (v-end? shape)) - ;; (+ base-y line-height) - ;; - ;; (col? shape) - ;; base-y - ;; - ;; :else - ;; y) - ] - - start-p)) - - (get-next-line - [{:keys [line-width line-height]} base-p] - - (cond-> base-p - col? - (gpt/add (start-hv layout-bounds (+ line-width layout-gap))) - - row? - (gpt/add (start-vv layout-bounds (+ line-height layout-gap))) - ) - - #_(let [next-x (if col? base-x (+ base-x line-width layout-gap)) - next-y (if row? base-y (+ base-y line-height layout-gap))] - [next-x next-y])) - - (add-lines [[total-width total-height] {:keys [line-width line-height]}] - [(+ total-width line-width) - (+ total-height line-height)]) - - (add-starts [[result base-p] layout-line] - (let [start-p (get-start-line layout-line base-p) - next-p (get-next-line layout-line base-p)] - [(conj result - (assoc layout-line :start-p start-p)) - next-p]))] - - (let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0])) - - total-width (+ total-width (* layout-gap (dec (count layout-lines)))) - total-height (+ total-height (* layout-gap (dec (count layout-lines)))) - - vertical-fill-space (- layout-height total-height) - horizontal-fill-space (- layout-width total-width) - num-line-fill (count (->> layout-lines (filter :line-fill?))) - - layout-lines - (->> layout-lines - (mapv #(cond-> % - (and col? (:line-fill? %)) - (update :line-height + (/ vertical-fill-space num-line-fill)) - - (and row? (:line-fill? %)) - (update :line-width + (/ horizontal-fill-space num-line-fill))))) - - total-height (if (and col? (> num-line-fill 0)) layout-height total-height) - total-width (if (and row? (> num-line-fill 0)) layout-width total-width) - - base-p (get-base-line total-width total-height) - - [layout-lines _ _ _ _] - (reduce add-starts [[] base-p] layout-lines)] - layout-lines)))) - -(defn calc-layout-line-data - [{:keys [layout-type layout-gap] :as shape} - {:keys [width height] :as layout-bounds} - {:keys [num-children line-width line-height] :as line-data}] - - (let [layout-gap - (cond - (= :packed layout-type) - layout-gap - - (= :space-around layout-type) - 0 - - (and (col? shape) (= :space-between layout-type)) - (/ (- width line-width) (dec num-children)) - - (and (row? shape) (= :space-between layout-type)) - (/ (- height line-height) (dec num-children))) - - margin-x - (if (and (col? shape) (= :space-around layout-type)) - (/ (- width line-width) (inc num-children) ) - 0) - - margin-y - (if (and (row? shape) (= :space-around layout-type)) - (/ (- height line-height) (inc num-children)) - 0)] - - (assoc line-data - :layout-bounds layout-bounds - :layout-gap layout-gap - :margin-x margin-x - :margin-y margin-y))) - -(defn next-p - "Calculates the position for the current shape given the layout-data context" - [parent - child-bounds - {:keys [start-p layout-gap margin-x margin-y] :as layout-data}] - - (let [width (width-points child-bounds) - height (height-points child-bounds) - - row? (row? parent) - col? (col? parent) - h-center? (h-center? parent) - h-end? (h-end? parent) - v-center? (v-center? parent) - v-end? (v-end? parent) - points (:points parent) - - corner-p - (cond-> start-p - (and row? h-center?) - (gpt/add (start-hv points (- (/ width 2)))) - - (and row? h-end?) - (gpt/add (start-hv points (- width))) - - (and col? v-center?) - (gpt/add (start-vv points (- (/ height 2)))) - - (and col? v-end?) - (gpt/add (start-vv points (- height))) - - (some? margin-x) - (gpt/add (start-hv points margin-x)) - - (some? margin-y) - (gpt/add (start-vv points margin-y))) - - next-p - (cond-> start-p - col? - (gpt/add (start-hv points (+ width layout-gap))) - - row? - (gpt/add (start-vv points (+ height layout-gap))) - - (some? margin-x) - (gpt/add (start-hv points margin-x)) - - (some? margin-y) - (gpt/add (start-vv points margin-y))) - - layout-data - (assoc layout-data :start-p next-p)] - - [corner-p layout-data])) - -(defn calc-fill-width-data - [child-bounds - {:keys [layout-gap] :as parent} - {:keys [layout-h-behavior] :as child} - {:keys [num-children line-width layout-bounds line-fill? child-fill?] :as layout-data}] - - (cond - (and (col? parent) (= :fill layout-h-behavior) child-fill?) - (let [fill-space (- (:width layout-bounds) line-width (* layout-gap num-children)) - fill-width (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-width (:width child-bounds))] - {:bounds {:width fill-width} - :modifiers [{:type :resize - :origin (gpt/point child-bounds) - :vector (gpt/point fill-scale 1)}]}) - - (and (row? parent) (= :fill layout-h-behavior) line-fill?) - (let [fill-scale (/ line-width (:width child-bounds))] - {:bounds {:width line-width} - :modifiers [{:type :resize - :origin (gpt/point child-bounds) - :vector (gpt/point fill-scale 1)}]}) - )) - -(defn calc-fill-height-data - [child-bounds - {:keys [layout-gap] :as parent} - {:keys [layout-v-behavior] :as child} - {:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}] - - (cond - (and (row? parent) (= :fill layout-v-behavior) child-fill?) - (let [fill-space (- (:height layout-bounds) line-height (* layout-gap num-children)) - fill-height (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-height (:height child-bounds))] - {:bounds {:height fill-height} - :modifiers [{:type :resize - :origin (gpt/point child-bounds) - :vector (gpt/point 1 fill-scale)}]}) - - (and (col? parent) (= :fill layout-v-behavior) line-fill?) - (let [fill-scale (/ line-height (:height child-bounds))] - {:bounds {:height line-height} - :modifiers [{:type :resize - :origin (gpt/point child-bounds) - :vector (gpt/point 1 fill-scale)}]}) - )) - -(defn normalize-child-modifiers - "Apply the modifiers and then normalized them against the parent coordinates" - [parent child modifiers transformed-parent] - - (let [transformed-child (gst/transform-shape child modifiers) - child-bb-before (gst/parent-coords-rect child parent) - child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) - scale-x (/ (:width child-bb-before) (:width child-bb-after)) - scale-y (/ (:height child-bb-before) (:height child-bb-after))] - (-> modifiers - (update :v2 #(conj % - {:type :resize - :transform (:transform transformed-parent) - :transform-inverse (:transform-inverse transformed-parent) - :origin (-> transformed-parent :points (nth 0)) - :vector (gpt/point scale-x scale-y)}))))) - -(defn calc-layout-data - "Digest the layout data to pass it to the constrains" - [{:keys [layout-dir points layout-padding layout-padding-type] :as parent} children] - - (let [;; Add padding to the bounds - {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding - [pad-top pad-right pad-bottom pad-left] - (if (= layout-padding-type :multiple) - [pad-top pad-right pad-bottom pad-left] - [pad-top pad-top pad-top pad-top]) - layout-bounds (-> points (pad-points pad-top pad-right pad-bottom pad-left)) - - ;; Reverse - reverse? (or (= :left layout-dir) (= :bottom layout-dir)) - children (cond->> children reverse? reverse) - - ;; Creates the layout lines information - layout-lines - (->> (calc-layout-lines parent children layout-bounds) - (calc-layout-lines-position parent layout-bounds) - (map (partial calc-layout-line-data parent layout-bounds)))] - - {:layout-lines layout-lines - :reverse? reverse?})) - -(defn calc-layout-modifiers - "Calculates the modifiers for the layout" - [parent child layout-line] - (let [child-bounds (gst/parent-coords-points child parent) - - ;;fill-width (calc-fill-width-data child-bounds parent child layout-line) - ;;fill-height (calc-fill-height-data child-bounds parent child layout-line) - - ;;child-bounds (cond-> child-bounds - ;; fill-width (merge (:bounds fill-width)) - ;; fill-height (merge (:bounds fill-height))) - - - [corner-p layout-line] (next-p parent child-bounds layout-line) - - move-vec (gpt/to-vec (origin child-bounds) corner-p) - - modifiers - (-> [] - #_(cond-> fill-width (d/concat-vec (:modifiers fill-width))) - #_(cond-> fill-height (d/concat-vec (:modifiers fill-height))) - (conj {:type :move :vector move-vec}))] - - [modifiers layout-line])) - diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index fc987dfc9a..7eaf9fb5af 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -11,8 +11,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.constraints :as gct] - [app.common.geom.shapes.layout :as gclo] - [app.common.geom.shapes.layout-new :as gcln] + [app.common.geom.shapes.layout :as gcl] [app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth] @@ -142,7 +141,7 @@ (letfn [(normalize-child [transformed-parent _snap-pixel? modif-tree child] (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) - child-modifiers (gcln/normalize-child-modifiers parent child modifiers transformed-parent)] + child-modifiers (gcl/normalize-child-modifiers parent child modifiers transformed-parent)] (cond-> modif-tree (not (ctm/empty-modifiers? child-modifiers)) (update-in [(:id child) :modifiers :v2] d/concat-vec (:v2 child-modifiers))))) @@ -153,12 +152,12 @@ (some? modifiers) (gtr/transform-shape modifiers) - (and (nil? modifiers) (group? child)) + (and (some? modifiers) (group? child)) (gtr/apply-group-modifiers objects modif-tree)))) (set-layout-modifiers [parent [layout-line modif-tree] child] (let [[modifiers layout-line] - (gcln/calc-layout-modifiers parent child layout-line) + (gcl/calc-layout-modifiers parent child layout-line) modif-tree (cond-> modif-tree @@ -175,7 +174,7 @@ children (->> children (map (partial apply-modifiers modif-tree))) - layout-data (gcln/calc-layout-data transformed-parent children) + layout-data (gcl/calc-layout-data transformed-parent children) children (into [] (cond-> children (:reverse? layout-data) reverse)) @@ -325,12 +324,13 @@ :else (recur (:parent-id current)))))) -#_(defn modif->js - [modif-tree objects] - (clj->js (into {} - (map (fn [[k v]] - [(get-in objects [k :name]) v])) - modif-tree))) +;;#?(:cljs +;; (defn modif->js +;; [modif-tree objects] +;; (clj->js (into {} +;; (map (fn [[k v]] +;; [(get-in objects [k :name]) v])) +;; modif-tree)))) (defn set-objects-modifiers [ids objects get-modifier ignore-constraints snap-pixel?] @@ -367,5 +367,6 @@ modif-tree))] - ;;(.log js/console ">result" (modif->js modif-tree objects)) + ;; #?(:cljs + ;; (.log js/console ">result" (modif->js modif-tree objects))) modif-tree)) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 07b57d1564..1de768035e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -24,10 +24,7 @@ [app.main.ui.workspace.shapes.frame.node-store :as fns] [app.main.ui.workspace.shapes.frame.thumbnail-render :as ftr] [beicon.core :as rx] - [rumext.v2 :as mf] - - [app.common.geom.shapes.layout :as gsl] - [app.common.geom.point :as gpt])) + [rumext.v2 :as mf])) (defn frame-shape-factory [shape-wrapper] From 025cac0228eb6124d4da1c0dfecd7fe2532b3aa8 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 10 Oct 2022 11:51:52 +0200 Subject: [PATCH 200/682] :sparkles: Drop-zone autolayout calculation --- common/src/app/common/geom/shapes/layout.cljc | 134 ++++++++++++++++++ common/src/app/common/pages/helpers.cljc | 6 +- common/src/app/common/types/shape_tree.cljc | 14 ++ .../app/main/ui/workspace/shapes/frame.cljs | 91 +++++++++++- .../src/app/main/ui/workspace/viewport.cljs | 93 +++++++++++- .../app/main/ui/workspace/viewport/hooks.cljs | 12 +- frontend/src/debug.cljs | 2 +- 7 files changed, 339 insertions(+), 13 deletions(-) diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index d0c616e85b..8d1fbed87c 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] + [app.common.geom.shapes.rect :as gsr] [app.common.geom.shapes.transforms :as gst])) ;; :layout ;; true if active, false if not @@ -571,3 +572,136 @@ (conj {:type :move :vector move-vec}))] [modifiers layout-line])) + + +(defn drop-areas + [{:keys [margin-x margin-y] :as frame} layout-data children] + + (let [col? (col? frame) + row? (row? frame) + h-center? (and row? (h-center? frame)) + h-end? (and row? (h-end? frame)) + v-center? (and col? (v-center? frame)) + v-end? (and row? (v-end? frame)) + layout-gap (:layout-gap frame 0) + + children (vec (cond->> children + (:reverse? layout-data) reverse)) + + redfn-child + (fn [[result parent-rect prev-x prev-y] [child next]] + (let [prev-x (or prev-x (:x parent-rect)) + prev-y (or prev-y (:y parent-rect)) + last? (nil? next) + + box-x (-> child :selrect :x) + box-y (-> child :selrect :y) + box-width (-> child :selrect :width) + box-height(-> child :selrect :height) + + + x (if row? (:x parent-rect) prev-x) + y (if col? (:y parent-rect) prev-y) + + width (cond + (and col? last?) + (- (+ (:x parent-rect) (:width parent-rect)) x) + + row? + (:width parent-rect) + + :else + (+ box-width (- box-x prev-x) (/ layout-gap 2))) + + height (cond + (and row? last?) + (- (+ (:y parent-rect) (:height parent-rect)) y) + + col? + (:height parent-rect) + + :else + (+ box-height (- box-y prev-y) (/ layout-gap 2))) + + line-area (gsr/make-rect x y width height) + result (conj result line-area)] + + [result parent-rect (+ x width) (+ y height)])) + + redfn-lines + (fn [[result from-idx prev-x prev-y] [{:keys [start-p layout-gap num-children line-width line-height]} next]] + (let [prev-x (or prev-x (:x frame)) + prev-y (or prev-y (:y frame)) + last? (nil? next) + + line-width + (if col? + (:width frame) + (+ line-width margin-x + (if col? (* layout-gap (dec num-children)) 0))) + + line-height + (if row? + (:height frame) + (+ line-height margin-y + (if row? + (* layout-gap (dec num-children)) + 0))) + + box-x + (- (:x start-p) + (cond + h-center? (/ line-width 2) + h-end? line-width + :else 0)) + + box-y + (- (:y start-p) + (cond + v-center? (/ line-height 2) + v-end? line-height + :else 0)) + + x (if col? (:x frame) prev-x) + y (if row? (:y frame) prev-y) + + width (cond + (and row? last?) + (- (+ (:x frame) (:width frame)) x) + + col? + (:width frame) + + :else + (+ line-width (- box-x prev-x) (/ layout-gap 2))) + + height (cond + (and col? last?) + (- (+ (:y frame) (:height frame)) y) + + row? + (:height frame) + + :else + (+ line-height (- box-y prev-y) (/ layout-gap 2))) + + line-area (gsr/make-rect x y width height) + + children (subvec children from-idx (+ from-idx num-children)) + + + ;; To debug the lines + ;;result (conj result line-area) + + result (first (reduce redfn-child [result line-area] (d/with-next children)))] + + [result (+ from-idx num-children) (+ x width) (+ y height)])) + + ret (first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data)))) + ] + + + ;;(.log js/console "RET" (clj->js ret)) + ret + + )) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index a680833d23..5d243c3635 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -28,8 +28,10 @@ (= frame-id uuid/zero))) (defn frame-shape? - [{:keys [type]}] - (= type :frame)) + ([objects id] + (= (get-in objects [id :type]) id)) + ([{:keys [type]}] + (= type :frame))) (defn group-shape? [{:keys [type]}] diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index e39869c98a..72388c2160 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -234,6 +234,20 @@ (if (nil? child-frame-id) (or current-id uuid/zero) (recur child-frame-id)))))) +(defn top-nested-frame-ids + "Search the top nested frame in a list of ids" + [objects ids] + + (let [frame-ids (->> ids (filter #(cph/frame-shape? objects %))) + frame-set (set frame-ids)] + (loop [current-id (first frame-ids)] + (let [current-shape (get objects current-id) + child-frame-id (d/seek #(contains? frame-set %) + (-> (:shapes current-shape) reverse))] + (if (nil? child-frame-id) + (or current-id uuid/zero) + (recur child-frame-id))))) + ) (defn get-viewer-frames ([objects] diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 1de768035e..a3745f16d2 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -24,7 +24,87 @@ [app.main.ui.workspace.shapes.frame.node-store :as fns] [app.main.ui.workspace.shapes.frame.thumbnail-render :as ftr] [beicon.core :as rx] - [rumext.v2 :as mf])) + [rumext.v2 :as mf] + + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.layout :as gsl] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.store :as st])) + +(mf/defc debug-layout + {::mf/wrap-props false} + [props] + + (let [shape (unchecked-get props "shape") + children (-> (wsh/lookup-page-objects @st/state) + (cph/get-immediate-children (:id shape))) + + layout-data (gsl/calc-layout-data shape children) + + drop-areas + (gsl/drop-areas shape layout-data children) + + ] + + [:g.debug-layout {:pointer-events "none"} + (for [[idx drop-area] (d/enumerate drop-areas)] + [:rect {:x (:x drop-area) + :y (:y drop-area) + :width (:width drop-area) + :height (:height drop-area) + :style {:fill "blue" + :fill-opacity 0.3 + :stroke "red" + :stroke-width 1 + :stroke-dasharray "3 6"}}]) + + + #_(for [[idx layout-line] (d/enumerate (:layout-lines layout-data))] + (let [col? (gsl/col? shape) + row? (gsl/row? shape) + h-center? (and row? (gsl/h-center? shape)) + h-end? (and row? (gsl/h-end? shape)) + v-center? (and col? (gsl/v-center? shape)) + v-end? (and row? (gsl/v-end? shape)) + + line-width + (+ (-> layout-line :line-width) + (:margin-x shape) + (if col? + (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) + 0)) + + line-height + (+ (-> layout-line :line-height) + (:margin-y shape) + (if row? + (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) + 0)) + ] + [:g {:key (dm/str "line-" idx)} + [:rect {:x (- (-> layout-line :start-p :x) + (cond + h-center? (/ line-width 2) + h-end? line-width + :else 0)) + :y (- (-> layout-line :start-p :y) + (cond + v-center? (/ line-height 2) + v-end? line-height + :else 0)) + :width line-width + :height line-height + :style {:fill "blue" + :fill-opacity 0.3} + }] + #_[:line {:x1 (-> layout-line :start-p :x) + :y1 (-> layout-line :start-p :y) + :x2 (+ (-> layout-line :start-p :x) (if col? line-width 0)) + :y2 (+ (-> layout-line :start-p :y) (if row? line-height 0)) + :transform (gsh/transform-str shape) + :style {:fill "none" + :stroke "red" + :stroke-width 2}}]]))])) (defn frame-shape-factory [shape-wrapper] @@ -39,9 +119,12 @@ childs-ref (mf/use-memo (mf/deps (:id shape)) #(refs/children-objects (:id shape))) childs (mf/deref childs-ref)] - [:& (mf/provider embed/context) {:value true} - [:& shape-container {:shape shape :ref ref :disable-shadows? (cph/root-frame? shape)} - [:& frame-shape {:shape shape :childs childs} ]]])))) + [:* + [:& (mf/provider embed/context) {:value true} + [:& shape-container {:shape shape :ref ref :disable-shadows? (cph/root-frame? shape)} + [:& frame-shape {:shape shape :childs childs} ]]] + + #_[:& debug-layout {:shape shape}]])))) (defn check-props [new-props old-props] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 5a4be44caa..4c219d3acd 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -41,7 +41,85 @@ [app.main.ui.workspace.viewport.widgets :as widgets] [beicon.core :as rx] [debug :refer [debug?]] - [rumext.v2 :as mf])) + [rumext.v2 :as mf] + + [app.common.uuid :as uuid] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.layout :as gsl] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.store :as st] + + )) + +(mf/defc debug-layout + {::mf/wrap-props false} + [props] + + (let [shape (unchecked-get props "shape") + objects (unchecked-get props "objects") + children (cph/get-immediate-children objects (:id shape)) + layout-data (gsl/calc-layout-data shape children) + drop-areas (gsl/drop-areas shape layout-data children)] + + [:g.debug-layout {:pointer-events "none"} + (for [[idx drop-area] (d/enumerate drop-areas)] + [:rect {:x (:x drop-area) + :y (:y drop-area) + :width (:width drop-area) + :height (:height drop-area) + :style {:fill "blue" + :fill-opacity 0.3 + :stroke "red" + :stroke-width 1 + :stroke-dasharray "3 6"}}]) + + + #_(for [[idx layout-line] (d/enumerate (:layout-lines layout-data))] + (let [col? (gsl/col? shape) + row? (gsl/row? shape) + h-center? (and row? (gsl/h-center? shape)) + h-end? (and row? (gsl/h-end? shape)) + v-center? (and col? (gsl/v-center? shape)) + v-end? (and row? (gsl/v-end? shape)) + + line-width + (+ (-> layout-line :line-width) + (:margin-x shape) + (if col? + (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) + 0)) + + line-height + (+ (-> layout-line :line-height) + (:margin-y shape) + (if row? + (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) + 0)) + ] + [:g {:key (dm/str "line-" idx)} + [:rect {:x (- (-> layout-line :start-p :x) + (cond + h-center? (/ line-width 2) + h-end? line-width + :else 0)) + :y (- (-> layout-line :start-p :y) + (cond + v-center? (/ line-height 2) + v-end? line-height + :else 0)) + :width line-width + :height line-height + :style {:fill "blue" + :fill-opacity 0.3} + }] + #_[:line {:x1 (-> layout-line :start-p :x) + :y1 (-> layout-line :start-p :y) + :x2 (+ (-> layout-line :start-p :x) (if col? line-width 0)) + :y2 (+ (-> layout-line :start-p :y) (if row? line-height 0)) + :transform (gsh/transform-str shape) + :style {:fill "none" + :stroke "red" + :stroke-width 2}}]]))])) ;; --- Viewport @@ -90,6 +168,7 @@ hover-ids (mf/use-state nil) hover (mf/use-state nil) hover-disabled? (mf/use-state false) + hover-top-frame-id (mf/use-state nil) frame-hover (mf/use-state nil) active-frames (mf/use-state #{}) @@ -186,7 +265,7 @@ (hooks/setup-viewport-size viewport-ref) (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing?) (hooks/setup-keyboard alt? mod? space?) - (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids @hover-disabled? focus zoom) + (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids hover-top-frame-id @hover-disabled? focus zoom) (hooks/setup-viewport-modifiers modifiers base-objects) (hooks/setup-shortcuts node-editing? drawing-path?) (hooks/setup-active-frames base-objects hover-ids selected active-frames zoom transform vbox) @@ -414,6 +493,16 @@ :hover-frame frame-parent :disabled-guides? disabled-guides?}]) + (let [selected-frame (when (= 1 (count selected-shapes)) + (let [selected-shape (get objects-modified (first selected))] + (when (= :frame (:type selected-shape)) + selected-shape))) + + top-frame (or selected-frame (get objects-modified @hover-top-frame-id))] + (when (and top-frame (not= uuid/zero top-frame) (:layout top-frame)) + [:& debug-layout {:shape top-frame + :objects objects-modified}])) + (when show-selection-handlers? [:g.selection-handlers {:clipPath "url(#clip-handlers)"} [:defs diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index c9071afc80..cc6c7bf8db 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -28,7 +28,8 @@ [beicon.core :as rx] [debug :refer [debug?]] [goog.events :as events] - [rumext.v2 :as mf]) + [rumext.v2 :as mf] + [app.common.types.shape-tree :as ctst]) (:import goog.events.EventType)) (defn setup-dom-events [viewport-ref zoom disable-paste in-viewport?] @@ -104,7 +105,8 @@ (some #(cph/is-parent? objects % group-id)) (not)))) -(defn setup-hover-shapes [page-id move-stream objects transform selected mod? hover hover-ids hover-disabled? focus zoom] +(defn setup-hover-shapes + [page-id move-stream objects transform selected mod? hover hover-ids hover-top-frame-id hover-disabled? focus zoom] (let [;; We use ref so we don't recreate the stream on a change zoom-ref (mf/use-ref zoom) mod-ref (mf/use-ref @mod?) @@ -143,9 +145,10 @@ (rx/map #(deref last-point-ref))) (->> move-stream + (rx/tap #(reset! last-point-ref %)) ;; When transforming shapes we stop querying the worker (rx/merge-map query-point) - (rx/tap #(reset! last-point-ref %))))))] + ))))] ;; Refresh the refs on a value change (mf/use-effect @@ -213,7 +216,8 @@ (first) (get objects))] (reset! hover hover-shape) - (reset! hover-ids ids)))))) + (reset! hover-ids ids) + (reset! hover-top-frame-id (ctst/top-nested-frame objects (deref last-point-ref)))))))) (defn setup-viewport-modifiers [modifiers objects] diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index a87d0cbacf..96c9866049 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -74,7 +74,7 @@ #{:app.main.data.workspace.notifications/handle-pointer-update :app.main.data.workspace.selection/change-hover-state}) -(defonce ^:dynamic *debug* (atom #{#_:events #_:text-outline})) +(defonce ^:dynamic *debug* (atom #{#_:events})) (defn debug-all! [] (reset! *debug* debug-options)) (defn debug-none! [] (reset! *debug* #{})) From c3ed46d3ab2f58465dda71d8d4fbc3032d3d68ef Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 14 Oct 2022 14:44:10 +0200 Subject: [PATCH 201/682] :sparkles: Move auto-layout children --- common/src/app/common/geom/point.cljc | 9 +- common/src/app/common/geom/shapes.cljc | 1 + .../app/common/geom/shapes/constraints.cljc | 4 +- common/src/app/common/geom/shapes/layout.cljc | 114 +++++++++++------- .../src/app/common/geom/shapes/modifiers.cljc | 17 ++- common/src/app/common/geom/shapes/rect.cljc | 23 ++-- .../app/common/geom/shapes/transforms.cljc | 43 ++++++- common/src/app/common/math.cljc | 5 + common/src/app/common/pages/helpers.cljc | 6 + common/src/app/common/types/modifiers.cljc | 5 +- common/src/app/common/types/shape_tree.cljc | 4 +- .../app/main/data/workspace/drawing/box.cljs | 49 +++++--- .../main/data/workspace/drawing/common.cljs | 3 +- .../src/app/main/data/workspace/shapes.cljs | 8 +- .../app/main/data/workspace/transforms.cljs | 89 +++++++++++--- .../src/app/main/ui/workspace/shapes.cljs | 78 ++++++------ .../app/main/ui/workspace/shapes/frame.cljs | 91 +------------- .../shapes/frame/dynamic_modifiers.cljs | 69 ++++++++++- .../src/app/main/ui/workspace/viewport.cljs | 94 ++------------- .../app/main/ui/workspace/viewport/debug.cljs | 54 +++++++++ .../app/main/ui/workspace/viewport/hooks.cljs | 8 +- frontend/src/debug.cljs | 12 +- 22 files changed, 457 insertions(+), 329 deletions(-) create mode 100644 frontend/src/app/main/ui/workspace/viewport/debug.cljs diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 9ce97f8af5..5b17160d9f 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -5,7 +5,7 @@ ;; Copyright (c) KALEIDOS INC (ns app.common.geom.point - (:refer-clojure :exclude [divide min max]) + (:refer-clojure :exclude [divide min max abs]) (:require #?(:cljs [cljs.pprint :as pp] :clj [clojure.pprint :as pp]) @@ -328,6 +328,13 @@ (update :x #(if (mth/almost-zero? %) 0.001 %)) (update :y #(if (mth/almost-zero? %) 0.001 %)))) + +(defn abs + [point] + (-> point + (update :x mth/abs) + (update :y mth/abs))) + ;; --- Debug (defmethod pp/simple-dispatch Point [obj] (pr obj)) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 5364b9daad..25da561f35 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -160,6 +160,7 @@ (dm/export gpr/join-rects) (dm/export gpr/join-selrects) (dm/export gpr/contains-selrect?) +(dm/export gpr/contains-point?) (dm/export gtr/move) (dm/export gtr/absolute-move) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 0c898348f6..2e9aa8a9ba 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -288,7 +288,9 @@ (defn calc-child-modifiers [parent child modifiers ignore-constraints transformed-parent] - (let [constraints-h + (let [modifiers (select-keys modifiers [:v2]) + + constraints-h (if-not ignore-constraints (:constraints-h child (default-constraints-h child)) :scale) diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index 8d1fbed87c..a5f0d79c54 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -7,9 +7,12 @@ (ns app.common.geom.shapes.layout (:require [app.common.data :as d] + [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] + [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.rect :as gsr] - [app.common.geom.shapes.transforms :as gst])) + [app.common.geom.shapes.transforms :as gst] + [app.common.pages.helpers :as cph])) ;; :layout ;; true if active, false if not ;; :layout-dir ;; :right, :left, :top, :bottom @@ -213,6 +216,8 @@ layout-height (height-points layout-bounds) row? (row? parent) col? (col? parent) + space-between? (= :space-between (:layout-type parent)) + space-around? (= :space-around (:layout-type parent)) h-center? (h-center? parent) h-end? (h-end? parent) v-center? (v-center? parent) @@ -258,11 +263,11 @@ start-p (cond-> base-p ;; X AXIS - (and col? h-center?) + (and col? h-center? (not space-around?) (not space-between?)) (-> (gpt/add (xv (/ layout-width 2))) (gpt/subtract (xv (/ (+ line-width children-gap) 2)))) - (and col? h-end?) + (and col? h-end? (not space-around?) (not space-between?)) (-> (gpt/add (xv layout-width)) (gpt/subtract (xv (+ line-width children-gap)))) @@ -273,11 +278,11 @@ (gpt/add (xv line-width)) ;; Y AXIS - (and row? v-center?) + (and row? v-center? (not space-around?) (not space-between?)) (-> (gpt/add (yv (/ layout-height 2))) (gpt/subtract (yv (/ (+ line-height children-gap) 2)))) - (and row? v-end?) + (and row? v-end? (not space-around?) (not space-between?)) (-> (gpt/add (yv layout-height)) (gpt/subtract (yv (+ line-height children-gap)))) @@ -385,17 +390,10 @@ (let [row? (row? parent) col? (col? parent) - layout-type (:layout-type parent) - space-around? (= :space-around layout-type) - space-between? (= :space-between layout-type) - - stretch-h? (and row? (or space-around? space-between?)) - stretch-v? (and col? (or space-around? space-between?)) - - h-center? (and (h-center? parent) (not stretch-h?)) - h-end? (and (h-end? parent) (not stretch-h?)) - v-center? (and (v-center? parent) (not stretch-v?)) - v-end? (and (v-end? parent) (not stretch-v?)) + h-center? (h-center? parent) + h-end? (h-end? parent) + v-center? (v-center? parent) + v-end? (v-end? parent) points (:points parent) xv (partial start-hv points) @@ -409,10 +407,10 @@ (and row? h-end?) (gpt/add (xv (- child-width))) - (and col? v-center? (not space-around?)) + (and col? v-center?) (gpt/add (yv (- (/ child-height 2)))) - (and col? v-end? (not space-around?)) + (and col? v-end?) (gpt/add (yv (- child-height))) (some? margin-x) @@ -584,29 +582,35 @@ v-center? (and col? (v-center? frame)) v-end? (and row? (v-end? frame)) layout-gap (:layout-gap frame 0) + reverse? (:reverse? layout-data) - children (vec (cond->> children - (:reverse? layout-data) reverse)) + children (vec (cond->> (d/enumerate children) + reverse? reverse)) redfn-child - (fn [[result parent-rect prev-x prev-y] [child next]] + (fn [[result parent-rect prev-x prev-y] [[index child] next]] (let [prev-x (or prev-x (:x parent-rect)) prev-y (or prev-y (:y parent-rect)) + last? (nil? next) - box-x (-> child :selrect :x) - box-y (-> child :selrect :y) - box-width (-> child :selrect :width) - box-height(-> child :selrect :height) + start-p (gpt/point (:selrect child)) + start-p (-> start-p + (gmt/transform-point-center (gco/center-shape child) (:transform frame)) + (gmt/transform-point-center (gco/center-shape frame) (:transform-inverse frame))) + + box-x (:x start-p) + box-y (:y start-p) + box-width (-> child :selrect :width) + box-height (-> child :selrect :height) - x (if row? (:x parent-rect) prev-x) y (if col? (:y parent-rect) prev-y) width (cond (and col? last?) (- (+ (:x parent-rect) (:width parent-rect)) x) - + row? (:width parent-rect) @@ -616,21 +620,42 @@ height (cond (and row? last?) (- (+ (:y parent-rect) (:height parent-rect)) y) - + col? (:height parent-rect) :else (+ box-height (- box-y prev-y) (/ layout-gap 2))) - - line-area (gsr/make-rect x y width height) - result (conj result line-area)] + + [line-area-1 line-area-2] + (if col? + (let [half-point-width (+ (- box-x x) (/ box-width 2))] + [(-> (gsr/make-rect x y half-point-width height) + (assoc :index (if reverse? (inc index) index))) + (-> (gsr/make-rect (+ x half-point-width) y (- width half-point-width) height) + (assoc :index (if reverse? index (inc index))))]) + (let [half-point-height (+ (- box-y y) (/ box-height 2))] + [(-> (gsr/make-rect x y width half-point-height) + (assoc :index (if reverse? (inc index) index))) + (-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height)) + (assoc :index (if reverse? index (inc index))))])) + + result (conj result line-area-1 line-area-2) + + ;;line-area + ;;(-> (gsr/make-rect x y width height) + ;; (assoc :index (if reverse? (inc index) index))) + ;;result (conj result line-area) + ;;result (conj result (gsr/make-rect box-x box-y box-width box-height)) + ] [result parent-rect (+ x width) (+ y height)])) - + redfn-lines (fn [[result from-idx prev-x prev-y] [{:keys [start-p layout-gap num-children line-width line-height]} next]] - (let [prev-x (or prev-x (:x frame)) + (let [start-p (gmt/transform-point-center start-p (gco/center-shape frame) (:transform-inverse frame)) + + prev-x (or prev-x (:x frame)) prev-y (or prev-y (:y frame)) last? (nil? next) @@ -668,7 +693,7 @@ width (cond (and row? last?) (- (+ (:x frame) (:width frame)) x) - + col? (:width frame) @@ -678,7 +703,7 @@ height (cond (and col? last?) (- (+ (:y frame) (:height frame)) y) - + row? (:height frame) @@ -695,13 +720,16 @@ result (first (reduce redfn-child [result line-area] (d/with-next children)))] - [result (+ from-idx num-children) (+ x width) (+ y height)])) + [result (+ from-idx num-children) (+ x width) (+ y height)]))] - ret (first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data)))) - ] + (first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data)))))) - - ;;(.log js/console "RET" (clj->js ret)) - ret - - )) +(defn get-drop-index + [frame-id objects position] + (let [frame (get objects frame-id) + position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame)) + children (cph/get-immediate-children objects frame-id) + layout-data (calc-layout-data frame children) + drop-areas (drop-areas frame layout-data children) + area (d/seek #(gsr/contains-point? % position) drop-areas)] + (:index area))) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 7eaf9fb5af..6cb5f0d6f9 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -166,9 +166,9 @@ [layout-line modif-tree]))] - (let [children (map (d/getf objects) (:shapes parent)) - modifiers (get-in modif-tree [(:id parent) :modifiers]) + (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) transformed-parent (gtr/transform-shape parent modifiers) + children (map (d/getf objects) (:shapes transformed-parent)) modif-tree (reduce (partial normalize-child transformed-parent _snap-pixel?) modif-tree children) @@ -343,27 +343,26 @@ (assoc id {:modifiers modifiers})))) modif-tree (reduce set-modifiers {} ids) - shapes-tree (resolve-tree-sequence ids objects) modif-tree (->> shapes-tree (reduce (fn [modif-tree shape] - (let [has-modifiers? (some? (get-in modif-tree [(:id shape) :modifiers])) + (let [modifiers (get-in modif-tree [(:id shape) :modifiers]) + has-modifiers? (some? modifiers) is-layout? (layout? shape) is-parent? (or (group? shape) (and (frame? shape) (not (layout? shape)))) - + root? (= uuid/zero (:id shape)) ;; If the current child is inside the layout we ignore the constraints is-inside-layout? (inside-layout? objects shape)] (cond-> modif-tree - (and has-modifiers? is-parent?) + (and has-modifiers? is-parent? (not root?)) (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?) - + is-layout? - (set-layout-modifiers objects shape snap-pixel?) - ))) + (set-layout-modifiers objects shape snap-pixel?)))) modif-tree))] diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 6637b7533a..895388371b 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -11,14 +11,21 @@ [app.common.math :as mth])) (defn make-rect - [x y width height] - (when (d/num? x y width height) - (let [width (max width 0.01) - height (max height 0.01)] - {:x x - :y y - :width width - :height height}))) + ([p1 p2] + (let [x1 (min (:x p1) (:x p2)) + y1 (min (:y p1) (:y p2)) + x2 (max (:x p1) (:x p2)) + y2 (max (:y p1) (:y p2))] + (make-rect x1 y1 (- x2 x1) (- y2 y1)))) + + ([x y width height] + (when (d/num? x y width height) + (let [width (max width 0.01) + height (max height 0.01)] + {:x x + :y y + :width width + :height height})))) (defn make-selrect [x y width height] diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 6c19575192..08bf7383e3 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -14,8 +14,10 @@ [app.common.geom.shapes.path :as gpa] [app.common.geom.shapes.rect :as gpr] [app.common.math :as mth] + [app.common.pages.helpers :as cph] [app.common.text :as txt] - [app.common.types.modifiers :as ctm])) + [app.common.types.modifiers :as ctm] + [app.common.uuid :as uuid])) (def ^:dynamic *skip-adjust* false) @@ -436,13 +438,46 @@ %))) shape)) +(defn- apply-structure-modifiers + [shape modifiers] + + (let [remove-children + (fn [shapes children-to-remove] + (let [remove? (set children-to-remove)] + (d/removev remove? shapes))) + + apply-modifier + (fn [shape {:keys [type value index]}] + (cond-> shape + (and (= type :add-children) (some? index)) + (update :shapes + (fn [shapes] + (if (vector? shapes) + (cph/insert-at-index shapes index value) + (d/concat-vec shapes value)))) + + (and (= type :add-children) (nil? index)) + (update :shapes d/concat-vec value) + + (= type :remove-children) + (update :shapes remove-children value)))] + + (reduce apply-modifier shape (:v3 modifiers)))) + (defn apply-modifiers [shape modifiers] (let [center (gco/center-shape shape) transform (ctm/modifiers->transform center modifiers)] - (-> shape - #_(set-flip-2 transform) - (apply-transform transform)))) + + (cond-> shape + #_(set-flip-2 transform) + (and (some? transform) + ;; Never transform the root frame + (not= uuid/zero (:id shape))) + (apply-transform transform) + + :always + (apply-structure-modifiers modifiers)))) (defn apply-objects-modifiers [objects modifiers] diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index d32531a26d..fc35d15f34 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -174,3 +174,8 @@ (defn max-abs [a b] (max (abs a) (abs b))) + +(defn sign + "Get the sign (+1 / -1) for the number" + [n] + (if (neg? n) -1 1)) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 5d243c3635..f984574f53 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -33,6 +33,12 @@ ([{:keys [type]}] (= type :frame))) +(defn layout-shape? + ([objects id] + (layout-shape? (get objects id))) + ([{:keys [type layout]}] + (and (= type :frame) layout))) + (defn group-shape? [{:keys [type]}] (= type :group)) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 7c8658cfba..5c905aeff0 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -162,9 +162,6 @@ :move (gmt/multiply (gmt/translate-matrix vector) matrix) - ;;:transform - ;;(gmt/multiply transform matrix) - :resize (gmt/multiply (-> (gmt/matrix) @@ -178,7 +175,7 @@ matrix) :rotation - ;; TODO LAYOUT: Comprobar que pasa si no hay centro + ;; TODO LAYOUT: Maybe an issue when no center data (gmt/multiply (-> (gmt/matrix) (gmt/translate center) diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 72388c2160..905854d030 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -234,6 +234,7 @@ (if (nil? child-frame-id) (or current-id uuid/zero) (recur child-frame-id)))))) + (defn top-nested-frame-ids "Search the top nested frame in a list of ids" [objects ids] @@ -246,8 +247,7 @@ (-> (:shapes current-shape) reverse))] (if (nil? child-frame-id) (or current-id uuid/zero) - (recur child-frame-id))))) - ) + (recur child-frame-id)))))) (defn get-viewer-frames ([objects] diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index dc9e867626..65843fc6bd 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -21,27 +21,37 @@ [beicon.core :as rx] [potok.core :as ptk])) -(defn truncate-zero [num default] - (if (mth/almost-zero? num) default num)) +(defn adjust-ratio + [point initial] + (let [v (gpt/to-vec point initial) + dx (mth/abs (:x v)) + dy (mth/abs (:y v)) + sx (mth/sign (:x v)) + sy (mth/sign (:y v))] + + (cond-> point + (> dx dy) + (assoc :y (- (:y point) (* sy (- dx dy)))) + + (> dy dx) + (assoc :x (- (:x point) (* sx (- dy dx))))))) + +(defn resize-shape [{:keys [x y width height] :as shape} initial point lock?] + (let [draw-rect (gsh/make-rect initial (cond-> point lock? (adjust-ratio initial))) + shape-rect (gsh/make-rect x y width height) + + scalev (gpt/point (/ (:width draw-rect) (:width shape-rect)) + (/ (:height draw-rect) (:height shape-rect))) + + movev (gpt/to-vec (gpt/point shape-rect) (gpt/point draw-rect))] -(defn resize-shape [{:keys [x y width height] :as shape} point lock?] - (let [;; The new shape behaves like a resize on the bottom-right corner - initial (gpt/point (+ x width) (+ y height)) - shapev (gpt/point width height) - deltav (gpt/to-vec initial point) - scalev (-> (gpt/divide (gpt/add shapev deltav) shapev) - (update :x truncate-zero 0.01) - (update :y truncate-zero 0.01)) - scalev (if lock? - (let [v (max (:x scalev) (:y scalev))] - (gpt/point v v)) - scalev)] (-> shape (assoc :click-draw? false) - (gsh/transform-shape (ctm/resize scalev (gpt/point x y)))))) + (gsh/transform-shape (ctm/resize scalev (gpt/point x y))) + (gsh/transform-shape (ctm/move movev))))) -(defn update-drawing [state point lock?] - (update-in state [:workspace-drawing :object] resize-shape point lock?)) +(defn update-drawing [state initial point lock?] + (update-in state [:workspace-drawing :object] resize-shape initial point lock?)) (defn move-drawing [{:keys [x y]}] @@ -57,8 +67,7 @@ layout (get state :workspace-layout) snap-pixel? (contains? layout :snap-pixel-grid) - initial (cond-> @ms/mouse-position - snap-pixel? gpt/round) + initial (cond-> @ms/mouse-position snap-pixel? gpt/round) page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -96,7 +105,7 @@ (rx/map #(conj current %))))) (rx/map (fn [[_ shift? point]] - #(update-drawing % (cond-> point snap-pixel? gpt/round) shift?))) + #(update-drawing % initial (cond-> point snap-pixel? gpt/round) shift?))) (rx/take-until stoper)) (rx/of (common/handle-finish-drawing))))))) diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index c13f76e707..2016c3c7d9 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -51,8 +51,7 @@ (and click-draw? (not text?)) (-> (assoc :width min-side :height min-side) - (gsh/transform-shape (ctm/move (- (/ min-side 2)) (- (/ min-side 2)))) - #_(ctm/add-move (- (/ min-side 2)) (- (/ min-side 2)))) + (gsh/transform-shape (ctm/move (- (/ min-side 2)) (- (/ min-side 2))))) (and click-draw? text?) (assoc :height 17 :width 4 :grow-type :auto-width) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index e7c8e67057..daa033365d 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -22,6 +22,7 @@ [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] [app.main.features :as features] [app.main.streams :as ms] @@ -146,6 +147,10 @@ ids (cph/clean-loops objects ids) lookup (d/getf objects) + layout-ids (->> ids + (mapcat (partial cph/get-parent-ids objects)) + (filter (partial cph/layout-shape? objects))) + components-v2 (features/active-feature? state :components-v2) groups-to-unmask @@ -266,7 +271,8 @@ (rx/of (dc/detach-comment-thread ids) (dwsl/update-layout-positions all-parents) - (dch/commit-changes changes))))))) + (dch/commit-changes changes) + (dwsl/update-layout-positions layout-ids))))))) (defn- viewport-center [state] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 9d8c186d54..7873b7ebaf 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -11,6 +11,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.layout :as gsl] [app.common.math :as mth] [app.common.pages.changes-builder :as pcb] [app.common.pages.common :as cpc] @@ -137,8 +138,20 @@ snap-pixel? (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) + workspace-modifiers (:workspace-modifiers state) + modif-tree - (gsh/set-objects-modifiers ids objects (constantly modifiers) ignore-constraints snap-pixel?)] + (gsh/set-objects-modifiers + ;; TODO LAYOUT: I don't like this + (concat (keys workspace-modifiers) ids) + objects + (fn [shape] + (let [modifiers (if (contains? ids (:id shape)) modifiers {}) + old-modifiers-v3 (get-in state [:workspace-modifiers (:id shape) :modifiers :v3])] + (cond-> modifiers + (some? old-modifiers-v3) + (assoc :v3 old-modifiers-v3)))) + ignore-constraints snap-pixel?)] (update state :workspace-modifiers merge modif-tree)))))) @@ -619,6 +632,40 @@ (rx/take 1) (rx/map #(start-move from-position)))))) +(defn set-change-frame-modifiers + [selected target-frame position] + + (ptk/reify ::set-change-frame-modifiers + ptk/UpdateEvent + (update [_ state] + (let [objects (wsh/lookup-page-objects state) + + origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) + + layout? (get-in objects [target-frame :layout]) + + drop-index + (when layout? (gsl/get-drop-index target-frame objects position)) + + modif-tree + (into {} + (mapcat + (fn [original-frame] + (let [shapes (->> (get origin-frame-ids original-frame) + (d/removev #(= target-frame %)))] + (cond + (not= original-frame target-frame) + [[original-frame {:modifiers {:v3 [{:type :remove-children :value shapes}]}}] + [target-frame {:modifiers {:v3 [{:type :add-children + :value shapes + :index drop-index}]}}]] + layout? + [[target-frame {:modifiers {:v3 [{:type :add-children + :value shapes + :index drop-index}]}}]])))) + (keys origin-frame-ids))] + (assoc state :workspace-modifiers modif-tree))))) + (defn- start-move ([from-position] (start-move from-position nil)) ([from-position ids] @@ -664,17 +711,25 @@ (if (empty? shapes) (rx/of (finish-transform)) (rx/concat - (->> position - ;; We ask for the snap position but we continue even if the result is not available - (rx/with-latest vector snap-delta) - ;; We try to use the previous snap so we don't have to wait for the result of the new - (rx/map snap/correct-snap-point) + (rx/merge + (->> position + (rx/map (fn [delta] + (let [position (gpt/add from-position delta) + target-frame (ctst/top-nested-frame objects position)] + (set-change-frame-modifiers selected target-frame position)))) + (rx/take-until stopper)) - #_(rx/map #(hash-map :displacement (gmt/translate-matrix %))) - (rx/map #(array-map :v2 [{:type :move :vector %}])) + (->> position + ;; We ask for the snap position but we continue even if the result is not available + (rx/with-latest vector snap-delta) - (rx/map (partial set-modifiers ids)) - (rx/take-until stopper)) + ;; We try to use the previous snap so we don't have to wait for the result of the new + (rx/map snap/correct-snap-point) + + (rx/map (fn [move-vec] {:v2 [{:type :move :vector move-vec}]})) + + (rx/map (partial set-modifiers ids)) + (rx/take-until stopper))) (rx/of (dwu/start-undo-transaction) (calculate-frame-for-move ids) @@ -767,18 +822,22 @@ page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) frame-id (ctst/top-nested-frame objects position) + layout? (get-in objects [frame-id :layout]) lookup (d/getf objects) + shapes (->> ids (cph/clean-loops objects) (keep lookup)) + moving-shapes - (->> ids - (cph/clean-loops objects) - (keep lookup) - (remove #(= (:frame-id %) frame-id))) + (cond->> shapes + (not layout?) + (remove #(= (:frame-id %) frame-id))) + + drop-index (when layout? (gsl/get-drop-index frame-id objects position)) changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) - (pcb/change-parent frame-id moving-shapes))] + (pcb/change-parent frame-id moving-shapes drop-index))] (when-not (empty? changes) (rx/of (dch/commit-changes changes) diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 2e71107b36..836114c514 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -12,7 +12,9 @@ others are defined using a generic wrapper implemented in common." (:require + [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] + [app.common.uuid :as uuid] [app.main.ui.context :as ctx] [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.image :as image] @@ -55,33 +57,36 @@ (mf/deps objects) #(cph/objects-by-frame objects))] - [:& (mf/provider ctx/active-frames) {:value active-frames} - ;; Render font faces only for shapes that are part of the root - ;; frame but don't belongs to any other frame. - (let [xform (comp - (remove cph/frame-shape?) - (mapcat #(cph/get-children-with-self objects (:id %))))] - [:& ff/fontfaces-style {:shapes (into [] xform shapes)}]) + [:g {:id (dm/str "shape-" uuid/zero)} + [:& (mf/provider ctx/active-frames) {:value active-frames} + ;; Render font faces only for shapes that are part of the root + ;; frame but don't belongs to any other frame. + (let [xform (comp + (remove cph/frame-shape?) + (mapcat #(cph/get-children-with-self objects (:id %))))] + [:& ff/fontfaces-style {:shapes (into [] xform shapes)}]) - (for [shape shapes] - (cond - (not (cph/frame-shape? shape)) - [:& shape-wrapper - {:shape shape - :key (:id shape)}] + [:g.frame-children + (for [shape shapes] + [:g.ws-shape-wrapper + (cond + (not (cph/frame-shape? shape)) + [:& shape-wrapper + {:shape shape + :key (:id shape)}] - (cph/root-frame? shape) - [:& root-frame-wrapper - {:shape shape - :key (:id shape) - :objects (get frame-objects (:id shape)) - :thumbnail? (not (contains? active-frames (:id shape)))}] + (cph/root-frame? shape) + [:& root-frame-wrapper + {:shape shape + :key (:id shape) + :objects (get frame-objects (:id shape)) + :thumbnail? (not (contains? active-frames (:id shape)))}] - :else - [:& nested-frame-wrapper - {:shape shape - :key (:id shape) - :objects (get frame-objects (:id shape))}]))])) + :else + [:& nested-frame-wrapper + {:shape shape + :key (:id shape) + :objects (get frame-objects (:id shape))}])])]]])) (mf/defc shape-wrapper {::mf/wrap [#(mf/memo' % (mf/check-props ["shape"]))] @@ -98,20 +103,21 @@ opts #js {:shape shape :thumbnail? thumbnail?}] (when (and (some? shape) (not (:hidden shape))) - (case (:type shape) - :path [:> path/path-wrapper opts] - :text [:> text/text-wrapper opts] - :group [:> group-wrapper opts] - :rect [:> rect-wrapper opts] - :image [:> image-wrapper opts] - :circle [:> circle-wrapper opts] - :svg-raw [:> svg-raw-wrapper opts] - :bool [:> bool-wrapper opts] + [:g.ws-shape-wrapper + (case (:type shape) + :path [:> path/path-wrapper opts] + :text [:> text/text-wrapper opts] + :group [:> group-wrapper opts] + :rect [:> rect-wrapper opts] + :image [:> image-wrapper opts] + :circle [:> circle-wrapper opts] + :svg-raw [:> svg-raw-wrapper opts] + :bool [:> bool-wrapper opts] - ;; Only used when drawing a new frame. - :frame [:> nested-frame-wrapper opts] + ;; Only used when drawing a new frame. + :frame [:> nested-frame-wrapper opts] - nil)))) + nil)]))) (def group-wrapper (group/group-wrapper-factory shape-wrapper)) (def svg-raw-wrapper (svg-raw/svg-raw-wrapper-factory shape-wrapper)) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index a3745f16d2..1de768035e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -24,87 +24,7 @@ [app.main.ui.workspace.shapes.frame.node-store :as fns] [app.main.ui.workspace.shapes.frame.thumbnail-render :as ftr] [beicon.core :as rx] - [rumext.v2 :as mf] - - [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.layout :as gsl] - [app.main.data.workspace.state-helpers :as wsh] - [app.main.store :as st])) - -(mf/defc debug-layout - {::mf/wrap-props false} - [props] - - (let [shape (unchecked-get props "shape") - children (-> (wsh/lookup-page-objects @st/state) - (cph/get-immediate-children (:id shape))) - - layout-data (gsl/calc-layout-data shape children) - - drop-areas - (gsl/drop-areas shape layout-data children) - - ] - - [:g.debug-layout {:pointer-events "none"} - (for [[idx drop-area] (d/enumerate drop-areas)] - [:rect {:x (:x drop-area) - :y (:y drop-area) - :width (:width drop-area) - :height (:height drop-area) - :style {:fill "blue" - :fill-opacity 0.3 - :stroke "red" - :stroke-width 1 - :stroke-dasharray "3 6"}}]) - - - #_(for [[idx layout-line] (d/enumerate (:layout-lines layout-data))] - (let [col? (gsl/col? shape) - row? (gsl/row? shape) - h-center? (and row? (gsl/h-center? shape)) - h-end? (and row? (gsl/h-end? shape)) - v-center? (and col? (gsl/v-center? shape)) - v-end? (and row? (gsl/v-end? shape)) - - line-width - (+ (-> layout-line :line-width) - (:margin-x shape) - (if col? - (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) - 0)) - - line-height - (+ (-> layout-line :line-height) - (:margin-y shape) - (if row? - (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) - 0)) - ] - [:g {:key (dm/str "line-" idx)} - [:rect {:x (- (-> layout-line :start-p :x) - (cond - h-center? (/ line-width 2) - h-end? line-width - :else 0)) - :y (- (-> layout-line :start-p :y) - (cond - v-center? (/ line-height 2) - v-end? line-height - :else 0)) - :width line-width - :height line-height - :style {:fill "blue" - :fill-opacity 0.3} - }] - #_[:line {:x1 (-> layout-line :start-p :x) - :y1 (-> layout-line :start-p :y) - :x2 (+ (-> layout-line :start-p :x) (if col? line-width 0)) - :y2 (+ (-> layout-line :start-p :y) (if row? line-height 0)) - :transform (gsh/transform-str shape) - :style {:fill "none" - :stroke "red" - :stroke-width 2}}]]))])) + [rumext.v2 :as mf])) (defn frame-shape-factory [shape-wrapper] @@ -119,12 +39,9 @@ childs-ref (mf/use-memo (mf/deps (:id shape)) #(refs/children-objects (:id shape))) childs (mf/deref childs-ref)] - [:* - [:& (mf/provider embed/context) {:value true} - [:& shape-container {:shape shape :ref ref :disable-shadows? (cph/root-frame? shape)} - [:& frame-shape {:shape shape :childs childs} ]]] - - #_[:& debug-layout {:shape shape}]])))) + [:& (mf/provider embed/context) {:value true} + [:& shape-container {:shape shape :ref ref :disable-shadows? (cph/root-frame? shape)} + [:& frame-shape {:shape shape :childs childs} ]]])))) (defn check-props [new-props old-props] diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 0ea026601e..6d95966d4d 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -13,8 +13,10 @@ [app.common.geom.shapes :as gsh] [app.common.types.modifiers :as ctm] [app.main.store :as st] + [app.main.ui.hooks :as hooks] [app.main.ui.workspace.viewport.utils :as vwu] [app.util.dom :as dom] + [app.util.globals :as globals] [rumext.v2 :as mf])) (defn- transform-no-resize @@ -78,13 +80,20 @@ [result width height])) +(defn get-shape-node + ([id] + (get-shape-node js/document id)) + + ([base-node id] + (if (= (.-id base-node) (dm/str "shape-" id)) + base-node + (dom/query base-node (dm/str "#shape-" id))))) + (defn get-nodes "Retrieve the DOM nodes to apply the matrix transformation" [base-node {:keys [id type masked-group?] :as shape}] (when (some? base-node) - (let [shape-node (if (= (.-id base-node) (dm/str "shape-" id)) - base-node - (dom/query base-node (dm/str "#shape-" id))) + (let [shape-node (get-shape-node base-node id) frame? (= :frame type) group? (= :group type) @@ -164,7 +173,7 @@ (defn set-transform-att! [node att value] - + (let [old-att (dom/get-attribute node (dm/str "data-old-" att)) new-value (if (some? old-att) (dm/str value " " old-att) @@ -269,6 +278,33 @@ (ctm/modifiers->transform center modifiers))) modifiers)))) + structure-changes + (mf/use-memo + (mf/deps modifiers) + (fn [] + (into {} + (comp (filter (fn [[_ val]] (-> val :modifiers :v3 some?))) + (map (fn [[key val]] + [key (-> val :modifiers :v3)]))) + + modifiers))) + + structure-changes (hooks/use-equal-memo structure-changes) + + add-children + (mf/use-memo + (mf/deps structure-changes) + (fn [] + (into [] + (mapcat (fn [[frame-id changes]] + (->> changes + (filter (fn [{:keys [type]}] (= type :add-children))) + (mapcat (fn [{:keys [value]}] + (->> value (map (fn [id] {:frame frame-id :shape id})))))))) + structure-changes))) + + add-children-prev (hooks/use-previous add-children) + shapes (mf/use-memo (mf/deps transforms) @@ -280,6 +316,31 @@ prev-modifiers (mf/use-var nil) prev-transforms (mf/use-var nil)] + (mf/use-effect + (mf/deps add-children) + (fn [] + (doseq [{:keys [frame shape]} add-children-prev] + (let [frame-node (get-shape-node node frame) + shape-node (get-shape-node shape) + mirror-node (dom/query frame-node (dm/fmt ".mirror-shape[href='#shape-%'" shape))] + (when mirror-node (.remove mirror-node)) + (dom/remove-attribute! (dom/get-parent shape-node) "display"))) + + (doseq [{:keys [frame shape]} add-children] + (let [frame-node (get-shape-node node frame) + shape-node (get-shape-node shape) + + use-node + (.createElementNS globals/document "http://www.w3.org/2000/svg" "use") + + contents-node + (or (dom/query frame-node ".frame-children") frame-node)] + + (dom/set-attribute! use-node "href" (dm/fmt "#shape-%" shape)) + (dom/add-class! use-node "mirror-shape") + (dom/append-child! contents-node use-node) + (dom/set-attribute! (dom/get-parent shape-node) "display" "none"))))) + (mf/use-layout-effect (mf/deps transforms) (fn [] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 4c219d3acd..2896f6d4b5 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -23,6 +23,7 @@ [app.main.ui.workspace.shapes.text.viewport-texts-html :as stvh] [app.main.ui.workspace.viewport.actions :as actions] [app.main.ui.workspace.viewport.comments :as comments] + [app.main.ui.workspace.viewport.debug :as wvd] [app.main.ui.workspace.viewport.drawarea :as drawarea] [app.main.ui.workspace.viewport.frame-grid :as frame-grid] [app.main.ui.workspace.viewport.gradients :as gradients] @@ -41,85 +42,7 @@ [app.main.ui.workspace.viewport.widgets :as widgets] [beicon.core :as rx] [debug :refer [debug?]] - [rumext.v2 :as mf] - - [app.common.uuid :as uuid] - [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.layout :as gsl] - [app.main.data.workspace.state-helpers :as wsh] - [app.main.store :as st] - - )) - -(mf/defc debug-layout - {::mf/wrap-props false} - [props] - - (let [shape (unchecked-get props "shape") - objects (unchecked-get props "objects") - children (cph/get-immediate-children objects (:id shape)) - layout-data (gsl/calc-layout-data shape children) - drop-areas (gsl/drop-areas shape layout-data children)] - - [:g.debug-layout {:pointer-events "none"} - (for [[idx drop-area] (d/enumerate drop-areas)] - [:rect {:x (:x drop-area) - :y (:y drop-area) - :width (:width drop-area) - :height (:height drop-area) - :style {:fill "blue" - :fill-opacity 0.3 - :stroke "red" - :stroke-width 1 - :stroke-dasharray "3 6"}}]) - - - #_(for [[idx layout-line] (d/enumerate (:layout-lines layout-data))] - (let [col? (gsl/col? shape) - row? (gsl/row? shape) - h-center? (and row? (gsl/h-center? shape)) - h-end? (and row? (gsl/h-end? shape)) - v-center? (and col? (gsl/v-center? shape)) - v-end? (and row? (gsl/v-end? shape)) - - line-width - (+ (-> layout-line :line-width) - (:margin-x shape) - (if col? - (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) - 0)) - - line-height - (+ (-> layout-line :line-height) - (:margin-y shape) - (if row? - (* (:layout-gap layout-line) (dec (-> layout-line :num-children))) - 0)) - ] - [:g {:key (dm/str "line-" idx)} - [:rect {:x (- (-> layout-line :start-p :x) - (cond - h-center? (/ line-width 2) - h-end? line-width - :else 0)) - :y (- (-> layout-line :start-p :y) - (cond - v-center? (/ line-height 2) - v-end? line-height - :else 0)) - :width line-width - :height line-height - :style {:fill "blue" - :fill-opacity 0.3} - }] - #_[:line {:x1 (-> layout-line :start-p :x) - :y1 (-> layout-line :start-p :y) - :x2 (+ (-> layout-line :start-p :x) (if col? line-width 0)) - :y2 (+ (-> layout-line :start-p :y) (if row? line-height 0)) - :transform (gsh/transform-str shape) - :style {:fill "none" - :stroke "red" - :stroke-width 2}}]]))])) + [rumext.v2 :as mf])) ;; --- Viewport @@ -493,15 +416,12 @@ :hover-frame frame-parent :disabled-guides? disabled-guides?}]) - (let [selected-frame (when (= 1 (count selected-shapes)) - (let [selected-shape (get objects-modified (first selected))] - (when (= :frame (:type selected-shape)) - selected-shape))) + ;; DEBUG LAYOUT DROP-ZONES + (when (debug? :layout-drop-zones) + [:& wvd/debug-layout {:selected-shapes selected-shapes + :objects objects-modified + :hover-top-frame-id @hover-top-frame-id}]) - top-frame (or selected-frame (get objects-modified @hover-top-frame-id))] - (when (and top-frame (not= uuid/zero top-frame) (:layout top-frame)) - [:& debug-layout {:shape top-frame - :objects objects-modified}])) (when show-selection-handlers? [:g.selection-handlers {:clipPath "url(#clip-handlers)"} diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs new file mode 100644 index 0000000000..0f23f9eee6 --- /dev/null +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -0,0 +1,54 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.workspace.viewport.debug + (:require + [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.layout :as gsl] + [app.common.pages.helpers :as cph] + [rumext.v2 :as mf])) + +(mf/defc debug-layout + "Debug component to show the auto-layout drop areas" + {::mf/wrap-props false} + [props] + + (let [objects (unchecked-get props "objects") + selected-shapes (unchecked-get props "selected-shapes") + hover-top-frame-id (unchecked-get props "hover-top-frame-id") + + selected-frame + (when (and (= (count selected-shapes) 1) (= :frame (-> selected-shapes first :type))) + (first selected-shapes)) + + shape (or selected-frame (get objects hover-top-frame-id))] + + (when (and shape (:layout shape)) + (let [children (cph/get-immediate-children objects (:id shape)) + layout-data (gsl/calc-layout-data shape children) + drop-areas (gsl/drop-areas shape layout-data children)] + [:g.debug-layout {:pointer-events "none" + :transform (gsh/transform-str shape)} + (for [[idx drop-area] (d/enumerate drop-areas)] + [:g.drop-area {:key (dm/str "drop-area-" idx)} + [:rect {:x (:x drop-area) + :y (:y drop-area) + :width (:width drop-area) + :height (:height drop-area) + :style {:fill "blue" + :fill-opacity 0.3 + :stroke "red" + :stroke-width 1 + :stroke-dasharray "3 6"}}] + [:text {:x (:x drop-area) + :y (:y drop-area) + :width (:width drop-area) + :height (:height drop-area) + :alignment-baseline "hanging" + :fill "black"} + (:index drop-area)]])])))) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index cc6c7bf8db..2ea2310dab 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -11,6 +11,7 @@ [app.common.pages :as cp] [app.common.pages.helpers :as cph] [app.common.types.shape-tree :as ctt] + [app.common.uuid :as uuid] [app.main.data.shortcuts :as dsc] [app.main.data.workspace :as dw] [app.main.data.workspace.path.shortcuts :as psc] @@ -28,8 +29,7 @@ [beicon.core :as rx] [debug :refer [debug?]] [goog.events :as events] - [rumext.v2 :as mf] - [app.common.types.shape-tree :as ctst]) + [rumext.v2 :as mf]) (:import goog.events.EventType)) (defn setup-dom-events [viewport-ref zoom disable-paste in-viewport?] @@ -217,7 +217,7 @@ (get objects))] (reset! hover hover-shape) (reset! hover-ids ids) - (reset! hover-top-frame-id (ctst/top-nested-frame objects (deref last-point-ref)))))))) + (reset! hover-top-frame-id (ctt/top-nested-frame objects (deref last-point-ref)))))))) (defn setup-viewport-modifiers [modifiers objects] @@ -225,7 +225,7 @@ (mf/use-memo (mf/deps objects) #(ctt/get-root-shapes-ids objects)) - modifiers (select-keys modifiers root-frame-ids)] + modifiers (select-keys modifiers (conj root-frame-ids uuid/zero))] (sfd/use-dynamic-modifiers objects globals/document modifiers))) (defn inside-vbox [vbox objects frame-id] diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 96c9866049..3a3b9f3669 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -67,6 +67,9 @@ ;; Disable frame thumbnails :disable-frame-thumbnails + + ;; Enable a widget to show the auto-layout drop-zones + :layout-drop-zones }) ;; These events are excluded when we activate the :events flag @@ -294,9 +297,16 @@ num-nodes (->> (dom/seq-nodes root-node) count)] #js {:number num-nodes})) -#_(defn modif->js +(defn modif->js [modif-tree objects] (clj->js (into {} (map (fn [[k v]] [(get-in objects [k :name]) v])) modif-tree))) + +(defn ^:export dump-modifiers + [] + (let [page-id (get @st/state :current-page-id) + objects (get-in @st/state [:workspace-data :pages-index page-id :objects])] + (.log js/console (modif->js (:workspace-modifiers @st/state) objects))) + nil) From 11f347941eae4bde24ba0ad053b25d6acd176c03 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 19 Oct 2022 13:27:44 +0200 Subject: [PATCH 202/682] :sparkles: Refactor for new modifiers --- .../app/common/geom/shapes/constraints.cljc | 78 ++--- common/src/app/common/geom/shapes/layout.cljc | 185 +++------- .../src/app/common/geom/shapes/modifiers.cljc | 187 +--------- .../common/geom/shapes/pixel_precision.cljc | 53 +++ common/src/app/common/geom/shapes/points.cljc | 61 ++++ .../app/common/geom/shapes/transforms.cljc | 163 +-------- common/src/app/common/types/modifiers.cljc | 325 +++++++++++------- .../app/main/data/workspace/drawing/box.cljs | 5 +- .../src/app/main/data/workspace/shapes.cljs | 1 - .../src/app/main/data/workspace/texts.cljs | 8 +- .../app/main/data/workspace/transforms.cljs | 88 ++--- .../src/app/main/ui/workspace/shapes.cljs | 2 +- .../shapes/frame/dynamic_modifiers.cljs | 104 +----- .../shapes/text/viewport_texts_html.cljs | 7 - .../app/main/ui/workspace/viewport/debug.cljs | 2 +- 15 files changed, 445 insertions(+), 824 deletions(-) create mode 100644 common/src/app/common/geom/shapes/pixel_precision.cljc create mode 100644 common/src/app/common/geom/shapes/points.cljc diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 2e9aa8a9ba..ce4171d026 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -6,13 +6,13 @@ (ns app.common.geom.shapes.constraints (:require - [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.intersect :as gsi] [app.common.geom.shapes.rect :as gre] [app.common.geom.shapes.transforms :as gst] [app.common.math :as mth] + [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) ;; Auxiliary methods to work in an specifica axis @@ -152,8 +152,7 @@ end-angl (gpt/angle-with-other end-before end-after) target-end (if (mth/close? end-angl 180) (- (gpt/length end-before)) (gpt/length end-before)) disp-vector-end (gpt/subtract end-after (gpt/scale (gpt/unit end-after) target-end))] - [{:type :move - :vector disp-vector-end}])) + (ctm/move disp-vector-end))) (defmethod constraint-modifier :fixed [_ axis child-points-before parent-points-before child-points-after parent-points-after transformed-parent] @@ -177,11 +176,7 @@ scale (* resize-sign (/ (gpt/length after-vec) (gpt/length before-vec))) ] - [{:type :resize - :vector (get-scale axis scale) - :origin c0 - :transform (:transform transformed-parent) - :transform-inverse (:transform-inverse transformed-parent)}])) + (ctm/resize (get-scale axis scale) c0 (:transform transformed-parent) (:transform-inverse transformed-parent)))) (defmethod constraint-modifier :center [_ axis child-points-before parent-points-before child-points-after parent-points-after] @@ -190,8 +185,7 @@ center-angl (gpt/angle-with-other center-before center-after) target-center (if (mth/close? center-angl 180) (- (gpt/length center-before)) (gpt/length center-before)) disp-vector-center (gpt/subtract center-after (gpt/scale (gpt/unit center-after) target-center))] - [{:type :move - :vector disp-vector-center}])) + (ctm/move disp-vector-center))) (defmethod constraint-modifier :default [_ _ _ _ _] []) @@ -222,29 +216,6 @@ :top :scale))) -#_(defn clean-modifiers - "Remove redundant modifiers" - [{:keys [displacement resize-vector resize-vector-2] :as modifiers}] - - (cond-> modifiers - ;; Displacement with value 0. We don't move in any direction - (and (some? displacement) - (mth/almost-zero? (:e displacement)) - (mth/almost-zero? (:f displacement))) - (dissoc :displacement) - - ;; Resize with value very close to 1 means no resize - (and (some? resize-vector) - (mth/almost-zero? (- 1.0 (:x resize-vector))) - (mth/almost-zero? (- 1.0 (:y resize-vector)))) - (dissoc :resize-origin :resize-vector) - - (and (some? resize-vector) - (mth/almost-zero? (- 1.0 (:x resize-vector-2))) - (mth/almost-zero? (- 1.0 (:y resize-vector-2)))) - (dissoc :resize-origin-2 :resize-vector-2))) - - (defn bounding-box-parent-transform "Returns a bounding box for the child in the same coordinate system as the parent. @@ -259,36 +230,27 @@ (defn normalize-modifiers "Before aplying constraints we need to remove the deformation caused by the resizing of the parent" - [constraints-h constraints-v modifiers child parent transformed-child transformed-parent] + [constraints-h constraints-v modifiers child parent transformed-child {:keys [transform transform-inverse] :as transformed-parent}] (let [child-bb-before (gst/parent-coords-rect child parent) child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) scale-x (/ (:width child-bb-before) (:width child-bb-after)) - scale-y (/ (:height child-bb-before) (:height child-bb-after))] + scale-y (/ (:height child-bb-before) (:height child-bb-after)) - (-> modifiers - (update :v2 #(cond-> % - (not= :scale constraints-h) - (conj - ;; This resize will leave the shape in its original position relative to the parent - {:type :resize - :transform (:transform transformed-parent) - :transform-inverse (:transform-inverse transformed-parent) - :origin (-> transformed-parent :points (nth 0)) - :vector (gpt/point scale-x 1)}) + ;; TODO LAYOUT: Is the first always the origin? + resize-origin (-> transformed-parent :points first)] - (not= :scale constraints-v) - (conj - {:type :resize - :transform (:transform transformed-parent) - :transform-inverse (:transform-inverse transformed-parent) - :origin (-> transformed-parent :points (nth 0)) - :vector (gpt/point 1 scale-y)})))))) + (cond-> modifiers + (not= :scale constraints-h) + (ctm/set-resize (gpt/point scale-x 1) resize-origin transform transform-inverse) + + (not= :scale constraints-v) + (ctm/set-resize (gpt/point 1 scale-y) resize-origin transform transform-inverse)))) (defn calc-child-modifiers [parent child modifiers ignore-constraints transformed-parent] - (let [modifiers (select-keys modifiers [:v2]) + (let [modifiers (ctm/select-child-modifiers modifiers) constraints-h (if-not ignore-constraints @@ -306,12 +268,12 @@ (let [transformed-child (gst/transform-shape child modifiers) modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child transformed-parent) - tranformed-child-2 (gst/transform-shape child modifiers) + transformed-child (gst/transform-shape child modifiers) parent-points-before (:points parent) child-points-before (bounding-box-parent-transform child parent) parent-points-after (:points transformed-parent) - child-points-after (bounding-box-parent-transform tranformed-child-2 transformed-parent) + child-points-after (bounding-box-parent-transform transformed-child transformed-parent) modifiers-h (constraint-modifier (constraints-h const->type+axis) :x child-points-before parent-points-before @@ -323,6 +285,6 @@ child-points-after parent-points-after transformed-parent)] - (update modifiers :v2 d/concat-vec modifiers-h modifiers-v))))) - - + (-> modifiers + (ctm/add-modifiers modifiers-h) + (ctm/add-modifiers modifiers-v)))))) diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/layout.cljc index a5f0d79c54..d3282b357e 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/layout.cljc @@ -10,9 +10,11 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] + [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.rect :as gsr] [app.common.geom.shapes.transforms :as gst] - [app.common.pages.helpers :as cph])) + [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm])) ;; :layout ;; true if active, false if not ;; :layout-dir ;; :right, :left, :top, :bottom @@ -56,107 +58,18 @@ [{:keys [layout-v-orientation]}] (= layout-v-orientation :bottom)) -(defn add-padding [transformed-rect {:keys [layout-padding-type layout-padding]}] - (let [{:keys [p1 p2 p3 p4]} layout-padding - [p1 p2 p3 p4] - (if (= layout-padding-type :multiple) - [p1 p2 p3 p4] - [p1 p1 p1 p1])] - - (-> transformed-rect - (update :y + p1) - (update :width - p2 p3) - (update :x + p3) - (update :height - p1 p4)))) - -;; FUNCTIONS TO WORK WITH POINTS SQUARES - -(defn origin - [points] - (nth points 0)) - -(defn start-hv - "Horizontal vector from the origin with a magnitude `val`" - [[p0 p1 _ _] val] - (-> (gpt/to-vec p0 p1) - (gpt/unit) - (gpt/scale val))) - -(defn end-hv - "Horizontal vector from the oposite to the origin in the x axis with a magnitude `val`" - [[p0 p1 _ _] val] - (-> (gpt/to-vec p1 p0) - (gpt/unit) - (gpt/scale val))) - -(defn start-vv - "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" - [[p0 _ _ p3] val] - (-> (gpt/to-vec p0 p3) - (gpt/unit) - (gpt/scale val))) - -(defn end-vv - "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" - [[p0 _ _ p3] val] - (-> (gpt/to-vec p3 p0) - (gpt/unit) - (gpt/scale val))) - -;;(defn start-hp -;; [[p0 _ _ _ :as points] val] -;; (gpt/add p0 (start-hv points val))) -;; -;;(defn end-hp -;; "Horizontal Vector from the oposite to the origin in the x axis with a magnitude `val`" -;; [[_ p1 _ _ :as points] val] -;; (gpt/add p1 (end-hv points val))) -;; -;;(defn start-vp -;; "Vertical Vector from the oposite to the origin in the x axis with a magnitude `val`" -;; [[p0 _ _ _ :as points] val] -;; (gpt/add p0 (start-vv points val))) -;; -;;(defn end-vp -;; "Vertical Vector from the oposite to the origin in the x axis with a magnitude `val`" -;; [[_ _ p3 _ :as points] val] -;; (gpt/add p3 (end-vv points val))) - -(defn width-points - [[p0 p1 _ _]] - (gpt/length (gpt/to-vec p0 p1))) - -(defn height-points - [[p0 _ _ p3]] - (gpt/length (gpt/to-vec p0 p3))) - -(defn pad-points - [[p0 p1 p2 p3 :as points] pad-top pad-right pad-bottom pad-left] - (let [top-v (start-vv points pad-top) - right-v (end-hv points pad-right) - bottom-v (end-vv points pad-bottom) - left-v (start-hv points pad-left)] - - [(-> p0 (gpt/add left-v) (gpt/add top-v)) - (-> p1 (gpt/add right-v) (gpt/add top-v)) - (-> p2 (gpt/add right-v) (gpt/add bottom-v)) - (-> p3 (gpt/add left-v) (gpt/add bottom-v))])) - -;;;; - - (defn calc-layout-lines [{:keys [layout-gap layout-wrap-type] :as parent} children layout-bounds] (let [wrap? (= layout-wrap-type :wrap) - layout-width (width-points layout-bounds) - layout-height (height-points layout-bounds) + layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) reduce-fn (fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child] (let [child-bounds (gst/parent-coords-points child parent) - child-width (width-points child-bounds) - child-height (height-points child-bounds) + child-width (gpo/width-points child-bounds) + child-height (gpo/height-points child-bounds) col? (col? parent) row? (row? parent) @@ -212,8 +125,8 @@ (defn calc-layout-lines-position [{:keys [layout-gap] :as parent} layout-bounds layout-lines] - (let [layout-width (width-points layout-bounds) - layout-height (height-points layout-bounds) + (let [layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) row? (row? parent) col? (col? parent) space-between? (= :space-between (:layout-type parent)) @@ -225,16 +138,16 @@ (letfn [;; short version to not repeat always with all arguments (xv [val] - (start-hv layout-bounds val)) + (gpo/start-hv layout-bounds val)) ;; short version to not repeat always with all arguments (yv [val] - (start-vv layout-bounds val)) + (gpo/start-vv layout-bounds val)) (get-base-line [total-width total-height] - (cond-> (origin layout-bounds) + (cond-> (gpo/origin layout-bounds) (and row? h-center?) (gpt/add (xv (/ (- layout-width total-width) 2))) @@ -348,8 +261,8 @@ layout-bounds {:keys [num-children line-width line-height child-fill?] :as line-data}] - (let [width (width-points layout-bounds) - height (height-points layout-bounds) + (let [width (gpo/width-points layout-bounds) + height (gpo/height-points layout-bounds) layout-gap (cond @@ -396,8 +309,8 @@ v-end? (v-end? parent) points (:points parent) - xv (partial start-hv points) - yv (partial start-vv points) + xv (partial gpo/start-hv points) + yv (partial gpo/start-vv points) corner-p (cond-> start-p @@ -447,26 +360,18 @@ (cond (and (col? parent) (= :fill layout-h-behavior) child-fill?) - (let [layout-width (width-points layout-bounds) + (let [layout-width (gpo/width-points layout-bounds) fill-space (- layout-width line-width (* layout-gap (dec num-children))) fill-width (/ fill-space (:num-child-fill layout-data)) fill-scale (/ fill-width child-width)] + {:width fill-width - :modifiers [{:type :resize - :origin child-origin - :transform transform - :transform-inverse transform-inverse - :vector (gpt/point fill-scale 1)}]}) + :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) (and (row? parent) (= :fill layout-h-behavior) line-fill?) (let [fill-scale (/ line-width child-width)] {:width line-width - :modifiers [{:type :resize - :origin child-origin - :transform transform - :transform-inverse transform-inverse - :vector (gpt/point fill-scale 1)}]}) - )) + :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}))) (defn calc-fill-height-data "Calculates the size and modifiers for the height of an auto-fill child" @@ -477,43 +382,33 @@ (cond (and (row? parent) (= :fill layout-v-behavior) child-fill?) - (let [layout-height (height-points layout-bounds) + (let [layout-height (gpo/height-points layout-bounds) fill-space (- layout-height line-height (* layout-gap (dec num-children))) fill-height (/ fill-space (:num-child-fill layout-data)) fill-scale (/ fill-height child-height)] {:height fill-height - :modifiers [{:type :resize - :origin child-origin - :transform transform - :transform-inverse transform-inverse - :vector (gpt/point 1 fill-scale)}]}) + :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) (and (col? parent) (= :fill layout-v-behavior) line-fill?) (let [fill-scale (/ line-height child-height)] {:height line-height - :modifiers [{:type :resize - :origin child-origin - :transform transform - :transform-inverse transform-inverse - :vector (gpt/point 1 fill-scale)}]}) - )) + :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))) (defn normalize-child-modifiers "Apply the modifiers and then normalized them against the parent coordinates" - [parent child modifiers transformed-parent] + [parent child modifiers {:keys [transform transform-inverse] :as transformed-parent}] (let [transformed-child (gst/transform-shape child modifiers) child-bb-before (gst/parent-coords-rect child parent) child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) scale-x (/ (:width child-bb-before) (:width child-bb-after)) - scale-y (/ (:height child-bb-before) (:height child-bb-after))] + scale-y (/ (:height child-bb-before) (:height child-bb-after)) + + resize-origin (-> transformed-parent :points first) ;; TODO LAYOUT: IS always the origin?n + resize-vector (gpt/point scale-x scale-y)] (-> modifiers - (update :v2 #(conj % - {:type :resize - :transform (:transform transformed-parent) - :transform-inverse (:transform-inverse transformed-parent) - :origin (-> transformed-parent :points (nth 0)) - :vector (gpt/point scale-x scale-y)}))))) + (ctm/select-child-modifiers) + (ctm/set-resize resize-vector resize-origin transform transform-inverse)))) (defn calc-layout-data "Digest the layout data to pass it to the constrains" @@ -529,7 +424,7 @@ ;; Normalize the points to remove flips points (gst/parent-coords-points parent parent) - layout-bounds (pad-points points pad-top pad-right pad-bottom pad-left) + layout-bounds (gpo/pad-points points pad-top pad-right pad-bottom pad-left) ;; Reverse reverse? (or (= :left layout-dir) (= :bottom layout-dir)) @@ -549,9 +444,9 @@ [parent child layout-line] (let [child-bounds (gst/parent-coords-points child parent) - child-origin (origin child-bounds) - child-width (width-points child-bounds) - child-height (height-points child-bounds) + child-origin (gpo/origin child-bounds) + child-width (gpo/width-points child-bounds) + child-height (gpo/height-points child-bounds) fill-width (calc-fill-width-data parent child child-origin child-width layout-line) fill-height (calc-fill-height-data parent child child-origin child-height layout-line) @@ -564,15 +459,15 @@ move-vec (gpt/to-vec child-origin corner-p) modifiers - (-> [] - (cond-> fill-width (d/concat-vec (:modifiers fill-width))) - (cond-> fill-height (d/concat-vec (:modifiers fill-height))) - (conj {:type :move :vector move-vec}))] + (-> (ctm/empty-modifiers) + (cond-> fill-width (ctm/add-modifiers (:modifiers fill-width))) + (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height))) + (ctm/set-move move-vec))] [modifiers layout-line])) -(defn drop-areas +(defn layout-drop-areas [{:keys [margin-x margin-y] :as frame} layout-data children] (let [col? (col? frame) @@ -730,6 +625,6 @@ position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame)) children (cph/get-immediate-children objects frame-id) layout-data (calc-layout-data frame children) - drop-areas (drop-areas frame layout-data children) + drop-areas (layout-drop-areas frame layout-data children) area (d/seek #(gsr/contains-point? % position) drop-areas)] (:index area))) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 6cb5f0d6f9..3660aec608 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -7,118 +7,22 @@ (ns app.common.geom.shapes.modifiers (:require [app.common.data :as d] - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] - [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.layout :as gcl] - [app.common.geom.shapes.rect :as gpr] + [app.common.geom.shapes.pixel-precision :as gpp] [app.common.geom.shapes.transforms :as gtr] - [app.common.math :as mth] [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) -;; TODO LAYOUT: ADAPT TO NEW MODIFIERS -(defn set-pixel-precision - "Adjust modifiers so they adjust to the pixel grid" - [modifiers shape] - - (if (and (some? (:resize-transform modifiers)) - (not (gmt/unit? (:resize-transform modifiers)))) - ;; If we're working with a rotation we don't handle pixel precision because - ;; the transformation won't have the precision anyway - modifiers - - (let [center (gco/center-shape shape) - base-bounds (-> (:points shape) (gpr/points->rect)) - - raw-bounds - (-> (gtr/transform-bounds (:points shape) center modifiers) - (gpr/points->rect)) - - flip-x? (neg? (get-in modifiers [:resize-vector :x])) - flip-y? (or (neg? (get-in modifiers [:resize-vector :y])) - (neg? (get-in modifiers [:resize-vector-2 :y]))) - - path? (= :path (:type shape)) - vertical-line? (and path? (<= (:width raw-bounds) 0.01)) - horizontal-line? (and path? (<= (:height raw-bounds) 0.01)) - - target-width (if vertical-line? - (:width raw-bounds) - (max 1 (mth/round (:width raw-bounds)))) - - target-height (if horizontal-line? - (:height raw-bounds) - (max 1 (mth/round (:height raw-bounds)))) - - target-p (cond-> (gpt/round (gpt/point raw-bounds)) - flip-x? - (update :x + target-width) - - flip-y? - (update :y + target-height)) - - ratio-width (/ target-width (:width raw-bounds)) - ratio-height (/ target-height (:height raw-bounds)) - - modifiers - (-> modifiers - (d/without-nils) - (d/update-in-when - [:resize-vector :x] #(* % ratio-width)) - - ;; If the resize-vector-2 modifier arrives means the resize-vector - ;; will only resize on the x axis - (cond-> (nil? (:resize-vector-2 modifiers)) - (d/update-in-when - [:resize-vector :y] #(* % ratio-height))) - - (d/update-in-when - [:resize-vector-2 :y] #(* % ratio-height))) - - origin (get modifiers :resize-origin) - origin-2 (get modifiers :resize-origin-2) - - resize-v (get modifiers :resize-vector) - resize-v-2 (get modifiers :resize-vector-2) - displacement (get modifiers :displacement) - - target-p-inv - (-> target-p - (gpt/transform - (cond-> (gmt/matrix) - (some? displacement) - (gmt/multiply (gmt/inverse displacement)) - - (and (some? resize-v) (some? origin)) - (gmt/scale (gpt/inverse resize-v) origin) - - (and (some? resize-v-2) (some? origin-2)) - (gmt/scale (gpt/inverse resize-v-2) origin-2)))) - - delta-v (gpt/subtract target-p-inv (gpt/point base-bounds)) - - modifiers - (-> modifiers - (d/update-when :displacement #(gmt/multiply (gmt/translate-matrix delta-v) %)) - (cond-> (nil? (:displacement modifiers)) - (assoc :displacement (gmt/translate-matrix delta-v))))] - modifiers))) - - (defn set-children-modifiers [modif-tree objects parent ignore-constraints snap-pixel?] - ;; TODO LAYOUT: SNAP PIXEL! (letfn [(set-child [transformed-parent _snap-pixel? modif-tree child] (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints transformed-parent) - ;;child-modifiers (cond-> child-modifiers snap-pixel? (set-pixel-precision child)) - ] + child-modifiers (cond-> child-modifiers snap-pixel? (gpp/set-pixel-precision child))] (cond-> modif-tree (not (ctm/empty-modifiers? child-modifiers)) - (update-in [(:id child) :modifiers :v2] d/concat-vec (:v2 child-modifiers)))))] - + (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))] (let [children (map (d/getf objects) (:shapes parent)) modifiers (get-in modif-tree [(:id parent) :modifiers]) transformed-parent (gtr/transform-shape parent modifiers)] @@ -144,7 +48,7 @@ child-modifiers (gcl/normalize-child-modifiers parent child modifiers transformed-parent)] (cond-> modif-tree (not (ctm/empty-modifiers? child-modifiers)) - (update-in [(:id child) :modifiers :v2] d/concat-vec (:v2 child-modifiers))))) + (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers)))) (apply-modifiers [modif-tree child] (let [modifiers (get-in modif-tree [(:id child) :modifiers])] @@ -162,7 +66,7 @@ modif-tree (cond-> modif-tree (d/not-empty? modifiers) - (update-in [(:id child) :modifiers :v2] d/concat-vec modifiers))] + (update-in [(:id child) :modifiers] ctm/add-modifiers modifiers))] [layout-line modif-tree]))] @@ -186,7 +90,7 @@ pending (rest layout-lines) from-idx 0] - + (if (and (some? layout-line) (<= from-idx max-idx)) (let [to-idx (+ from-idx (:num-children layout-line)) children (subvec children from-idx to-idx) @@ -197,73 +101,6 @@ modif-tree))))) -#_(defn set-layout-modifiers' - ;; TODO LAYOUT: SNAP PIXEL! - [modif-tree objects parent _snap-pixel?] - - (letfn [(transform-child [child] - (let [modifiers (get modif-tree (:id child)) - - child - (cond-> child - (some? modifiers) - (-> (merge modifiers) gtr/transform-shape) - - (and (nil? modifiers) (group? child)) - (gtr/apply-group-modifiers objects modif-tree)) - - child - (-> child - (gtr/apply-transform (gmt/transform-in (gco/center-shape parent) (:transform-inverse parent))))] - - child)) - - (set-layout-modifiers [parent transform [layout-data modif-tree] child] - (let [[modifiers layout-data] - (gcl/calc-layout-modifiers parent transform child layout-data) - - modif-tree - (cond-> modif-tree - (d/not-empty? modifiers) - (update-in [(:id child) :modifiers :v2] d/concat-vec modifiers))] - - [layout-data modif-tree]))] - - (let [modifiers (get modif-tree (:id parent)) - shape (-> parent (merge modifiers) gtr/transform-shape) - children (->> (:shapes shape) - (map (d/getf objects)) - (map transform-child)) - - center (gco/center-shape shape) - {:keys [transform transform-inverse]} shape - - shape - (-> shape - (gtr/apply-transform (gmt/transform-in center transform-inverse))) - - transformed-rect (:selrect shape) - - layout-data (gcl/calc-layout-data shape children transformed-rect) - children (into [] (cond-> children (:reverse? layout-data) reverse)) - - max-idx (dec (count children)) - layout-lines (:layout-lines layout-data)] - - (loop [modif-tree modif-tree - layout-line (first layout-lines) - pending (rest layout-lines) - from-idx 0] - (if (and (some? layout-line) (<= from-idx max-idx)) - (let [to-idx (+ from-idx (:num-children layout-line)) - children (subvec children from-idx to-idx) - - [_ modif-tree] - (reduce (partial set-layout-modifiers shape transform) [layout-line modif-tree] children)] - (recur modif-tree (first pending) (rest pending) to-idx)) - - modif-tree))))) - (defn get-first-layout [id objects] @@ -337,8 +174,11 @@ (let [set-modifiers (fn [modif-tree id] - (let [shape (get objects id) - modifiers (cond-> (get-modifier shape) snap-pixel? (set-pixel-precision shape))] + (let [root? (= uuid/zero id) + shape (get objects id) + modifiers (cond-> (get-modifier shape) + (and (not root?) snap-pixel?) + (gpp/set-pixel-precision shape))] (-> modif-tree (assoc id {:modifiers modifiers})))) @@ -349,11 +189,12 @@ (->> shapes-tree (reduce (fn [modif-tree shape] - (let [modifiers (get-in modif-tree [(:id shape) :modifiers]) + (let [root? (= uuid/zero (:id shape)) + modifiers (get-in modif-tree [(:id shape) :modifiers]) has-modifiers? (some? modifiers) is-layout? (layout? shape) is-parent? (or (group? shape) (and (frame? shape) (not (layout? shape)))) - root? (= uuid/zero (:id shape)) + ;; If the current child is inside the layout we ignore the constraints is-inside-layout? (inside-layout? objects shape)] diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc new file mode 100644 index 0000000000..03aa4359a7 --- /dev/null +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -0,0 +1,53 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.geom.shapes.pixel-precision + (:require + [app.common.geom.point :as gpt] + [app.common.geom.shapes.points :as gpo] + [app.common.geom.shapes.rect :as gpr] + [app.common.geom.shapes.transforms :as gtr] + [app.common.math :as mth] + [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm])) + +(defn size-pixel-precision + [modifiers shape] + (let [{:keys [points transform transform-inverse] :as shape} (gtr/transform-shape shape modifiers) + origin (gpo/origin points) + curr-width (gpo/width-points points) + curr-height (gpo/height-points points) + + path? (cph/path-shape? shape) + vertical-line? (and path? (<= curr-width 0.01)) + horizontal-line? (and path? (<= curr-height 0.01)) + + target-width (if vertical-line? curr-width (max 1 (mth/round curr-width))) + target-height (if horizontal-line? curr-height (max 1 (mth/round curr-height))) + + ratio-width (/ target-width curr-width) + ratio-height (/ target-height curr-height) + scalev (gpt/point ratio-width ratio-height)] + (-> modifiers + (ctm/set-resize scalev origin transform transform-inverse)))) + +(defn position-pixel-precision + [modifiers shape] + (let [{:keys [points]} (gtr/transform-shape shape modifiers) + bounds (gpr/points->rect points) + corner (gpt/point bounds) + target-corner (gpt/round corner) + deltav (gpt/to-vec corner target-corner)] + (-> modifiers + (ctm/set-move deltav)))) + +(defn set-pixel-precision + "Adjust modifiers so they adjust to the pixel grid" + [modifiers shape] + + (-> modifiers + (size-pixel-precision shape) + (position-pixel-precision shape))) diff --git a/common/src/app/common/geom/shapes/points.cljc b/common/src/app/common/geom/shapes/points.cljc new file mode 100644 index 0000000000..1f80f28df4 --- /dev/null +++ b/common/src/app/common/geom/shapes/points.cljc @@ -0,0 +1,61 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.geom.shapes.points + (:require + [app.common.geom.point :as gpt])) + +(defn origin + [points] + (nth points 0)) + +(defn start-hv + "Horizontal vector from the origin with a magnitude `val`" + [[p0 p1 _ _] val] + (-> (gpt/to-vec p0 p1) + (gpt/unit) + (gpt/scale val))) + +(defn end-hv + "Horizontal vector from the oposite to the origin in the x axis with a magnitude `val`" + [[p0 p1 _ _] val] + (-> (gpt/to-vec p1 p0) + (gpt/unit) + (gpt/scale val))) + +(defn start-vv + "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" + [[p0 _ _ p3] val] + (-> (gpt/to-vec p0 p3) + (gpt/unit) + (gpt/scale val))) + +(defn end-vv + "Vertical vector from the oposite to the origin in the x axis with a magnitude `val`" + [[p0 _ _ p3] val] + (-> (gpt/to-vec p3 p0) + (gpt/unit) + (gpt/scale val))) + +(defn width-points + [[p0 p1 _ _]] + (gpt/length (gpt/to-vec p0 p1))) + +(defn height-points + [[p0 _ _ p3]] + (gpt/length (gpt/to-vec p0 p3))) + +(defn pad-points + [[p0 p1 p2 p3 :as points] pad-top pad-right pad-bottom pad-left] + (let [top-v (start-vv points pad-top) + right-v (end-hv points pad-right) + bottom-v (end-vv points pad-bottom) + left-v (start-hv points pad-left)] + + [(-> p0 (gpt/add left-v) (gpt/add top-v)) + (-> p1 (gpt/add right-v) (gpt/add top-v)) + (-> p2 (gpt/add right-v) (gpt/add bottom-v)) + (-> p3 (gpt/add left-v) (gpt/add bottom-v))])) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 08bf7383e3..ce888ddf18 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -14,8 +14,6 @@ [app.common.geom.shapes.path :as gpa] [app.common.geom.shapes.rect :as gpr] [app.common.math :as mth] - [app.common.pages.helpers :as cph] - [app.common.text :as txt] [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) @@ -312,6 +310,8 @@ (dissoc :transform :transform-inverse)) (cond-> (some? selrect) (assoc :selrect selrect)) + + ;; TODO LAYOUT: Make sure the order of points is alright (cond-> (d/not-empty? points) (assoc :points points)) (assoc :rotation rotation)))) @@ -376,113 +376,22 @@ (assoc :flip-x (-> mask :flip-x)) (assoc :flip-y (-> mask :flip-y))))) - -#_(defn- set-flip [shape modifiers] - (let [rv1x (or (get-in modifiers [:resize-vector :x]) 1) - rv1y (or (get-in modifiers [:resize-vector :y]) 1) - rv2x (or (get-in modifiers [:resize-vector-2 :x]) 1) - rv2y (or (get-in modifiers [:resize-vector-2 :y]) 1)] - (cond-> shape - (or (neg? rv1x) (neg? rv2x)) - (-> (update :flip-x not) - (update :rotation -)) - (or (neg? rv1y) (neg? rv2y)) - (-> (update :flip-y not) - (update :rotation -))))) - -#_(defn- set-flip-2 [shape transform] - (let [pt-a (gpt/point (:selrect shape)) - pt-b (gpt/point (-> shape :selrect :x2) (-> shape :selrect :y2)) - - shape-transform (:transform shape (gmt/matrix)) - pt-a' (gpt/transform pt-a (gmt/multiply shape-transform transform )) - pt-b' (gpt/transform pt-b (gmt/multiply shape-transform transform )) - - {:keys [x y]} (gpt/to-vec pt-a' pt-b')] - - (cond-> shape - (neg? x) - (-> (update :flip-x not) - (update :rotation -)) - (neg? y) - (-> (update :flip-y not) - (update :rotation -))))) - -#_(defn- apply-displacement [shape] - (let [modifiers (:modifiers shape)] - (if (contains? modifiers :displacement) - (let [mov-vec (-> (gpt/point 0 0) - (gpt/transform (:displacement modifiers))) - shape (move shape mov-vec) - modifiers (dissoc modifiers :displacement)] - (-> shape - (assoc :modifiers modifiers) - (cond-> (empty-modifiers? modifiers) - (dissoc :modifiers)))) - shape))) - -(defn- apply-text-resize - [shape modifiers] - (if (and (= (:type shape) :text) - (:resize-scale-text modifiers)) - (let [merge-attrs (fn [attrs] - (let [font-size (-> (get attrs :font-size 14) - (d/parse-double) - (* (get-in modifiers [:resize-vector :x] 1)) - (* (get-in modifiers [:resize-vector-2 :x] 1)) - (str))] - (d/txt-merge attrs {:font-size font-size})))] - (update shape :content #(txt/transform-nodes - txt/is-text-node? - merge-attrs - %))) - shape)) - -(defn- apply-structure-modifiers - [shape modifiers] - - (let [remove-children - (fn [shapes children-to-remove] - (let [remove? (set children-to-remove)] - (d/removev remove? shapes))) - - apply-modifier - (fn [shape {:keys [type value index]}] - (cond-> shape - (and (= type :add-children) (some? index)) - (update :shapes - (fn [shapes] - (if (vector? shapes) - (cph/insert-at-index shapes index value) - (d/concat-vec shapes value)))) - - (and (= type :add-children) (nil? index)) - (update :shapes d/concat-vec value) - - (= type :remove-children) - (update :shapes remove-children value)))] - - (reduce apply-modifier shape (:v3 modifiers)))) - (defn apply-modifiers [shape modifiers] - (let [center (gco/center-shape shape) - transform (ctm/modifiers->transform center modifiers)] - + (let [transform (ctm/modifiers->transform modifiers)] (cond-> shape - #_(set-flip-2 transform) (and (some? transform) ;; Never transform the root frame (not= uuid/zero (:id shape))) (apply-transform transform) :always - (apply-structure-modifiers modifiers)))) + (ctm/apply-structure-modifiers modifiers)))) (defn apply-objects-modifiers [objects modifiers] (letfn [(process-shape [objects [id modifier]] - (update objects id apply-modifiers (:modifiers modifier)))] + (d/update-when objects id apply-modifiers (:modifiers modifier)))] (reduce process-shape objects modifiers))) (defn transform-shape @@ -495,66 +404,12 @@ ([shape modifiers] (cond-> shape (and (some? modifiers) (not (ctm/empty-modifiers? modifiers))) - (-> (apply-modifiers modifiers) - (apply-text-resize modifiers))))) - -(defn transform-bounds-v2 - [points center modifiers] - (let [transform (ctm/modifiers->transform center {:v2 modifiers}) - result (gco/transform-points points center transform)] - - ;;(.log js/console "??" (str transform) (clj->js result)) - result) - - #_(letfn [(apply-modifier [points {:keys [type vector origin]}] - (case type - :move - (let [displacement (gmt/translate-matrix vector)] - (gco/transform-points points displacement)) - - :resize - (gco/transform-points points origin (gmt/scale-matrix vector)) - - points))] - (->> modifiers - (reduce apply-modifier points)))) + (apply-modifiers modifiers)))) (defn transform-bounds - [points center {:keys [v2 displacement displacement-after resize-transform-inverse resize-vector resize-origin resize-vector-2 resize-origin-2]}] - - ;; FIXME: Improve Performance - (if (some? v2) - (transform-bounds-v2 points center v2) - (let [resize-transform-inverse (or resize-transform-inverse (gmt/matrix)) - - displacement - (when (some? displacement) - (gmt/multiply resize-transform-inverse displacement)) - - resize-origin - (when (some? resize-origin) - (gmt/transform-point-center resize-origin center resize-transform-inverse)) - - resize-origin-2 - (when (some? resize-origin-2) - (gmt/transform-point-center resize-origin-2 center resize-transform-inverse)) - ] - - (if (and (nil? displacement) (nil? resize-origin) (nil? resize-origin-2) (nil? displacement-after)) - points - - (cond-> points - (some? displacement) - (gco/transform-points displacement) - - (some? resize-origin) - (gco/transform-points resize-origin (gmt/scale-matrix resize-vector)) - - (some? resize-origin-2) - (gco/transform-points resize-origin-2 (gmt/scale-matrix resize-vector-2)) - - (some? displacement-after) - (gco/transform-points displacement-after)))))) + [points center modifiers] + (let [transform (ctm/modifiers->transform modifiers)] + (gco/transform-points points center transform))) (defn transform-selrect [selrect modifiers] diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 5c905aeff0..bd0715aecf 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -10,70 +10,168 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] - [app.common.spec :as us])) + [app.common.pages.helpers :as cph] + [app.common.spec :as us] + [app.common.text :as txt])) ;; --- Modifiers -;; The `modifiers` structure contains a list of transformations to -;; do make to a shape, in this order: +;; Moodifiers types +;; - geometry: Geometry +;; * move +;; * resize +;; * rotation +;; - structure-parent: Structure non recursive +;; * add-children +;; * remove-children +;; - structure-child: Structre recursive +;; * scale-content ;; -;; - resize-origin (gpt/point) + resize-vector (gpt/point)q -;; apply a scale vector to all points of the shapes, starting -;; from the origin point. -;; -;; - resize-origin-2 + resize-vector-2 -;; same as the previous one, for cases in that we need to make -;; two vectors from different origin points. -;; -;; - displacement (gmt/matrix) -;; apply a translation matrix to the shape -;; -;; - rotation (gmt/matrix) -;; apply a rotation matrix to the shape -;; -;; - resize-transform (gmt/matrix) + resize-transform-inverse (gmt/matrix) -;; a copy of the rotation matrix currently applied to the shape; -;; this is needed temporarily to apply the resize vectors. -;; -;; - resize-scale-text (bool) -;; tells if the resize vectors must be applied to text shapes -;; or not. + +(def conjv (fnil conj [])) + +;; Public builder API + +(defn empty-modifiers [] + {}) + +(defn set-move + ([modifiers x y] + (set-move modifiers (gpt/point x y))) + + ([modifiers vector] + (-> modifiers + (update :geometry conjv {:type :move :vector vector})))) + +(defn set-resize + ([modifiers vector origin] + (-> modifiers + (update :geometry conjv {:type :resize + :vector vector + :origin origin}))) + + ([modifiers vector origin transform transform-inverse] + (-> modifiers + (update :geometry conjv {:type :resize + :vector vector + :origin origin + :transform transform + :transform-inverse transform-inverse})))) + +(defn set-rotation + [modifiers center angle] + (-> modifiers + (update :geometry conjv {:type :rotation + :center center + :rotation angle}))) + +(defn set-remove-children + [modifiers shapes] + (-> modifiers + (update :structure-parent conjv {:type :remove-children + :value shapes})) + ) + +(defn set-add-children + [modifiers shapes index] + (-> modifiers + (update :structure-parent conjv {:type :add-children + :value shapes + :index index}))) + +(defn set-scale-content + [modifiers value] + (-> modifiers + (update :structure-child conjv {:type :scale-content :value value}))) + + +(defn add-modifiers + [modifiers new-modifiers] + + (cond-> modifiers + (some? (:geometry new-modifiers)) + (update :geometry #(d/concat-vec [] % (:geometry new-modifiers))) + + (some? (:structure-parent new-modifiers)) + (update :structure-parent #(d/concat-vec [] % (:structure-parent new-modifiers))) + + (some? (:structure-child new-modifiers)) + (update :structure-child #(d/concat-vec [] % (:structure-child new-modifiers))))) + + +;; These are convenience methods to create single operation modifiers without the builder (defn move ([x y] - (move (gpt/point x y))) + (set-move (empty-modifiers) (gpt/point x y))) ([vector] - {:v2 [{:type :move :vector vector}]})) + (set-move (empty-modifiers) vector))) (defn resize - [vector origin] - {:v2 [{:type :resize :vector vector :origin origin}]}) + ([vector origin] + (set-resize (empty-modifiers) vector origin)) + + ([vector origin transform transform-inverse] + (set-resize (empty-modifiers) vector origin transform transform-inverse))) + +(defn rotation + [shape center angle] + (let [shape-center (gco/center-shape shape) + rotation (-> (gmt/matrix) + (gmt/rotate angle center) + (gmt/rotate (- angle) shape-center))] + + (-> (empty-modifiers) + (set-rotation shape-center angle) + (set-move (gpt/transform (gpt/point 1 1) rotation))))) + +(defn remove-children + [shapes] + (-> (empty-modifiers) + (set-remove-children shapes))) + +(defn add-children + [shapes index] + (-> (empty-modifiers) + (set-add-children shapes index))) + +(defn scale-content + [value] + (-> (empty-modifiers) + (set-scale-content value))) + +(defn select-child-modifiers + [modifiers] + (select-keys modifiers [:geometry :structure-child])) + +(defn select-structure + [modifiers] + (select-keys modifiers [:structure-parent])) (defn add-move ([object x y] (add-move object (gpt/point x y))) ([object vector] - (assoc-in - object - [:modifiers :displacement] - (gmt/translate-matrix (:x vector) (:y vector))))) + (update object :modifiers (move vector)))) (defn add-resize [object vector origin] - (-> object - (assoc-in [:modifiers :resize-vector] vector) - (assoc-in [:modifiers :resize-origin] origin))) + (update object :modifiers (resize vector origin))) -(defn empty-modifiers? [modifiers] - (empty? (dissoc modifiers :ignore-geometry?))) +(defn empty-modifiers? + [modifiers] + (and (empty? (:geometry modifiers)) + (empty? (:structure-parent modifiers)) + (empty? (:structure-child modifiers)))) -(defn resize-modifiers +(defn change-dimensions [shape attr value] (us/assert map? shape) (us/assert #{:width :height} attr) (us/assert number? value) + (let [{:keys [proportion proportion-lock]} shape size (select-keys (:selrect shape) [:width :height]) new-size (if-not proportion-lock @@ -99,10 +197,8 @@ scalev (gpt/divide (gpt/point width height) (gpt/point sr-width sr-height))] - {:resize-vector scalev - :resize-origin origin - :resize-transform shape-transform - :resize-transform-inverse shape-transform-inv})) + + (resize scalev origin shape-transform shape-transform-inv))) (defn change-orientation-modifiers [shape orientation] @@ -124,26 +220,8 @@ scalev (gpt/divide (gpt/point new-width new-height) (gpt/point sr-width sr-height))] - {:resize-vector scalev - :resize-origin origin - :resize-transform shape-transform - :resize-transform-inverse shape-transform-inv})) -(defn rotation-modifiers - [shape center angle] - (let [shape-center (gco/center-shape shape) - rotation (-> (gmt/matrix) - (gmt/rotate angle center) - (gmt/rotate (- angle) shape-center))] - - {:v2 [{:type :rotation - :center shape-center - :rotation angle} - - {:type :move - :vector (gpt/transform (gpt/point 1 1) rotation)}]} - #_{:rotation angle - :displacement displacement})) + (resize scalev origin shape-transform shape-transform-inv))) (defn merge-modifiers [objects modifiers] @@ -155,7 +233,29 @@ (->> modifiers (reduce set-modifier objects)))) -(defn modifiers-v2->transform +(defn only-move? + [modifier] + (and (= 1 (-> modifier :geometry count)) + (= :move (-> modifier :geometry first :type)))) + +(defn get-frame-add-children + [modif-tree] + + (let [structure-changes + (into {} + (comp (filter (fn [[_ val]] (-> val :modifiers :structure-parent some?))) + (map (fn [[key val]] + [key (-> val :modifiers :structure-parent)]))) + modif-tree)] + (into [] + (mapcat (fn [[frame-id changes]] + (->> changes + (filter (fn [{:keys [type]}] (= type :add-children))) + (mapcat (fn [{:keys [value]}] + (->> value (map (fn [id] {:frame frame-id :shape id})))))))) + structure-changes))) + +(defn modifiers->transform [modifiers] (letfn [(apply-modifier [matrix {:keys [type vector rotation center origin transform transform-inverse] :as modifier}] (case type @@ -182,77 +282,58 @@ (gmt/multiply (gmt/rotate-matrix rotation)) (gmt/translate (gpt/negate center))) matrix)))] - (->> modifiers + (->> modifiers :geometry (reduce apply-modifier (gmt/matrix))))) -(defn- normalize-scale - "We normalize the scale so it's not too close to 0" - [scale] - (cond - (and (< scale 0) (> scale -0.01)) -0.01 - (and (>= scale 0) (< scale 0.01)) 0.01 - :else scale)) +(defn scale-text-content + [content value] -(defn modifiers->transform - ([modifiers] - (modifiers->transform nil modifiers)) + (->> content + (txt/transform-nodes + txt/is-text-node? + (fn [attrs] + (let [font-size (-> (get attrs :font-size 14) + (d/parse-double) + (* value) + (str)) ] + (d/txt-merge attrs {:font-size font-size})))))) - ([center modifiers] - (if (some? (:v2 modifiers)) - (modifiers-v2->transform (:v2 modifiers)) - (let [displacement (:displacement modifiers) - displacement-after (:displacement-after modifiers) - resize-v1 (:resize-vector modifiers) - resize-v2 (:resize-vector-2 modifiers) - origin-1 (:resize-origin modifiers (gpt/point)) - origin-2 (:resize-origin-2 modifiers (gpt/point)) +(defn apply-scale-content + [shape value] - ;; Normalize x/y vector coordinates because scale by 0 is infinite - resize-1 (when (some? resize-v1) - (gpt/point (normalize-scale (:x resize-v1)) - (normalize-scale (:y resize-v1)))) + (cond-> shape + (cph/text-shape? shape) + (update :content scale-text-content value))) - resize-2 (when (some? resize-v2) - (gpt/point (normalize-scale (:x resize-v2)) - (normalize-scale (:y resize-v2)))) +(defn apply-structure-modifiers + [shape modifiers] + (let [remove-children + (fn [shapes children-to-remove] + (let [remove? (set children-to-remove)] + (d/removev remove? shapes))) - resize-transform (:resize-transform modifiers) - resize-transform-inverse (:resize-transform-inverse modifiers) - rt-modif (:rotation modifiers)] - (cond-> (gmt/matrix) - (some? displacement-after) - (gmt/multiply displacement-after) + apply-modifier + (fn [shape {:keys [type value index]}] + (cond-> shape + (and (= type :add-children) (some? index)) + (update :shapes + (fn [shapes] + (if (vector? shapes) + (cph/insert-at-index shapes index value) + (d/concat-vec shapes value)))) - (some? resize-1) - (-> (gmt/translate origin-1) - (cond-> (some? resize-transform) - (gmt/multiply resize-transform)) - (gmt/scale resize-1) - (cond-> (some? resize-transform-inverse) - (gmt/multiply resize-transform-inverse)) - (gmt/translate (gpt/negate origin-1))) + (and (= type :add-children) (nil? index)) + (update :shapes d/concat-vec value) - (some? resize-2) - (-> (gmt/translate origin-2) - (cond-> (some? resize-transform) - (gmt/multiply resize-transform)) - (gmt/scale resize-2) - (cond-> (some? resize-transform-inverse) - (gmt/multiply resize-transform-inverse)) - (gmt/translate (gpt/negate origin-2))) + (= type :remove-children) + (update :shapes remove-children value) - (some? displacement) - (gmt/multiply displacement) + (= type :scale-content) + (apply-scale-content value)))] - (some? rt-modif) - (-> (gmt/translate center) - (gmt/multiply (gmt/rotate-matrix rt-modif)) - (gmt/translate (gpt/negate center)))))) - )) -(defn only-move? - [modifier] - (and (= 1 (-> modifier :v2 count)) - (= :move (-> modifier :v2 first :type)))) + (as-> shape $ + (reduce apply-modifier $ (:structure-parent modifiers)) + (reduce apply-modifier $ (:structure-child modifiers))))) diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index 65843fc6bd..ad91a12262 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -47,8 +47,9 @@ (-> shape (assoc :click-draw? false) - (gsh/transform-shape (ctm/resize scalev (gpt/point x y))) - (gsh/transform-shape (ctm/move movev))))) + (gsh/transform-shape (-> (ctm/empty-modifiers) + (ctm/set-resize scalev (gpt/point x y)) + (ctm/set-move movev)))))) (defn update-drawing [state initial point lock?] (update-in state [:workspace-drawing :object] resize-shape initial point lock?)) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index daa033365d..2f15ac2268 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -22,7 +22,6 @@ [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shape-layout :as dwsl] - [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] [app.main.features :as features] [app.main.streams :as ms] diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 1c9f5f147c..dbfb5f453a 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -320,8 +320,8 @@ (letfn [(update-fn [shape] (let [{:keys [selrect grow-type]} shape {shape-width :width shape-height :height} selrect - modifier-width (ctm/resize-modifiers shape :width new-width) - modifier-height (ctm/resize-modifiers shape :height new-height)] + modifier-width (ctm/change-dimensions shape :width new-width) + modifier-height (ctm/change-dimensions shape :height new-height)] ;; TODO LAYOUT: MEZCLAR ESTOS EN UN UNICO MODIFIER (cond-> shape (and (not-changed? shape-width new-width) (= grow-type :auto-width)) @@ -346,8 +346,8 @@ (defn apply-text-modifier [shape {:keys [width height position-data]}] - (let [modifier-width (when width (ctm/resize-modifiers shape :width width)) - modifier-height (when height (ctm/resize-modifiers shape :height height)) + (let [modifier-width (when width (ctm/change-dimensions shape :width width)) + modifier-height (when height (ctm/change-dimensions shape :height height)) ;; TODO LAYOUT: MEZCLAR LOS DOS EN UN UNICO MODIFIER new-shape diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 7873b7ebaf..f304dbe318 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -146,11 +146,14 @@ (concat (keys workspace-modifiers) ids) objects (fn [shape] - (let [modifiers (if (contains? ids (:id shape)) modifiers {}) - old-modifiers-v3 (get-in state [:workspace-modifiers (:id shape) :modifiers :v3])] + (let [ + modifiers (if (contains? ids (:id shape)) modifiers (ctm/empty-modifiers)) + + structure-modifiers (ctm/select-structure + (get-in state [:workspace-modifiers (:id shape) :modifiers]))] (cond-> modifiers - (some? old-modifiers-v3) - (assoc :v3 old-modifiers-v3)))) + (some? structure-modifiers) + (ctm/add-modifiers structure-modifiers)))) ignore-constraints snap-pixel?)] (update state :workspace-modifiers merge modif-tree)))))) @@ -175,7 +178,7 @@ get-modifier (fn [shape] - (ctm/rotation-modifiers shape center angle)) + (ctm/rotation shape center angle)) modif-tree (gsh/set-objects-modifiers ids objects get-modifier false false)] @@ -396,32 +399,18 @@ resize-origin (cond-> (gmt/transform-point-center handler-origin shape-center shape-transform) (some? displacement) - (gpt/add displacement))] + (gpt/add displacement)) - (rx/of (set-modifiers ids - {:v2 (-> [] - (cond-> displacement - (conj {:type :move - :vector displacement})) - (conj {:type :resize - :vector scalev - :origin resize-origin - :transform shape-transform - :transform-inverse shape-transform-inverse})) - ;;:displacement displacement - ;;:resize-vector scalev - ;;:resize-origin resize-origin - ;;:resize-transform shape-transform - ;;:resize-scale-text scale-text - ;;:resize-transform-inverse shape-transform-inverse - })) - #_(rx/of (set-modifiers ids - {:displacement displacement - :resize-vector scalev - :resize-origin resize-origin - :resize-transform shape-transform - :resize-scale-text scale-text - :resize-transform-inverse shape-transform-inverse})))) + modifiers + (-> (ctm/empty-modifiers) + (cond-> displacement + (ctm/set-move displacement)) + (ctm/set-resize scalev resize-origin shape-transform shape-transform-inverse) + + (cond-> scale-text + (ctm/set-scale-content (:x scalev))))] + + (rx/of (set-modifiers ids modifiers)))) ;; Unifies the instantaneous proportion lock modifier ;; activated by Shift key and the shapes own proportion @@ -471,7 +460,7 @@ snap-pixel? (and (contains? (:workspace-layout state) :snap-pixel-grid) (int? value)) get-modifier - (fn [shape] (ctm/resize-modifiers shape attr value)) + (fn [shape] (ctm/change-dimensions shape attr value)) modif-tree (gsh/set-objects-modifiers ids objects get-modifier false snap-pixel?)] @@ -655,15 +644,13 @@ (d/removev #(= target-frame %)))] (cond (not= original-frame target-frame) - [[original-frame {:modifiers {:v3 [{:type :remove-children :value shapes}]}}] - [target-frame {:modifiers {:v3 [{:type :add-children - :value shapes - :index drop-index}]}}]] + [[original-frame {:modifiers (ctm/remove-children shapes)}] + [target-frame {:modifiers (ctm/add-children shapes drop-index)}]] + layout? - [[target-frame {:modifiers {:v3 [{:type :add-children - :value shapes - :index drop-index}]}}]])))) + [[target-frame {:modifiers (ctm/add-children shapes drop-index)}]])))) (keys origin-frame-ids))] + (assoc state :workspace-modifiers modif-tree))))) (defn- start-move @@ -725,8 +712,7 @@ ;; We try to use the previous snap so we don't have to wait for the result of the new (rx/map snap/correct-snap-point) - - (rx/map (fn [move-vec] {:v2 [{:type :move :vector move-vec}]})) + (rx/map ctm/move) (rx/map (partial set-modifiers ids)) (rx/take-until stopper))) @@ -867,14 +853,9 @@ origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2)))] (rx/of (set-modifiers selected - {:v2 [{:type :resize - :vector (gpt/point -1.0 1.0) - :origin origin} - {:type :move - :vector (gpt/point (:width selrect) 0)}]} - #_{:resize-vector (gpt/point -1.0 1.0) - :resize-origin origin - :displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))} + (-> (ctm/empty-modifiers) + (ctm/set-resize (gpt/point -1.0 1.0) origin) + (ctm/move (gpt/point (:width selrect) 0))) true) (apply-modifiers)))))) @@ -889,13 +870,8 @@ origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect))] (rx/of (set-modifiers selected - {:v2 [{:type :resize - :vector (gpt/point 1.0 -1.0) - :origin origin} - {:type :move - :vector (gpt/point 0 (:height selrect))}]} - #_{:resize-vector (gpt/point 1.0 -1.0) - :resize-origin origin - :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))} + (-> (ctm/empty-modifiers) + (ctm/set-resize (gpt/point 1.0 -1.0) origin) + (ctm/move (gpt/point 0 (:height selrect)))) true) (apply-modifiers)))))) diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 836114c514..a6ca1742b5 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -103,7 +103,7 @@ opts #js {:shape shape :thumbnail? thumbnail?}] (when (and (some? shape) (not (:hidden shape))) - [:g.ws-shape-wrapper + [:g.workspace-shape-wrapper (case (:type shape) :path [:> path/path-wrapper opts] :text [:> text/text-wrapper opts] diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 6d95966d4d..288ebcbff5 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -19,67 +19,6 @@ [app.util.globals :as globals] [rumext.v2 :as mf])) -(defn- transform-no-resize - "If we apply a scale directly to the texts it will show deformed so we need to create this - correction matrix to \"undo\" the resize but keep the other transformations." - [{:keys [x y width height points transform transform-inverse] :as shape} current-transform modifiers] - - (let [corner-pt (first points) - corner-pt (cond-> corner-pt (some? transform-inverse) (gpt/transform transform-inverse)) - - resize-x? (some? (:resize-vector modifiers)) - resize-y? (some? (:resize-vector-2 modifiers)) - - flip-x? (neg? (get-in modifiers [:resize-vector :x])) - flip-y? (or (neg? (get-in modifiers [:resize-vector :y])) - (neg? (get-in modifiers [:resize-vector-2 :y]))) - - result (cond-> (gmt/matrix) - (and (some? transform) (or resize-x? resize-y?)) - (gmt/multiply transform) - - resize-x? - (gmt/scale (gpt/inverse (:resize-vector modifiers)) corner-pt) - - resize-y? - (gmt/scale (gpt/inverse (:resize-vector-2 modifiers)) corner-pt) - - flip-x? - (gmt/scale (gpt/point -1 1) corner-pt) - - flip-y? - (gmt/scale (gpt/point 1 -1) corner-pt) - - (and (some? transform) (or resize-x? resize-y?)) - (gmt/multiply transform-inverse)) - - [width height] - (if (or resize-x? resize-y?) - (let [pc (cond-> (gpt/point x y) - (some? transform) - (gpt/transform transform) - - (some? current-transform) - (gpt/transform current-transform)) - - pw (cond-> (gpt/point (+ x width) y) - (some? transform) - (gpt/transform transform) - - (some? current-transform) - (gpt/transform current-transform)) - - ph (cond-> (gpt/point x (+ y height)) - (some? transform) - (gpt/transform transform) - - (some? current-transform) - (gpt/transform current-transform))] - [(gpt/distance pc pw) (gpt/distance pc ph)]) - [width height])] - - [result width height])) - (defn get-shape-node ([id] (get-shape-node js/document id)) @@ -191,15 +130,8 @@ (let [transform (get transforms id) modifiers (get-in modifiers [id :modifiers])] - ;; TODO LAYOUT: Adapt to new modifiers (doseq [node nodes] (cond - ;; Text shapes need special treatment because their resize only change - ;; the text area, not the change size/position - (dom/class? node "frame-thumbnail") - (let [[transform] (transform-no-resize shape transform modifiers)] - (set-transform-att! node "transform" transform)) - (dom/class? node "frame-children") (set-transform-att! node "transform" (gmt/inverse transform)) @@ -256,11 +188,7 @@ (/ (:height shape) (:height shape')))] ;; Reverse the change in size so we can recalculate the layout (-> modifiers - (update :v2 conj {:type :resize - :vector scalev - :transform (:transform shape') - :transform-inverse (:transform-inverse shape') - :origin (-> shape' :points first)})))) + (ctm/set-resize scalev (-> shape' :points first) (:transform shape') (:transform-inverse shape'))))) (defn use-dynamic-modifiers [objects node modifiers] @@ -272,37 +200,13 @@ (when (some? modifiers) (d/mapm (fn [id {modifiers :modifiers}] (let [shape (get objects id) - center (gsh/center-shape shape) text? (= :text (:type shape)) modifiers (cond-> modifiers text? (adapt-text-modifiers shape))] - (ctm/modifiers->transform center modifiers))) + (ctm/modifiers->transform modifiers))) modifiers)))) - structure-changes - (mf/use-memo - (mf/deps modifiers) - (fn [] - (into {} - (comp (filter (fn [[_ val]] (-> val :modifiers :v3 some?))) - (map (fn [[key val]] - [key (-> val :modifiers :v3)]))) - - modifiers))) - - structure-changes (hooks/use-equal-memo structure-changes) - - add-children - (mf/use-memo - (mf/deps structure-changes) - (fn [] - (into [] - (mapcat (fn [[frame-id changes]] - (->> changes - (filter (fn [{:keys [type]}] (= type :add-children))) - (mapcat (fn [{:keys [value]}] - (->> value (map (fn [id] {:frame frame-id :shape id})))))))) - structure-changes))) - + add-children (mf/use-memo (mf/deps modifiers) #(ctm/get-frame-add-children modifiers)) + add-children (hooks/use-equal-memo add-children) add-children-prev (hooks/use-previous add-children) shapes diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 74e8057e08..50e2e6ebc5 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -35,13 +35,6 @@ (with-meta (meta (:position-data shape)))) (dissoc :position-data :transform :transform-inverse))) -#_(defn strip-modifier - [modifier] - (if (or (some? (dm/get-in modifier [:modifiers :resize-vector])) - (some? (dm/get-in modifier [:modifiers :resize-vector-2]))) - modifier - (d/update-when modifier :modifiers dissoc :displacement :rotation))) - (defn fix-position [shape modifier] (let [shape' (-> shape (assoc :grow-type :fixed) diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index 0f23f9eee6..ef81d5fa9c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -31,7 +31,7 @@ (when (and shape (:layout shape)) (let [children (cph/get-immediate-children objects (:id shape)) layout-data (gsl/calc-layout-data shape children) - drop-areas (gsl/drop-areas shape layout-data children)] + drop-areas (gsl/layout-drop-areas shape layout-data children)] [:g.debug-layout {:pointer-events "none" :transform (gsh/transform-str shape)} (for [[idx drop-area] (d/enumerate drop-areas)] From af098bb64d21a269b5a59705519fd623ab091d62 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 20 Oct 2022 17:10:36 +0200 Subject: [PATCH 203/682] :sparkles: Adds integration with new UI --- common/src/app/common/geom/shapes.cljc | 2 +- .../shapes/{layout.cljc => flex_layout.cljc} | 352 ++++++++++-------- .../src/app/common/geom/shapes/modifiers.cljc | 80 ++-- .../common/geom/shapes/pixel_precision.cljc | 12 +- common/src/app/common/types/modifiers.cljc | 47 ++- .../test/common_tests/geom_shapes_test.cljc | 36 +- .../app/main/data/workspace/shape_layout.cljs | 9 +- .../app/main/data/workspace/transforms.cljs | 199 +++++----- frontend/src/app/main/snap.cljs | 7 +- .../src/app/main/ui/workspace/shapes.cljs | 7 +- .../sidebar/options/menus/layout_item.cljs | 12 +- .../sidebar/options/shapes/frame.cljs | 2 +- .../app/main/ui/workspace/viewport/debug.cljs | 2 +- 13 files changed, 431 insertions(+), 336 deletions(-) rename common/src/app/common/geom/shapes/{layout.cljc => flex_layout.cljc} (67%) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 25da561f35..941e6b6342 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -13,8 +13,8 @@ [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.corners :as gsc] + [app.common.geom.shapes.flex-layout :as gcl] [app.common.geom.shapes.intersect :as gin] - [app.common.geom.shapes.layout :as gcl] [app.common.geom.shapes.modifiers :as gsm] [app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.rect :as gpr] diff --git a/common/src/app/common/geom/shapes/layout.cljc b/common/src/app/common/geom/shapes/flex_layout.cljc similarity index 67% rename from common/src/app/common/geom/shapes/layout.cljc rename to common/src/app/common/geom/shapes/flex_layout.cljc index d3282b357e..b67e12e4de 100644 --- a/common/src/app/common/geom/shapes/layout.cljc +++ b/common/src/app/common/geom/shapes/flex_layout.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) KALEIDOS INC -(ns app.common.geom.shapes.layout +(ns app.common.geom.shapes.flex-layout (:require [app.common.data :as d] [app.common.geom.matrix :as gmt] @@ -16,52 +16,95 @@ [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm])) -;; :layout ;; true if active, false if not -;; :layout-dir ;; :right, :left, :top, :bottom -;; :layout-gap ;; number could be negative -;; :layout-type ;; :packed, :space-between, :space-around +;; :layout ;; :flex, :grid in the future +;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column +;; :layout-gap-type ;; :simple, :multiple +;; :layout-gap ;; {:row-gap number , :column-gap number} +;; :layout-align-items ;; :start :end :center :strech +;; :layout-justify-content ;; :start :center :end :space-between :space-around +;; :layout-align-content ;; :start :center :end :space-between :space-around :strech (by default) ;; :layout-wrap-type ;; :wrap, :no-wrap ;; :layout-padding-type ;; :simple, :multiple ;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative -;; :layout-h-orientation ;; :top, :center, :bottom -;; :layout-v-orientation ;; :left, :center, :right + +;; ITEMS +;; :layout-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} +;; :layout-margin-type ;; :simple :multiple +;; :layout-h-behavior ;; :fill :fix :auto +;; :layout-v-behavior ;; :fill :fix :auto +;; :layout-max-h ;; num +;; :layout-min-h ;; num +;; :layout-max-w ;; num +;; :layout-min-w (defn col? - [{:keys [layout-dir]}] - (or (= :right layout-dir) (= :left layout-dir))) + [{:keys [layout-flex-dir]}] + (or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir))) (defn row? - [{:keys [layout-dir]}] - (or (= :top layout-dir) (= :bottom layout-dir))) + [{:keys [layout-flex-dir]}] + (or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir))) (defn h-start? - [{:keys [layout-h-orientation]}] - (= layout-h-orientation :left)) + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (col? shape) + (= layout-align-items :start)) + (and (row? shape) + (= layout-justify-content :start)))) (defn h-center? - [{:keys [layout-h-orientation]}] - (= layout-h-orientation :center)) + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (col? shape) + (= layout-align-items :center)) + (and (row? shape) + (= layout-justify-content :center)))) (defn h-end? - [{:keys [layout-h-orientation]}] - (= layout-h-orientation :right)) + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (col? shape) + (= layout-align-items :end)) + (and (row? shape) + (= layout-justify-content :end)))) (defn v-start? - [{:keys [layout-v-orientation]}] - (= layout-v-orientation :top)) + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (row? shape) + (= layout-align-items :start)) + (and (col? shape) + (= layout-justify-content :start)))) (defn v-center? - [{:keys [layout-v-orientation]}] - (= layout-v-orientation :center)) + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (row? shape) + (= layout-align-items :center)) + (and (col? shape) + (= layout-justify-content :center)))) (defn v-end? - [{:keys [layout-v-orientation]}] - (= layout-v-orientation :bottom)) + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (row? shape) + (= layout-align-items :end)) + (and (col? shape) + (= layout-justify-content :end)))) + +(defn gaps + [{:keys [layout-gap layout-gap-type]}] + (let [layout-gap-row (or (-> layout-gap :row-gap) 0) + layout-gap-col (if (= layout-gap-type :simple) + layout-gap-row + (or (-> layout-gap :column-gap) 0))] + [layout-gap-row layout-gap-col])) (defn calc-layout-lines - [{:keys [layout-gap layout-wrap-type] :as parent} children layout-bounds] + "Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation" + [{:keys [layout-wrap-type] :as parent} children layout-bounds] (let [wrap? (= layout-wrap-type :wrap) + col? (col? parent) + row? (row? parent) + + [layout-gap-row layout-gap-col] (gaps parent) + layout-width (gpo/width-points layout-bounds) layout-height (gpo/height-points layout-bounds) @@ -71,39 +114,36 @@ child-width (gpo/width-points child-bounds) child-height (gpo/height-points child-bounds) - col? (col? parent) - row? (row? parent) - cur-child-fill? - (or (and col? (= :fill (:layout-h-behavior child))) - (and row? (= :fill (:layout-v-behavior child)))) - - cur-line-fill? (or (and row? (= :fill (:layout-h-behavior child))) (and col? (= :fill (:layout-v-behavior child)))) + cur-line-fill? + (or (and col? (= :fill (:layout-h-behavior child))) + (and row? (= :fill (:layout-v-behavior child)))) + ;; TODO LAYOUT: ADD MINWIDTH/HEIGHT - next-width (if (or (and col? cur-child-fill?) - (and row? cur-line-fill?)) + next-width (if (or (and row? cur-child-fill?) + (and col? cur-line-fill?)) 0 child-width) - next-height (if (or (and row? cur-child-fill?) - (and col? cur-line-fill?)) + next-height (if (or (and col? cur-child-fill?) + (and row? cur-line-fill?)) 0 child-height) - next-total-width (+ line-width next-width (* layout-gap (dec num-children))) - next-total-height (+ line-height next-height (* layout-gap (dec num-children)))] + next-total-width (+ line-width next-width (* layout-gap-row (dec num-children))) + next-total-height (+ line-height next-height (* layout-gap-col (dec num-children)))] (if (and (some? line-data) (or (not wrap?) - (and col? (<= next-total-width layout-width)) - (and row? (<= next-total-height layout-height)))) + (and row? (<= next-total-width layout-width)) + (and col? (<= next-total-height layout-height)))) ;; When :fill we add min width (0 by default) - [{:line-width (if col? (+ line-width next-width) (max line-width next-width)) - :line-height (if row? (+ line-height next-height) (max line-height next-height)) + [{:line-width (if row? (+ line-width next-width) (max line-width next-width)) + :line-height (if col? (+ line-height next-height) (max line-height next-height)) :num-children (inc num-children) :child-fill? (or cur-child-fill? child-fill?) :line-fill? (or cur-line-fill? line-fill?) @@ -123,14 +163,16 @@ (cond-> layout-lines (some? line-data) (conj line-data)))) (defn calc-layout-lines-position - [{:keys [layout-gap] :as parent} layout-bounds layout-lines] + [{:keys [layout-justify-content] :as parent} layout-bounds layout-lines] (let [layout-width (gpo/width-points layout-bounds) layout-height (gpo/height-points layout-bounds) + [layout-gap-row layout-gap-col] (gaps parent) + row? (row? parent) col? (col? parent) - space-between? (= :space-between (:layout-type parent)) - space-around? (= :space-around (:layout-type parent)) + space-between? (= layout-justify-content :space-between) + space-around? (= layout-justify-content :space-around) h-center? (h-center? parent) h-end? (h-end? parent) v-center? (v-center? parent) @@ -148,61 +190,62 @@ [total-width total-height] (cond-> (gpo/origin layout-bounds) - (and row? h-center?) + (and col? h-center?) (gpt/add (xv (/ (- layout-width total-width) 2))) - (and row? h-end?) + (and col? h-end?) (gpt/add (xv (- layout-width total-width))) - (and col? v-center?) + (and row? v-center?) (gpt/add (yv (/ (- layout-height total-height) 2))) - (and col? v-end?) + (and row? v-end?) (gpt/add (yv (- layout-height total-height))))) (get-start-line [{:keys [line-width line-height num-children child-fill?]} base-p] - (let [children-gap (* layout-gap (dec num-children)) + (let [children-gap-width (* layout-gap-row (dec num-children)) + children-gap-height (* layout-gap-col (dec num-children)) - line-width (if (and col? child-fill?) - (- layout-width (* layout-gap (dec num-children))) + line-width (if (and row? child-fill?) + (- layout-width (* layout-gap-row (dec num-children))) line-width) - line-height (if (and row? child-fill?) - (- layout-height (* layout-gap (dec num-children))) + line-height (if (and col? child-fill?) + (- layout-height (* layout-gap-col (dec num-children))) line-height) start-p (cond-> base-p ;; X AXIS - (and col? h-center? (not space-around?) (not space-between?)) + (and row? h-center? (not space-around?) (not space-between?)) (-> (gpt/add (xv (/ layout-width 2))) - (gpt/subtract (xv (/ (+ line-width children-gap) 2)))) + (gpt/subtract (xv (/ (+ line-width children-gap-width) 2)))) - (and col? h-end? (not space-around?) (not space-between?)) + (and row? h-end? (not space-around?) (not space-between?)) (-> (gpt/add (xv layout-width)) - (gpt/subtract (xv (+ line-width children-gap)))) + (gpt/subtract (xv (+ line-width children-gap-width)))) - (and row? h-center?) + (and col? h-center?) (gpt/add (xv (/ line-width 2))) - (and row? h-end?) + (and col? h-end?) (gpt/add (xv line-width)) ;; Y AXIS - (and row? v-center? (not space-around?) (not space-between?)) + (and col? v-center? (not space-around?) (not space-between?)) (-> (gpt/add (yv (/ layout-height 2))) - (gpt/subtract (yv (/ (+ line-height children-gap) 2)))) + (gpt/subtract (yv (/ (+ line-height children-gap-height) 2)))) - (and row? v-end? (not space-around?) (not space-between?)) + (and col? v-end? (not space-around?) (not space-between?)) (-> (gpt/add (yv layout-height)) - (gpt/subtract (yv (+ line-height children-gap)))) + (gpt/subtract (yv (+ line-height children-gap-height)))) - (and col? v-center?) + (and row? v-center?) (gpt/add (yv (/ line-height 2))) - (and col? v-end?) + (and row? v-end?) (gpt/add (yv line-height)))] start-p)) @@ -211,11 +254,11 @@ [{:keys [line-width line-height]} base-p] (cond-> base-p - row? - (gpt/add (xv (+ line-width layout-gap))) - col? - (gpt/add (yv (+ line-height layout-gap))))) + (gpt/add (xv (+ line-width layout-gap-row))) + + row? + (gpt/add (yv (+ line-height layout-gap-col))))) (add-lines [[total-width total-height] {:keys [line-width line-height]}] [(+ total-width line-width) @@ -230,24 +273,25 @@ (let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0])) - total-width (+ total-width (* layout-gap (dec (count layout-lines)))) - total-height (+ total-height (* layout-gap (dec (count layout-lines)))) + total-width (+ total-width (* layout-gap-row (dec (count layout-lines)))) + total-height (+ total-height (* layout-gap-col (dec (count layout-lines)))) vertical-fill-space (- layout-height total-height) + horizontal-fill-space (- layout-width total-width) num-line-fill (count (->> layout-lines (filter :line-fill?))) layout-lines (->> layout-lines (mapv #(cond-> % - (and col? (:line-fill? %)) + (and row? (:line-fill? %)) (update :line-height + (/ vertical-fill-space num-line-fill)) - (and row? (:line-fill? %)) + (and col? (:line-fill? %)) (update :line-width + (/ horizontal-fill-space num-line-fill))))) - total-height (if (and col? (> num-line-fill 0)) layout-height total-height) - total-width (if (and row? (> num-line-fill 0)) layout-width total-width) + total-width (if (and col? (> num-line-fill 0)) layout-width total-width) + total-height (if (and row? (> num-line-fill 0)) layout-height total-height) base-p (get-base-line total-width total-height) @@ -257,40 +301,54 @@ (defn calc-layout-line-data "Calculates the baseline for a flex layout" - [{:keys [layout-type layout-gap] :as shape} + [{:keys [layout-justify-content] :as shape} layout-bounds - {:keys [num-children line-width line-height child-fill?] :as line-data}] + {:keys [num-children line-width line-height] :as line-data}] (let [width (gpo/width-points layout-bounds) height (gpo/height-points layout-bounds) - layout-gap - (cond - (or (= :packed layout-type) child-fill?) - layout-gap + row? (row? shape) + col? (col? shape) + space-between? (= layout-justify-content :space-between) + space-around? (= layout-justify-content :space-around) - (= :space-around layout-type) - 0 + [layout-gap-row layout-gap-col] (gaps shape) - (and (col? shape) (= :space-between layout-type)) - (/ (- width line-width) (dec num-children)) + layout-gap-row + (cond (and row? space-around?) + 0 - (and (row? shape) (= :space-between layout-type)) - (/ (- height line-height) (dec num-children))) + (and row? space-between?) + (/ (- width line-width) (dec num-children)) + + :else + layout-gap-row) + + layout-gap-col + (cond (and col? space-around?) + 0 + + (and col? space-between?) + (/ (- height line-height) (dec num-children)) + + :else + layout-gap-col) margin-x - (if (and (col? shape) (= :space-around layout-type)) + (if (and row? space-around?) (/ (- width line-width) (inc num-children)) 0) margin-y - (if (and (row? shape) (= :space-around layout-type)) + (if (and col? space-around?) (/ (- height line-height) (inc num-children)) 0)] (assoc line-data :layout-bounds layout-bounds - :layout-gap layout-gap + :layout-gap-row layout-gap-row + :layout-gap-col layout-gap-col :margin-x margin-x :margin-y margin-y))) @@ -298,7 +356,7 @@ "Calculates the position for the current shape given the layout-data context" [parent child-width child-height - {:keys [start-p layout-gap margin-x margin-y] :as layout-data}] + {:keys [start-p layout-gap-row layout-gap-col margin-x margin-y] :as layout-data}] (let [row? (row? parent) col? (col? parent) @@ -314,16 +372,16 @@ corner-p (cond-> start-p - (and row? h-center?) + (and col? h-center?) (gpt/add (xv (- (/ child-width 2)))) - (and row? h-end?) + (and col? h-end?) (gpt/add (xv (- child-width))) - (and col? v-center?) + (and row? v-center?) (gpt/add (yv (- (/ child-height 2)))) - (and col? v-end?) + (and row? v-end?) (gpt/add (yv (- child-height))) (some? margin-x) @@ -334,11 +392,11 @@ next-p (cond-> start-p - col? - (gpt/add (xv (+ child-width layout-gap))) - row? - (gpt/add (yv (+ child-height layout-gap))) + (gpt/add (xv (+ child-width layout-gap-row))) + + col? + (gpt/add (yv (+ child-height layout-gap-col))) (some? margin-x) (gpt/add (xv margin-x)) @@ -353,46 +411,48 @@ (defn calc-fill-width-data "Calculates the size and modifiers for the width of an auto-fill child" - [{:keys [layout-gap transform transform-inverse] :as parent} + [{:keys [transform transform-inverse] :as parent} {:keys [layout-h-behavior] :as child} child-origin child-width {:keys [num-children line-width line-fill? child-fill? layout-bounds] :as layout-data}] - (cond - (and (col? parent) (= :fill layout-h-behavior) child-fill?) - (let [layout-width (gpo/width-points layout-bounds) - fill-space (- layout-width line-width (* layout-gap (dec num-children))) - fill-width (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-width child-width)] + (let [[layout-gap-row _] (gaps parent)] + (cond + (and (row? parent) (= :fill layout-h-behavior) child-fill?) + (let [layout-width (gpo/width-points layout-bounds) + fill-space (- layout-width line-width (* layout-gap-row (dec num-children))) + fill-width (/ fill-space (:num-child-fill layout-data)) + fill-scale (/ fill-width child-width)] - {:width fill-width - :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) + {:width fill-width + :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) - (and (row? parent) (= :fill layout-h-behavior) line-fill?) - (let [fill-scale (/ line-width child-width)] - {:width line-width - :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}))) + (and (col? parent) (= :fill layout-h-behavior) line-fill?) + (let [fill-scale (/ line-width child-width)] + {:width line-width + :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)})))) (defn calc-fill-height-data "Calculates the size and modifiers for the height of an auto-fill child" - [{:keys [layout-gap transform transform-inverse] :as parent} + [{:keys [transform transform-inverse] :as parent} {:keys [layout-v-behavior] :as child} child-origin child-height {:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}] - (cond - (and (row? parent) (= :fill layout-v-behavior) child-fill?) - (let [layout-height (gpo/height-points layout-bounds) - fill-space (- layout-height line-height (* layout-gap (dec num-children))) - fill-height (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-height child-height)] - {:height fill-height - :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) + (let [[_ layout-gap-col] (gaps parent)] + (cond + (and (col? parent) (= :fill layout-v-behavior) child-fill?) + (let [layout-height (gpo/height-points layout-bounds) + fill-space (- layout-height line-height (* layout-gap-col (dec num-children))) + fill-height (/ fill-space (:num-child-fill layout-data)) + fill-scale (/ fill-height child-height)] + {:height fill-height + :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) - (and (col? parent) (= :fill layout-v-behavior) line-fill?) - (let [fill-scale (/ line-height child-height)] - {:height line-height - :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))) + (and (row? parent) (= :fill layout-v-behavior) line-fill?) + (let [fill-scale (/ line-height child-height)] + {:height line-height + :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)})))) (defn normalize-child-modifiers "Apply the modifiers and then normalized them against the parent coordinates" @@ -412,7 +472,7 @@ (defn calc-layout-data "Digest the layout data to pass it to the constrains" - [{:keys [layout-dir layout-padding layout-padding-type] :as parent} children] + [{:keys [layout-flex-dir layout-padding layout-padding-type] :as parent} children] (let [;; Add padding to the bounds {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding @@ -427,7 +487,7 @@ layout-bounds (gpo/pad-points points pad-top pad-right pad-bottom pad-left) ;; Reverse - reverse? (or (= :left layout-dir) (= :bottom layout-dir)) + reverse? (or (= :reverse-row layout-flex-dir) (= :reverse-column layout-flex-dir)) children (cond->> children reverse? reverse) ;; Creates the layout lines information @@ -476,7 +536,9 @@ h-end? (and row? (h-end? frame)) v-center? (and col? (v-center? frame)) v-end? (and row? (v-end? frame)) - layout-gap (:layout-gap frame 0) + layout-gap-row (or (-> frame :layout-gap :row-gap) 0) + ;;layout-gap-col (or (-> frame :layout-gap :column-gap) 0) + layout-gap layout-gap-row ;; TODO LAYOUT: FIXME reverse? (:reverse? layout-data) children (vec (cond->> (d/enumerate children) @@ -499,31 +561,31 @@ box-width (-> child :selrect :width) box-height (-> child :selrect :height) - x (if row? (:x parent-rect) prev-x) - y (if col? (:y parent-rect) prev-y) + x (if col? (:x parent-rect) prev-x) + y (if row? (:y parent-rect) prev-y) width (cond - (and col? last?) + (and row? last?) (- (+ (:x parent-rect) (:width parent-rect)) x) - row? + col? (:width parent-rect) :else (+ box-width (- box-x prev-x) (/ layout-gap 2))) height (cond - (and row? last?) + (and col? last?) (- (+ (:y parent-rect) (:height parent-rect)) y) - col? + row? (:height parent-rect) :else (+ box-height (- box-y prev-y) (/ layout-gap 2))) [line-area-1 line-area-2] - (if col? + (if row? (let [half-point-width (+ (- box-x x) (/ box-width 2))] [(-> (gsr/make-rect x y half-point-width height) (assoc :index (if reverse? (inc index) index))) @@ -555,16 +617,16 @@ last? (nil? next) line-width - (if col? + (if row? (:width frame) (+ line-width margin-x - (if col? (* layout-gap (dec num-children)) 0))) + (if row? (* layout-gap (dec num-children)) 0))) line-height - (if row? + (if col? (:height frame) (+ line-height margin-y - (if row? + (if col? (* layout-gap (dec num-children)) 0))) @@ -582,24 +644,24 @@ v-end? line-height :else 0)) - x (if col? (:x frame) prev-x) - y (if row? (:y frame) prev-y) + x (if row? (:x frame) prev-x) + y (if col? (:y frame) prev-y) width (cond - (and row? last?) + (and col? last?) (- (+ (:x frame) (:width frame)) x) - col? + row? (:width frame) :else (+ line-width (- box-x prev-x) (/ layout-gap 2))) height (cond - (and col? last?) + (and row? last?) (- (+ (:y frame) (:height frame)) y) - row? + col? (:height frame) :else diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 3660aec608..6f7d9a5ec6 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -8,9 +8,10 @@ (:require [app.common.data :as d] [app.common.geom.shapes.constraints :as gct] - [app.common.geom.shapes.layout :as gcl] + [app.common.geom.shapes.flex-layout :as gcl] [app.common.geom.shapes.pixel-precision :as gpp] [app.common.geom.shapes.transforms :as gtr] + [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) @@ -101,7 +102,7 @@ modif-tree))))) -(defn get-first-layout +(defn get-tree-root [id objects] (loop [current id @@ -127,24 +128,40 @@ (recur (:id parent) result))))) (defn resolve-tree-sequence - ;; TODO LAYOUT: Esta ahora puesto al zero pero tiene que mirar todas las raices "Given the ids that have changed search for layout roots to recalculate" - [_ids objects] - (->> (tree-seq - #(d/not-empty? (get-in objects [% :shapes])) - #(get-in objects [% :shapes]) - uuid/zero) + [modif-tree objects] - (map #(get objects %)))) + (let [redfn + (fn [result id] + (if (= id uuid/zero) + result + (let [root (get-tree-root id objects) -(defn resolve-layout-ids - "Given a list of ids, resolve the parent layouts that will need to update. This will go upwards - in the tree while a layout is found" - [ids objects] + ;; Remove the children from the current root + result + (into #{} (remove #(cph/is-child? objects root %)) result) - (into (d/ordered-set) - (map #(get-first-layout % objects)) - ids)) + contains-parent? + (some #(cph/is-child? objects % root) result)] + + (cond-> result + (not contains-parent?) + (conj root))))) + + generate-tree + (fn [id] + (->> (tree-seq + #(d/not-empty? (get-in objects [% :shapes])) + #(get-in objects [% :shapes]) + id) + + (map #(get objects %)))) + + roots (->> modif-tree keys (reduce redfn #{}))] + + (concat + (when (contains? modif-tree uuid/zero) [(get objects uuid/zero)]) + (mapcat generate-tree roots)))) (defn inside-layout? [objects shape] @@ -170,34 +187,31 @@ ;; modif-tree)))) (defn set-objects-modifiers - [ids objects get-modifier ignore-constraints snap-pixel?] + [modif-tree objects ignore-constraints snap-pixel?] - (let [set-modifiers - (fn [modif-tree id] - (let [root? (= uuid/zero id) - shape (get objects id) - modifiers (cond-> (get-modifier shape) - (and (not root?) snap-pixel?) - (gpp/set-pixel-precision shape))] - (-> modif-tree - (assoc id {:modifiers modifiers})))) - - modif-tree (reduce set-modifiers {} ids) - shapes-tree (resolve-tree-sequence ids objects) + (let [shapes-tree (resolve-tree-sequence modif-tree objects) modif-tree (->> shapes-tree (reduce (fn [modif-tree shape] + (let [root? (= uuid/zero (:id shape)) + modifiers (get-in modif-tree [(:id shape) :modifiers]) - has-modifiers? (some? modifiers) + modifiers (cond-> modifiers + (and (not root?) (ctm/has-geometry? modifiers) snap-pixel?) + (gpp/set-pixel-precision shape)) + + modif-tree (-> modif-tree (assoc-in [(:id shape) :modifiers] modifiers)) + + has-modifiers? (ctm/child-modifiers? modifiers) is-layout? (layout? shape) is-parent? (or (group? shape) (and (frame? shape) (not (layout? shape)))) ;; If the current child is inside the layout we ignore the constraints is-inside-layout? (inside-layout? objects shape)] - + (cond-> modif-tree (and has-modifiers? is-parent? (not root?)) (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?) @@ -207,6 +221,6 @@ modif-tree))] - ;; #?(:cljs - ;; (.log js/console ">result" (modif->js modif-tree objects))) + ;;#?(:cljs + ;; (.log js/console ">result" (modif->js modif-tree objects))) modif-tree)) diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc index 03aa4359a7..40e32ae72b 100644 --- a/common/src/app/common/geom/shapes/pixel_precision.cljc +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -31,8 +31,10 @@ ratio-width (/ target-width curr-width) ratio-height (/ target-height curr-height) scalev (gpt/point ratio-width ratio-height)] - (-> modifiers - (ctm/set-resize scalev origin transform transform-inverse)))) + (cond-> modifiers + (or (not (mth/almost-zero? (- ratio-width 1))) + (not (mth/almost-zero? (- ratio-height 1)))) + (ctm/set-resize scalev origin transform transform-inverse)))) (defn position-pixel-precision [modifiers shape] @@ -41,8 +43,10 @@ corner (gpt/point bounds) target-corner (gpt/round corner) deltav (gpt/to-vec corner target-corner)] - (-> modifiers - (ctm/set-move deltav)))) + (cond-> modifiers + (or (not (mth/almost-zero? (:x deltav))) + (not (mth/almost-zero? (:y deltav)))) + (ctm/set-move deltav)))) (defn set-pixel-precision "Adjust modifiers so they adjust to the pixel grid" diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index bd0715aecf..22c5ff6368 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -24,7 +24,8 @@ ;; - structure-parent: Structure non recursive ;; * add-children ;; * remove-children -;; - structure-child: Structre recursive +;; * reflow +;; - structure-child: Structure recursive ;; * scale-content ;; @@ -47,44 +48,48 @@ ([modifiers vector origin] (-> modifiers (update :geometry conjv {:type :resize - :vector vector - :origin origin}))) + :vector vector + :origin origin}))) ([modifiers vector origin transform transform-inverse] (-> modifiers (update :geometry conjv {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + :vector vector + :origin origin + :transform transform + :transform-inverse transform-inverse})))) (defn set-rotation [modifiers center angle] (-> modifiers (update :geometry conjv {:type :rotation - :center center - :rotation angle}))) + :center center + :rotation angle}))) (defn set-remove-children [modifiers shapes] (-> modifiers (update :structure-parent conjv {:type :remove-children - :value shapes})) + :value shapes})) ) (defn set-add-children [modifiers shapes index] (-> modifiers (update :structure-parent conjv {:type :add-children - :value shapes - :index index}))) + :value shapes + :index index}))) + +(defn set-reflow + [modifiers] + (-> modifiers + (update :structure-parent conjv {:type :reflow}))) (defn set-scale-content [modifiers value] (-> modifiers (update :structure-child conjv {:type :scale-content :value value}))) - (defn add-modifiers [modifiers new-modifiers] @@ -124,7 +129,7 @@ (-> (empty-modifiers) (set-rotation shape-center angle) - (set-move (gpt/transform (gpt/point 1 1) rotation))))) + (set-move (gpt/transform (gpt/point 0 0) rotation))))) (defn remove-children [shapes] @@ -136,11 +141,21 @@ (-> (empty-modifiers) (set-add-children shapes index))) +(defn reflow + [] + (-> (empty-modifiers) + (set-reflow))) + (defn scale-content [value] (-> (empty-modifiers) (set-scale-content value))) +(defn child-modifiers? + [{:keys [geometry structure-child]}] + (or (d/not-empty? geometry) + (d/not-empty? structure-child))) + (defn select-child-modifiers [modifiers] (select-keys modifiers [:geometry :structure-child])) @@ -337,3 +352,7 @@ (as-> shape $ (reduce apply-modifier $ (:structure-parent modifiers)) (reduce apply-modifier $ (:structure-child modifiers))))) + +(defn has-geometry? + [{:keys [geometry]}] + (d/not-empty? geometry)) diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc index f86487de2c..e36a487e71 100644 --- a/common/test/common_tests/geom_shapes_test.cljc +++ b/common/test/common_tests/geom_shapes_test.cljc @@ -10,6 +10,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth :refer [close?]] + [app.common.types.modifiers :as ctm] [app.common.types.shape :as cts] [clojure.test :as t])) @@ -59,7 +60,7 @@ (t/testing "Transform shape with translation modifiers" (t/are [type] - (let [modifiers {:displacement (gmt/translate-matrix (gpt/point 10 -10))}] + (let [modifiers (ctm/move (gpt/point 10 -10))] (let [shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/is (not= shape-before shape-after)) @@ -91,9 +92,7 @@ (t/testing "Transform shape with resize modifiers" (t/are [type] - (let [modifiers {:resize-origin (gpt/point 0 0) - :resize-vector (gpt/point 2 2) - :resize-transform (gmt/matrix)} + (let [modifiers (ctm/resize (gpt/point 2 2) (gpt/point 0 0)) shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/is (not= shape-before shape-after)) @@ -113,9 +112,7 @@ (t/testing "Transform with empty resize" (t/are [type] - (let [modifiers {:resize-origin (gpt/point 0 0) - :resize-vector (gpt/point 1 1) - :resize-transform (gmt/matrix)} + (let [modifiers (ctm/resize (gpt/point 1 1) (gpt/point 0 0)) shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/are [prop] @@ -126,9 +123,7 @@ (t/testing "Transform with resize=0" (t/are [type] - (let [modifiers {:resize-origin (gpt/point 0 0) - :resize-vector (gpt/point 0 0) - :resize-transform (gmt/matrix)} + (let [modifiers (ctm/resize (gpt/point 0 0) (gpt/point 0 0)) shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/is (> (get-in shape-before [:selrect :width]) @@ -142,13 +137,13 @@ (t/testing "Transform shape with rotation modifiers" (t/are [type] - (let [modifiers {:rotation 30} - shape-before (create-test-shape type {:modifiers modifiers}) + (let [shape-before (create-test-shape type) + modifiers (ctm/rotation shape-before (gsh/center-shape shape-before) 30 ) + shape-before (assoc shape-before :modifiers modifiers) shape-after (gsh/transform-shape shape-before)] - (t/is (not= shape-before shape-after)) + (t/is (not= (:selrect shape-before) (:selrect shape-after))) - ;; Selrect won't change with a rotation, but points will (t/is (close? (get-in shape-before [:selrect :x]) (get-in shape-after [:selrect :x]))) @@ -166,9 +161,9 @@ (t/testing "Transform shape with rotation = 0 should leave equal selrect" (t/are [type] - (let [modifiers {:rotation 0} - shape-before (create-test-shape type {:modifiers modifiers}) - shape-after (gsh/transform-shape shape-before)] + (let [shape-before (create-test-shape type) + modifiers (ctm/rotation shape-before (gsh/center-shape shape-before) 0) + shape-after (gsh/transform-shape (assoc shape-before :modifiers modifiers))] (t/are [prop] (t/is (close? (get-in shape-before [:selrect prop]) (get-in shape-after [:selrect prop]))) @@ -177,12 +172,13 @@ (t/testing "Transform shape with invalid selrect fails gracefully" (t/are [type selrect] - (let [modifiers {:displacement (gmt/matrix)} + (let [modifiers (ctm/move 0 0) shape-before (-> (create-test-shape type {:modifiers modifiers}) (assoc :selrect selrect)) shape-after (gsh/transform-shape shape-before)] - (= (:selrect shape-before) - (:selrect shape-after))) + + (t/is (not= (:selrect shape-before) + (:selrect shape-after)))) :rect {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf} :path {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf} diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index cffd33827b..da2262c536 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm] [app.main.data.workspace.changes :as dwc] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.transforms :as dwt] @@ -25,8 +26,7 @@ :layout-wrap-type :layout-padding-type :layout-padding - ]) - + :layout-gap-type]) (def initial-flex-layout {:layout :flex @@ -51,8 +51,9 @@ (let [objects (wsh/lookup-page-objects state) ids (->> ids (filter #(get-in objects [% :layout])))] (if (d/not-empty? ids) - (rx/of (dwt/set-modifiers ids) - (dwt/apply-modifiers)) + (let [modif-tree (dwt/create-modif-tree ids (ctm/reflow))] + (rx/of (dwt/set-modifiers modif-tree) + (dwt/apply-modifiers))) (rx/empty)))))) ;; TODO LAYOUT: Remove constraints from children diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index f304dbe318..1769d41ae7 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -11,7 +11,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.layout :as gsl] + [app.common.geom.shapes.flex-layout :as gsl] [app.common.math :as mth] [app.common.pages.changes-builder :as pcb] [app.common.pages.common :as cpc] @@ -117,46 +117,69 @@ (declare get-ignore-tree) +(defn create-modif-tree + [ids modifiers] + (us/verify (s/coll-of uuid?) ids) + (into {} (map #(vector % {:modifiers modifiers})) ids)) + +(defn build-modif-tree + [ids objects get-modifier] + (us/verify (s/coll-of uuid?) ids) + (into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids)) + +(defn build-change-frame-modifiers + [modif-tree objects selected target-frame position] + + (let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) + layout? (get-in objects [target-frame :layout]) + child-set (set (get-in objects [target-frame :shapes])) + drop-index (when layout? (gsl/get-drop-index target-frame objects position)) + + update-frame-modifiers + (fn [modif-tree [original-frame shapes]] + (let [shapes (->> shapes (d/removev #(= target-frame %))) + shapes (cond->> shapes + (and layout? (= original-frame target-frame)) + ;; When movining inside a layout frame remove the shapes that are not immediate children + (filterv #(contains? child-set %)))] + (cond-> modif-tree + (not= original-frame target-frame) + (-> (update-in [original-frame :modifiers] ctm/set-remove-children shapes) + (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index)) + + (and layout? (= original-frame target-frame)) + (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index))))] + + (reduce update-frame-modifiers modif-tree origin-frame-ids))) + +(defn modif->js + [modif-tree objects] + (clj->js (into {} + (map (fn [[k v]] + [(get-in objects [k :name]) v])) + modif-tree))) + (defn set-modifiers - ([ids] - (set-modifiers ids nil false)) + ([modif-tree] + (set-modifiers modif-tree false)) - ([ids modifiers] - (set-modifiers ids modifiers false)) + ([modif-tree ignore-constraints] + (set-modifiers modif-tree ignore-constraints false)) - ([ids modifiers ignore-constraints] - (set-modifiers ids modifiers ignore-constraints false)) - - ([ids modifiers ignore-constraints ignore-snap-pixel] - (us/verify (s/coll-of uuid?) ids) + ([modif-tree ignore-constraints ignore-snap-pixel] (ptk/reify ::set-modifiers ptk/UpdateEvent (update [_ state] - (let [objects (wsh/lookup-page-objects state) - ids (into #{} (remove #(get-in objects [% :blocked] false)) ids) + (let [objects + (wsh/lookup-page-objects state) - snap-pixel? (and (not ignore-snap-pixel) - (contains? (:workspace-layout state) :snap-pixel-grid)) - - workspace-modifiers (:workspace-modifiers state) + snap-pixel? + (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) modif-tree - (gsh/set-objects-modifiers - ;; TODO LAYOUT: I don't like this - (concat (keys workspace-modifiers) ids) - objects - (fn [shape] - (let [ - modifiers (if (contains? ids (:id shape)) modifiers (ctm/empty-modifiers)) + (gsh/set-objects-modifiers modif-tree objects ignore-constraints snap-pixel?)] - structure-modifiers (ctm/select-structure - (get-in state [:workspace-modifiers (:id shape) :modifiers]))] - (cond-> modifiers - (some? structure-modifiers) - (ctm/add-modifiers structure-modifiers)))) - ignore-constraints snap-pixel?)] - - (update state :workspace-modifiers merge modif-tree)))))) + (assoc state :workspace-modifiers modif-tree)))))) ;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints). (defn- set-rotation-modifiers @@ -171,8 +194,6 @@ ids (->> shapes (remove #(get % :blocked false)) - #_(mapcat #(cph/get-children objects (:id %))) - #_(concat shapes) (filter #((cpc/editable-attrs (:type %)) :rotation)) (map :id)) @@ -181,9 +202,10 @@ (ctm/rotation shape center angle)) modif-tree - (gsh/set-objects-modifiers ids objects get-modifier false false)] + (-> (build-modif-tree ids objects get-modifier) + (gsh/set-objects-modifiers objects false false))] - (update state :workspace-modifiers merge modif-tree)))))) + (assoc state :workspace-modifiers modif-tree)))))) (defn- update-grow-type [shape old-shape] @@ -408,9 +430,11 @@ (ctm/set-resize scalev resize-origin shape-transform shape-transform-inverse) (cond-> scale-text - (ctm/set-scale-content (:x scalev))))] + (ctm/set-scale-content (:x scalev)))) - (rx/of (set-modifiers ids modifiers)))) + modif-tree (create-modif-tree ids modifiers)] + + (rx/of (set-modifiers modif-tree)))) ;; Unifies the instantaneous proportion lock modifier ;; activated by Shift key and the shapes own proportion @@ -463,7 +487,8 @@ (fn [shape] (ctm/change-dimensions shape attr value)) modif-tree - (gsh/set-objects-modifiers ids objects get-modifier false snap-pixel?)] + (-> (build-modif-tree ids objects get-modifier) + (gsh/set-objects-modifiers objects false snap-pixel?))] (assoc state :workspace-modifiers modif-tree))) @@ -487,7 +512,8 @@ (fn [shape] (ctm/change-orientation-modifiers shape orientation)) modif-tree - (gsh/set-objects-modifiers ids objects get-modifier false snap-pixel?)] + (-> (build-modif-tree ids objects get-modifier) + (gsh/set-objects-modifiers objects false snap-pixel?))] (assoc state :workspace-modifiers modif-tree))) @@ -621,37 +647,6 @@ (rx/take 1) (rx/map #(start-move from-position)))))) -(defn set-change-frame-modifiers - [selected target-frame position] - - (ptk/reify ::set-change-frame-modifiers - ptk/UpdateEvent - (update [_ state] - (let [objects (wsh/lookup-page-objects state) - - origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) - - layout? (get-in objects [target-frame :layout]) - - drop-index - (when layout? (gsl/get-drop-index target-frame objects position)) - - modif-tree - (into {} - (mapcat - (fn [original-frame] - (let [shapes (->> (get origin-frame-ids original-frame) - (d/removev #(= target-frame %)))] - (cond - (not= original-frame target-frame) - [[original-frame {:modifiers (ctm/remove-children shapes)}] - [target-frame {:modifiers (ctm/add-children shapes drop-index)}]] - - layout? - [[target-frame {:modifiers (ctm/add-children shapes drop-index)}]])))) - (keys origin-frame-ids))] - - (assoc state :workspace-modifiers modif-tree))))) (defn- start-move ([from-position] (start-move from-position nil)) @@ -699,22 +694,21 @@ (rx/of (finish-transform)) (rx/concat (rx/merge - (->> position - (rx/map (fn [delta] - (let [position (gpt/add from-position delta) - target-frame (ctst/top-nested-frame objects position)] - (set-change-frame-modifiers selected target-frame position)))) - (rx/take-until stopper)) - (->> position ;; We ask for the snap position but we continue even if the result is not available (rx/with-latest vector snap-delta) ;; We try to use the previous snap so we don't have to wait for the result of the new (rx/map snap/correct-snap-point) - (rx/map ctm/move) - (rx/map (partial set-modifiers ids)) + (rx/map + (fn [move-vector] + (let [position (gpt/add from-position move-vector) + target-frame (ctst/top-nested-frame objects position)] + (-> (create-modif-tree ids (ctm/move move-vector)) + (build-change-frame-modifiers objects selected target-frame position) + (set-modifiers))))) + (rx/take-until stopper))) (rx/of (dwu/start-undo-transaction) @@ -762,8 +756,8 @@ (rx/merge (->> move-events (rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0)) - (rx/map #(ctm/move %)) - (rx/map (partial set-modifiers selected)) + (rx/map #(create-modif-tree selected (ctm/move %))) + (rx/map (partial set-modifiers)) (rx/take-until stopper)) (rx/of (move-selected direction shift?))) @@ -793,11 +787,12 @@ cpos (gpt/point (:x bbox) (:y bbox)) pos (gpt/point (or (:x position) (:x bbox)) (or (:y position) (:y bbox))) - delta (gpt/subtract pos cpos)] + delta (gpt/subtract pos cpos) - (rx/of - (set-modifiers [id] (ctm/move delta)) - (apply-modifiers [id])))))) + modif-tree (create-modif-tree [id] (ctm/move delta))] + + (rx/of (set-modifiers modif-tree) + (apply-modifiers)))))) (defn- calculate-frame-for-move [ids] @@ -816,7 +811,11 @@ moving-shapes (cond->> shapes (not layout?) - (remove #(= (:frame-id %) frame-id))) + (remove #(= (:frame-id %) frame-id)) + + layout? + (remove #(and (= (:frame-id %) frame-id) + (not= (:parent-id %) frame-id)))) drop-index (when layout? (gsl/get-drop-index frame-id objects position)) @@ -850,13 +849,15 @@ selected (wsh/lookup-selected state {:omit-blocked? true}) shapes (map #(get objects %) selected) selrect (gsh/selection-rect shapes) - origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2)))] + origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2))) - (rx/of (set-modifiers selected - (-> (ctm/empty-modifiers) - (ctm/set-resize (gpt/point -1.0 1.0) origin) - (ctm/move (gpt/point (:width selrect) 0))) - true) + modif-tree (create-modif-tree + selected + (-> (ctm/empty-modifiers) + (ctm/set-resize (gpt/point -1.0 1.0) origin) + (ctm/move (gpt/point (:width selrect) 0))))] + + (rx/of (set-modifiers modif-tree true) (apply-modifiers)))))) (defn flip-vertical-selected [] @@ -867,11 +868,13 @@ selected (wsh/lookup-selected state {:omit-blocked? true}) shapes (map #(get objects %) selected) selrect (gsh/selection-rect shapes) - origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect))] + origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect)) - (rx/of (set-modifiers selected - (-> (ctm/empty-modifiers) - (ctm/set-resize (gpt/point 1.0 -1.0) origin) - (ctm/move (gpt/point 0 (:height selrect)))) - true) + modif-tree (create-modif-tree + selected + (-> (ctm/empty-modifiers) + (ctm/set-resize (gpt/point 1.0 -1.0) origin) + (ctm/move (gpt/point 0 (:height selrect)))))] + + (rx/of (set-modifiers modif-tree true) (apply-modifiers)))))) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index ce2b97c245..f9585b5bf0 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -358,7 +358,8 @@ "Snaps a position given an old snap to a different position. We use this to provide a temporal snap while the new is being processed." [[position [snap-pos snap-delta]]] - (if (some? snap-delta) + (if (nil? snap-delta) + position (let [dx (if (not= 0 (:x snap-delta)) (- (+ (:x snap-pos) (:x snap-delta)) (:x position)) 0) @@ -372,6 +373,4 @@ dy (if (> (mth/abs dy) snap-accuracy) 0 dy)] (-> position (update :x + dx) - (update :y + dy))) - - position)) + (update :y + dy))))) diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index a6ca1742b5..0b92d475ef 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -68,24 +68,21 @@ [:g.frame-children (for [shape shapes] - [:g.ws-shape-wrapper + [:g.ws-shape-wrapper {:key (:id shape)} (cond (not (cph/frame-shape? shape)) [:& shape-wrapper - {:shape shape - :key (:id shape)}] + {:shape shape}] (cph/root-frame? shape) [:& root-frame-wrapper {:shape shape - :key (:id shape) :objects (get frame-objects (:id shape)) :thumbnail? (not (contains? active-frames (:id shape)))}] :else [:& nested-frame-wrapper {:shape shape - :key (:id shape) :objects (get frame-objects (:id shape))}])])]]])) (mf/defc shape-wrapper diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index d27cb93a57..e0b8fd357c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -84,38 +84,38 @@ [:div.layout-behavior.horizontal [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Fix width" - :class (dom/classnames :activated (= layout-h-behavior :fix)) + :class (dom/classnames :active (= layout-h-behavior :fix)) :on-click #(on-change-behavior :h :fix)} i/auto-fix-layout] (when fill? [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Width 100%" - :class (dom/classnames :activated (= layout-h-behavior :fill)) + :class (dom/classnames :active (= layout-h-behavior :fill)) :on-click #(on-change-behavior :h :fill)} i/auto-fill]) (when auto? [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Fit content" - :class (dom/classnames :activated (= layout-v-behavior :auto)) + :class (dom/classnames :active (= layout-v-behavior :auto)) :on-click #(on-change-behavior :h :auto)} i/auto-hug])] [:div.layout-behavior [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Fix height" - :class (dom/classnames :activated (= layout-v-behavior :fix)) + :class (dom/classnames :active (= layout-v-behavior :fix)) :on-click #(on-change-behavior :v :fix)} i/auto-fix-layout] (when fill? [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Height 100%" - :class (dom/classnames :activated (= layout-v-behavior :fill)) + :class (dom/classnames :active (= layout-v-behavior :fill)) :on-click #(on-change-behavior :v :fill)} i/auto-fill]) (when auto? [:button.behavior-btn.tooltip.tooltip-bottom-left {:alt "Fit content" - :class (dom/classnames :activated (= layout-v-behavior :auto)) + :class (dom/classnames :active (= layout-v-behavior :auto)) :on-click #(on-change-behavior :v :auto)} i/auto-hug])]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index bd9df2411f..048fff5c04 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -47,7 +47,7 @@ [:* [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}] - (when (or (:layout shape) is-layout-child?) + (when is-layout-child? [:& layout-item-menu {:ids ids :type type diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index ef81d5fa9c..869caa8ca5 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -9,7 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.layout :as gsl] + [app.common.geom.shapes.flex-layout :as gsl] [app.common.pages.helpers :as cph] [rumext.v2 :as mf])) From 58fd20094a0c56ec1f345d0ef32aa1626ecbd008 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 21 Oct 2022 16:03:11 +0200 Subject: [PATCH 204/682] :sparkles: Adapted dynamic modifiers and options for new modifiers --- common/src/app/common/types/modifiers.cljc | 11 +++++---- .../shapes/frame/dynamic_modifiers.cljs | 24 +++++++++++-------- .../main/ui/workspace/sidebar/options.cljs | 7 ++---- .../sidebar/options/menus/measures.cljs | 10 ++++---- 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 22c5ff6368..fe308d402a 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -27,6 +27,7 @@ ;; * reflow ;; - structure-child: Structure recursive ;; * scale-content +;; * rotation ;; (def conjv (fnil conj [])) @@ -62,6 +63,8 @@ (defn set-rotation [modifiers center angle] (-> modifiers + (update :structure-child conjv {:type :rotation + :rotation angle}) (update :geometry conjv {:type :rotation :center center :rotation angle}))) @@ -327,11 +330,12 @@ (let [remove? (set children-to-remove)] (d/removev remove? shapes))) - - apply-modifier - (fn [shape {:keys [type value index]}] + (fn [shape {:keys [type value index rotation]}] (cond-> shape + (= type :rotation) + (update :rotation #(mod (+ % rotation) 360)) + (and (= type :add-children) (some? index)) (update :shapes (fn [shapes] @@ -348,7 +352,6 @@ (= type :scale-content) (apply-scale-content value)))] - (as-> shape $ (reduce apply-modifier $ (:structure-parent modifiers)) (reduce apply-modifier $ (:structure-child modifiers))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 288ebcbff5..b5a34ba654 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -248,18 +248,22 @@ (mf/use-layout-effect (mf/deps transforms) (fn [] - (let [is-prev-val? (d/not-empty? @prev-modifiers) - is-cur-val? (d/not-empty? modifiers)] - (when (and (not is-prev-val?) is-cur-val?) - (start-transform! node shapes)) + (let [curr-shapes-set (into #{} (map :id) shapes) + prev-shapes-set (into #{} (map :id) @prev-shapes) - (when is-cur-val? + new-shapes (->> shapes (remove #(contains? prev-shapes-set (:id %)))) + removed-shapes (->> @prev-shapes (remove #(contains? curr-shapes-set (:id %))))] + + (when (d/not-empty? new-shapes) + (start-transform! node new-shapes)) + + (when (d/not-empty? shapes) (update-transform! node shapes transforms modifiers)) - (when (and is-prev-val? (not is-cur-val?)) - (remove-transform! node @prev-shapes)) + (when (d/not-empty? removed-shapes) + (remove-transform! node @prev-shapes))) - (reset! prev-modifiers modifiers) - (reset! prev-transforms transforms) - (reset! prev-shapes shapes)))))) + (reset! prev-modifiers modifiers) + (reset! prev-transforms transforms) + (reset! prev-shapes shapes))))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 99f65f0fe3..1a0faed463 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -60,12 +60,9 @@ {::mf/wrap [mf/memo]} [{:keys [selected section shapes shapes-with-children page-id file-id]}] (let [drawing (mf/deref refs/workspace-drawing) - base-objects (-> (mf/deref refs/workspace-page-objects)) + objects (mf/deref refs/workspace-page-objects) shared-libs (mf/deref refs/workspace-libraries) - modifiers (mf/deref refs/workspace-modifiers) - objects-modified (mf/with-memo [base-objects modifiers] - (ctm/merge-modifiers base-objects modifiers)) - selected-shapes (into [] (keep (d/getf objects-modified)) selected)] + selected-shapes (into [] (keep (d/getf objects)) selected)] [:div.tool-window [:div.tool-window-content [:& tab-container {:on-change-tab #(st/emit! (udw/set-options-mode %)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 8a9e73f3c9..2fdea6e9f6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -68,7 +68,9 @@ ;; -- User/drawing coords (mf/defc measures-menu [{:keys [ids ids-with-children values type all-types shape] :as props}] - (let [options (if (= type :multiple) + (let [workspace-modifiers (mf/deref refs/workspace-modifiers) + + options (if (= type :multiple) (reduce #(union %1 %2) (map #(get type->options %) all-types)) (get type->options type)) @@ -83,7 +85,9 @@ ;; the shape with the mouse, generate a copy of the shapes applying ;; the transient transformations. shapes (as-> old-shapes $ - #_(map gsh/transform-shape $) + (map (fn [shape] + (let [modifiers (get-in workspace-modifiers [(:id shape) :modifiers])] + (gsh/transform-shape shape modifiers))) $) (map gsh/translate-to-frame $ frames)) ;; For rotated or stretched shapes, the origin point we show in the menu @@ -441,5 +445,3 @@ (tr "workspace.options.show-in-viewer")]]) ]]])) - - From b8c90fdcf3c63b470520de0b768ca802c5036ecf Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 24 Oct 2022 15:58:38 +0200 Subject: [PATCH 205/682] :sparkles: Refactor flex layout namespace --- .../app/common/geom/shapes/flex_layout.cljc | 694 +----------------- .../geom/shapes/flex_layout/drop_area.cljc | 179 +++++ .../common/geom/shapes/flex_layout/lines.cljc | 309 ++++++++ .../geom/shapes/flex_layout/modifiers.cljc | 103 +++ .../geom/shapes/flex_layout/positions.cljc | 68 ++ common/src/app/common/types/shape/layout.cljc | 129 ++++ .../main/ui/workspace/sidebar/options.cljs | 1 - 7 files changed, 798 insertions(+), 685 deletions(-) create mode 100644 common/src/app/common/geom/shapes/flex_layout/drop_area.cljc create mode 100644 common/src/app/common/geom/shapes/flex_layout/lines.cljc create mode 100644 common/src/app/common/geom/shapes/flex_layout/modifiers.cljc create mode 100644 common/src/app/common/geom/shapes/flex_layout/positions.cljc diff --git a/common/src/app/common/geom/shapes/flex_layout.cljc b/common/src/app/common/geom/shapes/flex_layout.cljc index b67e12e4de..8361d230fd 100644 --- a/common/src/app/common/geom/shapes/flex_layout.cljc +++ b/common/src/app/common/geom/shapes/flex_layout.cljc @@ -6,687 +6,13 @@ (ns app.common.geom.shapes.flex-layout (:require - [app.common.data :as d] - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] - [app.common.geom.shapes.common :as gco] - [app.common.geom.shapes.points :as gpo] - [app.common.geom.shapes.rect :as gsr] - [app.common.geom.shapes.transforms :as gst] - [app.common.pages.helpers :as cph] - [app.common.types.modifiers :as ctm])) - -;; :layout ;; :flex, :grid in the future -;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column -;; :layout-gap-type ;; :simple, :multiple -;; :layout-gap ;; {:row-gap number , :column-gap number} -;; :layout-align-items ;; :start :end :center :strech -;; :layout-justify-content ;; :start :center :end :space-between :space-around -;; :layout-align-content ;; :start :center :end :space-between :space-around :strech (by default) -;; :layout-wrap-type ;; :wrap, :no-wrap -;; :layout-padding-type ;; :simple, :multiple -;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative - -;; ITEMS -;; :layout-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} -;; :layout-margin-type ;; :simple :multiple -;; :layout-h-behavior ;; :fill :fix :auto -;; :layout-v-behavior ;; :fill :fix :auto -;; :layout-max-h ;; num -;; :layout-min-h ;; num -;; :layout-max-w ;; num -;; :layout-min-w - -(defn col? - [{:keys [layout-flex-dir]}] - (or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir))) - -(defn row? - [{:keys [layout-flex-dir]}] - (or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir))) - -(defn h-start? - [{:keys [layout-align-items layout-justify-content] :as shape}] - (or (and (col? shape) - (= layout-align-items :start)) - (and (row? shape) - (= layout-justify-content :start)))) - -(defn h-center? - [{:keys [layout-align-items layout-justify-content] :as shape}] - (or (and (col? shape) - (= layout-align-items :center)) - (and (row? shape) - (= layout-justify-content :center)))) - -(defn h-end? - [{:keys [layout-align-items layout-justify-content] :as shape}] - (or (and (col? shape) - (= layout-align-items :end)) - (and (row? shape) - (= layout-justify-content :end)))) - -(defn v-start? - [{:keys [layout-align-items layout-justify-content] :as shape}] - (or (and (row? shape) - (= layout-align-items :start)) - (and (col? shape) - (= layout-justify-content :start)))) - -(defn v-center? - [{:keys [layout-align-items layout-justify-content] :as shape}] - (or (and (row? shape) - (= layout-align-items :center)) - (and (col? shape) - (= layout-justify-content :center)))) - -(defn v-end? - [{:keys [layout-align-items layout-justify-content] :as shape}] - (or (and (row? shape) - (= layout-align-items :end)) - (and (col? shape) - (= layout-justify-content :end)))) - -(defn gaps - [{:keys [layout-gap layout-gap-type]}] - (let [layout-gap-row (or (-> layout-gap :row-gap) 0) - layout-gap-col (if (= layout-gap-type :simple) - layout-gap-row - (or (-> layout-gap :column-gap) 0))] - [layout-gap-row layout-gap-col])) - -(defn calc-layout-lines - "Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation" - [{:keys [layout-wrap-type] :as parent} children layout-bounds] - - (let [wrap? (= layout-wrap-type :wrap) - col? (col? parent) - row? (row? parent) - - [layout-gap-row layout-gap-col] (gaps parent) - - layout-width (gpo/width-points layout-bounds) - layout-height (gpo/height-points layout-bounds) - - reduce-fn - (fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child] - (let [child-bounds (gst/parent-coords-points child parent) - child-width (gpo/width-points child-bounds) - child-height (gpo/height-points child-bounds) - - cur-child-fill? - (or (and row? (= :fill (:layout-h-behavior child))) - (and col? (= :fill (:layout-v-behavior child)))) - - cur-line-fill? - (or (and col? (= :fill (:layout-h-behavior child))) - (and row? (= :fill (:layout-v-behavior child)))) - - ;; TODO LAYOUT: ADD MINWIDTH/HEIGHT - next-width (if (or (and row? cur-child-fill?) - (and col? cur-line-fill?)) - 0 - child-width) - - next-height (if (or (and col? cur-child-fill?) - (and row? cur-line-fill?)) - 0 - child-height) - - next-total-width (+ line-width next-width (* layout-gap-row (dec num-children))) - next-total-height (+ line-height next-height (* layout-gap-col (dec num-children)))] - - (if (and (some? line-data) - (or (not wrap?) - (and row? (<= next-total-width layout-width)) - (and col? (<= next-total-height layout-height)))) - - ;; When :fill we add min width (0 by default) - [{:line-width (if row? (+ line-width next-width) (max line-width next-width)) - :line-height (if col? (+ line-height next-height) (max line-height next-height)) - :num-children (inc num-children) - :child-fill? (or cur-child-fill? child-fill?) - :line-fill? (or cur-line-fill? line-fill?) - :num-child-fill (cond-> num-child-fill cur-child-fill? inc)} - result] - - [{:line-width next-width - :line-height next-height - :num-children 1 - :child-fill? cur-child-fill? - :line-fill? cur-line-fill? - :num-child-fill (if cur-child-fill? 1 0)} - (cond-> result (some? line-data) (conj line-data))]))) - - [line-data layout-lines] (reduce reduce-fn [nil []] children)] - - (cond-> layout-lines (some? line-data) (conj line-data)))) - -(defn calc-layout-lines-position - [{:keys [layout-justify-content] :as parent} layout-bounds layout-lines] - - (let [layout-width (gpo/width-points layout-bounds) - layout-height (gpo/height-points layout-bounds) - [layout-gap-row layout-gap-col] (gaps parent) - - row? (row? parent) - col? (col? parent) - space-between? (= layout-justify-content :space-between) - space-around? (= layout-justify-content :space-around) - h-center? (h-center? parent) - h-end? (h-end? parent) - v-center? (v-center? parent) - v-end? (v-end? parent)] - - (letfn [;; short version to not repeat always with all arguments - (xv [val] - (gpo/start-hv layout-bounds val)) - - ;; short version to not repeat always with all arguments - (yv [val] - (gpo/start-vv layout-bounds val)) - - (get-base-line - [total-width total-height] - - (cond-> (gpo/origin layout-bounds) - (and col? h-center?) - (gpt/add (xv (/ (- layout-width total-width) 2))) - - (and col? h-end?) - (gpt/add (xv (- layout-width total-width))) - - (and row? v-center?) - (gpt/add (yv (/ (- layout-height total-height) 2))) - - (and row? v-end?) - (gpt/add (yv (- layout-height total-height))))) - - (get-start-line - [{:keys [line-width line-height num-children child-fill?]} base-p] - - (let [children-gap-width (* layout-gap-row (dec num-children)) - children-gap-height (* layout-gap-col (dec num-children)) - - line-width (if (and row? child-fill?) - (- layout-width (* layout-gap-row (dec num-children))) - line-width) - - line-height (if (and col? child-fill?) - (- layout-height (* layout-gap-col (dec num-children))) - line-height) - - start-p - (cond-> base-p - ;; X AXIS - (and row? h-center? (not space-around?) (not space-between?)) - (-> (gpt/add (xv (/ layout-width 2))) - (gpt/subtract (xv (/ (+ line-width children-gap-width) 2)))) - - (and row? h-end? (not space-around?) (not space-between?)) - (-> (gpt/add (xv layout-width)) - (gpt/subtract (xv (+ line-width children-gap-width)))) - - (and col? h-center?) - (gpt/add (xv (/ line-width 2))) - - (and col? h-end?) - (gpt/add (xv line-width)) - - ;; Y AXIS - (and col? v-center? (not space-around?) (not space-between?)) - (-> (gpt/add (yv (/ layout-height 2))) - (gpt/subtract (yv (/ (+ line-height children-gap-height) 2)))) - - (and col? v-end? (not space-around?) (not space-between?)) - (-> (gpt/add (yv layout-height)) - (gpt/subtract (yv (+ line-height children-gap-height)))) - - (and row? v-center?) - (gpt/add (yv (/ line-height 2))) - - (and row? v-end?) - (gpt/add (yv line-height)))] - - start-p)) - - (get-next-line - [{:keys [line-width line-height]} base-p] - - (cond-> base-p - col? - (gpt/add (xv (+ line-width layout-gap-row))) - - row? - (gpt/add (yv (+ line-height layout-gap-col))))) - - (add-lines [[total-width total-height] {:keys [line-width line-height]}] - [(+ total-width line-width) - (+ total-height line-height)]) - - (add-starts [[result base-p] layout-line] - (let [start-p (get-start-line layout-line base-p) - next-p (get-next-line layout-line base-p)] - [(conj result - (assoc layout-line :start-p start-p)) - next-p]))] - - (let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0])) - - total-width (+ total-width (* layout-gap-row (dec (count layout-lines)))) - total-height (+ total-height (* layout-gap-col (dec (count layout-lines)))) - - vertical-fill-space (- layout-height total-height) - - horizontal-fill-space (- layout-width total-width) - num-line-fill (count (->> layout-lines (filter :line-fill?))) - - layout-lines - (->> layout-lines - (mapv #(cond-> % - (and row? (:line-fill? %)) - (update :line-height + (/ vertical-fill-space num-line-fill)) - - (and col? (:line-fill? %)) - (update :line-width + (/ horizontal-fill-space num-line-fill))))) - - total-width (if (and col? (> num-line-fill 0)) layout-width total-width) - total-height (if (and row? (> num-line-fill 0)) layout-height total-height) - - base-p (get-base-line total-width total-height) - - [layout-lines _ _ _ _] - (reduce add-starts [[] base-p] layout-lines)] - layout-lines)))) - -(defn calc-layout-line-data - "Calculates the baseline for a flex layout" - [{:keys [layout-justify-content] :as shape} - layout-bounds - {:keys [num-children line-width line-height] :as line-data}] - - (let [width (gpo/width-points layout-bounds) - height (gpo/height-points layout-bounds) - - row? (row? shape) - col? (col? shape) - space-between? (= layout-justify-content :space-between) - space-around? (= layout-justify-content :space-around) - - [layout-gap-row layout-gap-col] (gaps shape) - - layout-gap-row - (cond (and row? space-around?) - 0 - - (and row? space-between?) - (/ (- width line-width) (dec num-children)) - - :else - layout-gap-row) - - layout-gap-col - (cond (and col? space-around?) - 0 - - (and col? space-between?) - (/ (- height line-height) (dec num-children)) - - :else - layout-gap-col) - - margin-x - (if (and row? space-around?) - (/ (- width line-width) (inc num-children)) - 0) - - margin-y - (if (and col? space-around?) - (/ (- height line-height) (inc num-children)) - 0)] - - (assoc line-data - :layout-bounds layout-bounds - :layout-gap-row layout-gap-row - :layout-gap-col layout-gap-col - :margin-x margin-x - :margin-y margin-y))) - -(defn next-p - "Calculates the position for the current shape given the layout-data context" - [parent - child-width child-height - {:keys [start-p layout-gap-row layout-gap-col margin-x margin-y] :as layout-data}] - - (let [row? (row? parent) - col? (col? parent) - - h-center? (h-center? parent) - h-end? (h-end? parent) - v-center? (v-center? parent) - v-end? (v-end? parent) - points (:points parent) - - xv (partial gpo/start-hv points) - yv (partial gpo/start-vv points) - - corner-p - (cond-> start-p - (and col? h-center?) - (gpt/add (xv (- (/ child-width 2)))) - - (and col? h-end?) - (gpt/add (xv (- child-width))) - - (and row? v-center?) - (gpt/add (yv (- (/ child-height 2)))) - - (and row? v-end?) - (gpt/add (yv (- child-height))) - - (some? margin-x) - (gpt/add (xv margin-x)) - - (some? margin-y) - (gpt/add (yv margin-y))) - - next-p - (cond-> start-p - row? - (gpt/add (xv (+ child-width layout-gap-row))) - - col? - (gpt/add (yv (+ child-height layout-gap-col))) - - (some? margin-x) - (gpt/add (xv margin-x)) - - (some? margin-y) - (gpt/add (yv margin-y))) - - layout-data - (assoc layout-data :start-p next-p)] - - [corner-p layout-data])) - -(defn calc-fill-width-data - "Calculates the size and modifiers for the width of an auto-fill child" - [{:keys [transform transform-inverse] :as parent} - {:keys [layout-h-behavior] :as child} - child-origin child-width - {:keys [num-children line-width line-fill? child-fill? layout-bounds] :as layout-data}] - - (let [[layout-gap-row _] (gaps parent)] - (cond - (and (row? parent) (= :fill layout-h-behavior) child-fill?) - (let [layout-width (gpo/width-points layout-bounds) - fill-space (- layout-width line-width (* layout-gap-row (dec num-children))) - fill-width (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-width child-width)] - - {:width fill-width - :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) - - (and (col? parent) (= :fill layout-h-behavior) line-fill?) - (let [fill-scale (/ line-width child-width)] - {:width line-width - :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)})))) - -(defn calc-fill-height-data - "Calculates the size and modifiers for the height of an auto-fill child" - [{:keys [transform transform-inverse] :as parent} - {:keys [layout-v-behavior] :as child} - child-origin child-height - {:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}] - - (let [[_ layout-gap-col] (gaps parent)] - (cond - (and (col? parent) (= :fill layout-v-behavior) child-fill?) - (let [layout-height (gpo/height-points layout-bounds) - fill-space (- layout-height line-height (* layout-gap-col (dec num-children))) - fill-height (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-height child-height)] - {:height fill-height - :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) - - (and (row? parent) (= :fill layout-v-behavior) line-fill?) - (let [fill-scale (/ line-height child-height)] - {:height line-height - :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)})))) - -(defn normalize-child-modifiers - "Apply the modifiers and then normalized them against the parent coordinates" - [parent child modifiers {:keys [transform transform-inverse] :as transformed-parent}] - - (let [transformed-child (gst/transform-shape child modifiers) - child-bb-before (gst/parent-coords-rect child parent) - child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) - scale-x (/ (:width child-bb-before) (:width child-bb-after)) - scale-y (/ (:height child-bb-before) (:height child-bb-after)) - - resize-origin (-> transformed-parent :points first) ;; TODO LAYOUT: IS always the origin?n - resize-vector (gpt/point scale-x scale-y)] - (-> modifiers - (ctm/select-child-modifiers) - (ctm/set-resize resize-vector resize-origin transform transform-inverse)))) - -(defn calc-layout-data - "Digest the layout data to pass it to the constrains" - [{:keys [layout-flex-dir layout-padding layout-padding-type] :as parent} children] - - (let [;; Add padding to the bounds - {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding - [pad-top pad-right pad-bottom pad-left] - (if (= layout-padding-type :multiple) - [pad-top pad-right pad-bottom pad-left] - [pad-top pad-top pad-top pad-top]) - - ;; Normalize the points to remove flips - points (gst/parent-coords-points parent parent) - - layout-bounds (gpo/pad-points points pad-top pad-right pad-bottom pad-left) - - ;; Reverse - reverse? (or (= :reverse-row layout-flex-dir) (= :reverse-column layout-flex-dir)) - children (cond->> children reverse? reverse) - - ;; Creates the layout lines information - layout-lines - (->> (calc-layout-lines parent children layout-bounds) - (calc-layout-lines-position parent layout-bounds) - (map (partial calc-layout-line-data parent layout-bounds)))] - - {:layout-lines layout-lines - :reverse? reverse?})) - -(defn calc-layout-modifiers - "Calculates the modifiers for the layout" - [parent child layout-line] - (let [child-bounds (gst/parent-coords-points child parent) - - child-origin (gpo/origin child-bounds) - child-width (gpo/width-points child-bounds) - child-height (gpo/height-points child-bounds) - - fill-width (calc-fill-width-data parent child child-origin child-width layout-line) - fill-height (calc-fill-height-data parent child child-origin child-height layout-line) - - child-width (or (:width fill-width) child-width) - child-height (or (:height fill-height) child-height) - - [corner-p layout-line] (next-p parent child-width child-height layout-line) - - move-vec (gpt/to-vec child-origin corner-p) - - modifiers - (-> (ctm/empty-modifiers) - (cond-> fill-width (ctm/add-modifiers (:modifiers fill-width))) - (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height))) - (ctm/set-move move-vec))] - - [modifiers layout-line])) - - -(defn layout-drop-areas - [{:keys [margin-x margin-y] :as frame} layout-data children] - - (let [col? (col? frame) - row? (row? frame) - h-center? (and row? (h-center? frame)) - h-end? (and row? (h-end? frame)) - v-center? (and col? (v-center? frame)) - v-end? (and row? (v-end? frame)) - layout-gap-row (or (-> frame :layout-gap :row-gap) 0) - ;;layout-gap-col (or (-> frame :layout-gap :column-gap) 0) - layout-gap layout-gap-row ;; TODO LAYOUT: FIXME - reverse? (:reverse? layout-data) - - children (vec (cond->> (d/enumerate children) - reverse? reverse)) - - redfn-child - (fn [[result parent-rect prev-x prev-y] [[index child] next]] - (let [prev-x (or prev-x (:x parent-rect)) - prev-y (or prev-y (:y parent-rect)) - - last? (nil? next) - - start-p (gpt/point (:selrect child)) - start-p (-> start-p - (gmt/transform-point-center (gco/center-shape child) (:transform frame)) - (gmt/transform-point-center (gco/center-shape frame) (:transform-inverse frame))) - - box-x (:x start-p) - box-y (:y start-p) - box-width (-> child :selrect :width) - box-height (-> child :selrect :height) - - x (if col? (:x parent-rect) prev-x) - y (if row? (:y parent-rect) prev-y) - - width (cond - (and row? last?) - (- (+ (:x parent-rect) (:width parent-rect)) x) - - col? - (:width parent-rect) - - :else - (+ box-width (- box-x prev-x) (/ layout-gap 2))) - - height (cond - (and col? last?) - (- (+ (:y parent-rect) (:height parent-rect)) y) - - row? - (:height parent-rect) - - :else - (+ box-height (- box-y prev-y) (/ layout-gap 2))) - - [line-area-1 line-area-2] - (if row? - (let [half-point-width (+ (- box-x x) (/ box-width 2))] - [(-> (gsr/make-rect x y half-point-width height) - (assoc :index (if reverse? (inc index) index))) - (-> (gsr/make-rect (+ x half-point-width) y (- width half-point-width) height) - (assoc :index (if reverse? index (inc index))))]) - (let [half-point-height (+ (- box-y y) (/ box-height 2))] - [(-> (gsr/make-rect x y width half-point-height) - (assoc :index (if reverse? (inc index) index))) - (-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height)) - (assoc :index (if reverse? index (inc index))))])) - - result (conj result line-area-1 line-area-2) - - ;;line-area - ;;(-> (gsr/make-rect x y width height) - ;; (assoc :index (if reverse? (inc index) index))) - ;;result (conj result line-area) - ;;result (conj result (gsr/make-rect box-x box-y box-width box-height)) - ] - - [result parent-rect (+ x width) (+ y height)])) - - redfn-lines - (fn [[result from-idx prev-x prev-y] [{:keys [start-p layout-gap num-children line-width line-height]} next]] - (let [start-p (gmt/transform-point-center start-p (gco/center-shape frame) (:transform-inverse frame)) - - prev-x (or prev-x (:x frame)) - prev-y (or prev-y (:y frame)) - last? (nil? next) - - line-width - (if row? - (:width frame) - (+ line-width margin-x - (if row? (* layout-gap (dec num-children)) 0))) - - line-height - (if col? - (:height frame) - (+ line-height margin-y - (if col? - (* layout-gap (dec num-children)) - 0))) - - box-x - (- (:x start-p) - (cond - h-center? (/ line-width 2) - h-end? line-width - :else 0)) - - box-y - (- (:y start-p) - (cond - v-center? (/ line-height 2) - v-end? line-height - :else 0)) - - x (if row? (:x frame) prev-x) - y (if col? (:y frame) prev-y) - - width (cond - (and col? last?) - (- (+ (:x frame) (:width frame)) x) - - row? - (:width frame) - - :else - (+ line-width (- box-x prev-x) (/ layout-gap 2))) - - height (cond - (and row? last?) - (- (+ (:y frame) (:height frame)) y) - - col? - (:height frame) - - :else - (+ line-height (- box-y prev-y) (/ layout-gap 2))) - - line-area (gsr/make-rect x y width height) - - children (subvec children from-idx (+ from-idx num-children)) - - - ;; To debug the lines - ;;result (conj result line-area) - - result (first (reduce redfn-child [result line-area] (d/with-next children)))] - - [result (+ from-idx num-children) (+ x width) (+ y height)]))] - - (first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data)))))) - -(defn get-drop-index - [frame-id objects position] - (let [frame (get objects frame-id) - position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame)) - children (cph/get-immediate-children objects frame-id) - layout-data (calc-layout-data frame children) - drop-areas (layout-drop-areas frame layout-data children) - area (d/seek #(gsr/contains-point? % position) drop-areas)] - (:index area))) + [app.common.data.macros :as dm] + [app.common.geom.shapes.flex-layout.drop-area :as fdr] + [app.common.geom.shapes.flex-layout.lines :as fli] + [app.common.geom.shapes.flex-layout.modifiers :as fmo])) + +(dm/export fli/calc-layout-data) +(dm/export fmo/normalize-child-modifiers) +(dm/export fmo/calc-layout-modifiers) +(dm/export fdr/layout-drop-areas) +(dm/export fdr/get-drop-index) diff --git a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc new file mode 100644 index 0000000000..228e09b133 --- /dev/null +++ b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc @@ -0,0 +1,179 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.geom.shapes.flex-layout.drop-area + (:require + [app.common.data :as d] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] + [app.common.geom.shapes.common :as gco] + [app.common.geom.shapes.flex-layout.lines :as fli] + [app.common.geom.shapes.rect :as gsr] + [app.common.pages.helpers :as cph] + [app.common.types.shape.layout :as ctl])) + +(defn layout-drop-areas + [{:keys [margin-x margin-y] :as frame} layout-data children] + + (let [col? (ctl/col? frame) + row? (ctl/row? frame) + h-center? (and row? (ctl/h-center? frame)) + h-end? (and row? (ctl/h-end? frame)) + v-center? (and col? (ctl/v-center? frame)) + v-end? (and row? (ctl/v-end? frame)) + reverse? (:reverse? layout-data) + + [layout-gap-row layout-gap-col] (ctl/gaps frame) + + children (vec (cond->> (d/enumerate children) + reverse? reverse)) + + redfn-child + (fn [[result parent-rect prev-x prev-y] [[index child] next]] + (let [prev-x (or prev-x (:x parent-rect)) + prev-y (or prev-y (:y parent-rect)) + + last? (nil? next) + + start-p (gpt/point (:selrect child)) + start-p (-> start-p + (gmt/transform-point-center (gco/center-shape child) (:transform frame)) + (gmt/transform-point-center (gco/center-shape frame) (:transform-inverse frame))) + + box-x (:x start-p) + box-y (:y start-p) + box-width (-> child :selrect :width) + box-height (-> child :selrect :height) + + x (if col? (:x parent-rect) prev-x) + y (if row? (:y parent-rect) prev-y) + + width (cond + (and row? last?) + (- (+ (:x parent-rect) (:width parent-rect)) x) + + col? + (:width parent-rect) + + :else + (+ box-width (- box-x prev-x) (/ layout-gap-row 2))) + + height (cond + (and col? last?) + (- (+ (:y parent-rect) (:height parent-rect)) y) + + row? + (:height parent-rect) + + :else + (+ box-height (- box-y prev-y) (/ layout-gap-col 2))) + + [line-area-1 line-area-2] + (if row? + (let [half-point-width (+ (- box-x x) (/ box-width 2))] + [(-> (gsr/make-rect x y half-point-width height) + (assoc :index (if reverse? (inc index) index))) + (-> (gsr/make-rect (+ x half-point-width) y (- width half-point-width) height) + (assoc :index (if reverse? index (inc index))))]) + (let [half-point-height (+ (- box-y y) (/ box-height 2))] + [(-> (gsr/make-rect x y width half-point-height) + (assoc :index (if reverse? (inc index) index))) + (-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height)) + (assoc :index (if reverse? index (inc index))))])) + + result (conj result line-area-1 line-area-2) + + ;;line-area + ;;(-> (gsr/make-rect x y width height) + ;; (assoc :index (if reverse? (inc index) index))) + ;;result (conj result line-area) + ;;result (conj result (gsr/make-rect box-x box-y box-width box-height)) + ] + + [result parent-rect (+ x width) (+ y height)])) + + redfn-lines + (fn [[result from-idx prev-x prev-y] [{:keys [start-p layout-gap-row layout-gap-col num-children line-width line-height]} next]] + (let [start-p (gmt/transform-point-center start-p (gco/center-shape frame) (:transform-inverse frame)) + + prev-x (or prev-x (:x frame)) + prev-y (or prev-y (:y frame)) + last? (nil? next) + + line-width + (if row? + (:width frame) + (+ line-width margin-x + (if row? (* layout-gap-row (dec num-children)) 0))) + + line-height + (if col? + (:height frame) + (+ line-height margin-y + (if col? + (* layout-gap-col (dec num-children)) + 0))) + + box-x + (- (:x start-p) + (cond + h-center? (/ line-width 2) + h-end? line-width + :else 0)) + + box-y + (- (:y start-p) + (cond + v-center? (/ line-height 2) + v-end? line-height + :else 0)) + + x (if row? (:x frame) prev-x) + y (if col? (:y frame) prev-y) + + width (cond + (and col? last?) + (- (+ (:x frame) (:width frame)) x) + + row? + (:width frame) + + :else + (+ line-width (- box-x prev-x) (/ layout-gap-row 2))) + + height (cond + (and row? last?) + (- (+ (:y frame) (:height frame)) y) + + col? + (:height frame) + + :else + (+ line-height (- box-y prev-y) (/ layout-gap-col 2))) + + line-area (gsr/make-rect x y width height) + + children (subvec children from-idx (+ from-idx num-children)) + + + ;; To debug the lines + ;;result (conj result line-area) + + result (first (reduce redfn-child [result line-area] (d/with-next children)))] + + [result (+ from-idx num-children) (+ x width) (+ y height)]))] + + (first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data)))))) + +(defn get-drop-index + [frame-id objects position] + (let [frame (get objects frame-id) + position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame)) + children (cph/get-immediate-children objects frame-id) + layout-data (fli/calc-layout-data frame children) + drop-areas (layout-drop-areas frame layout-data children) + area (d/seek #(gsr/contains-point? % position) drop-areas)] + (:index area))) diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc new file mode 100644 index 0000000000..a1e5cc3c44 --- /dev/null +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -0,0 +1,309 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.geom.shapes.flex-layout.lines + (:require + [app.common.geom.point :as gpt] + [app.common.geom.shapes.points :as gpo] + [app.common.geom.shapes.transforms :as gst] + [app.common.types.shape.layout :as ctl])) + +(defn layout-bounds + [{:keys [layout-padding layout-padding-type] :as shape}] + (let [;; Add padding to the bounds + {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding + [pad-top pad-right pad-bottom pad-left] + (if (= layout-padding-type :multiple) + [pad-top pad-right pad-bottom pad-left] + [pad-top pad-top pad-top pad-top]) + + ;; Normalize the points to remove flips + ;; TODO LAYOUT: Need function to normalize the points + points (gst/parent-coords-points shape shape)] + + (gpo/pad-points points pad-top pad-right pad-bottom pad-left))) + +(defn init-layout-lines + "Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation" + [shape children layout-bounds] + + (let [wrap? (ctl/wrap? shape) + col? (ctl/col? shape) + row? (ctl/row? shape) + + [layout-gap-row layout-gap-col] (ctl/gaps shape) + layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) + + reduce-fn + (fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child] + (let [child-bounds (gst/parent-coords-points child shape) + child-width (gpo/width-points child-bounds) + child-height (gpo/height-points child-bounds) + child-min-width (ctl/child-min-width child) + child-min-height (ctl/child-min-height child) + + fill-width? (ctl/fill-width? child) + fill-height? (ctl/fill-height? child) + + cur-child-fill? (or (and row? fill-width?) (and col? fill-height?)) + cur-line-fill? (or (and col? fill-width?) (and row? fill-height?)) + + next-width (if fill-width? child-min-width child-width) + next-height (if fill-height? child-min-height child-height) + + next-line-width (+ line-width next-width (* layout-gap-row (dec num-children))) + next-line-height (+ line-height next-height (* layout-gap-col (dec num-children)))] + + (if (and (some? line-data) + (or (not wrap?) + (and row? (<= next-line-width layout-width)) + (and col? (<= next-line-height layout-height)))) + + [{:line-width (if row? (+ line-width next-width) (max line-width next-width)) + :line-height (if col? (+ line-height next-height) (max line-height next-height)) + :num-children (inc num-children) + :child-fill? (or cur-child-fill? child-fill?) + :line-fill? (or cur-line-fill? line-fill?) + :num-child-fill (cond-> num-child-fill cur-child-fill? inc)} + result] + + [{:line-width next-width + :line-height next-height + :num-children 1 + :child-fill? cur-child-fill? + :line-fill? cur-line-fill? + :num-child-fill (if cur-child-fill? 1 0)} + (cond-> result (some? line-data) (conj line-data))]))) + + [line-data layout-lines] (reduce reduce-fn [nil []] children)] + + (cond-> layout-lines (some? line-data) (conj line-data)))) + +(defn get-base-line + [parent layout-bounds total-width total-height] + + (let [layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) + row? (ctl/row? parent) + col? (ctl/col? parent) + h-center? (ctl/h-center? parent) + h-end? (ctl/h-end? parent) + v-center? (ctl/v-center? parent) + v-end? (ctl/v-end? parent) + hv (partial gpo/start-hv layout-bounds) + vv (partial gpo/start-vv layout-bounds)] + + (cond-> (gpo/origin layout-bounds) + (and col? h-center?) + (gpt/add (hv (/ (- layout-width total-width) 2))) + + (and col? h-end?) + (gpt/add (hv (- layout-width total-width))) + + (and row? v-center?) + (gpt/add (vv (/ (- layout-height total-height) 2))) + + (and row? v-end?) + (gpt/add (vv (- layout-height total-height)))))) + +(defn get-next-line + [parent layout-bounds {:keys [line-width line-height]} base-p] + + (let [row? (ctl/row? parent) + col? (ctl/col? parent) + + [layout-gap-row layout-gap-col] (ctl/gaps parent) + + hv #(gpo/start-hv layout-bounds %) + vv #(gpo/start-vv layout-bounds %)] + + (cond-> base-p + col? + (gpt/add (hv (+ line-width layout-gap-row))) + + row? + (gpt/add (vv (+ line-height layout-gap-col)))))) + +(defn get-start-line + [parent layout-bounds {:keys [line-width line-height num-children child-fill? ]} base-p] + + (let [layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) + [layout-gap-row layout-gap-col] (ctl/gaps parent) + + row? (ctl/row? parent) + col? (ctl/col? parent) + space-between? (ctl/space-between? parent) + space-around? (ctl/space-around? parent) + h-center? (ctl/h-center? parent) + h-end? (ctl/h-end? parent) + v-center? (ctl/v-center? parent) + v-end? (ctl/v-end? parent) + + hv #(gpo/start-hv layout-bounds %) + vv #(gpo/start-vv layout-bounds %) + + children-gap-width (* layout-gap-row (dec num-children)) + children-gap-height (* layout-gap-col (dec num-children)) + + line-width (if (and row? child-fill?) + (- layout-width (* layout-gap-row (dec num-children))) + line-width) + + line-height (if (and col? child-fill?) + (- layout-height (* layout-gap-col (dec num-children))) + line-height) + + start-p + (cond-> base-p + ;; X AXIS + (and row? h-center? (not space-around?) (not space-between?)) + (-> (gpt/add (hv (/ layout-width 2))) + (gpt/subtract (hv (/ (+ line-width children-gap-width) 2)))) + + (and row? h-end? (not space-around?) (not space-between?)) + (-> (gpt/add (hv layout-width)) + (gpt/subtract (hv (+ line-width children-gap-width)))) + + (and col? h-center?) + (gpt/add (hv (/ line-width 2))) + + (and col? h-end?) + (gpt/add (hv line-width)) + + ;; Y AXIS + (and col? v-center? (not space-around?) (not space-between?)) + (-> (gpt/add (vv (/ layout-height 2))) + (gpt/subtract (vv (/ (+ line-height children-gap-height) 2)))) + + (and col? v-end? (not space-around?) (not space-between?)) + (-> (gpt/add (vv layout-height)) + (gpt/subtract (vv (+ line-height children-gap-height)))) + + (and row? v-center?) + (gpt/add (vv (/ line-height 2))) + + (and row? v-end?) + (gpt/add (vv line-height)))] + + start-p)) + + +(defn add-lines-positions + [parent layout-bounds layout-lines] + + (let [layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) + [layout-gap-row layout-gap-col] (ctl/gaps parent) + + row? (ctl/row? parent) + col? (ctl/col? parent)] + + (letfn [(add-lines [[total-width total-height] {:keys [line-width line-height]}] + [(+ total-width line-width) + (+ total-height line-height)]) + + (add-starts [[result base-p] layout-line] + (let [start-p (get-start-line parent layout-bounds layout-line base-p) + next-p (get-next-line parent layout-bounds layout-line base-p)] + + [(conj result + (assoc layout-line :start-p start-p)) + next-p]))] + + (let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0])) + + total-width (+ total-width (* layout-gap-row (dec (count layout-lines)))) + total-height (+ total-height (* layout-gap-col (dec (count layout-lines)))) + + vertical-fill-space (- layout-height total-height) + horizontal-fill-space (- layout-width total-width) + + num-line-fill (count (->> layout-lines (filter :line-fill?))) + + layout-lines + (->> layout-lines + (mapv #(cond-> % + (and row? (:line-fill? %)) + (update :line-height + (/ vertical-fill-space num-line-fill)) + + (and col? (:line-fill? %)) + (update :line-width + (/ horizontal-fill-space num-line-fill))))) + + total-width (if (and col? (> num-line-fill 0)) layout-width total-width) + total-height (if (and row? (> num-line-fill 0)) layout-height total-height) + + base-p (get-base-line parent layout-bounds total-width total-height)] + + (first (reduce add-starts [[] base-p] layout-lines)))))) + +(defn add-line-spacing + "Calculates the baseline for a flex layout" + [shape layout-bounds {:keys [num-children line-width line-height] :as line-data}] + + (let [width (gpo/width-points layout-bounds) + height (gpo/height-points layout-bounds) + + row? (ctl/row? shape) + col? (ctl/col? shape) + space-between? (ctl/space-between? shape) + space-around? (ctl/space-around? shape) + + [layout-gap-row layout-gap-col] (ctl/gaps shape) + + layout-gap-row + (cond (and row? space-around?) + 0 + + (and row? space-between?) + (/ (- width line-width) (dec num-children)) + + :else + layout-gap-row) + + layout-gap-col + (cond (and col? space-around?) + 0 + + (and col? space-between?) + (/ (- height line-height) (dec num-children)) + + :else + layout-gap-col) + + margin-x + (if (and row? space-around?) + (/ (- width line-width) (inc num-children)) + 0) + + margin-y + (if (and col? space-around?) + (/ (- height line-height) (inc num-children)) + 0)] + (assoc line-data + :layout-bounds layout-bounds + :layout-gap-row layout-gap-row + :layout-gap-col layout-gap-col + :margin-x margin-x + :margin-y margin-y))) + +(defn calc-layout-data + "Digest the layout data to pass it to the constrains" + [shape children] + + (let [layout-bounds (layout-bounds shape) + reverse? (ctl/reverse? shape) + children (cond->> children reverse? reverse) + + ;; Creates the layout lines information + layout-lines + (->> (init-layout-lines shape children layout-bounds) + (add-lines-positions shape layout-bounds) + (mapv (partial add-line-spacing shape layout-bounds)))] + + {:layout-lines layout-lines + :reverse? reverse?})) diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc new file mode 100644 index 0000000000..4343960d06 --- /dev/null +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -0,0 +1,103 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.geom.shapes.flex-layout.modifiers + (:require + [app.common.geom.point :as gpt] + [app.common.geom.shapes.flex-layout.positions :as fpo] + [app.common.geom.shapes.points :as gpo] + [app.common.geom.shapes.transforms :as gst] + [app.common.types.modifiers :as ctm] + [app.common.types.shape.layout :as ctl])) + + +(defn normalize-child-modifiers + "Apply the modifiers and then normalized them against the parent coordinates" + [parent child modifiers {:keys [transform transform-inverse] :as transformed-parent}] + + (let [transformed-child (gst/transform-shape child modifiers) + child-bb-before (gst/parent-coords-rect child parent) + child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) + scale-x (/ (:width child-bb-before) (:width child-bb-after)) + scale-y (/ (:height child-bb-before) (:height child-bb-after)) + + resize-origin (-> transformed-parent :points first) ;; TODO LAYOUT: IS always the origin?n + resize-vector (gpt/point scale-x scale-y)] + (-> modifiers + (ctm/select-child-modifiers) + (ctm/set-resize resize-vector resize-origin transform transform-inverse)))) + +(defn calc-fill-width-data + "Calculates the size and modifiers for the width of an auto-fill child" + [{:keys [transform transform-inverse] :as parent} + {:keys [layout-h-behavior] :as child} + child-origin child-width + {:keys [num-children line-width line-fill? child-fill? layout-bounds] :as layout-data}] + + (let [[layout-gap-row _] (ctl/gaps parent)] + (cond + (and (ctl/row? parent) (= :fill layout-h-behavior) child-fill?) + (let [layout-width (gpo/width-points layout-bounds) + fill-space (- layout-width line-width (* layout-gap-row (dec num-children))) + fill-width (/ fill-space (:num-child-fill layout-data)) + fill-scale (/ fill-width child-width)] + + {:width fill-width + :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) + + (and (ctl/col? parent) (= :fill layout-h-behavior) line-fill?) + (let [fill-scale (/ line-width child-width)] + {:width line-width + :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)})))) + +(defn calc-fill-height-data + "Calculates the size and modifiers for the height of an auto-fill child" + [{:keys [transform transform-inverse] :as parent} + {:keys [layout-v-behavior] :as child} + child-origin child-height + {:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}] + + (let [[_ layout-gap-col] (ctl/gaps parent)] + (cond + (and (ctl/col? parent) (= :fill layout-v-behavior) child-fill?) + (let [layout-height (gpo/height-points layout-bounds) + fill-space (- layout-height line-height (* layout-gap-col (dec num-children))) + fill-height (/ fill-space (:num-child-fill layout-data)) + fill-scale (/ fill-height child-height)] + {:height fill-height + :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) + + (and (ctl/row? parent) (= :fill layout-v-behavior) line-fill?) + (let [fill-scale (/ line-height child-height)] + {:height line-height + :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)})))) + +(defn calc-layout-modifiers + "Calculates the modifiers for the layout" + [parent child layout-line] + (let [child-bounds (gst/parent-coords-points child parent) + + child-origin (gpo/origin child-bounds) + child-width (gpo/width-points child-bounds) + child-height (gpo/height-points child-bounds) + + fill-width (calc-fill-width-data parent child child-origin child-width layout-line) + fill-height (calc-fill-height-data parent child child-origin child-height layout-line) + + child-width (or (:width fill-width) child-width) + child-height (or (:height fill-height) child-height) + + [corner-p layout-line] (fpo/get-child-position parent child-width child-height layout-line) + + move-vec (gpt/to-vec child-origin corner-p) + + modifiers + (-> (ctm/empty-modifiers) + (cond-> fill-width (ctm/add-modifiers (:modifiers fill-width))) + (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height))) + (ctm/set-move move-vec))] + + [modifiers layout-line])) diff --git a/common/src/app/common/geom/shapes/flex_layout/positions.cljc b/common/src/app/common/geom/shapes/flex_layout/positions.cljc new file mode 100644 index 0000000000..e230da22e2 --- /dev/null +++ b/common/src/app/common/geom/shapes/flex_layout/positions.cljc @@ -0,0 +1,68 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.geom.shapes.flex-layout.positions + (:require + [app.common.geom.point :as gpt] + [app.common.geom.shapes.points :as gpo] + [app.common.types.shape.layout :as ctl])) + +(defn get-child-position + "Calculates the position for the current shape given the layout-data context" + [parent + child-width child-height + {:keys [start-p layout-gap-row layout-gap-col margin-x margin-y] :as layout-data}] + + (let [row? (ctl/row? parent) + col? (ctl/col? parent) + + h-center? (ctl/h-center? parent) + h-end? (ctl/h-end? parent) + v-center? (ctl/v-center? parent) + v-end? (ctl/v-end? parent) + points (:points parent) + + hv (partial gpo/start-hv points) + vv (partial gpo/start-vv points) + + corner-p + (cond-> start-p + (and col? h-center?) + (gpt/add (hv (- (/ child-width 2)))) + + (and col? h-end?) + (gpt/add (hv (- child-width))) + + (and row? v-center?) + (gpt/add (vv (- (/ child-height 2)))) + + (and row? v-end?) + (gpt/add (vv (- child-height))) + + (some? margin-x) + (gpt/add (hv margin-x)) + + (some? margin-y) + (gpt/add (vv margin-y))) + + next-p + (cond-> start-p + row? + (gpt/add (hv (+ child-width layout-gap-row))) + + col? + (gpt/add (vv (+ child-height layout-gap-col))) + + (some? margin-x) + (gpt/add (hv margin-x)) + + (some? margin-y) + (gpt/add (vv margin-y))) + + layout-data + (assoc layout-data :start-p next-p)] + + [corner-p layout-data])) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 498df01ec6..b25e3cb270 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -9,6 +9,27 @@ [app.common.spec :as us] [clojure.spec.alpha :as s])) +;; :layout ;; :flex, :grid in the future +;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column +;; :layout-gap-type ;; :simple, :multiple +;; :layout-gap ;; {:row-gap number , :column-gap number} +;; :layout-align-items ;; :start :end :center :strech +;; :layout-justify-content ;; :start :center :end :space-between :space-around +;; :layout-align-content ;; :start :center :end :space-between :space-around :strech (by default) +;; :layout-wrap-type ;; :wrap, :no-wrap +;; :layout-padding-type ;; :simple, :multiple +;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative + +;; ITEMS +;; :layout-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} +;; :layout-margin-type ;; :simple :multiple +;; :layout-h-behavior ;; :fill :fix :auto +;; :layout-v-behavior ;; :fill :fix :auto +;; :layout-max-h ;; num +;; :layout-min-h ;; num +;; :layout-max-w ;; num +;; :layout-min-w + (s/def ::layout #{:flex :grid}) (s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column}) (s/def ::layout-gap-type #{:simple :multiple}) @@ -75,3 +96,111 @@ ::layout-max-w ::layout-min-w ::layout-align-self])) + + +(defn wrap? [{:keys [layout-wrap-type]}] + (= layout-wrap-type :wrap)) + +(defn fill-width? [child] + (= :fill (:layout-h-behavior child))) + +(defn fill-height? [child] + (= :fill (:layout-v-behavior child))) + +(defn col? + [{:keys [layout-flex-dir]}] + (or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir))) + +(defn row? + [{:keys [layout-flex-dir]}] + (or (= :row layout-flex-dir) (= :reverse-row layout-flex-dir))) + +(defn gaps + [{:keys [layout-gap layout-gap-type]}] + (let [layout-gap-row (or (-> layout-gap :row-gap) 0) + layout-gap-col (if (= layout-gap-type :simple) + layout-gap-row + (or (-> layout-gap :column-gap) 0))] + [layout-gap-row layout-gap-col])) + +(defn child-min-width + [child] + (if (and (fill-width? child) + (some? (:layout-min-h child))) + (max 0 (:layout-min-h child)) + 0)) + +(defn child-max-width + [child] + (if (and (fill-width? child) + (some? (:layout-min-h child))) + (max 0 (:layout-min-h child)) + 0)) + +(defn child-min-height + [child] + (if (and (fill-width? child) + (some? (:layout-min-v child))) + (max 0 (:layout-min-v child)) + 0)) + +(defn child-max-height + [child] + (if (and (fill-width? child) + (some? (:layout-min-v child))) + (max 0 (:layout-min-v child)) + 0)) + +(defn h-start? + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (col? shape) + (= layout-align-items :start)) + (and (row? shape) + (= layout-justify-content :start)))) + +(defn h-center? + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (col? shape) + (= layout-align-items :center)) + (and (row? shape) + (= layout-justify-content :center)))) + +(defn h-end? + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (col? shape) + (= layout-align-items :end)) + (and (row? shape) + (= layout-justify-content :end)))) + +(defn v-start? + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (row? shape) + (= layout-align-items :start)) + (and (col? shape) + (= layout-justify-content :start)))) + +(defn v-center? + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (row? shape) + (= layout-align-items :center)) + (and (col? shape) + (= layout-justify-content :center)))) + +(defn v-end? + [{:keys [layout-align-items layout-justify-content] :as shape}] + (or (and (row? shape) + (= layout-align-items :end)) + (and (col? shape) + (= layout-justify-content :end)))) +(defn reverse? + [{:keys [layout-flex-dir]}] + (or (= :reverse-row layout-flex-dir) + (= :reverse-column layout-flex-dir))) + +(defn space-between? + [{:keys [layout-justify-content]}] + (= layout-justify-content :space-between)) + +(defn space-around? + [{:keys [layout-justify-content]}] + (= layout-justify-content :space-around)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 1a0faed463..5024d3dd97 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.sidebar.options (:require [app.common.data :as d] - [app.common.types.modifiers :as ctm] [app.main.data.workspace :as udw] [app.main.refs :as refs] [app.main.store :as st] From 4b61e3228f892ac4ea55c36138ff6a9ca229ae73 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 25 Oct 2022 15:23:01 +0200 Subject: [PATCH 206/682] :sparkles: Add min/max width/height for elements --- .../common/geom/shapes/flex_layout/lines.cljc | 227 ++++++++++++------ .../geom/shapes/flex_layout/modifiers.cljc | 55 ++--- .../src/app/common/geom/shapes/modifiers.cljc | 17 +- common/src/app/common/types/shape/layout.cljc | 28 +-- .../sidebar/options/menus/layout_item.cljs | 5 +- 5 files changed, 203 insertions(+), 129 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index a1e5cc3c44..2ac988748f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -6,11 +6,15 @@ (ns app.common.geom.shapes.flex-layout.lines (:require + [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.transforms :as gst] + [app.common.math :as mth] [app.common.types.shape.layout :as ctl])) +(def conjv (fnil conj [])) + (defn layout-bounds [{:keys [layout-padding layout-padding-type] :as shape}] (let [;; Add padding to the bounds @@ -38,53 +42,65 @@ layout-width (gpo/width-points layout-bounds) layout-height (gpo/height-points layout-bounds) - reduce-fn - (fn [[{:keys [line-width line-height num-children line-fill? child-fill? num-child-fill] :as line-data} result] child] - (let [child-bounds (gst/parent-coords-points child shape) - child-width (gpo/width-points child-bounds) - child-height (gpo/height-points child-bounds) - child-min-width (ctl/child-min-width child) - child-min-height (ctl/child-min-height child) + calculate-line-data + (fn [[{:keys [line-min-width line-min-height + line-max-width line-max-height + num-children + children-data] :as line-data} result] child] - fill-width? (ctl/fill-width? child) + (let [child-bounds (gst/parent-coords-points child shape) + child-width (gpo/width-points child-bounds) + child-height (gpo/height-points child-bounds) + child-min-width (ctl/child-min-width child) + child-min-height (ctl/child-min-height child) + child-max-width (ctl/child-max-width child) + child-max-height (ctl/child-max-height child) + + fill-width? (ctl/fill-width? child) fill-height? (ctl/fill-height? child) - cur-child-fill? (or (and row? fill-width?) (and col? fill-height?)) - cur-line-fill? (or (and col? fill-width?) (and row? fill-height?)) + ;; We need this info later to calculate the child resizes when fill + child-data {:id (:id child) + :child-min-width (if fill-width? child-min-width child-width) + :child-min-height (if fill-height? child-min-height child-height) + :child-max-width (if fill-width? child-max-width child-width) + :child-max-height (if fill-height? child-max-height child-height)} - next-width (if fill-width? child-min-width child-width) - next-height (if fill-height? child-min-height child-height) - - next-line-width (+ line-width next-width (* layout-gap-row (dec num-children))) - next-line-height (+ line-height next-height (* layout-gap-col (dec num-children)))] + next-min-width (if fill-width? child-min-width child-width) + next-min-height (if fill-height? child-min-height child-height) + next-max-width (if fill-width? child-max-width child-width) + next-max-height (if fill-height? child-max-height child-height) + next-line-min-width (+ line-min-width next-min-width (* layout-gap-row num-children)) + next-line-min-height (+ line-min-height next-min-height (* layout-gap-col num-children))] (if (and (some? line-data) (or (not wrap?) - (and row? (<= next-line-width layout-width)) - (and col? (<= next-line-height layout-height)))) + (and row? (<= next-line-min-width layout-width)) + (and col? (<= next-line-min-height layout-height)))) - [{:line-width (if row? (+ line-width next-width) (max line-width next-width)) - :line-height (if col? (+ line-height next-height) (max line-height next-height)) + [{:line-min-width (if row? (+ line-min-width next-min-width) (max line-min-width next-min-width)) + :line-max-width (if row? (+ line-max-width next-max-width) (max line-max-width next-max-width)) + :line-min-height (if col? (+ line-min-height next-min-height) (max line-min-height next-min-height)) + :line-max-height (if col? (+ line-max-height next-max-height) (max line-max-height next-max-height)) :num-children (inc num-children) - :child-fill? (or cur-child-fill? child-fill?) - :line-fill? (or cur-line-fill? line-fill?) - :num-child-fill (cond-> num-child-fill cur-child-fill? inc)} + :children-data (conjv children-data child-data)} result] - [{:line-width next-width - :line-height next-height + [{:line-min-width next-min-width + :line-min-height next-min-height + :line-max-width next-max-width + :line-max-height next-max-height :num-children 1 - :child-fill? cur-child-fill? - :line-fill? cur-line-fill? - :num-child-fill (if cur-child-fill? 1 0)} + :children-data [child-data]} (cond-> result (some? line-data) (conj line-data))]))) - [line-data layout-lines] (reduce reduce-fn [nil []] children)] + [line-data layout-lines] (reduce calculate-line-data [nil []] children)] (cond-> layout-lines (some? line-data) (conj line-data)))) (defn get-base-line - [parent layout-bounds total-width total-height] + "Main axis line" + [parent layout-bounds total-width total-height num-lines] (let [layout-width (gpo/width-points layout-bounds) layout-height (gpo/height-points layout-bounds) @@ -95,20 +111,25 @@ v-center? (ctl/v-center? parent) v-end? (ctl/v-end? parent) hv (partial gpo/start-hv layout-bounds) - vv (partial gpo/start-vv layout-bounds)] + vv (partial gpo/start-vv layout-bounds) + + ;; Adjust the totals so it takes into account the gaps + [layout-gap-row layout-gap-col] (ctl/gaps parent) + lines-gap-row (* (dec num-lines) layout-gap-row) + lines-gap-col (* (dec num-lines) layout-gap-col)] (cond-> (gpo/origin layout-bounds) - (and col? h-center?) - (gpt/add (hv (/ (- layout-width total-width) 2))) - - (and col? h-end?) - (gpt/add (hv (- layout-width total-width))) - (and row? v-center?) - (gpt/add (vv (/ (- layout-height total-height) 2))) + (gpt/add (vv (/ (- layout-height total-height lines-gap-col) 2))) (and row? v-end?) - (gpt/add (vv (- layout-height total-height)))))) + (gpt/add (vv (- layout-height total-height lines-gap-col))) + + (and col? h-center?) + (gpt/add (hv (/ (- layout-width total-width lines-gap-row) 2))) + + (and col? h-end?) + (gpt/add (hv (- layout-width total-width lines-gap-row)))))) (defn get-next-line [parent layout-bounds {:keys [line-width line-height]} base-p] @@ -129,6 +150,7 @@ (gpt/add (vv (+ line-height layout-gap-col)))))) (defn get-start-line + "Cross axis line. It's position is fixed along the different lines" [parent layout-bounds {:keys [line-width line-height num-children child-fill? ]} base-p] (let [layout-width (gpo/width-points layout-bounds) @@ -150,14 +172,6 @@ children-gap-width (* layout-gap-row (dec num-children)) children-gap-height (* layout-gap-col (dec num-children)) - line-width (if (and row? child-fill?) - (- layout-width (* layout-gap-row (dec num-children))) - line-width) - - line-height (if (and col? child-fill?) - (- layout-height (* layout-gap-col (dec num-children))) - line-height) - start-p (cond-> base-p ;; X AXIS @@ -192,20 +206,60 @@ start-p)) +(defn add-space-to-items + ;; Distributes the remainder space between the lines + [prop prop-min prop-max to-share items] + (let [num-items (->> items (remove #(mth/close? (get % prop) (get % prop-max))) count) + per-line-target (/ to-share num-items)] + (loop [current (first items) + items (rest items) + remainder to-share + result []] + (if (nil? current) + [result remainder] + (let [cur-val (or (get current prop) (get current prop-min) 0) + max-val (get current prop-max) + cur-inc (if (> (+ cur-val per-line-target) max-val) + (- max-val cur-val) + per-line-target) + current (assoc current prop (+ cur-val cur-inc)) + remainder (- remainder cur-inc) + result (conj result current)] + (recur (first items) (rest items) remainder result)))))) + +(defn distribute-space + [prop prop-min prop-max min-value bound-value items] + (loop [to-share (- bound-value min-value) + items items] + (if (<= to-share 0) + items + (let [[items remainder] (add-space-to-items prop prop-min prop-max to-share items)] + (assert (<= remainder to-share) (str remainder ">" to-share)) + (if (or (<= remainder 0) (= remainder to-share)) + items + (recur remainder items)))))) (defn add-lines-positions [parent layout-bounds layout-lines] - (let [layout-width (gpo/width-points layout-bounds) - layout-height (gpo/height-points layout-bounds) + (let [row? (ctl/row? parent) + col? (ctl/col? parent) + [layout-gap-row layout-gap-col] (ctl/gaps parent) - row? (ctl/row? parent) - col? (ctl/col? parent)] + layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds)] - (letfn [(add-lines [[total-width total-height] {:keys [line-width line-height]}] - [(+ total-width line-width) - (+ total-height line-height)]) + (letfn [(add-lines [[total-width total-height] + {:keys [line-width line-height]}] + [(+ total-width line-width) (+ total-height line-height)]) + + (add-ranges [[total-min-width total-min-height total-max-width total-max-height] + {:keys [line-min-width line-min-height line-max-width line-max-height]}] + [(+ total-min-width line-min-width) + (+ total-min-height line-min-height) + (+ total-max-width line-max-width) + (+ total-max-height line-max-height)]) (add-starts [[result base-p] layout-line] (let [start-p (get-start-line parent layout-bounds layout-line base-p) @@ -215,29 +269,44 @@ (assoc layout-line :start-p start-p)) next-p]))] - (let [[total-width total-height] (->> layout-lines (reduce add-lines [0 0])) + (let [[total-min-width total-min-height total-max-width total-max-height] + (->> layout-lines (reduce add-ranges [0 0 0 0])) - total-width (+ total-width (* layout-gap-row (dec (count layout-lines)))) - total-height (+ total-height (* layout-gap-col (dec (count layout-lines)))) + get-layout-width (fn [{:keys [num-children]}] (- layout-width (* layout-gap-row (dec num-children)))) + get-layout-height (fn [{:keys [num-children]}] (- layout-height (* layout-gap-col (dec num-children)))) - vertical-fill-space (- layout-height total-height) - horizontal-fill-space (- layout-width total-width) - - num-line-fill (count (->> layout-lines (filter :line-fill?))) + num-lines (count layout-lines) + ;; Distributes the space between the layout lines based on its max/min constraints layout-lines - (->> layout-lines - (mapv #(cond-> % - (and row? (:line-fill? %)) - (update :line-height + (/ vertical-fill-space num-line-fill)) + (cond->> layout-lines + row? + (map #(assoc % :line-width (max (:line-min-width %) (min (get-layout-width %) (:line-max-width %))))) - (and col? (:line-fill? %)) - (update :line-width + (/ horizontal-fill-space num-line-fill))))) + col? + (map #(assoc % :line-height (max (:line-min-height %) (min (get-layout-height %) (:line-max-height %))))) - total-width (if (and col? (> num-line-fill 0)) layout-width total-width) - total-height (if (and row? (> num-line-fill 0)) layout-height total-height) + (and row? (>= total-min-height layout-height)) + (map #(assoc % :line-height (:line-min-height %))) - base-p (get-base-line parent layout-bounds total-width total-height)] + (and row? (<= total-max-height layout-height)) + (map #(assoc % :line-height (:line-max-height %))) + + (and row? (< total-min-height layout-height total-max-height)) + (distribute-space :line-height :line-min-height :line-max-height total-min-height (- layout-height (* (dec num-lines) layout-gap-col))) + + (and col? (>= total-min-width layout-width)) + (map #(assoc % :line-width (:line-min-width %))) + + (and col? (<= total-max-width layout-width)) + (map #(assoc % :line-width (:line-max-width %))) + + (and col? (< total-min-width layout-width total-max-width)) + (distribute-space :line-width :line-min-width :line-max-width total-min-width (- layout-width (* (dec num-lines) layout-gap-row)))) + + [total-width total-height] (->> layout-lines (reduce add-lines [0 0])) + + base-p (get-base-line parent layout-bounds total-width total-height num-lines)] (first (reduce add-starts [[] base-p] layout-lines)))))) @@ -291,6 +360,23 @@ :margin-x margin-x :margin-y margin-y))) +(defn add-children-resizes + [shape {:keys [line-min-width line-width line-min-height line-height] :as line-data}] + + (let [row? (ctl/row? shape) + col? (ctl/col? shape)] + (update line-data :children-data + (fn [children-data] + (cond->> children-data + row? + (distribute-space :child-width :child-min-width :child-max-width line-min-width line-width) + + col? + (distribute-space :child-height :child-min-height :child-max-height line-min-height line-height) + + :always + (d/index-by :id)))))) + (defn calc-layout-data "Digest the layout data to pass it to the constrains" [shape children] @@ -303,7 +389,8 @@ layout-lines (->> (init-layout-lines shape children layout-bounds) (add-lines-positions shape layout-bounds) - (mapv (partial add-line-spacing shape layout-bounds)))] + (mapv (partial add-line-spacing shape layout-bounds)) + (mapv (partial add-children-resizes shape)))] {:layout-lines layout-lines :reverse? reverse?})) diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 4343960d06..731df28a59 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -33,47 +33,40 @@ (defn calc-fill-width-data "Calculates the size and modifiers for the width of an auto-fill child" [{:keys [transform transform-inverse] :as parent} - {:keys [layout-h-behavior] :as child} + child child-origin child-width - {:keys [num-children line-width line-fill? child-fill? layout-bounds] :as layout-data}] + {:keys [children-data line-width] :as layout-data}] - (let [[layout-gap-row _] (ctl/gaps parent)] - (cond - (and (ctl/row? parent) (= :fill layout-h-behavior) child-fill?) - (let [layout-width (gpo/width-points layout-bounds) - fill-space (- layout-width line-width (* layout-gap-row (dec num-children))) - fill-width (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-width child-width)] + (cond + (and (ctl/row? parent) (ctl/fill-width? child)) + (let [target-width (get-in children-data [(:id child) :child-width]) + fill-scale (/ target-width child-width)] + {:width target-width + :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) - {:width fill-width - :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) - - (and (ctl/col? parent) (= :fill layout-h-behavior) line-fill?) - (let [fill-scale (/ line-width child-width)] - {:width line-width - :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)})))) + (and (ctl/col? parent) (ctl/fill-width? child)) + (let [fill-scale (/ line-width child-width)] + {:width line-width + :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}))) (defn calc-fill-height-data "Calculates the size and modifiers for the height of an auto-fill child" [{:keys [transform transform-inverse] :as parent} - {:keys [layout-v-behavior] :as child} + child child-origin child-height - {:keys [num-children line-height layout-bounds line-fill? child-fill?] :as layout-data}] + {:keys [children-data line-height] :as layout-data}] - (let [[_ layout-gap-col] (ctl/gaps parent)] - (cond - (and (ctl/col? parent) (= :fill layout-v-behavior) child-fill?) - (let [layout-height (gpo/height-points layout-bounds) - fill-space (- layout-height line-height (* layout-gap-col (dec num-children))) - fill-height (/ fill-space (:num-child-fill layout-data)) - fill-scale (/ fill-height child-height)] - {:height fill-height - :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) + (cond + (and (ctl/col? parent) (ctl/fill-height? child)) + (let [target-height (get-in children-data [(:id child) :child-height]) + fill-scale (/ target-height child-height)] + {:height target-height + :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) - (and (ctl/row? parent) (= :fill layout-v-behavior) line-fill?) - (let [fill-scale (/ line-height child-height)] - {:height line-height - :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)})))) + (and (ctl/row? parent) (ctl/fill-height? child)) + (let [fill-scale (/ line-height child-height)] + {:height line-height + :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))) (defn calc-layout-modifiers "Calculates the modifiers for the layout" diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 6f7d9a5ec6..31b86a2973 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -75,23 +75,18 @@ transformed-parent (gtr/transform-shape parent modifiers) children (map (d/getf objects) (:shapes transformed-parent)) - modif-tree (reduce (partial normalize-child transformed-parent _snap-pixel?) modif-tree children) + modif-tree (reduce (partial normalize-child transformed-parent _snap-pixel?) modif-tree children) + children (->> children (map (partial apply-modifiers modif-tree))) + layout-data (gcl/calc-layout-data transformed-parent children) + children (into [] (cond-> children (:reverse? layout-data) reverse)) + max-idx (dec (count children)) - children (->> children (map (partial apply-modifiers modif-tree))) - - layout-data (gcl/calc-layout-data transformed-parent children) - - children (into [] (cond-> children (:reverse? layout-data) reverse)) - - max-idx (dec (count children)) - layout-lines (:layout-lines layout-data)] + layout-lines (:layout-lines layout-data)] (loop [modif-tree modif-tree layout-line (first layout-lines) pending (rest layout-lines) from-idx 0] - - (if (and (some? layout-line) (<= from-idx max-idx)) (let [to-idx (+ from-idx (:num-children layout-line)) children (subvec children from-idx to-idx) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index b25e3cb270..8f4b431288 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -80,7 +80,7 @@ (s/def ::layout-margin-type #{:simple :multiple}) (s/def ::layout-h-behavior #{:fill :fix :auto}) (s/def ::layout-v-behavior #{:fill :fix :auto}) -(s/def ::layout-align-self #{:start :end :center :strech :baseline}) +(s/def ::layout-align-self #{:start :end :center :strech}) (s/def ::layout-max-h ::us/safe-number) (s/def ::layout-min-h ::us/safe-number) (s/def ::layout-max-w ::us/safe-number) @@ -126,30 +126,30 @@ (defn child-min-width [child] (if (and (fill-width? child) - (some? (:layout-min-h child))) - (max 0 (:layout-min-h child)) + (some? (:layout-min-w child))) + (max 0 (:layout-min-w child)) 0)) (defn child-max-width [child] (if (and (fill-width? child) + (some? (:layout-max-w child))) + (max 0 (:layout-max-w child)) + ##Inf)) + +(defn child-min-height + [child] + (if (and (fill-height? child) (some? (:layout-min-h child))) (max 0 (:layout-min-h child)) 0)) -(defn child-min-height - [child] - (if (and (fill-width? child) - (some? (:layout-min-v child))) - (max 0 (:layout-min-v child)) - 0)) - (defn child-max-height [child] - (if (and (fill-width? child) - (some? (:layout-min-v child))) - (max 0 (:layout-min-v child)) - 0)) + (if (and (fill-height? child) + (some? (:layout-max-h child))) + (max 0 (:layout-max-h child)) + ##Inf)) (defn h-start? [{:keys [layout-align-items layout-justify-content] :as shape}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index e0b8fd357c..c01ea7ddb7 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -218,6 +218,5 @@ :placeholder "--" :on-click #(dom/select-target %) :on-change (partial on-size-change item) - ;; :value (get values item) - :value 100}]]])]])]] - )) + :value (get values item) + :nillable true}]]])]])]])) From 81d2f9dd9d38575efd2b984f568ecb7f6a8f51b8 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 26 Oct 2022 15:45:09 +0200 Subject: [PATCH 207/682] :sparkles: Adds align-content options --- .../common/geom/shapes/flex_layout/lines.cljc | 123 +++++++++++++----- .../geom/shapes/flex_layout/modifiers.cljc | 12 +- .../geom/shapes/flex_layout/positions.cljc | 39 +++++- common/src/app/common/types/shape/layout.cljc | 57 +++++++- .../app/main/data/workspace/shape_layout.cljs | 2 +- .../options/menus/layout_container.cljs | 20 +-- .../sidebar/options/menus/layout_item.cljs | 4 +- 7 files changed, 204 insertions(+), 53 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index 2ac988748f..478bbc746a 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -56,6 +56,12 @@ child-max-width (ctl/child-max-width child) child-max-height (ctl/child-max-height child) + [child-margin-top child-margin-right child-margin-bottom child-margin-left] + (ctl/child-margins child) + + child-margin-width (+ child-margin-left child-margin-right) + child-margin-height (+ child-margin-top child-margin-bottom) + fill-width? (ctl/fill-width? child) fill-height? (ctl/fill-height? child) @@ -66,10 +72,10 @@ :child-max-width (if fill-width? child-max-width child-width) :child-max-height (if fill-height? child-max-height child-height)} - next-min-width (if fill-width? child-min-width child-width) - next-min-height (if fill-height? child-min-height child-height) - next-max-width (if fill-width? child-max-width child-width) - next-max-height (if fill-height? child-max-height child-height) + next-min-width (+ child-margin-width (if fill-width? child-min-width child-width)) + next-min-height (+ child-margin-height (if fill-height? child-min-height child-height)) + next-max-width (+ child-margin-width (if fill-width? child-max-width child-width)) + next-max-height (+ child-margin-height (if fill-height? child-max-height child-height)) next-line-min-width (+ line-min-width next-min-width (* layout-gap-row num-children)) next-line-min-height (+ line-min-height next-min-height (* layout-gap-col num-children))] @@ -106,52 +112,100 @@ layout-height (gpo/height-points layout-bounds) row? (ctl/row? parent) col? (ctl/col? parent) - h-center? (ctl/h-center? parent) - h-end? (ctl/h-end? parent) - v-center? (ctl/v-center? parent) - v-end? (ctl/v-end? parent) hv (partial gpo/start-hv layout-bounds) vv (partial gpo/start-vv layout-bounds) + end? (ctl/content-end? parent) + center? (ctl/content-center? parent) + around? (ctl/content-around? parent) + ;; Adjust the totals so it takes into account the gaps [layout-gap-row layout-gap-col] (ctl/gaps parent) lines-gap-row (* (dec num-lines) layout-gap-row) - lines-gap-col (* (dec num-lines) layout-gap-col)] + lines-gap-col (* (dec num-lines) layout-gap-col) + + free-width-gap (- layout-width total-width lines-gap-row) + free-height-gap (- layout-height total-height lines-gap-col) + free-width (- layout-width total-width) + free-height (- layout-height total-height)] (cond-> (gpo/origin layout-bounds) - (and row? v-center?) - (gpt/add (vv (/ (- layout-height total-height lines-gap-col) 2))) + row? + (cond-> center? + (gpt/add (vv (/ free-height-gap 2))) - (and row? v-end?) - (gpt/add (vv (- layout-height total-height lines-gap-col))) + end? + (gpt/add (vv free-height-gap)) - (and col? h-center?) - (gpt/add (hv (/ (- layout-width total-width lines-gap-row) 2))) + around? + (gpt/add (vv (/ free-height (inc num-lines))))) - (and col? h-end?) - (gpt/add (hv (- layout-width total-width lines-gap-row)))))) + col? + (cond-> center? + (gpt/add (hv (/ free-width-gap 2))) + + end? + (gpt/add (hv free-width-gap)) + + around? + (gpt/add (hv (/ free-width (inc num-lines)))))))) (defn get-next-line - [parent layout-bounds {:keys [line-width line-height]} base-p] + [parent layout-bounds {:keys [line-width line-height]} base-p total-width total-height num-lines] - (let [row? (ctl/row? parent) + (let [layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) + row? (ctl/row? parent) col? (ctl/col? parent) [layout-gap-row layout-gap-col] (ctl/gaps parent) hv #(gpo/start-hv layout-bounds %) - vv #(gpo/start-vv layout-bounds %)] + vv #(gpo/start-vv layout-bounds %) + + stretch? (ctl/content-stretch? parent) + between? (ctl/content-between? parent) + around? (ctl/content-around? parent) + + free-width (- layout-width total-width) + free-height (- layout-height total-height) + + line-gap-row (cond + stretch? + (/ free-width num-lines) + + between? + (/ free-width (dec num-lines)) + + around? + (/ free-width (inc num-lines)) + + :else + layout-gap-row) + + line-gap-col (cond + stretch? + (/ free-height num-lines) + + between? + (/ free-height (dec num-lines)) + + around? + (/ free-height (inc num-lines)) + + :else + layout-gap-col)] (cond-> base-p - col? - (gpt/add (hv (+ line-width layout-gap-row))) - row? - (gpt/add (vv (+ line-height layout-gap-col)))))) + (gpt/add (vv (+ line-height (max layout-gap-col line-gap-col)))) + + col? + (gpt/add (hv (+ line-width (max layout-gap-row line-gap-row))))))) (defn get-start-line "Cross axis line. It's position is fixed along the different lines" - [parent layout-bounds {:keys [line-width line-height num-children child-fill? ]} base-p] + [parent layout-bounds {:keys [line-width line-height num-children]} base-p total-width total-height num-lines] (let [layout-width (gpo/width-points layout-bounds) layout-height (gpo/height-points layout-bounds) @@ -165,6 +219,7 @@ h-end? (ctl/h-end? parent) v-center? (ctl/v-center? parent) v-end? (ctl/v-end? parent) + content-stretch? (ctl/content-stretch? parent) hv #(gpo/start-hv layout-bounds %) vv #(gpo/start-vv layout-bounds %) @@ -172,6 +227,16 @@ children-gap-width (* layout-gap-row (dec num-children)) children-gap-height (* layout-gap-col (dec num-children)) + line-height + (if (and row? content-stretch?) + (+ line-height (/ (- layout-height total-height) num-lines)) + line-height) + + line-width + (if (and col? content-stretch?) + (+ line-width (/ (- layout-width total-width) num-lines)) + line-width) + start-p (cond-> base-p ;; X AXIS @@ -261,9 +326,9 @@ (+ total-max-width line-max-width) (+ total-max-height line-max-height)]) - (add-starts [[result base-p] layout-line] - (let [start-p (get-start-line parent layout-bounds layout-line base-p) - next-p (get-next-line parent layout-bounds layout-line base-p)] + (add-starts [total-width total-height num-lines [result base-p] layout-line] + (let [start-p (get-start-line parent layout-bounds layout-line base-p total-width total-height num-lines) + next-p (get-next-line parent layout-bounds layout-line base-p total-width total-height num-lines)] [(conj result (assoc layout-line :start-p start-p)) @@ -308,7 +373,7 @@ base-p (get-base-line parent layout-bounds total-width total-height num-lines)] - (first (reduce add-starts [[] base-p] layout-lines)))))) + (first (reduce (partial add-starts total-width total-height num-lines) [[] base-p] layout-lines)))))) (defn add-line-spacing "Calculates the baseline for a flex layout" diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 731df28a59..02e778550f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -45,8 +45,9 @@ :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) (and (ctl/col? parent) (ctl/fill-width? child)) - (let [fill-scale (/ line-width child-width)] - {:width line-width + (let [target-width (- line-width (ctl/child-width-margin child)) + fill-scale (/ target-width child-width)] + {:width target-width :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}))) (defn calc-fill-height-data @@ -64,8 +65,9 @@ :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) (and (ctl/row? parent) (ctl/fill-height? child)) - (let [fill-scale (/ line-height child-height)] - {:height line-height + (let [target-height (- line-height (ctl/child-height-margin child)) + fill-scale (/ target-height child-height)] + {:height target-height :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))) (defn calc-layout-modifiers @@ -83,7 +85,7 @@ child-width (or (:width fill-width) child-width) child-height (or (:height fill-height) child-height) - [corner-p layout-line] (fpo/get-child-position parent child-width child-height layout-line) + [corner-p layout-line] (fpo/get-child-position parent child child-width child-height layout-line) move-vec (gpt/to-vec child-origin corner-p) diff --git a/common/src/app/common/geom/shapes/flex_layout/positions.cljc b/common/src/app/common/geom/shapes/flex_layout/positions.cljc index e230da22e2..c9b4c13aad 100644 --- a/common/src/app/common/geom/shapes/flex_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/positions.cljc @@ -12,15 +12,17 @@ (defn get-child-position "Calculates the position for the current shape given the layout-data context" - [parent + [parent child child-width child-height {:keys [start-p layout-gap-row layout-gap-col margin-x margin-y] :as layout-data}] (let [row? (ctl/row? parent) col? (ctl/col? parent) + h-start? (ctl/h-start? parent) h-center? (ctl/h-center? parent) h-end? (ctl/h-end? parent) + v-start? (ctl/v-start? parent) v-center? (ctl/v-center? parent) v-end? (ctl/v-end? parent) points (:points parent) @@ -28,6 +30,8 @@ hv (partial gpo/start-hv points) vv (partial gpo/start-vv points) + [margin-top margin-right margin-bottom margin-left] (ctl/child-margins child) + corner-p (cond-> start-p (and col? h-center?) @@ -36,12 +40,39 @@ (and col? h-end?) (gpt/add (hv (- child-width))) + col? + (gpt/add (vv margin-top)) + + (and col? h-start?) + (gpt/add (hv margin-left)) + + (and col? h-center?) + (gpt/add (hv (/ (- margin-left margin-right) 2))) + + (and col? h-end?) + (gpt/add (hv (- margin-right))) + + ;; X COORD (and row? v-center?) (gpt/add (vv (- (/ child-height 2)))) (and row? v-end?) (gpt/add (vv (- child-height))) + row? + (gpt/add (hv margin-left)) + + (and row? v-start?) + (gpt/add (vv margin-top)) + + (and row? v-center?) + (gpt/add (vv (/ (- margin-top margin-bottom) 2))) + + (and row? v-end?) + (gpt/add (vv (- margin-bottom))) + + ;; Margins + (some? margin-x) (gpt/add (hv margin-x)) @@ -50,6 +81,12 @@ next-p (cond-> start-p + (and row? (or (> margin-left 0) (> margin-right 0))) + (gpt/add (hv (+ margin-left margin-right))) + + (and col? (or (> margin-top 0) (> margin-bottom 0))) + (gpt/add (vv (+ margin-top margin-bottom))) + row? (gpt/add (hv (+ child-width layout-gap-row))) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 8f4b431288..23b02cd6df 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -13,9 +13,9 @@ ;; :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column ;; :layout-gap-type ;; :simple, :multiple ;; :layout-gap ;; {:row-gap number , :column-gap number} -;; :layout-align-items ;; :start :end :center :strech +;; :layout-align-items ;; :start :end :center :stretch ;; :layout-justify-content ;; :start :center :end :space-between :space-around -;; :layout-align-content ;; :start :center :end :space-between :space-around :strech (by default) +;; :layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default) ;; :layout-wrap-type ;; :wrap, :no-wrap ;; :layout-padding-type ;; :simple, :multiple ;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative @@ -34,8 +34,8 @@ (s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column}) (s/def ::layout-gap-type #{:simple :multiple}) (s/def ::layout-gap ::us/safe-number) -(s/def ::layout-align-items #{:start :end :center :strech}) -(s/def ::layout-align-content #{:start :end :center :space-between :space-around :strech}) +(s/def ::layout-align-items #{:start :end :center :stretch}) +(s/def ::layout-align-content #{:start :end :center :space-between :space-around :stretch}) (s/def ::layout-justify-content #{:start :center :end :space-between :space-around}) (s/def ::layout-wrap-type #{:wrap :no-wrap}) (s/def ::layout-padding-type #{:simple :multiple}) @@ -80,7 +80,7 @@ (s/def ::layout-margin-type #{:simple :multiple}) (s/def ::layout-h-behavior #{:fill :fix :auto}) (s/def ::layout-v-behavior #{:fill :fix :auto}) -(s/def ::layout-align-self #{:start :end :center :strech}) +(s/def ::layout-align-self #{:start :end :center :stretch}) (s/def ::layout-max-h ::us/safe-number) (s/def ::layout-min-h ::us/safe-number) (s/def ::layout-max-w ::us/safe-number) @@ -151,6 +151,26 @@ (max 0 (:layout-max-h child)) ##Inf)) +(defn child-margins + [{{:keys [m1 m2 m3 m4]} :layout-margin :keys [layout-margin-type]}] + (let [m1 (or m1 0) + m2 (or m2 0) + m3 (or m3 0) + m4 (or m4 0)] + (if (= layout-margin-type :multiple) + [m1 m2 m3 m4] + [m1 m1 m1 m1]))) + +(defn child-height-margin + [child] + (let [[top _ bottom _] (child-margins child)] + (+ top bottom))) + +(defn child-width-margin + [child] + (let [[_ right _ left] (child-margins child)] + (+ right left))) + (defn h-start? [{:keys [layout-align-items layout-justify-content] :as shape}] (or (and (col? shape) @@ -192,6 +212,33 @@ (= layout-align-items :end)) (and (col? shape) (= layout-justify-content :end)))) + +(defn content-start? + [{:keys [layout-align-content]}] + (= :start layout-align-content)) + +(defn content-center? + [{:keys [layout-align-content]}] + (= :center layout-align-content)) + +(defn content-end? + [{:keys [layout-align-content]}] + (= :end layout-align-content)) + +(defn content-between? + [{:keys [layout-align-content]}] + (= :space-between layout-align-content)) + +(defn content-around? + [{:keys [layout-align-content]}] + (= :space-around layout-align-content)) + +(defn content-stretch? + [{:keys [layout-align-content]}] + (or (= :stretch layout-align-content) + (nil? layout-align-content))) + + (defn reverse? [{:keys [layout-flex-dir]}] (or (= :reverse-row layout-flex-dir) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index da2262c536..9ab3536bca 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -35,7 +35,7 @@ :layout-gap {:row-gap 0 :column-gap 0} :layout-align-items :start :layout-justify-content :start - :layout-align-content :strech + :layout-align-content :stretch :layout-wrap-type :no-wrap :layout-padding-type :simple :layout-padding {:p1 0 :p2 0 :p3 0 :p4 0}}) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index 1d1191a747..4eb03a287f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -21,9 +21,9 @@ :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column :layout-gap-type ;; :simple, :multiple :layout-gap ;; {:row-gap number , :column-gap number} - :layout-align-items ;; :start :end :center :strech + :layout-align-items ;; :start :end :center :stretch :layout-justify-content ;; :start :center :end :space-between :space-around - :layout-align-content ;; :start :center :end :space-between :space-around :strech (by default) + :layout-align-content ;; :start :center :end :space-between :space-around :stretch (by default) :layout-wrap-type ;; :wrap, :no-wrap :layout-padding-type ;; :simple, :multiple :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative @@ -37,13 +37,13 @@ :start i/align-items-column-start :end i/align-items-column-end :center i/align-items-column-center - :strech i/align-items-column-strech + :stretch i/align-items-column-strech :baseline i/align-items-column-baseline) (case val :start i/align-items-row-start :end i/align-items-row-end :center i/align-items-row-center - :strech i/align-items-row-strech + :stretch i/align-items-row-strech :baseline i/align-items-row-baseline)) :justify-content (if is-col? (case val @@ -66,7 +66,7 @@ :center i/align-content-column-center :space-around i/align-content-column-around :space-between i/align-content-column-between - :strech nil) + :stretch nil) (case val :start i/align-content-row-start @@ -74,20 +74,20 @@ :center i/align-content-row-center :space-around i/align-content-row-around :space-between i/align-content-row-between - :strech nil)) + :stretch nil)) :align-self (if is-col? (case val :start i/align-self-column-top :end i/align-self-column-bottom :center i/align-self-column-center - :strech i/align-self-column-strech + :stretch i/align-self-column-strech :baseline i/align-self-column-baseline) (case val :start i/align-self-row-left :end i/align-self-row-right :center i/align-self-row-center - :strech i/align-self-row-strech + :stretch i/align-self-row-strech :baseline i/align-self-row-baseline)))) (mf/defc direction-btn @@ -129,7 +129,7 @@ [{:keys [is-col? align-items set-align] :as props}] [:div.align-items-style - (for [align [:start :center :end :strech]] + (for [align [:start :center :end :stretch]] [:button.align-start.tooltip {:class (dom/classnames :active (= align-items align) :tooltip-bottom-left (not= align :start) @@ -312,7 +312,7 @@ align-content (:layout-align-content values) set-align-content (fn [value] (if (= align-content value) - (st/emit! (dwsl/update-layout ids {:layout-align-content :strech})) + (st/emit! (dwsl/update-layout ids {:layout-align-content :stretch})) (st/emit! (dwsl/update-layout ids {:layout-align-content value})))) ;; Gap diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index c01ea7ddb7..f36291ca21 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -26,7 +26,7 @@ :layout-item-min-h ;; num :layout-item-max-w ;; num :layout-item-min-w ;; num - :layout-item-align-self ;; :start :end :center :strech :baseline + :layout-item-align-self ;; :start :end :center :stretch :baseline ]) (mf/defc margin-section @@ -122,7 +122,7 @@ (mf/defc align-self-row [{:keys [is-col? align-self set-align-self] :as props}] - (let [dir-v [:start :center :end :strech :baseline]] + (let [dir-v [:start :center :end :stretch :baseline]] [:div.align-self-style (for [align dir-v] [:button.align-self.tooltip.tooltip-bottom From 503a1dabac2f20422f5a9f5086f39be57d5bb35d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 Oct 2022 12:22:50 +0200 Subject: [PATCH 208/682] :sparkles: Align self and stretch fixes --- .../common/geom/shapes/flex_layout/lines.cljc | 189 ++----------- .../geom/shapes/flex_layout/positions.cljc | 260 ++++++++++++++---- common/src/app/common/types/shape/layout.cljc | 12 + 3 files changed, 242 insertions(+), 219 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index 478bbc746a..c05a0cf52f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -7,7 +7,7 @@ (ns app.common.geom.shapes.flex-layout.lines (:require [app.common.data :as d] - [app.common.geom.point :as gpt] + [app.common.geom.shapes.flex-layout.positions :as flp] [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.transforms :as gst] [app.common.math :as mth] @@ -104,172 +104,6 @@ (cond-> layout-lines (some? line-data) (conj line-data)))) -(defn get-base-line - "Main axis line" - [parent layout-bounds total-width total-height num-lines] - - (let [layout-width (gpo/width-points layout-bounds) - layout-height (gpo/height-points layout-bounds) - row? (ctl/row? parent) - col? (ctl/col? parent) - hv (partial gpo/start-hv layout-bounds) - vv (partial gpo/start-vv layout-bounds) - - end? (ctl/content-end? parent) - center? (ctl/content-center? parent) - around? (ctl/content-around? parent) - - ;; Adjust the totals so it takes into account the gaps - [layout-gap-row layout-gap-col] (ctl/gaps parent) - lines-gap-row (* (dec num-lines) layout-gap-row) - lines-gap-col (* (dec num-lines) layout-gap-col) - - free-width-gap (- layout-width total-width lines-gap-row) - free-height-gap (- layout-height total-height lines-gap-col) - free-width (- layout-width total-width) - free-height (- layout-height total-height)] - - (cond-> (gpo/origin layout-bounds) - row? - (cond-> center? - (gpt/add (vv (/ free-height-gap 2))) - - end? - (gpt/add (vv free-height-gap)) - - around? - (gpt/add (vv (/ free-height (inc num-lines))))) - - col? - (cond-> center? - (gpt/add (hv (/ free-width-gap 2))) - - end? - (gpt/add (hv free-width-gap)) - - around? - (gpt/add (hv (/ free-width (inc num-lines)))))))) - -(defn get-next-line - [parent layout-bounds {:keys [line-width line-height]} base-p total-width total-height num-lines] - - (let [layout-width (gpo/width-points layout-bounds) - layout-height (gpo/height-points layout-bounds) - row? (ctl/row? parent) - col? (ctl/col? parent) - - [layout-gap-row layout-gap-col] (ctl/gaps parent) - - hv #(gpo/start-hv layout-bounds %) - vv #(gpo/start-vv layout-bounds %) - - stretch? (ctl/content-stretch? parent) - between? (ctl/content-between? parent) - around? (ctl/content-around? parent) - - free-width (- layout-width total-width) - free-height (- layout-height total-height) - - line-gap-row (cond - stretch? - (/ free-width num-lines) - - between? - (/ free-width (dec num-lines)) - - around? - (/ free-width (inc num-lines)) - - :else - layout-gap-row) - - line-gap-col (cond - stretch? - (/ free-height num-lines) - - between? - (/ free-height (dec num-lines)) - - around? - (/ free-height (inc num-lines)) - - :else - layout-gap-col)] - - (cond-> base-p - row? - (gpt/add (vv (+ line-height (max layout-gap-col line-gap-col)))) - - col? - (gpt/add (hv (+ line-width (max layout-gap-row line-gap-row))))))) - -(defn get-start-line - "Cross axis line. It's position is fixed along the different lines" - [parent layout-bounds {:keys [line-width line-height num-children]} base-p total-width total-height num-lines] - - (let [layout-width (gpo/width-points layout-bounds) - layout-height (gpo/height-points layout-bounds) - [layout-gap-row layout-gap-col] (ctl/gaps parent) - - row? (ctl/row? parent) - col? (ctl/col? parent) - space-between? (ctl/space-between? parent) - space-around? (ctl/space-around? parent) - h-center? (ctl/h-center? parent) - h-end? (ctl/h-end? parent) - v-center? (ctl/v-center? parent) - v-end? (ctl/v-end? parent) - content-stretch? (ctl/content-stretch? parent) - - hv #(gpo/start-hv layout-bounds %) - vv #(gpo/start-vv layout-bounds %) - - children-gap-width (* layout-gap-row (dec num-children)) - children-gap-height (* layout-gap-col (dec num-children)) - - line-height - (if (and row? content-stretch?) - (+ line-height (/ (- layout-height total-height) num-lines)) - line-height) - - line-width - (if (and col? content-stretch?) - (+ line-width (/ (- layout-width total-width) num-lines)) - line-width) - - start-p - (cond-> base-p - ;; X AXIS - (and row? h-center? (not space-around?) (not space-between?)) - (-> (gpt/add (hv (/ layout-width 2))) - (gpt/subtract (hv (/ (+ line-width children-gap-width) 2)))) - - (and row? h-end? (not space-around?) (not space-between?)) - (-> (gpt/add (hv layout-width)) - (gpt/subtract (hv (+ line-width children-gap-width)))) - - (and col? h-center?) - (gpt/add (hv (/ line-width 2))) - - (and col? h-end?) - (gpt/add (hv line-width)) - - ;; Y AXIS - (and col? v-center? (not space-around?) (not space-between?)) - (-> (gpt/add (vv (/ layout-height 2))) - (gpt/subtract (vv (/ (+ line-height children-gap-height) 2)))) - - (and col? v-end? (not space-around?) (not space-between?)) - (-> (gpt/add (vv layout-height)) - (gpt/subtract (vv (+ line-height children-gap-height)))) - - (and row? v-center?) - (gpt/add (vv (/ line-height 2))) - - (and row? v-end?) - (gpt/add (vv line-height)))] - - start-p)) (defn add-space-to-items ;; Distributes the remainder space between the lines @@ -327,8 +161,8 @@ (+ total-max-height line-max-height)]) (add-starts [total-width total-height num-lines [result base-p] layout-line] - (let [start-p (get-start-line parent layout-bounds layout-line base-p total-width total-height num-lines) - next-p (get-next-line parent layout-bounds layout-line base-p total-width total-height num-lines)] + (let [start-p (flp/get-start-line parent layout-bounds layout-line base-p total-width total-height num-lines) + next-p (flp/get-next-line parent layout-bounds layout-line base-p total-width total-height num-lines)] [(conj result (assoc layout-line :start-p start-p)) @@ -342,6 +176,17 @@ num-lines (count layout-lines) + ;; When align-items is stretch we need to adjust the main axis size to grow for the full content + stretch-width-fix + (if (and col? (ctl/content-stretch? parent)) + (/ (- layout-width (* layout-gap-row (dec num-lines)) total-max-width) num-lines) + 0) + + stretch-height-fix + (if (and row? (ctl/content-stretch? parent)) + (/ (- layout-height (* layout-gap-col (dec num-lines)) total-max-height) num-lines) + 0) + ;; Distributes the space between the layout lines based on its max/min constraints layout-lines (cond->> layout-lines @@ -355,7 +200,7 @@ (map #(assoc % :line-height (:line-min-height %))) (and row? (<= total-max-height layout-height)) - (map #(assoc % :line-height (:line-max-height %))) + (map #(assoc % :line-height (+ (:line-max-height %) stretch-height-fix))) (and row? (< total-min-height layout-height total-max-height)) (distribute-space :line-height :line-min-height :line-max-height total-min-height (- layout-height (* (dec num-lines) layout-gap-col))) @@ -364,14 +209,14 @@ (map #(assoc % :line-width (:line-min-width %))) (and col? (<= total-max-width layout-width)) - (map #(assoc % :line-width (:line-max-width %))) + (map #(assoc % :line-width (+ (:line-max-width %) stretch-width-fix))) (and col? (< total-min-width layout-width total-max-width)) (distribute-space :line-width :line-min-width :line-max-width total-min-width (- layout-width (* (dec num-lines) layout-gap-row)))) [total-width total-height] (->> layout-lines (reduce add-lines [0 0])) - base-p (get-base-line parent layout-bounds total-width total-height num-lines)] + base-p (flp/get-base-line parent layout-bounds total-width total-height num-lines)] (first (reduce (partial add-starts total-width total-height num-lines) [[] base-p] layout-lines)))))) diff --git a/common/src/app/common/geom/shapes/flex_layout/positions.cljc b/common/src/app/common/geom/shapes/flex_layout/positions.cljc index c9b4c13aad..30437fd16f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/positions.cljc @@ -10,69 +10,239 @@ [app.common.geom.shapes.points :as gpo] [app.common.types.shape.layout :as ctl])) +(defn get-base-line + [parent layout-bounds total-width total-height num-lines] + + (let [layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) + row? (ctl/row? parent) + col? (ctl/col? parent) + hv (partial gpo/start-hv layout-bounds) + vv (partial gpo/start-vv layout-bounds) + + end? (ctl/content-end? parent) + center? (ctl/content-center? parent) + around? (ctl/content-around? parent) + + ;; Adjust the totals so it takes into account the gaps + [layout-gap-row layout-gap-col] (ctl/gaps parent) + lines-gap-row (* (dec num-lines) layout-gap-row) + lines-gap-col (* (dec num-lines) layout-gap-col) + + free-width-gap (- layout-width total-width lines-gap-row) + free-height-gap (- layout-height total-height lines-gap-col) + free-width (- layout-width total-width) + free-height (- layout-height total-height)] + + (cond-> (gpo/origin layout-bounds) + row? + (cond-> center? + (gpt/add (vv (/ free-height-gap 2))) + + end? + (gpt/add (vv free-height-gap)) + + around? + (gpt/add (vv (/ free-height (inc num-lines))))) + + col? + (cond-> center? + (gpt/add (hv (/ free-width-gap 2))) + + end? + (gpt/add (hv free-width-gap)) + + around? + (gpt/add (hv (/ free-width (inc num-lines)))))))) + +(defn get-next-line + [parent layout-bounds {:keys [line-width line-height]} base-p total-width total-height num-lines] + + (let [layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) + row? (ctl/row? parent) + col? (ctl/col? parent) + + [layout-gap-row layout-gap-col] (ctl/gaps parent) + + hv #(gpo/start-hv layout-bounds %) + vv #(gpo/start-vv layout-bounds %) + + stretch? (ctl/content-stretch? parent) + between? (ctl/content-between? parent) + around? (ctl/content-around? parent) + + free-width (- layout-width total-width) + free-height (- layout-height total-height) + + line-gap-row + (cond + stretch? + (/ free-width num-lines) + + between? + (/ free-width (dec num-lines)) + + around? + (/ free-width (inc num-lines)) + + :else + layout-gap-row) + + line-gap-col + (cond + stretch? + (/ free-height num-lines) + + between? + (/ free-height (dec num-lines)) + + around? + (/ free-height (inc num-lines)) + + :else + layout-gap-col)] + + (cond-> base-p + row? + (gpt/add (vv (+ line-height (max layout-gap-col line-gap-col)))) + + col? + (gpt/add (hv (+ line-width (max layout-gap-row line-gap-row))))))) + +(defn get-start-line + "Cross axis line. It's position is fixed along the different lines" + [parent layout-bounds {:keys [line-width line-height num-children]} base-p total-width total-height num-lines] + + (let [layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) + [layout-gap-row layout-gap-col] (ctl/gaps parent) + + row? (ctl/row? parent) + col? (ctl/col? parent) + space-between? (ctl/space-between? parent) + space-around? (ctl/space-around? parent) + h-center? (ctl/h-center? parent) + h-end? (ctl/h-end? parent) + v-center? (ctl/v-center? parent) + v-end? (ctl/v-end? parent) + content-stretch? (ctl/content-stretch? parent) + + hv #(gpo/start-hv layout-bounds %) + vv #(gpo/start-vv layout-bounds %) + + children-gap-width (* layout-gap-row (dec num-children)) + children-gap-height (* layout-gap-col (dec num-children)) + + line-height + (if (and row? content-stretch?) + (+ line-height (/ (- layout-height total-height) num-lines)) + line-height) + + line-width + (if (and col? content-stretch?) + (+ line-width (/ (- layout-width total-width) num-lines)) + line-width) + + start-p + (cond-> base-p + ;; X AXIS + (and row? h-center? (not space-around?) (not space-between?)) + (-> (gpt/add (hv (/ layout-width 2))) + (gpt/subtract (hv (/ (+ line-width children-gap-width) 2)))) + + (and row? h-end? (not space-around?) (not space-between?)) + (-> (gpt/add (hv layout-width)) + (gpt/subtract (hv (+ line-width children-gap-width)))) + + ;; Y AXIS + (and col? v-center? (not space-around?) (not space-between?)) + (-> (gpt/add (vv (/ layout-height 2))) + (gpt/subtract (vv (/ (+ line-height children-gap-height) 2)))) + + (and col? v-end? (not space-around?) (not space-between?)) + (-> (gpt/add (vv layout-height)) + (gpt/subtract (vv (+ line-height children-gap-height)))))] + + start-p)) + (defn get-child-position "Calculates the position for the current shape given the layout-data context" [parent child child-width child-height - {:keys [start-p layout-gap-row layout-gap-col margin-x margin-y] :as layout-data}] + {:keys [start-p layout-gap-row layout-gap-col margin-x margin-y line-height line-width] :as layout-data}] - (let [row? (ctl/row? parent) - col? (ctl/col? parent) + (let [row? (ctl/row? parent) + col? (ctl/col? parent) + h-start? (ctl/h-start? parent) + h-center? (ctl/h-center? parent) + h-end? (ctl/h-end? parent) + v-start? (ctl/v-start? parent) + v-center? (ctl/v-center? parent) + v-end? (ctl/v-end? parent) - h-start? (ctl/h-start? parent) - h-center? (ctl/h-center? parent) - h-end? (ctl/h-end? parent) - v-start? (ctl/v-start? parent) - v-center? (ctl/v-center? parent) - v-end? (ctl/v-end? parent) - points (:points parent) + self-start? (ctl/align-self-start? child) + self-end? (ctl/align-self-end? child) + self-center? (ctl/align-self-center? child) + align-self? (or self-start? self-end? self-center?) - hv (partial gpo/start-hv points) - vv (partial gpo/start-vv points) + v-start? (if (or col? (not align-self?)) v-start? self-start?) + v-center? (if (or col? (not align-self?)) v-center? self-center?) + v-end? (if (or col? (not align-self?)) v-end? self-end?) + + h-start? (if (or row? (not align-self?)) h-start? self-start?) + h-center? (if (or row? (not align-self?)) h-center? self-center?) + h-end? (if (or row? (not align-self?)) h-end? self-end?) [margin-top margin-right margin-bottom margin-left] (ctl/child-margins child) + points (:points parent) + hv (partial gpo/start-hv points) + vv (partial gpo/start-vv points) + corner-p (cond-> start-p - (and col? h-center?) - (gpt/add (hv (- (/ child-width 2)))) - - (and col? h-end?) - (gpt/add (hv (- child-width))) - + ;; COLUMN DIRECTION col? - (gpt/add (vv margin-top)) + (cond-> (some? margin-top) + (gpt/add (vv margin-top)) - (and col? h-start?) - (gpt/add (hv margin-left)) + h-center? + (gpt/add (hv (- (/ child-width 2)))) - (and col? h-center?) - (gpt/add (hv (/ (- margin-left margin-right) 2))) + h-end? + (gpt/add (hv (- child-width))) - (and col? h-end?) - (gpt/add (hv (- margin-right))) + h-start? + (gpt/add (hv margin-left)) - ;; X COORD - (and row? v-center?) - (gpt/add (vv (- (/ child-height 2)))) + h-center? + (gpt/add (hv (+ (/ line-width 2) (/ (- margin-left margin-right) 2)))) - (and row? v-end?) - (gpt/add (vv (- child-height))) + h-end? + (gpt/add (hv (+ line-width (- margin-right))))) + ;; ROW DIRECTION row? - (gpt/add (hv margin-left)) + (cond-> v-center? + (gpt/add (vv (- (/ child-height 2)))) - (and row? v-start?) - (gpt/add (vv margin-top)) + v-end? + (gpt/add (vv (- child-height))) - (and row? v-center?) - (gpt/add (vv (/ (- margin-top margin-bottom) 2))) + (some? margin-left) + (gpt/add (hv margin-left)) - (and row? v-end?) - (gpt/add (vv (- margin-bottom))) + v-start? + (gpt/add (vv margin-top)) + + v-center? + (gpt/add (vv (+ (/ line-height 2) (/ (- margin-top margin-bottom) 2)))) + + v-end? + (gpt/add (vv (+ line-height (- margin-bottom))))) ;; Margins - (some? margin-x) (gpt/add (hv margin-x)) @@ -81,18 +251,14 @@ next-p (cond-> start-p - (and row? (or (> margin-left 0) (> margin-right 0))) - (gpt/add (hv (+ margin-left margin-right))) - - (and col? (or (> margin-top 0) (> margin-bottom 0))) - (gpt/add (vv (+ margin-top margin-bottom))) - row? - (gpt/add (hv (+ child-width layout-gap-row))) + (-> (gpt/add (hv (+ child-width layout-gap-row))) + (gpt/add (hv (+ margin-left margin-right)))) col? - (gpt/add (vv (+ child-height layout-gap-col))) - + (-> (gpt/add (vv (+ margin-top margin-bottom))) + (gpt/add (vv (+ child-height layout-gap-col)))) + (some? margin-x) (gpt/add (hv margin-x)) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 23b02cd6df..98a7677115 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -251,3 +251,15 @@ (defn space-around? [{:keys [layout-justify-content]}] (= layout-justify-content :space-around)) + +(defn align-self-start? [{:keys [layout-align-self]}] + (= :start layout-align-self)) + +(defn align-self-end? [{:keys [layout-align-self]}] + (= :end layout-align-self)) + +(defn align-self-center? [{:keys [layout-align-self]}] + (= :center layout-align-self)) + +(defn align-self-stretch? [{:keys [layout-align-self]}] + (= :stretch layout-align-self)) From 94602feab1e9eec59b64885e21b12bffc96f19f0 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 Oct 2022 15:18:21 +0200 Subject: [PATCH 209/682] :sparkles: Updated translation keys --- frontend/translations/de.po | 18 +++++++++--------- frontend/translations/en.po | 16 ++++++++-------- frontend/translations/es.po | 16 ++++++++-------- frontend/translations/eu.po | 18 +++++++++--------- frontend/translations/fr.po | 18 +++++++++--------- frontend/translations/he.po | 18 +++++++++--------- frontend/translations/hr.po | 18 +++++++++--------- frontend/translations/pl.po | 14 +++++++------- frontend/translations/pt_BR.po | 18 +++++++++--------- frontend/translations/pt_PT.po | 18 +++++++++--------- frontend/translations/tr.po | 18 +++++++++--------- frontend/translations/zh_CN.po | 18 +++++++++--------- 12 files changed, 104 insertions(+), 104 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index f046c5808e..22c2a26497 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -3827,19 +3827,19 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Erweiterte Optionen" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "Max.Höhe" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "Max.Breite" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Min.Höhe" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Min.Breite" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -3847,19 +3847,19 @@ msgid "workspace.options.layout-item.title" msgstr "Elementgröße ändern" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Maximale Höhe" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Maximale Breite" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Mindesthöhe" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Mindestbreite" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -4701,4 +4701,4 @@ msgid "workspace.updates.update" msgstr "Aktualisieren" msgid "workspace.viewport.click-to-close-path" -msgstr "Klicken Sie, um den Pfad zu schließen" \ No newline at end of file +msgstr "Klicken Sie, um den Pfad zu schließen" diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 3bccfefadb..45cd4a44fe 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3490,35 +3490,35 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Advanced options" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "Max.Height" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "Max.Width" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Min.Height" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Min.Width" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Maximum height" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Maximum width" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Minimum height" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Minimum width" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs diff --git a/frontend/translations/es.po b/frontend/translations/es.po index efed7f4802..00a47ffed8 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -3889,35 +3889,35 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Opciones avanzadas" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "Altura.Max" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "Ancho.Max" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Altura.Min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Ancho.Min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Altura máxima" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Ancho máximo" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Altura mínima" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Ancho mínimo" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs diff --git a/frontend/translations/eu.po b/frontend/translations/eu.po index c2064616d6..148c2066c2 100644 --- a/frontend/translations/eu.po +++ b/frontend/translations/eu.po @@ -3682,19 +3682,19 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Aukera aurreratuak" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "Gehieneko altuera" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "Gehieneko zabalera" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Gutxieneko altuera" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Gutxieneko zabalera" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -3706,19 +3706,19 @@ msgid "workspace.options.layout-item.title" msgstr "Elementuaren tamaina aldatzea" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Gehieneko altuera" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Gehieneko zabalaera" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Gutxieneko altuera" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Gutxieneko zabalera" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -4524,4 +4524,4 @@ msgid "workspace.updates.update" msgstr "Eguneratu" msgid "workspace.viewport.click-to-close-path" -msgstr "Egin klik bidea ixteko" \ No newline at end of file +msgstr "Egin klik bidea ixteko" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 0efd2d5606..05cc81b4fa 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -3404,19 +3404,19 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Options avancées" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "Hauteur max" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "Largeur max" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Hauteur min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Largeur min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -3424,19 +3424,19 @@ msgid "workspace.options.layout-item.title" msgstr "Redimensionnement de l'élément" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Hauteur maximale" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Largeur maximale" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Hauteur minimale" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Largeur minimale" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -4131,4 +4131,4 @@ msgid "workspace.updates.update" msgstr "Actualiser" msgid "workspace.viewport.click-to-close-path" -msgstr "Cliquez pour fermer le chemin" \ No newline at end of file +msgstr "Cliquez pour fermer le chemin" diff --git a/frontend/translations/he.po b/frontend/translations/he.po index 5c58ec20cc..b49a949243 100644 --- a/frontend/translations/he.po +++ b/frontend/translations/he.po @@ -3782,19 +3782,19 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "אפשרויות מתקדמות" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "גובה מר.‏" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "רוחב מר.‏" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "גובה מז.‏" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "רוחב מז.‏" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -3810,19 +3810,19 @@ msgid "workspace.options.layout-item.title" msgstr "שינוי גודל רכיבים" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "גובה מרבי" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "רוחב מרבי" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "גובה מזערי" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "רוחב מזערי" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -4660,4 +4660,4 @@ msgid "workspace.updates.update" msgstr "עדכון" msgid "workspace.viewport.click-to-close-path" -msgstr "לחיצה תסגור את הנתיב" \ No newline at end of file +msgstr "לחיצה תסגור את הנתיב" diff --git a/frontend/translations/hr.po b/frontend/translations/hr.po index 83f6625286..473d978e15 100644 --- a/frontend/translations/hr.po +++ b/frontend/translations/hr.po @@ -3731,19 +3731,19 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Napredne opcije" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "Max.visina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "Max.širina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Min.visina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Min.širina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -3751,19 +3751,19 @@ msgid "workspace.options.layout-item.title" msgstr "Promjena veličine elementa" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Maksimalna visina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Maksimalna širina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Minimalna visina" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Minimalna širina" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -4602,4 +4602,4 @@ msgstr "Ažuriraj" #, fuzzy msgid "workspace.viewport.click-to-close-path" -msgstr "Pritisni da zatvoriš path" \ No newline at end of file +msgstr "Pritisni da zatvoriš path" diff --git a/frontend/translations/pl.po b/frontend/translations/pl.po index 3fdb268ff0..72e0fa8b71 100644 --- a/frontend/translations/pl.po +++ b/frontend/translations/pl.po @@ -3545,27 +3545,27 @@ msgid "workspace.options.layer-options.title.multiple" msgstr "Wybrane warstwy" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Min.Wysokość" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Min.Szerokość" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Maksymalna wysokość" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Maksymalna szerokość" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Minimalna wysokość" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Minimalna szerokość" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -4320,4 +4320,4 @@ msgid "workspace.updates.update" msgstr "Aktualizuj" msgid "workspace.viewport.click-to-close-path" -msgstr "Kliknij, aby zamknąć ścieżkę" \ No newline at end of file +msgstr "Kliknij, aby zamknąć ścieżkę" diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 0b83476979..47b4ba86df 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -3681,19 +3681,19 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Opções avançadas" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "Altura.Máx" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "Largura.Máx" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Altura.Min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Altura.Min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -3701,19 +3701,19 @@ msgid "workspace.options.layout-item.title" msgstr "Redimensionar elemento" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Altura máxima" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Largura máxima" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Altura mínima" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Largura mínima" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -4527,4 +4527,4 @@ msgid "workspace.updates.update" msgstr "Atualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Clique para fechar o caminho" \ No newline at end of file +msgstr "Clique para fechar o caminho" diff --git a/frontend/translations/pt_PT.po b/frontend/translations/pt_PT.po index 423d51d110..d3bae61338 100644 --- a/frontend/translations/pt_PT.po +++ b/frontend/translations/pt_PT.po @@ -3678,19 +3678,19 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Opções avançadas" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "Altura.Máx" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "Largura.Máx" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Altura.Min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Largura.Min" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -3698,19 +3698,19 @@ msgid "workspace.options.layout-item.title" msgstr "Redimensionar elementos" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Altura máxima" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Largura máxima" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Altura mínima" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Largura mínima" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -4526,4 +4526,4 @@ msgid "workspace.updates.update" msgstr "Atualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Clica para fechar o caminho" \ No newline at end of file +msgstr "Clica para fechar o caminho" diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 239d4c3155..14d9d75b91 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -3820,19 +3820,19 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "Gelişmiş seçenekler" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "Azami Yükseklik" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "Azami Genişlik" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "Asgari Yükseklik" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "Asgari Genişlik" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -3848,19 +3848,19 @@ msgid "workspace.options.layout-item.title" msgstr "Öge yeniden boyutlandırma" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "Azami yükseklik" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "Azami genişlik" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "Asgari yükseklik" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "Asgari genişlik" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -4700,4 +4700,4 @@ msgid "workspace.updates.update" msgstr "Güncelle" msgid "workspace.viewport.click-to-close-path" -msgstr "Yolu kapatmak için tıklayın" \ No newline at end of file +msgstr "Yolu kapatmak için tıklayın" diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 9f55473b38..cf3e34c855 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -3565,19 +3565,19 @@ msgid "workspace.options.layout-item.advanced-ops" msgstr "高级选项" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-h" +msgid "workspace.options.layout-item.layout-item-max-h" msgstr "最大高度" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-max-w" +msgid "workspace.options.layout-item.layout-item-max-w" msgstr "最大宽度" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-h" +msgid "workspace.options.layout-item.layout-item-min-h" msgstr "最小高度" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.layout-min-w" +msgid "workspace.options.layout-item.layout-item-min-w" msgstr "最小宽度" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -3585,19 +3585,19 @@ msgid "workspace.options.layout-item.title" msgstr "调整大小" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-h" +msgid "workspace.options.layout-item.title.layout-item-max-h" msgstr "最大高度" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-max-w" +msgid "workspace.options.layout-item.title.layout-item-max-w" msgstr "最大宽度" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-h" +msgid "workspace.options.layout-item.title.layout-item-min-h" msgstr "最小高度" #: src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs -msgid "workspace.options.layout-item.title.layout-min-w" +msgid "workspace.options.layout-item.title.layout-item-min-w" msgstr "最小宽度" #: src/app/main/ui/workspace/sidebar/options/menus/layout.cljs @@ -4405,4 +4405,4 @@ msgid "workspace.updates.update" msgstr "更新" msgid "workspace.viewport.click-to-close-path" -msgstr "单击以闭合路径" \ No newline at end of file +msgstr "单击以闭合路径" From cebda20dd4c55725f3346dd88466f7190f79e40f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 Oct 2022 15:18:41 +0200 Subject: [PATCH 210/682] :sparkles: Adapt to handoff changes --- common/src/app/common/pages/common.cljc | 562 +++++++++--------- common/src/app/common/types/shape/layout.cljc | 94 +-- frontend/src/app/main/refs.cljs | 5 +- frontend/src/app/main/render.cljs | 10 +- .../ui/viewer/handoff/selection_feedback.cljs | 2 - .../src/app/main/ui/viewer/interactions.cljs | 8 +- .../options/menus/layout_container.cljs | 23 +- .../sidebar/options/menus/layout_item.cljs | 52 +- 8 files changed, 384 insertions(+), 372 deletions(-) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index 3a47fdcd58..de34e6b742 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -83,14 +83,16 @@ :layout-h-orientation :layout-container :layout-v-orientation :layout-container - :layout-margin :layout-item - :layout-margin-type :layout-item - :layout-h-behavior :layout-item - :layout-v-behavior :layout-item - :layout-max-h :layout-item - :layout-min-h :layout-item - :layout-max-w :layout-item - :layout-min-w :layout-item}) + :layout-item-margin :layout-item + :layout-item-margin-type :layout-item + :layout-item-h-sizing :layout-item + :layout-item-v-sizing :layout-item + :layout-item-max-h :layout-item + :layout-item-min-h :layout-item + :layout-item-max-w :layout-item + :layout-item-min-w :layout-item + :layout-item-align-self :layout-item + }) ;; Attributes that may directly be edited by the user with forms (def editable-attrs @@ -133,56 +135,59 @@ :exports :layout - :layout-dir + :layout-flex-dir :layout-gap - :layout-type + :layout-gap-type + :layout-align-items + :layout-justify-content + :layout-align-content :layout-wrap-type :layout-padding-type :layout-padding - :layout-h-orientation - :layout-v-orientation - :layout-margin - :layout-margin-type - :layout-h-behavior - :layout-v-behavior - :layout-max-h - :layout-min-h - :layout-max-w - :layout-min-w} + :layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self} :group #{:proportion-lock :width :height - :x :y - :rotation - :selrect - :points + :x :y + :rotation + :selrect + :points - :constraints-h - :constraints-v - :fixed-scroll - :parent-id - :frame-id + :constraints-h + :constraints-v + :fixed-scroll + :parent-id + :frame-id - :opacity - :blend-mode - :blocked - :hidden + :opacity + :blend-mode + :blocked + :hidden - :shadow + :shadow - :blur + :blur - :exports + :exports - :layout-margin - :layout-margin-type - :layout-h-behavior - :layout-v-behavior - :layout-max-h - :layout-min-h - :layout-max-w - :layout-min-w} + :layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self} :rect #{:proportion-lock :width :height @@ -229,14 +234,15 @@ :exports - :layout-margin - :layout-margin-type - :layout-h-behavior - :layout-v-behavior - :layout-max-h - :layout-min-h - :layout-max-w - :layout-min-w} + :layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self} :circle #{:proportion-lock :width :height @@ -281,143 +287,237 @@ :exports - :layout-margin - :layout-margin-type - :layout-h-behavior - :layout-v-behavior - :layout-max-h - :layout-min-h - :layout-max-w - :layout-min-w} + :layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self} - :path #{:proportion-lock - :width :height - :x :y - :rotation - :selrect - :points + :path #{:proportion-lock + :width :height + :x :y + :rotation + :selrect + :points - :constraints-h - :constraints-v - :fixed-scroll - :parent-id - :frame-id + :constraints-h + :constraints-v + :fixed-scroll + :parent-id + :frame-id - :opacity - :blend-mode - :blocked - :hidden + :opacity + :blend-mode + :blocked + :hidden - :fills - :fill-color - :fill-opacity - :fill-color-ref-id - :fill-color-ref-file - :fill-color-gradient + :fills + :fill-color + :fill-opacity + :fill-color-ref-id + :fill-color-ref-file + :fill-color-gradient - :strokes - :stroke-style - :stroke-alignment - :stroke-width - :stroke-color - :stroke-color-ref-id - :stroke-color-ref-file - :stroke-opacity - :stroke-color-gradient - :stroke-cap-start - :stroke-cap-end + :strokes + :stroke-style + :stroke-alignment + :stroke-width + :stroke-color + :stroke-color-ref-id + :stroke-color-ref-file + :stroke-opacity + :stroke-color-gradient + :stroke-cap-start + :stroke-cap-end - :shadow + :shadow - :blur + :blur - :exports + :exports - :layout-margin - :layout-margin-type - :layout-h-behavior - :layout-v-behavior - :layout-max-h - :layout-min-h - :layout-max-w - :layout-min-w} + :layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self} - :text #{:proportion-lock - :width :height - :x :y - :rotation - :selrect - :points + :text #{:proportion-lock + :width :height + :x :y + :rotation + :selrect + :points - :constraints-h - :constraints-v - :fixed-scroll - :parent-id - :frame-id + :constraints-h + :constraints-v + :fixed-scroll + :parent-id + :frame-id - :opacity - :blend-mode - :blocked - :hidden + :opacity + :blend-mode + :blocked + :hidden - :fill-color - :fill-opacity - :fill-color-ref-id - :fill-color-ref-file - :fill-color-gradient + :fill-color + :fill-opacity + :fill-color-ref-id + :fill-color-ref-file + :fill-color-gradient - :stroke-style - :stroke-alignment - :stroke-width - :stroke-color - :stroke-color-ref-id - :stroke-color-ref-file - :stroke-opacity - :stroke-color-gradient - :stroke-cap-start - :stroke-cap-end + :stroke-style + :stroke-alignment + :stroke-width + :stroke-color + :stroke-color-ref-id + :stroke-color-ref-file + :stroke-opacity + :stroke-color-gradient + :stroke-cap-start + :stroke-cap-end - :shadow + :shadow - :blur + :blur - :typography-ref-id - :typography-ref-file + :typography-ref-id + :typography-ref-file - :font-id - :font-family - :font-variant-id - :font-size - :font-weight - :font-style + :font-id + :font-family + :font-variant-id + :font-size + :font-weight + :font-style - :text-align + :text-align - :text-direction + :text-direction - :line-height - :letter-spacing + :line-height + :letter-spacing - :vertical-align + :vertical-align - :text-decoration + :text-decoration - :text-transform + :text-transform - :grow-type + :grow-type - :exports + :exports - :layout-margin - :layout-margin-type - :layout-h-behavior - :layout-v-behavior - :layout-max-h - :layout-min-h - :layout-max-w - :layout-min-w} + :layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self} - :image #{:proportion-lock + :image #{:proportion-lock + :width :height + :x :y + :rotation + :rx :ry + :r1 :r2 :r3 :r4 + :selrect + :points + + :constraints-h + :constraints-v + :fixed-scroll + :parent-id + :frame-id + + :opacity + :blend-mode + :blocked + :hidden + + :shadow + + :blur + + :exports + + :layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self} + + :svg-raw #{:proportion-lock + :width :height + :x :y + :rotation + :rx :ry + :r1 :r2 :r3 :r4 + :selrect + :points + + :constraints-h + :constraints-v + :fixed-scroll + :parent-id + :frame-id + + :opacity + :blend-mode + :blocked + :hidden + + :fills + :fill-color + :fill-opacity + :fill-color-ref-id + :fill-color-ref-file + :fill-color-gradient + + :strokes + :stroke-style + :stroke-alignment + :stroke-width + :stroke-color + :stroke-color-ref-id + :stroke-color-ref-file + :stroke-opacity + :stroke-color-gradient + :stroke-cap-start + :stroke-cap-end + + :shadow + + :blur + + :exports + + :layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self} + + :bool #{:proportion-lock :width :height :x :y :rotation @@ -437,124 +537,36 @@ :blocked :hidden + :fill-color + :fill-opacity + :fill-color-ref-id + :fill-color-ref-file + :fill-color-gradient + + :stroke-style + :stroke-alignment + :stroke-width + :stroke-color + :stroke-color-ref-id + :stroke-color-ref-file + :stroke-opacity + :stroke-color-gradient + :stroke-cap-start + :stroke-cap-end + :shadow :blur :exports - :layout-margin - :layout-margin-type - :layout-h-behavior - :layout-v-behavior - :layout-max-h - :layout-min-h - :layout-max-w - :layout-min-w} - - :svg-raw #{:proportion-lock - :width :height - :x :y - :rotation - :rx :ry - :r1 :r2 :r3 :r4 - :selrect - :points - - :constraints-h - :constraints-v - :fixed-scroll - :parent-id - :frame-id - - :opacity - :blend-mode - :blocked - :hidden - - :fills - :fill-color - :fill-opacity - :fill-color-ref-id - :fill-color-ref-file - :fill-color-gradient - - :strokes - :stroke-style - :stroke-alignment - :stroke-width - :stroke-color - :stroke-color-ref-id - :stroke-color-ref-file - :stroke-opacity - :stroke-color-gradient - :stroke-cap-start - :stroke-cap-end - - :shadow - - :blur - - :exports - - :layout-margin - :layout-margin-type - :layout-h-behavior - :layout-v-behavior - :layout-max-h - :layout-min-h - :layout-max-w - :layout-min-w} - - :bool #{:proportion-lock - :width :height - :x :y - :rotation - :rx :ry - :r1 :r2 :r3 :r4 - :selrect - :points - - :constraints-h - :constraints-v - :fixed-scroll - :parent-id - :frame-id - - :opacity - :blend-mode - :blocked - :hidden - - :fill-color - :fill-opacity - :fill-color-ref-id - :fill-color-ref-file - :fill-color-gradient - - :stroke-style - :stroke-alignment - :stroke-width - :stroke-color - :stroke-color-ref-id - :stroke-color-ref-file - :stroke-opacity - :stroke-color-gradient - :stroke-cap-start - :stroke-cap-end - - :shadow - - :blur - - :exports - - :layout-margin - :layout-margin-type - :layout-h-behavior - :layout-v-behavior - :layout-max-h - :layout-min-h - :layout-max-w - :layout-min-w}}) + :layout-item-margin + :layout-item-margin-type + :layout-item-h-sizing + :layout-item-v-sizing + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self}}) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 98a7677115..32f54e16e8 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -21,14 +21,14 @@ ;; :layout-padding ;; {:p1 num :p2 num :p3 num :p4 num} number could be negative ;; ITEMS -;; :layout-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} -;; :layout-margin-type ;; :simple :multiple -;; :layout-h-behavior ;; :fill :fix :auto -;; :layout-v-behavior ;; :fill :fix :auto -;; :layout-max-h ;; num -;; :layout-min-h ;; num -;; :layout-max-w ;; num -;; :layout-min-w +;; :layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} +;; :layout-item-margin-type ;; :simple :multiple +;; :layout-item-h-sizing ;; :fill :fix :auto +;; :layout-item-v-sizing ;; :fill :fix :auto +;; :layout-item-max-h ;; num +;; :layout-item-min-h ;; num +;; :layout-item-max-w ;; num +;; :layout-item-min-w (s/def ::layout #{:flex :grid}) (s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column}) @@ -74,38 +74,38 @@ (s/def ::m3 ::us/safe-number) (s/def ::m4 ::us/safe-number) -(s/def ::layout-margin (s/keys :req-un [::m1] - :opt-un [::m2 ::m3 ::m4])) +(s/def ::layout-item-margin (s/keys :req-un [::m1] + :opt-un [::m2 ::m3 ::m4])) -(s/def ::layout-margin-type #{:simple :multiple}) -(s/def ::layout-h-behavior #{:fill :fix :auto}) -(s/def ::layout-v-behavior #{:fill :fix :auto}) -(s/def ::layout-align-self #{:start :end :center :stretch}) -(s/def ::layout-max-h ::us/safe-number) -(s/def ::layout-min-h ::us/safe-number) -(s/def ::layout-max-w ::us/safe-number) -(s/def ::layout-min-w ::us/safe-number) +(s/def ::layout-item-margin-type #{:simple :multiple}) +(s/def ::layout-item-h-sizing #{:fill :fix :auto}) +(s/def ::layout-item-v-sizing #{:fill :fix :auto}) +(s/def ::layout-item-align-self #{:start :end :center :stretch}) +(s/def ::layout-item-max-h ::us/safe-number) +(s/def ::layout-item-min-h ::us/safe-number) +(s/def ::layout-item-max-w ::us/safe-number) +(s/def ::layout-item-min-w ::us/safe-number) (s/def ::layout-child-props - (s/keys :opt-un [::layout-margin - ::layout-margin-type - ::layout-h-behavior - ::layout-v-behavior - ::layout-max-h - ::layout-min-h - ::layout-max-w - ::layout-min-w - ::layout-align-self])) + (s/keys :opt-un [::layout-item-margin + ::layout-item-margin-type + ::layout-item-h-sizing + ::layout-item-v-sizing + ::layout-item-max-h + ::layout-item-min-h + ::layout-item-max-w + ::layout-item-min-w + ::layout-item-align-self])) (defn wrap? [{:keys [layout-wrap-type]}] (= layout-wrap-type :wrap)) (defn fill-width? [child] - (= :fill (:layout-h-behavior child))) + (= :fill (:layout-item-h-sizing child))) (defn fill-height? [child] - (= :fill (:layout-v-behavior child))) + (= :fill (:layout-item-v-sizing child))) (defn col? [{:keys [layout-flex-dir]}] @@ -126,38 +126,38 @@ (defn child-min-width [child] (if (and (fill-width? child) - (some? (:layout-min-w child))) - (max 0 (:layout-min-w child)) + (some? (:layout-item-min-w child))) + (max 0 (:layout-item-min-w child)) 0)) (defn child-max-width [child] (if (and (fill-width? child) - (some? (:layout-max-w child))) - (max 0 (:layout-max-w child)) + (some? (:layout-item-max-w child))) + (max 0 (:layout-item-max-w child)) ##Inf)) (defn child-min-height [child] (if (and (fill-height? child) - (some? (:layout-min-h child))) - (max 0 (:layout-min-h child)) + (some? (:layout-item-min-h child))) + (max 0 (:layout-item-min-h child)) 0)) (defn child-max-height [child] (if (and (fill-height? child) - (some? (:layout-max-h child))) - (max 0 (:layout-max-h child)) + (some? (:layout-item-max-h child))) + (max 0 (:layout-item-max-h child)) ##Inf)) (defn child-margins - [{{:keys [m1 m2 m3 m4]} :layout-margin :keys [layout-margin-type]}] + [{{:keys [m1 m2 m3 m4]} :layout-item-margin :keys [layout-item-margin-type]}] (let [m1 (or m1 0) m2 (or m2 0) m3 (or m3 0) m4 (or m4 0)] - (if (= layout-margin-type :multiple) + (if (= layout-item-margin-type :multiple) [m1 m2 m3 m4] [m1 m1 m1 m1]))) @@ -252,14 +252,14 @@ [{:keys [layout-justify-content]}] (= layout-justify-content :space-around)) -(defn align-self-start? [{:keys [layout-align-self]}] - (= :start layout-align-self)) +(defn align-self-start? [{:keys [layout-item-align-self]}] + (= :start layout-item-align-self)) -(defn align-self-end? [{:keys [layout-align-self]}] - (= :end layout-align-self)) +(defn align-self-end? [{:keys [layout-item-align-self]}] + (= :end layout-item-align-self)) -(defn align-self-center? [{:keys [layout-align-self]}] - (= :center layout-align-self)) +(defn align-self-center? [{:keys [layout-item-align-self]}] + (= :center layout-item-align-self)) -(defn align-self-stretch? [{:keys [layout-align-self]}] - (= :stretch layout-align-self)) +(defn align-self-stretch? [{:keys [layout-item-align-self]}] + (= :stretch layout-item-align-self)) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index fb66b3b6b4..4f6015f09c 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -442,7 +442,10 @@ (l/derived (fn [state] (let [objects (wsh/lookup-viewer-objects state page-id)] - (filterv #(= :flex (:layout (cph/get-parent objects %))) ids))) + (into [] + (comp (filter #(= :flex (:layout (cph/get-parent objects %)))) + (map #(get objects %))) + ids))) st/state =)) (def colorpicker diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 77fa6f5a1e..a2627d2dad 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -254,7 +254,7 @@ objects (mf/with-memo [frame-id objects vector] - (let [update-fn #(update-in %1 %2 ctm/add-move vector)] + (let [update-fn #(update %1 %2 gsh/transform-shape (ctm/move vector))] (->> children-ids (into [frame-id]) (reduce update-fn objects)))) @@ -290,7 +290,6 @@ :xmlnsXlink "http://www.w3.org/1999/xlink" :xmlns:penpot (when include-metadata? "https://penpot.app/xmlns") :fill "none"} - [:& shape-wrapper {:shape frame}]]])) @@ -313,10 +312,9 @@ (mf/use-memo (mf/deps vector objects group-id) (fn [] - (let [modifier-ids (cons group-id (cph/get-children-ids objects group-id)) - update-fn #(update %1 %2 ctm/add-move vector) - modifiers (reduce update-fn {} modifier-ids)] - (ctm/merge-modifiers objects modifiers)))) + (let [children-ids (cons group-id (cph/get-children-ids objects group-id)) + update-fn #(update %1 %2 gsh/transform-shape (ctm/move vector))] + (reduce update-fn objects children-ids)))) group (get objects group-id) width (* (:width group) zoom) diff --git a/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs index 0b7af463a4..9d3325710c 100644 --- a/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/selection_feedback.cljs @@ -57,8 +57,6 @@ shapes (resolve-shapes objects [hover]) hover-shape (or (first shapes) frame) - hover-shape (gsh/translate-to-frame hover-shape size) - selected-shapes (resolve-shapes objects selected) selrect (gsh/selection-rect selected-shapes)] diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index 72d8228282..51b136d113 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm] [app.common.types.page :as ctp] @@ -29,13 +30,10 @@ (defn prepare-objects [frame size objects] - (let [ - frame-id (:id frame) + (let [frame-id (:id frame) vector (-> (gpt/point (:x size) (:y size)) (gpt/negate)) - - update-fn #(d/update-when %1 %2 ctm/add-move vector)] - + update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move vector))] (->> (cph/get-children-ids objects frame-id) (into [frame-id]) (reduce update-fn objects)))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index 4eb03a287f..71545cb596 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -18,7 +18,7 @@ (def layout-container-flex-attrs [:layout ;; :flex, :grid in the future - :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column + :layout-flex-dir ;; :row, :reverse-row, :column, :reverse-column :layout-gap-type ;; :simple, :multiple :layout-gap ;; {:row-gap number , :column-gap number} :layout-align-items ;; :start :end :center :stretch @@ -67,7 +67,7 @@ :space-around i/align-content-column-around :space-between i/align-content-column-between :stretch nil) - + (case val :start i/align-content-row-start :end i/align-content-row-end @@ -129,7 +129,7 @@ [{:keys [is-col? align-items set-align] :as props}] [:div.align-items-style - (for [align [:start :center :end :stretch]] + (for [align [:start :center :end #_:stretch #_:baseline]] [:button.align-start.tooltip {:class (dom/classnames :active (= align-items align) :tooltip-bottom-left (not= align :start) @@ -274,13 +274,14 @@ (st/emit! (dwsl/remove-layout ids)) (reset! open? false)) - set-flex (fn [] - (st/emit! (dwsl/remove-layout ids)) - (on-add-layout :flex)) - - set-grid (fn [] - (st/emit! (dwsl/remove-layout ids)) - (on-add-layout :grid)) + ;; Uncomment when activating the grid options + ;; set-flex (fn [] + ;; (st/emit! (dwsl/remove-layout ids)) + ;; (on-add-layout :flex)) + ;; + ;; set-grid (fn [] + ;; (st/emit! (dwsl/remove-layout ids)) + ;; (on-add-layout :grid)) ;; Flex-direction @@ -365,7 +366,7 @@ [:span "Layout"] (if (:layout values) [:div.title-actions - [:div.layout-btns + #_[:div.layout-btns [:button {:on-click set-flex :class (dom/classnames :active (= :flex layout-type))} "Flex"] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index f36291ca21..4ce0b57e36 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -32,7 +32,7 @@ (mf/defc margin-section [{:keys [values change-margin-style on-margin-change] :as props}] - (let [margin-type (or (:layout-margin-type values) :simple)] + (let [margin-type (or (:layout-item-margin-type values) :simple)] [:div.margin-row [:div.margin-icons @@ -57,7 +57,7 @@ {:placeholder "--" :on-click #(dom/select-target %) :on-change (partial on-margin-change :simple) - :value (or (-> values :layout-margin :m1) 0)}]]] + :value (or (-> values :layout-item-margin :m1) 0)}]]] (= margin-type :multiple) (for [num [:m1 :m2 :m3 :m4]] @@ -73,10 +73,10 @@ {:placeholder "--" :on-click #(dom/select-target %) :on-change (partial on-margin-change num) - :value (or (-> values :layout-margin num) 0)}]]]))]])) + :value (or (-> values :layout-item-margin num) 0)}]]]))]])) (mf/defc element-behavior - [{:keys [is-layout-container? is-layout-child? layout-h-behavior layout-v-behavior on-change-behavior] :as props}] + [{:keys [is-layout-container? is-layout-child? layout-item-h-sizing layout-item-v-sizing on-change-behavior] :as props}] (let [fill? is-layout-child? auto? is-layout-container?] @@ -84,45 +84,45 @@ [:div.layout-behavior.horizontal [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Fix width" - :class (dom/classnames :active (= layout-h-behavior :fix)) + :class (dom/classnames :active (= layout-item-h-sizing :fix)) :on-click #(on-change-behavior :h :fix)} i/auto-fix-layout] (when fill? [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Width 100%" - :class (dom/classnames :active (= layout-h-behavior :fill)) + :class (dom/classnames :active (= layout-item-h-sizing :fill)) :on-click #(on-change-behavior :h :fill)} i/auto-fill]) (when auto? [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Fit content" - :class (dom/classnames :active (= layout-v-behavior :auto)) + :class (dom/classnames :active (= layout-item-v-sizing :auto)) :on-click #(on-change-behavior :h :auto)} i/auto-hug])] [:div.layout-behavior [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Fix height" - :class (dom/classnames :active (= layout-v-behavior :fix)) + :class (dom/classnames :active (= layout-item-v-sizing :fix)) :on-click #(on-change-behavior :v :fix)} i/auto-fix-layout] (when fill? [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Height 100%" - :class (dom/classnames :active (= layout-v-behavior :fill)) + :class (dom/classnames :active (= layout-item-v-sizing :fill)) :on-click #(on-change-behavior :v :fill)} i/auto-fill]) (when auto? [:button.behavior-btn.tooltip.tooltip-bottom-left {:alt "Fit content" - :class (dom/classnames :active (= layout-v-behavior :auto)) + :class (dom/classnames :active (= layout-item-v-sizing :auto)) :on-click #(on-change-behavior :v :auto)} i/auto-hug])]])) (mf/defc align-self-row [{:keys [is-col? align-self set-align-self] :as props}] - (let [dir-v [:start :center :end :stretch :baseline]] + (let [dir-v [:start :center :end #_:stretch #_:baseline]] [:div.align-self-style (for [align dir-v] [:button.align-self.tooltip.tooltip-bottom @@ -143,11 +143,13 @@ change-margin-style (fn [type] - (st/emit! (dwsl/update-layout-child ids {:layout-margin-type type}))) + (st/emit! (dwsl/update-layout-child ids {:layout-item-margin-type type}))) - align-self (:layout-align-self values) + align-self (:layout-item-align-self values) set-align-self (fn [value] - (st/emit! (dwsl/update-layout-child ids {:layout-align-self value}))) + (if (= align-self value) + (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil})) + (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value})))) saved-dir (:layout-flex-dir values) is-col? (or (= :column saved-dir) (= :reverse-column saved-dir)) @@ -155,14 +157,14 @@ on-margin-change (fn [type val] (if (= type :simple) - (st/emit! (dwsl/update-layout-child ids {:layout-margin {:m1 val :m2 val :m3 val :m4 val}})) - (st/emit! (dwsl/update-layout-child ids {:layout-margin {type val}})))) + (st/emit! (dwsl/update-layout-child ids {:layout-item-margin {:m1 val :m2 val :m3 val :m4 val}})) + (st/emit! (dwsl/update-layout-child ids {:layout-item-margin {type val}})))) on-change-behavior (fn [dir value] (if (= dir :h) - (st/emit! (dwsl/update-layout-child ids {:layout-h-behavior value})) - (st/emit! (dwsl/update-layout-child ids {:layout-v-behavior value})))) + (st/emit! (dwsl/update-layout-child ids {:layout-item-h-sizing value})) + (st/emit! (dwsl/update-layout-child ids {:layout-item-v-sizing value})))) on-size-change (fn [measure value] @@ -177,8 +179,8 @@ [:div.row-title "Sizing"] [:& element-behavior {:is-layout-child? is-layout-child? :is-layout-container? is-layout-container? - :layout-v-behavior (or (:layout-v-behavior values) :fix) - :layout-h-behavior (or (:layout-h-behavior values) :fix) + :layout-item-v-sizing (or (:layout-item-v-sizing values) :fix) + :layout-item-h-sizing (or (:layout-item-h-sizing values) :fix) :on-change-behavior on-change-behavior}]] @@ -201,14 +203,14 @@ :align-self align-self :set-align-self set-align-self}]]] [:div.input-wrapper - (for [item [:layout-max-h :layout-min-h :layout-max-w :layout-min-w]] + (for [item [:layout-item-max-h :layout-item-min-h :layout-item-max-w :layout-item-min-w]] [:div.tooltip.tooltip-bottom {:key (d/name item) :alt (tr (dm/str "workspace.options.layout-item.title." (d/name item))) - :class (dom/classnames "maxH" (= item :layout-max-h) - "minH" (= item :layout-min-h) - "maxW" (= item :layout-max-w) - "minW" (= item :layout-min-w))} + :class (dom/classnames "maxH" (= item :layout-item-max-h) + "minH" (= item :layout-item-min-h) + "maxW" (= item :layout-item-max-w) + "minW" (= item :layout-item-min-w))} [:div.input-element {:alt (tr (dm/str "workspace.options.layout-item." (d/name item)))} [:> numeric-input From 0274567d83fb69f85b82c65ebb8ac79bf3048a6f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 Oct 2022 16:34:26 +0200 Subject: [PATCH 211/682] :sparkles: Visual adjustments --- common/src/app/common/types/shape/layout.cljc | 2 ++ frontend/resources/images/icons/layout-columns.svg | 3 +++ frontend/resources/images/icons/layout-rows.svg | 3 +++ .../main/partials/sidebar-element-options.scss | 4 ++++ frontend/src/app/main/refs.cljs | 8 ++++++++ frontend/src/app/main/ui/components/shape_icon.cljs | 11 ++++++++++- frontend/src/app/main/ui/icons.cljs | 2 ++ .../workspace/sidebar/options/menus/layout_item.cljs | 12 ++++++++---- .../ui/workspace/sidebar/options/menus/measures.cljs | 8 ++++++++ 9 files changed, 48 insertions(+), 5 deletions(-) create mode 100644 frontend/resources/images/icons/layout-columns.svg create mode 100644 frontend/resources/images/icons/layout-rows.svg diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 32f54e16e8..53aa8fc6e9 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -97,6 +97,8 @@ ::layout-item-min-w ::layout-item-align-self])) +(defn layout? [shape] + (and (= :frame (:type shape)) (= :flex (:layout shape)))) (defn wrap? [{:keys [layout-wrap-type]}] (= layout-wrap-type :wrap)) diff --git a/frontend/resources/images/icons/layout-columns.svg b/frontend/resources/images/icons/layout-columns.svg new file mode 100644 index 0000000000..4c9381c99c --- /dev/null +++ b/frontend/resources/images/icons/layout-columns.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/images/icons/layout-rows.svg b/frontend/resources/images/icons/layout-rows.svg new file mode 100644 index 0000000000..f09a1ced87 --- /dev/null +++ b/frontend/resources/images/icons/layout-rows.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 3c785391e1..7e956838d2 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -182,6 +182,10 @@ &.error { border-color: $color-danger; } + + &[disabled] { + color: $color-gray-30; + } } .input-select { diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 4f6015f09c..9c5f35f7c4 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -265,6 +265,14 @@ [ids] (l/derived #(into [] (keep (d/getf %)) ids) workspace-page-objects =)) +(defn parents-by-ids + [ids] + (l/derived + (fn [objects] + (let [parent-ids (into #{} (keep #(get-in objects [% :parent-id])) ids)] + (into [] (keep #(get objects %)) parent-ids))) + workspace-page-objects =)) + (defn children-objects [id] (l/derived diff --git a/frontend/src/app/main/ui/components/shape_icon.cljs b/frontend/src/app/main/ui/components/shape_icon.cljs index 27a8794b93..ab42a2e4cb 100644 --- a/frontend/src/app/main/ui/components/shape_icon.cljs +++ b/frontend/src/app/main/ui/components/shape_icon.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.components.shape-icon (:require + [app.common.types.shape.layout :as ctl] [app.main.ui.icons :as i] [rumext.v2 :as mf])) @@ -13,7 +14,15 @@ (mf/defc element-icon [{:keys [shape main-instance?] :as props}] (case (:type shape) - :frame i/artboard + :frame (cond + (and (ctl/layout? shape) (ctl/col? shape)) + i/layout-columns + + (and (ctl/layout? shape) (ctl/row? shape)) + i/layout-rows + + :else + i/artboard) :image i/image :line i/line :circle i/circle diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 69dd22c147..e6c073122e 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -133,6 +133,8 @@ (def justify-content-row-end (icon-xref :justify-content-row-end)) (def justify-content-row-start (icon-xref :justify-content-row-start)) (def layers (icon-xref :layers)) +(def layout-columns (icon-xref :layout-columns)) +(def layout-rows (icon-xref :layout-rows)) (def letter-spacing (icon-xref :letter-spacing)) (def libraries (icon-xref :libraries)) (def library (icon-xref :library)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index 4ce0b57e36..c360170173 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -8,7 +8,9 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace.shape-layout :as dwsl] + [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.icons :as i] @@ -129,7 +131,7 @@ {:class (dom/classnames :active (= align-self align) :tooltip-bottom-left (not= align :start) :tooltip-bottom (= align :start)) - :alt (dm/str "Align self " (d/name align)) ;; TODO añadir lineas de texto a tradus + :alt (dm/str "Align self " (d/name align)) :on-click #(set-align-self align) :key (str "align-self" align)} (get-layout-flex-icon :align-self align is-col?)])])) @@ -141,6 +143,9 @@ (let [open? (mf/use-state false) toggle-open (fn [] (swap! open? not)) + selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + selection-parents (mf/deref selection-parents-ref) + change-margin-style (fn [type] (st/emit! (dwsl/update-layout-child ids {:layout-item-margin-type type}))) @@ -151,8 +156,7 @@ (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self nil})) (st/emit! (dwsl/update-layout-child ids {:layout-item-align-self value})))) - saved-dir (:layout-flex-dir values) - is-col? (or (= :column saved-dir) (= :reverse-column saved-dir)) + is-col? (every? ctl/col? selection-parents) on-margin-change (fn [type val] @@ -182,7 +186,7 @@ :layout-item-v-sizing (or (:layout-item-v-sizing values) :fix) :layout-item-h-sizing (or (:layout-item-h-sizing values) :fix) :on-change-behavior on-change-behavior}]] - + [:& margin-section {:values values :change-margin-style change-margin-style diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 2fdea6e9f6..cba05bc1ee 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.geom.shapes :as gsh] + [app.common.types.shape.layout :as ctl] [app.common.types.shape.radius :as ctsr] [app.main.constants :refer [size-presets]] [app.main.data.workspace :as udw] @@ -81,6 +82,11 @@ [shape]) frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes) + selection-parents-ref (mf/use-memo (mf/deps ids) #(refs/parents-by-ids ids)) + selection-parents (mf/deref selection-parents-ref) + + flex-child? (->> selection-parents (some ctl/layout?)) + ;; To show interactively the measures while the user is manipulating ;; the shape with the mouse, generate a copy of the shapes applying ;; the transient transformations. @@ -327,11 +333,13 @@ :placeholder "--" :on-click select-all :on-change on-pos-x-change + :disabled flex-child? :value (:x values)}]] [:div.input-element.Yaxis {:title (tr "workspace.options.y")} [:> numeric-input {:no-validate true :placeholder "--" :on-click select-all + :disabled flex-child? :on-change on-pos-y-change :value (:y values)}]]]) From 7f0054959ff92dc096ddbe80638d2ba9ec5f4ca0 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 2 Nov 2022 14:14:22 +0100 Subject: [PATCH 212/682] :sparkles: Hug content to frames --- common/src/app/common/geom/shapes.cljc | 5 - .../app/common/geom/shapes/constraints.cljc | 2 +- .../app/common/geom/shapes/flex_layout.cljc | 11 +- .../geom/shapes/flex_layout/bounds.cljc | 113 +++++++++++ .../common/geom/shapes/flex_layout/lines.cljc | 14 +- .../geom/shapes/flex_layout/modifiers.cljc | 29 +-- .../src/app/common/geom/shapes/modifiers.cljc | 192 ++++++++++++------ common/src/app/common/geom/shapes/points.cljc | 17 +- common/src/app/common/geom/shapes/rect.cljc | 13 ++ .../app/common/geom/shapes/transforms.cljc | 33 ++- common/src/app/common/types/modifiers.cljc | 120 +++++++---- common/src/app/common/types/shape/layout.cljc | 6 + .../test/common_tests/geom_shapes_test.cljc | 2 - .../app/main/data/workspace/shape_layout.cljs | 7 +- frontend/src/app/main/refs.cljs | 3 +- .../sidebar/options/menus/layout_item.cljs | 25 ++- .../sidebar/options/shapes/frame.cljs | 25 +-- 17 files changed, 448 insertions(+), 169 deletions(-) create mode 100644 common/src/app/common/geom/shapes/flex_layout/bounds.cljc diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 941e6b6342..163a29b1cd 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -13,7 +13,6 @@ [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.corners :as gsc] - [app.common.geom.shapes.flex-layout :as gcl] [app.common.geom.shapes.intersect :as gin] [app.common.geom.shapes.modifiers :as gsm] [app.common.geom.shapes.path :as gsp] @@ -184,10 +183,6 @@ ;; Constratins (dm/export gct/calc-child-modifiers) -;; Layout -(dm/export gcl/calc-layout-data) -(dm/export gcl/calc-layout-modifiers) - ;; PATHS (dm/export gsp/content->selrect) (dm/export gsp/transform-content) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index ce4171d026..2be4becadb 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -265,7 +265,7 @@ (if (and (= :scale constraints-h) (= :scale constraints-v)) modifiers - (let [transformed-child (gst/transform-shape child modifiers) + (let [transformed-child (gst/transform-shape child (ctm/select-child-modifiers modifiers)) modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child transformed-parent) transformed-child (gst/transform-shape child modifiers) diff --git a/common/src/app/common/geom/shapes/flex_layout.cljc b/common/src/app/common/geom/shapes/flex_layout.cljc index 8361d230fd..b61eabf097 100644 --- a/common/src/app/common/geom/shapes/flex_layout.cljc +++ b/common/src/app/common/geom/shapes/flex_layout.cljc @@ -7,12 +7,15 @@ (ns app.common.geom.shapes.flex-layout (:require [app.common.data.macros :as dm] + [app.common.geom.shapes.flex-layout.bounds :as fbo] [app.common.geom.shapes.flex-layout.drop-area :as fdr] [app.common.geom.shapes.flex-layout.lines :as fli] [app.common.geom.shapes.flex-layout.modifiers :as fmo])) -(dm/export fli/calc-layout-data) -(dm/export fmo/normalize-child-modifiers) -(dm/export fmo/calc-layout-modifiers) -(dm/export fdr/layout-drop-areas) +(dm/export fbo/layout-content-bounds) (dm/export fdr/get-drop-index) +(dm/export fdr/layout-drop-areas) +(dm/export fli/calc-layout-data) +(dm/export fmo/layout-child-modifiers) +(dm/export fmo/normalize-child-modifiers) + diff --git a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc new file mode 100644 index 0000000000..e8cd52ce2d --- /dev/null +++ b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc @@ -0,0 +1,113 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.common.geom.shapes.flex-layout.bounds + (:require + [app.common.geom.point :as gpt] + [app.common.geom.shapes.common :as gco] + [app.common.geom.shapes.points :as gpo] + [app.common.geom.shapes.rect :as gre] + [app.common.math :as mth] + [app.common.types.shape.layout :as ctl])) + +(defn- child-layout-bound-points + "Returns the bounds of the children as points" + [parent child] + + (let [row? (ctl/row? parent) + col? (ctl/col? parent) + + hv (partial gpo/start-hv (:points parent)) + vv (partial gpo/start-vv (:points parent)) + + v-start? (ctl/v-start? parent) + v-center? (ctl/v-center? parent) + v-end? (ctl/v-end? parent) + h-start? (ctl/h-start? parent) + h-center? (ctl/h-center? parent) + h-end? (ctl/h-end? parent) + + base-p (first (:points child)) + + width (-> child :selrect :width) + height (-> child :selrect :height) + + min-width (if (ctl/fill-width? child) + (ctl/child-min-width child) + width) + + min-height (if (ctl/fill-height? child) + (ctl/child-min-height child) + height) + + ;; This is the leftmost (when row) or topmost (when col) point + ;; Will be added always to the bounds and then calculated the other limits + ;; from there + base-p (cond-> base-p + (and row? v-center?) + (gpt/add (vv (/ height 2))) + + (and row? v-end?) + (gpt/add (vv height)) + + (and col? h-center?) + (gpt/add (hv (/ width 2))) + + (and col? h-end?) + (gpt/add (hv width)))] + + (cond-> [base-p] + (and (mth/almost-zero? min-width) (mth/almost-zero? min-height)) + (conj (cond-> base-p + row? + (gpt/add (hv width)) + + col? + (gpt/add (vv height)))) + + (not (mth/almost-zero? min-width)) + (conj (cond-> base-p + (or row? h-start?) + (gpt/add (hv min-width)) + + (and col? h-center?) + (gpt/add (hv (/ min-width 2))) + + (and col? h-center?) + (gpt/subtract (hv min-width)))) + + (not (mth/almost-zero? min-height)) + (conj (cond-> base-p + (or col? v-start?) + (gpt/add (vv min-height)) + + (and row? v-center?) + (gpt/add (vv (/ min-height 2))) + + (and row? v-end?) + (gpt/subtract (vv min-height))))))) + +(defn layout-content-bounds + [{:keys [layout-padding] :as parent} children] + + (let [{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding + pad-top (or pad-top 0) + pad-right (or pad-right 0) + pad-bottom (or pad-bottom 0) + pad-left (or pad-left 0) + + child-bounds + (fn [{:keys [points] :as child}] + (if (or (ctl/fill-height? child) (ctl/fill-height? child)) + (child-layout-bound-points parent child) + points))] + + (as-> children $ + (mapcat child-bounds $) + (gco/transform-points $ (gco/center-shape parent) (:transform-inverse parent)) + (gre/squared-points $) + (gpo/pad-points $ (- pad-top) (- pad-right) (- pad-bottom) (- pad-left)) + (gre/points->rect $)))) diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index c05a0cf52f..e26894fa68 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -34,10 +34,13 @@ "Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation" [shape children layout-bounds] - (let [wrap? (ctl/wrap? shape) - col? (ctl/col? shape) + (let [col? (ctl/col? shape) row? (ctl/row? shape) + wrap? (and (ctl/wrap? shape) + (or col? (not (ctl/auto-width? shape))) + (or row? (not (ctl/auto-height? shape)))) + [layout-gap-row layout-gap-col] (ctl/gaps shape) layout-width (gpo/width-points layout-bounds) layout-height (gpo/height-points layout-bounds) @@ -278,6 +281,12 @@ (update line-data :children-data (fn [children-data] (cond->> children-data + row? + (map #(assoc % :child-width (:child-min-width %))) + + col? + (map #(assoc % :child-height (:child-min-height %))) + row? (distribute-space :child-width :child-min-width :child-max-width line-min-width line-width) @@ -304,3 +313,4 @@ {:layout-lines layout-lines :reverse? reverse?})) + diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 02e778550f..fc0b606831 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -13,10 +13,9 @@ [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl])) - (defn normalize-child-modifiers "Apply the modifiers and then normalized them against the parent coordinates" - [parent child modifiers {:keys [transform transform-inverse] :as transformed-parent}] + [modifiers parent child {:keys [transform transform-inverse] :as transformed-parent}] (let [transformed-child (gst/transform-shape child modifiers) child-bb-before (gst/parent-coords-rect child parent) @@ -38,14 +37,16 @@ {:keys [children-data line-width] :as layout-data}] (cond - (and (ctl/row? parent) (ctl/fill-width? child)) - (let [target-width (get-in children-data [(:id child) :child-width]) + (ctl/row? parent) + (let [target-width (max (get-in children-data [(:id child) :child-width]) 0.01) fill-scale (/ target-width child-width)] {:width target-width :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) - (and (ctl/col? parent) (ctl/fill-width? child)) - (let [target-width (- line-width (ctl/child-width-margin child)) + (ctl/col? parent) + (let [target-width (max (- line-width (ctl/child-width-margin child)) 0.01) + max-width (ctl/child-max-width child) + target-width (min max-width target-width) fill-scale (/ target-width child-width)] {:width target-width :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}))) @@ -58,19 +59,21 @@ {:keys [children-data line-height] :as layout-data}] (cond - (and (ctl/col? parent) (ctl/fill-height? child)) - (let [target-height (get-in children-data [(:id child) :child-height]) + (ctl/col? parent) + (let [target-height (max (get-in children-data [(:id child) :child-height]) 0.01) fill-scale (/ target-height child-height)] {:height target-height :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) - (and (ctl/row? parent) (ctl/fill-height? child)) - (let [target-height (- line-height (ctl/child-height-margin child)) + (ctl/row? parent) + (let [target-height (max (- line-height (ctl/child-height-margin child)) 0.01) + max-height (ctl/child-max-height child) + target-height (min max-height target-height) fill-scale (/ target-height child-height)] {:height target-height :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))) -(defn calc-layout-modifiers +(defn layout-child-modifiers "Calculates the modifiers for the layout" [parent child layout-line] (let [child-bounds (gst/parent-coords-points child parent) @@ -79,8 +82,8 @@ child-width (gpo/width-points child-bounds) child-height (gpo/height-points child-bounds) - fill-width (calc-fill-width-data parent child child-origin child-width layout-line) - fill-height (calc-fill-height-data parent child child-origin child-height layout-line) + fill-width (when (ctl/fill-width? child) (calc-fill-width-data parent child child-origin child-width layout-line)) + fill-height (when (ctl/fill-height? child) (calc-fill-height-data parent child child-origin child-height layout-line)) child-width (or (:width fill-width) child-width) child-height (or (:height fill-height) child-height) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 31b86a2973..9f7b9ae5c1 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -7,27 +7,42 @@ (ns app.common.geom.shapes.modifiers (:require [app.common.data :as d] + [app.common.data.macros :as dm] + [app.common.geom.point :as gpt] [app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.flex-layout :as gcl] [app.common.geom.shapes.pixel-precision :as gpp] [app.common.geom.shapes.transforms :as gtr] [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid])) +;;#?(:cljs +;; (defn modif->js +;; [modif-tree objects] +;; (clj->js (into {} +;; (map (fn [[k v]] +;; [(get-in objects [k :name]) v])) +;; modif-tree)))) + + (defn set-children-modifiers [modif-tree objects parent ignore-constraints snap-pixel?] - (letfn [(set-child [transformed-parent _snap-pixel? modif-tree child] - (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) - child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints transformed-parent) - child-modifiers (cond-> child-modifiers snap-pixel? (gpp/set-pixel-precision child))] - (cond-> modif-tree - (not (ctm/empty-modifiers? child-modifiers)) - (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))] - (let [children (map (d/getf objects) (:shapes parent)) - modifiers (get-in modif-tree [(:id parent) :modifiers]) - transformed-parent (gtr/transform-shape parent modifiers)] - (reduce (partial set-child transformed-parent snap-pixel?) modif-tree children)))) + (let [children (map (d/getf objects) (:shapes parent)) + modifiers (get-in modif-tree [(:id parent) :modifiers]) + transformed-parent (gtr/transform-shape parent modifiers) + parent (gtr/transform-shape parent (ctm/select-parent-modifiers modifiers)) + + set-child + (fn [modif-tree child] + (let [child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints transformed-parent) + child-modifiers (cond-> child-modifiers snap-pixel? (gpp/set-pixel-precision child))] + (cond-> modif-tree + (not (ctm/empty-modifiers? child-modifiers)) + (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))] + + (reduce set-child modif-tree children))) (defn group? [shape] (or (= :group (:type shape)) @@ -36,17 +51,15 @@ (defn frame? [shape] (= :frame (:type shape))) -(defn layout? [shape] - (and (frame? shape) - (:layout shape))) - (defn set-layout-modifiers ;; TODO LAYOUT: SNAP PIXEL! [modif-tree objects parent _snap-pixel?] - (letfn [(normalize-child [transformed-parent _snap-pixel? modif-tree child] + (letfn [(process-child [transformed-parent _snap-pixel? modif-tree child] (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) - child-modifiers (gcl/normalize-child-modifiers parent child modifiers transformed-parent)] + child-modifiers (-> modifiers + (ctm/select-child-geometry-modifiers) + (gcl/normalize-child-modifiers parent child transformed-parent))] (cond-> modif-tree (not (ctm/empty-modifiers? child-modifiers)) (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers)))) @@ -60,9 +73,9 @@ (and (some? modifiers) (group? child)) (gtr/apply-group-modifiers objects modif-tree)))) - (set-layout-modifiers [parent [layout-line modif-tree] child] + (set-child-modifiers [parent [layout-line modif-tree] child] (let [[modifiers layout-line] - (gcl/calc-layout-modifiers parent child layout-line) + (gcl/layout-child-modifiers parent child layout-line) modif-tree (cond-> modif-tree @@ -75,8 +88,9 @@ transformed-parent (gtr/transform-shape parent modifiers) children (map (d/getf objects) (:shapes transformed-parent)) - modif-tree (reduce (partial normalize-child transformed-parent _snap-pixel?) modif-tree children) + modif-tree (reduce (partial process-child transformed-parent _snap-pixel?) modif-tree children) children (->> children (map (partial apply-modifiers modif-tree))) + layout-data (gcl/calc-layout-data transformed-parent children) children (into [] (cond-> children (:reverse? layout-data) reverse)) max-idx (dec (count children)) @@ -92,11 +106,56 @@ children (subvec children from-idx to-idx) [_ modif-tree] - (reduce (partial set-layout-modifiers transformed-parent) [layout-line modif-tree] children)] + (reduce (partial set-child-modifiers transformed-parent) [layout-line modif-tree] children)] (recur modif-tree (first pending) (rest pending) to-idx)) modif-tree))))) +(defn set-auto-modifiers + [modif-tree objects parent] + (letfn [(apply-modifiers [child] + (let [modifiers (get-in modif-tree [(:id child) :modifiers])] + (cond-> child + (some? modifiers) + (gtr/transform-shape modifiers) + + (and (some? modifiers) (group? child)) + (gtr/apply-group-modifiers objects modif-tree)))) + + (set-parent-auto-width + [modifiers parent auto-width] + (let [origin (-> parent :points first) + scale-width (/ auto-width (-> parent :selrect :width) )] + (-> modifiers + (ctm/set-resize-parent (gpt/point scale-width 1) origin (:transform parent) (:transform-inverse parent))))) + + (set-parent-auto-height + [modifiers parent auto-height] + (let [origin (-> parent :points first) + scale-height (/ auto-height (-> parent :selrect :height) )] + (-> modifiers + (ctm/set-resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))] + + (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) + transformed-parent (gtr/transform-shape parent modifiers) + children (->> transformed-parent + :shapes + (map (comp apply-modifiers (d/getf objects)))) + + {auto-width :width auto-height :height} + (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) + (gcl/layout-content-bounds parent children)) + + modifiers + (cond-> modifiers + (and (some? auto-width) (ctl/auto-width? parent)) + (set-parent-auto-width transformed-parent auto-width) + + (and (some? auto-height) (ctl/auto-height? parent)) + (set-parent-auto-height transformed-parent auto-height))] + + (assoc-in modif-tree [(:id parent) :modifiers] modifiers)))) + (defn get-tree-root [id objects] @@ -109,7 +168,9 @@ result ;; Frame found, but not layout we return the last layout found (or the id) - (and (= :frame (:type parent)) + (and (and (= :frame (:type parent)) + (not (ctl/auto-width? parent)) + (not (ctl/auto-height? parent))) (not (:layout parent))) result @@ -124,7 +185,9 @@ (defn resolve-tree-sequence "Given the ids that have changed search for layout roots to recalculate" - [modif-tree objects] + [ids objects] + + (assert (or (nil? ids) (set? ids)) (dm/str "tree sequence from not set: " ids)) (let [redfn (fn [result id] @@ -152,10 +215,10 @@ (map #(get objects %)))) - roots (->> modif-tree keys (reduce redfn #{}))] + roots (->> ids (reduce redfn #{}))] (concat - (when (contains? modif-tree uuid/zero) [(get objects uuid/zero)]) + (when (contains? ids uuid/zero) [(get objects uuid/zero)]) (mapcat generate-tree roots)))) (defn inside-layout? @@ -173,48 +236,55 @@ :else (recur (:parent-id current)))))) -;;#?(:cljs -;; (defn modif->js -;; [modif-tree objects] -;; (clj->js (into {} -;; (map (fn [[k v]] -;; [(get-in objects [k :name]) v])) -;; modif-tree)))) +(defn- calculate-modifiers + ([objects snap-pixel? ignore-constraints [modif-tree recalculate] shape] + (calculate-modifiers objects snap-pixel? ignore-constraints false [modif-tree recalculate] shape)) + + ([objects snap-pixel? ignore-constraints ignore-auto? [modif-tree recalculate] shape] + (let [shape-id (:id shape) + root? (= uuid/zero shape-id) + modifiers (get-in modif-tree [shape-id :modifiers]) + + modifiers (cond-> modifiers + (and (not root?) (ctm/has-geometry? modifiers) snap-pixel?) + (gpp/set-pixel-precision shape)) + + modif-tree (-> modif-tree (assoc-in [shape-id :modifiers] modifiers)) + + has-modifiers? (ctm/child-modifiers? modifiers) + is-layout? (ctl/layout? shape) + is-auto? (or (ctl/auto-height? shape) (ctl/auto-width? shape)) + is-parent? (or (group? shape) (and (frame? shape) (not (ctl/layout? shape)))) + + ;; If the current child is inside the layout we ignore the constraints + is-inside-layout? (inside-layout? objects shape)] + + [(cond-> modif-tree + (and has-modifiers? is-parent? (not root?)) + (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?) + + is-layout? + (set-layout-modifiers objects shape snap-pixel?) + + (and (not ignore-auto?) is-auto?) + (set-auto-modifiers objects shape)) + + (cond-> recalculate + ;; Auto-width/height can change the positions in the parent so we need to recalculate + (and (not ignore-auto?) is-auto?) + (conj (:id shape)))]))) (defn set-objects-modifiers [modif-tree objects ignore-constraints snap-pixel?] - (let [shapes-tree (resolve-tree-sequence modif-tree objects) + (let [shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects) - modif-tree - (->> shapes-tree - (reduce - (fn [modif-tree shape] - - (let [root? (= uuid/zero (:id shape)) + [modif-tree recalculate] + (reduce (partial calculate-modifiers objects snap-pixel? ignore-constraints) [modif-tree #{}] shapes-tree) - modifiers (get-in modif-tree [(:id shape) :modifiers]) - modifiers (cond-> modifiers - (and (not root?) (ctm/has-geometry? modifiers) snap-pixel?) - (gpp/set-pixel-precision shape)) - - modif-tree (-> modif-tree (assoc-in [(:id shape) :modifiers] modifiers)) - - has-modifiers? (ctm/child-modifiers? modifiers) - is-layout? (layout? shape) - is-parent? (or (group? shape) (and (frame? shape) (not (layout? shape)))) - - ;; If the current child is inside the layout we ignore the constraints - is-inside-layout? (inside-layout? objects shape)] - - (cond-> modif-tree - (and has-modifiers? is-parent? (not root?)) - (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?) - - is-layout? - (set-layout-modifiers objects shape snap-pixel?)))) - - modif-tree))] + shapes-tree (resolve-tree-sequence recalculate objects) + [modif-tree _] + (reduce (partial calculate-modifiers objects snap-pixel? ignore-constraints true) [modif-tree #{}] shapes-tree)] ;;#?(:cljs ;; (.log js/console ">result" (modif->js modif-tree objects))) diff --git a/common/src/app/common/geom/shapes/points.cljc b/common/src/app/common/geom/shapes/points.cljc index 1f80f28df4..adae25aced 100644 --- a/common/src/app/common/geom/shapes/points.cljc +++ b/common/src/app/common/geom/shapes/points.cljc @@ -50,12 +50,13 @@ (defn pad-points [[p0 p1 p2 p3 :as points] pad-top pad-right pad-bottom pad-left] - (let [top-v (start-vv points pad-top) - right-v (end-hv points pad-right) - bottom-v (end-vv points pad-bottom) - left-v (start-hv points pad-left)] + (when (some? points) + (let [top-v (start-vv points pad-top) + right-v (end-hv points pad-right) + bottom-v (end-vv points pad-bottom) + left-v (start-hv points pad-left)] - [(-> p0 (gpt/add left-v) (gpt/add top-v)) - (-> p1 (gpt/add right-v) (gpt/add top-v)) - (-> p2 (gpt/add right-v) (gpt/add bottom-v)) - (-> p3 (gpt/add left-v) (gpt/add bottom-v))])) + [(-> p0 (gpt/add left-v) (gpt/add top-v)) + (-> p1 (gpt/add right-v) (gpt/add top-v)) + (-> p2 (gpt/add right-v) (gpt/add bottom-v)) + (-> p3 (gpt/add left-v) (gpt/add bottom-v))]))) diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 895388371b..d523f83213 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -87,6 +87,19 @@ (when (d/num? minx miny maxx maxy) (make-rect minx miny (- maxx minx) (- maxy miny)))))) +(defn squared-points + [points] + (when (d/not-empty? points) + (let [minx (transduce (keep :x) min ##Inf points) + miny (transduce (keep :y) min ##Inf points) + maxx (transduce (keep :x) max ##-Inf points) + maxy (transduce (keep :y) max ##-Inf points)] + (when (d/num? minx miny maxx maxy) + [(gpt/point minx miny) + (gpt/point maxx miny) + (gpt/point maxx maxy) + (gpt/point minx maxy)])))) + (defn points->selrect [points] (when-let [rect (points->rect points)] (let [{:keys [x y width height]} rect] diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index ce888ddf18..bdaa878078 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -434,20 +434,35 @@ (map (comp gpr/points->selrect :points transform-shape)) (gpr/join-selrects))) +(defn apply-children-modifiers + [objects modif-tree children] + (->> children + (map (fn [child] + (let [modifiers (get-in modif-tree [(:id child) :modifiers]) + child (transform-shape child modifiers) + parent? (or (= :group (:type child)) (= :bool (:type child)))] + (cond-> child + parent? + (apply-children-modifiers objects modif-tree))))))) + (defn apply-group-modifiers "Apply the modifiers to the group children to calculate its selection rect" - [group objects modif-tree] + [parent objects modif-tree] + (let [children (->> (:shapes parent) + (map (d/getf objects)) + (apply-children-modifiers objects modif-tree))] + (cond-> parent + (= :group (:type parent)) + (update-group-selrect children)))) + +(defn get-children-bounds + [parent objects modif-tree] (let [children - (->> (:shapes group) + (->> (:shapes parent) (map (d/getf objects)) - (map (fn [shape] - (let [modifiers (get modif-tree (:id shape)) - shape (-> shape (merge modifiers) transform-shape)] - (if (= :group (:type shape)) - (apply-group-modifiers shape objects modif-tree) - shape)))))] - (update-group-selrect group children))) + (apply-children-modifiers objects modif-tree))] + (->> children (mapcat :points) gpr/points->rect))) (defn parent-coords-rect [child parent] diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index fe308d402a..a99c5b893b 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -17,7 +17,11 @@ ;; --- Modifiers ;; Moodifiers types -;; - geometry: Geometry +;; - geometry-parent: Geometry non-recursive +;; * move +;; * resize +;; * rotation +;; - geometry-child: Geometry recursive ;; * move ;; * resize ;; * rotation @@ -28,7 +32,6 @@ ;; - structure-child: Structure recursive ;; * scale-content ;; * rotation -;; (def conjv (fnil conj [])) @@ -37,37 +40,59 @@ (defn empty-modifiers [] {}) +(defn set-move-parent + ([modifiers x y] + (set-move-parent modifiers (gpt/point x y))) + + ([modifiers vector] + (-> modifiers + (update :geometry-parent conjv {:type :move :vector vector})))) + +(defn set-resize-parent + ([modifiers vector origin] + (-> modifiers + (update :geometry-parent conjv {:type :resize + :vector vector + :origin origin}))) + + ([modifiers vector origin transform transform-inverse] + (-> modifiers + (update :geometry-parent conjv {:type :resize + :vector vector + :origin origin + :transform transform + :transform-inverse transform-inverse})))) (defn set-move ([modifiers x y] (set-move modifiers (gpt/point x y))) ([modifiers vector] (-> modifiers - (update :geometry conjv {:type :move :vector vector})))) + (update :geometry-child conjv {:type :move :vector vector})))) (defn set-resize ([modifiers vector origin] (-> modifiers - (update :geometry conjv {:type :resize - :vector vector - :origin origin}))) + (update :geometry-child conjv {:type :resize + :vector vector + :origin origin}))) ([modifiers vector origin transform transform-inverse] (-> modifiers - (update :geometry conjv {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + (update :geometry-child conjv {:type :resize + :vector vector + :origin origin + :transform transform + :transform-inverse transform-inverse})))) (defn set-rotation [modifiers center angle] (-> modifiers (update :structure-child conjv {:type :rotation :rotation angle}) - (update :geometry conjv {:type :rotation - :center center - :rotation angle}))) + (update :geometry-child conjv {:type :rotation + :center center + :rotation angle}))) (defn set-remove-children [modifiers shapes] @@ -97,8 +122,11 @@ [modifiers new-modifiers] (cond-> modifiers - (some? (:geometry new-modifiers)) - (update :geometry #(d/concat-vec [] % (:geometry new-modifiers))) + (some? (:geometry-child new-modifiers)) + (update :geometry-child #(d/concat-vec [] % (:geometry-child new-modifiers))) + + (some? (:geometry-parent new-modifiers)) + (update :geometry-parent #(d/concat-vec [] % (:geometry-parent new-modifiers))) (some? (:structure-parent new-modifiers)) (update :structure-parent #(d/concat-vec [] % (:structure-parent new-modifiers))) @@ -116,6 +144,13 @@ ([vector] (set-move (empty-modifiers) vector))) +(defn move-parent + ([x y] + (set-move-parent (empty-modifiers) (gpt/point x y))) + + ([vector] + (set-move-parent (empty-modifiers) vector))) + (defn resize ([vector origin] (set-resize (empty-modifiers) vector origin)) @@ -123,6 +158,13 @@ ([vector origin transform transform-inverse] (set-resize (empty-modifiers) vector origin transform transform-inverse))) +(defn resize-parent + ([vector origin] + (set-resize-parent (empty-modifiers) vector origin)) + + ([vector origin transform transform-inverse] + (set-resize-parent (empty-modifiers) vector origin transform transform-inverse))) + (defn rotation [shape center angle] (let [shape-center (gco/center-shape shape) @@ -155,32 +197,30 @@ (set-scale-content value))) (defn child-modifiers? - [{:keys [geometry structure-child]}] - (or (d/not-empty? geometry) + [{:keys [geometry-child structure-child]}] + (or (d/not-empty? geometry-child) (d/not-empty? structure-child))) (defn select-child-modifiers [modifiers] - (select-keys modifiers [:geometry :structure-child])) + (select-keys modifiers [:geometry-child :structure-child])) + +(defn select-child-geometry-modifiers + [modifiers] + (select-keys modifiers [:geometry-child])) + +(defn select-parent-modifiers + [modifiers] + (select-keys modifiers [:geometry-parent :structure-parent])) (defn select-structure [modifiers] (select-keys modifiers [:structure-parent])) -(defn add-move - ([object x y] - (add-move object (gpt/point x y))) - - ([object vector] - (update object :modifiers (move vector)))) - -(defn add-resize - [object vector origin] - (update object :modifiers (resize vector origin))) - (defn empty-modifiers? [modifiers] - (and (empty? (:geometry modifiers)) + (and (empty? (:geometry-child modifiers)) + (empty? (:geometry-parent modifiers)) (empty? (:structure-parent modifiers)) (empty? (:structure-child modifiers)))) @@ -253,8 +293,10 @@ (defn only-move? [modifier] - (and (= 1 (-> modifier :geometry count)) - (= :move (-> modifier :geometry first :type)))) + (or (and (= 1 (-> modifier :geometry-child count)) + (= :move (-> modifier :geometry-child first :type))) + (and (= 1 (-> modifier :geometry-parent count)) + (= :move (-> modifier :geometry-parent first :type))))) (defn get-frame-add-children [modif-tree] @@ -300,8 +342,11 @@ (gmt/multiply (gmt/rotate-matrix rotation)) (gmt/translate (gpt/negate center))) matrix)))] - (->> modifiers :geometry - (reduce apply-modifier (gmt/matrix))))) + (let [modifiers (if (d/not-empty? (:geometry-parent modifiers)) + (d/concat-vec (:geometry-parent modifiers) (:geometry-child modifiers)) + (:geometry-child modifiers))] + (->> modifiers + (reduce apply-modifier (gmt/matrix)))))) (defn scale-text-content [content value] @@ -357,5 +402,6 @@ (reduce apply-modifier $ (:structure-child modifiers))))) (defn has-geometry? - [{:keys [geometry]}] - (d/not-empty? geometry)) + [{:keys [geometry-parent geometry-child]}] + (or (d/not-empty? geometry-parent) + (d/not-empty? geometry-child))) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 53aa8fc6e9..8ab1f67c47 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -109,6 +109,12 @@ (defn fill-height? [child] (= :fill (:layout-item-v-sizing child))) +(defn auto-width? [child] + (= :auto (:layout-item-h-sizing child))) + +(defn auto-height? [child] + (= :auto (:layout-item-v-sizing child))) + (defn col? [{:keys [layout-flex-dir]}] (or (= :column layout-flex-dir) (= :reverse-column layout-flex-dir))) diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc index e36a487e71..eae43ef1b6 100644 --- a/common/test/common_tests/geom_shapes_test.cljc +++ b/common/test/common_tests/geom_shapes_test.cljc @@ -142,8 +142,6 @@ shape-before (assoc shape-before :modifiers modifiers) shape-after (gsh/transform-shape shape-before)] - (t/is (not= (:selrect shape-before) (:selrect shape-after))) - (t/is (close? (get-in shape-before [:selrect :x]) (get-in shape-after [:selrect :x]))) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 9ab3536bca..956d24e1e7 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace.changes :as dwc] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.transforms :as dwt] @@ -68,7 +69,6 @@ (rx/of (dwc/update-shapes ids #(merge % initial-grid-layout)) (update-layout-positions ids)))))) - (defn remove-layout [ids] (ptk/reify ::remove-layout @@ -91,6 +91,7 @@ ptk/WatchEvent (watch [_ state _] (let [objects (wsh/lookup-page-objects state) - parent-ids (->> ids (map #(cph/get-parent-id objects %)))] + parent-ids (->> ids (map #(cph/get-parent-id objects %))) + layout-ids (->> ids (filter (comp ctl/layout? (d/getf objects))))] (rx/of (dwc/update-shapes ids #(d/deep-merge (or % {}) changes)) - (update-layout-positions parent-ids)))))) + (update-layout-positions (d/concat-vec layout-ids parent-ids))))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 9c5f35f7c4..fe9b1ca489 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -11,6 +11,7 @@ [app.common.data.macros :as dm] [app.common.pages.helpers :as cph] [app.common.types.shape-tree :as ctt] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace.state-helpers :as wsh] [app.main.store :as st] [okulary.core :as l])) @@ -442,7 +443,7 @@ (l/derived (fn [objects] (->> ids - (some #(-> (cph/get-parent objects %) :layout)))) + (some #(-> (cph/get-parent objects %) ctl/layout?)))) workspace-page-objects)) (defn get-flex-child-viewer? diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index c360170173..97aff08791 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -80,7 +80,7 @@ (mf/defc element-behavior [{:keys [is-layout-container? is-layout-child? layout-item-h-sizing layout-item-v-sizing on-change-behavior] :as props}] (let [fill? is-layout-child? - auto? is-layout-container?] + auto? is-layout-container?] [:div.btn-wrapper [:div.layout-behavior.horizontal @@ -98,7 +98,7 @@ (when auto? [:button.behavior-btn.tooltip.tooltip-bottom {:alt "Fit content" - :class (dom/classnames :active (= layout-item-v-sizing :auto)) + :class (dom/classnames :active (= layout-item-h-sizing :auto)) :on-click #(on-change-behavior :h :auto)} i/auto-hug])] @@ -188,9 +188,11 @@ :on-change-behavior on-change-behavior}]] - [:& margin-section {:values values - :change-margin-style change-margin-style - :on-margin-change on-margin-change}] + (when is-layout-child? + [:& margin-section {:values values + :change-margin-style change-margin-style + :on-margin-change on-margin-change}]) + [:div.advanced-ops-container [:button.advanced-ops.toltip.tooltip-bottom {:on-click toggle-open @@ -200,12 +202,13 @@ (when @open? [:div.advanced-ops-body - [:div.layout-row - [:div.direction-wrap.row-title "Align"] - [:div.btn-wrapper - [:& align-self-row {:is-col? is-col? - :align-self align-self - :set-align-self set-align-self}]]] + (when is-layout-child? + [:div.layout-row + [:div.direction-wrap.row-title "Align"] + [:div.btn-wrapper + [:& align-self-row {:is-col? is-col? + :align-self align-self + :set-align-self set-align-self}]]]) [:div.input-wrapper (for [item [:layout-item-max-h :layout-item-min-h :layout-item-max-w :layout-item-min-w]] [:div.tooltip.tooltip-bottom diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 048fff5c04..219518ec1a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.workspace.sidebar.options.shapes.frame (:require + [app.common.types.shape.layout :as ctl] [app.main.features :as features] [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] @@ -35,7 +36,8 @@ layout-item-values (select-keys shape layout-item-attrs) is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) - is-layout-child? (mf/deref is-layout-child-ref)] + is-layout-child? (mf/deref is-layout-child-ref) + is-layout-container? (ctl/layout? shape)] [:* [:& measures-menu {:ids [(:id shape)] :values measure-values @@ -43,18 +45,17 @@ :shape shape}] [:& constraints-menu {:ids ids :values constraint-values}] - (when layout-active? - [:* - [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}] + (when (or layout-active? is-layout-container?) + [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}]) - (when is-layout-child? - [:& layout-item-menu - {:ids ids - :type type - :values layout-item-values - :is-layout-child? is-layout-child? - :is-layout-container? (:layout shape) - :shape shape}])]) + (when (and layout-active? (or is-layout-child? is-layout-container?)) + [:& layout-item-menu + {:ids ids + :type type + :values layout-item-values + :is-layout-child? is-layout-child? + :is-layout-container? is-layout-container? + :shape shape}]) [:& layer-menu {:ids ids :type type From 4ecc166055fb28736cfc7b3c4dec4b408fa0701a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 3 Nov 2022 15:11:29 +0100 Subject: [PATCH 213/682] :sparkles: Remove fill/auto when resizing --- .../src/app/common/geom/shapes/modifiers.cljc | 9 +- common/src/app/common/pages/helpers.cljc | 9 +- common/src/app/common/types/modifiers.cljc | 15 +- common/src/app/common/types/shape/layout.cljc | 12 +- .../app/main/data/workspace/modifiers.cljs | 284 ++++++++++++++ .../app/main/data/workspace/selection.cljs | 7 +- .../app/main/data/workspace/shape_layout.cljs | 10 +- .../app/main/data/workspace/transforms.cljs | 347 +++--------------- frontend/src/app/main/refs.cljs | 7 +- .../sidebar/options/menus/measures.cljs | 8 + .../src/app/main/ui/workspace/viewport.cljs | 2 +- .../app/main/ui/workspace/viewport/debug.cljs | 40 +- frontend/src/app/util/snap_data.cljs | 78 ++-- 13 files changed, 479 insertions(+), 349 deletions(-) create mode 100644 frontend/src/app/main/data/workspace/modifiers.cljs diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 9f7b9ae5c1..878c977592 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -138,20 +138,21 @@ (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) transformed-parent (gtr/transform-shape parent modifiers) + children (->> transformed-parent :shapes (map (comp apply-modifiers (d/getf objects)))) {auto-width :width auto-height :height} - (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) - (gcl/layout-content-bounds parent children)) + (when (and (d/not-empty? children) (or (ctl/auto-height? transformed-parent) (ctl/auto-width? transformed-parent))) + (gcl/layout-content-bounds transformed-parent children)) modifiers (cond-> modifiers - (and (some? auto-width) (ctl/auto-width? parent)) + (and (some? auto-width) (ctl/auto-width? transformed-parent)) (set-parent-auto-width transformed-parent auto-width) - (and (some? auto-height) (ctl/auto-height? parent)) + (and (some? auto-height) (ctl/auto-height? transformed-parent)) (set-parent-auto-height transformed-parent auto-height))] (assoc-in modif-tree [(:id parent) :modifiers] modifiers)))) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index f984574f53..55b3c571e6 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -71,9 +71,12 @@ (defn get-children-ids [objects id] - (if-let [shapes (-> (get objects id) :shapes (some-> vec))] - (into shapes (mapcat #(get-children-ids objects %)) shapes) - [])) + (letfn [(get-children-ids-rec + [id processed] + (when (not (contains? processed id)) + (when-let [shapes (-> (get objects id) :shapes (some-> vec))] + (into shapes (mapcat #(get-children-ids-rec % (conj processed id))) shapes))))] + (get-children-ids-rec id #{}))) (defn get-children [objects id] diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index a99c5b893b..25e17df9d9 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -32,6 +32,7 @@ ;; - structure-child: Structure recursive ;; * scale-content ;; * rotation +;; * change-properties (def conjv (fnil conj [])) @@ -118,6 +119,13 @@ (-> modifiers (update :structure-child conjv {:type :scale-content :value value}))) +(defn set-change-property + [modifiers property value] + (-> modifiers + (update :structure-child conjv {:type :change-property + :property property + :value value}))) + (defn add-modifiers [modifiers new-modifiers] @@ -376,7 +384,7 @@ (d/removev remove? shapes))) apply-modifier - (fn [shape {:keys [type value index rotation]}] + (fn [shape {:keys [type property value index rotation]}] (cond-> shape (= type :rotation) (update :rotation #(mod (+ % rotation) 360)) @@ -395,7 +403,10 @@ (update :shapes remove-children value) (= type :scale-content) - (apply-scale-content value)))] + (apply-scale-content value) + + (= type :change-property) + (assoc property value)))] (as-> shape $ (reduce apply-modifier $ (:structure-parent modifiers)) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 8ab1f67c47..87a1ad3fe7 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -97,8 +97,16 @@ ::layout-item-min-w ::layout-item-align-self])) -(defn layout? [shape] - (and (= :frame (:type shape)) (= :flex (:layout shape)))) +(defn layout? + ([objects id] + (layout? (get objects id))) + ([shape] + (and (= :frame (:type shape)) (= :flex (:layout shape))))) + +(defn layout-child? [objects shape] + (let [parent-id (:parent-id shape) + parent (get objects parent-id)] + (layout? parent))) (defn wrap? [{:keys [layout-wrap-type]}] (= layout-wrap-type :wrap)) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs new file mode 100644 index 0000000000..4861414e34 --- /dev/null +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -0,0 +1,284 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.data.workspace.modifiers + "Events related with shapes transformations" + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.flex-layout :as gsl] + [app.common.math :as mth] + [app.common.pages.common :as cpc] + [app.common.pages.helpers :as cph] + [app.common.spec :as us] + [app.common.types.modifiers :as ctm] + [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.comments :as-alias dwcm] + [app.main.data.workspace.guides :as-alias dwg] + [app.main.data.workspace.state-helpers :as wsh] + [app.main.data.workspace.undo :as dwu] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [potok.core :as ptk])) + +;; -- temporary modifiers ------------------------------------------- + +;; During an interactive transformation of shapes (e.g. when resizing or rotating +;; a group with the mouse), there are a lot of objects that need to be modified +;; (in this case, the group and all its children). +;; +;; To avoid updating the shapes theirselves, and forcing redraw of all components +;; that depend on the "objects" global state, we set a "modifiers" structure, with +;; the changes that need to be applied, and store it in :workspace-modifiers global +;; variable. The viewport reads this and merges it into the objects list it uses to +;; paint the viewport content, redrawing only the objects that have new modifiers. +;; +;; When the interaction is finished (e.g. user releases mouse button), the +;; apply-modifiers event is done, that consolidates all modifiers into the base +;; geometric attributes of the shapes. + + +(defn- check-delta + "If the shape is a component instance, check its relative position respect the + root of the component, and see if it changes after applying a transformation." + [shape root transformed-shape transformed-root objects modif-tree] + (let [root + (cond + (:component-root? shape) + shape + + (nil? root) + (cph/get-root-shape objects shape) + + :else root) + + transformed-root + (cond + (:component-root? transformed-shape) + transformed-shape + + (nil? transformed-root) + (as-> (cph/get-root-shape objects transformed-shape) $ + (gsh/transform-shape (merge $ (get modif-tree (:id $))))) + + :else transformed-root) + + shape-delta + (when root + (gpt/point (- (gsh/left-bound shape) (gsh/left-bound root)) + (- (gsh/top-bound shape) (gsh/top-bound root)))) + + transformed-shape-delta + (when transformed-root + (gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root)) + (- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root)))) + + ;; There are cases in that the coordinates change slightly (e.g. when + ;; rounding to pixel, or when recalculating text positions in different + ;; zoom levels). To take this into account, we ignore movements smaller + ;; than 1 pixel. + distance (if (and shape-delta transformed-shape-delta) + (gpt/distance-vector shape-delta transformed-shape-delta) + (gpt/point 0 0)) + + ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))] + + [root transformed-root ignore-geometry?])) + +(defn- get-ignore-tree + "Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers" + ([modif-tree objects shape] + (get-ignore-tree modif-tree objects shape nil nil {})) + + ([modif-tree objects shape root transformed-root ignore-tree] + (let [children (map (d/getf objects) (:shapes shape)) + + shape-id (:id shape) + transformed-shape (gsh/transform-shape shape (get modif-tree shape-id)) + + [root transformed-root ignore-geometry?] + (check-delta shape root transformed-shape transformed-root objects modif-tree) + + ignore-tree (assoc ignore-tree shape-id ignore-geometry?) + + set-child + (fn [ignore-tree child] + (get-ignore-tree modif-tree objects child root transformed-root ignore-tree))] + + (reduce set-child ignore-tree children)))) + +(defn- update-grow-type + [shape old-shape] + (let [auto-width? (= :auto-width (:grow-type shape)) + auto-height? (= :auto-height (:grow-type shape)) + + changed-width? (not (mth/close? (:width shape) (:width old-shape))) + changed-height? (not (mth/close? (:height shape) (:height old-shape))) + + change-to-fixed? (or (and auto-width? (or changed-height? changed-width?)) + (and auto-height? changed-height?))] + (cond-> shape + change-to-fixed? + (assoc :grow-type :fixed)))) + +(defn- clear-local-transform [] + (ptk/reify ::clear-local-transform + ptk/UpdateEvent + (update [_ state] + (-> state + (dissoc :workspace-modifiers) + (dissoc ::current-move-selected))))) + +(defn create-modif-tree + [ids modifiers] + (us/verify (s/coll-of uuid?) ids) + (into {} (map #(vector % {:modifiers modifiers})) ids)) + +(defn build-modif-tree + [ids objects get-modifier] + (us/verify (s/coll-of uuid?) ids) + (into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids)) + +(defn build-change-frame-modifiers + [modif-tree objects selected target-frame position] + + (let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) + layout? (get-in objects [target-frame :layout]) + child-set (set (get-in objects [target-frame :shapes])) + drop-index (when layout? (gsl/get-drop-index target-frame objects position)) + + update-frame-modifiers + (fn [modif-tree [original-frame shapes]] + (let [shapes (->> shapes (d/removev #(= target-frame %))) + shapes (cond->> shapes + (and layout? (= original-frame target-frame)) + ;; When movining inside a layout frame remove the shapes that are not immediate children + (filterv #(contains? child-set %)))] + (cond-> modif-tree + (not= original-frame target-frame) + (-> (update-in [original-frame :modifiers] ctm/set-remove-children shapes) + (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index)) + + (and layout? (= original-frame target-frame)) + (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index))))] + + (reduce update-frame-modifiers modif-tree origin-frame-ids))) + +(defn modif->js + [modif-tree objects] + (clj->js (into {} + (map (fn [[k v]] + [(get-in objects [k :name]) v])) + modif-tree))) + +(defn set-modifiers + ([modif-tree] + (set-modifiers modif-tree false)) + + ([modif-tree ignore-constraints] + (set-modifiers modif-tree ignore-constraints false)) + + ([modif-tree ignore-constraints ignore-snap-pixel] + (ptk/reify ::set-modifiers + ptk/UpdateEvent + (update [_ state] + (let [objects + (wsh/lookup-page-objects state) + + snap-pixel? + (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) + + modif-tree + (gsh/set-objects-modifiers modif-tree objects ignore-constraints snap-pixel?)] + + (assoc state :workspace-modifiers modif-tree)))))) + +;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints). +(defn set-rotation-modifiers + ([angle shapes] + (set-rotation-modifiers angle shapes (-> shapes gsh/selection-rect gsh/center-selrect))) + + ([angle shapes center] + (ptk/reify ::set-rotation-modifiers + ptk/UpdateEvent + (update [_ state] + (let [objects (wsh/lookup-page-objects state) + ids + (->> shapes + (remove #(get % :blocked false)) + (filter #((cpc/editable-attrs (:type %)) :rotation)) + (map :id)) + + get-modifier + (fn [shape] + (ctm/rotation shape center angle)) + + modif-tree + (-> (build-modif-tree ids objects get-modifier) + (gsh/set-objects-modifiers objects false false))] + + (assoc state :workspace-modifiers modif-tree)))))) + + +(defn apply-modifiers + ([] + (apply-modifiers nil)) + + ([{:keys [undo-transation?] :or {undo-transation? true}}] + (ptk/reify ::apply-modifiers + ptk/WatchEvent + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + object-modifiers (get state :workspace-modifiers) + + ids (or (keys object-modifiers) []) + ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids) + + shapes (map (d/getf objects) ids) + ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes) + (reduce merge {}))] + + (rx/concat + (if undo-transation? + (rx/of (dwu/start-undo-transaction)) + (rx/empty)) + (rx/of (ptk/event ::dwg/move-frame-guides ids-with-children) + (ptk/event ::dwcm/move-frame-comment-threads ids-with-children) + (dch/update-shapes + ids + (fn [shape] + (let [modif (get-in object-modifiers [(:id shape) :modifiers]) + text-shape? (cph/text-shape? shape)] + (-> shape + (gsh/transform-shape modif) + (cond-> text-shape? + (update-grow-type shape))))) + {:reg-objects? true + :ignore-tree ignore-tree + ;; Attributes that can change in the transform. This way we don't have to check + ;; all the attributes + :attrs [:selrect + :points + :x + :y + :width + :height + :content + :transform + :transform-inverse + :rotation + :position-data + :flip-x + :flip-y + :grow-type + :layout-item-h-sizing + :layout-item-v-sizing + ]}) + (clear-local-transform)) + (if undo-transation? + (rx/of (dwu/commit-undo-transaction)) + (rx/empty)))))))) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index e662eb4863..4d3dffaa7e 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -21,6 +21,7 @@ [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] + [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.zoom :as dwz] @@ -552,8 +553,11 @@ (filter #(= :frame (get-in % [:obj :type]))) (map #(vector (:old-id %) (get-in % [:obj :id])))) - id-duplicated (first new-selected)] + id-duplicated (first new-selected) + frames (into #{} + (map #(get-in objects [% :frame-id])) + selected)] (rx/concat (->> (rx/from dup-frames) (rx/map (fn [[old-id new-id]] (dwt/duplicate-thumbnail old-id new-id)))) @@ -561,6 +565,7 @@ ;; Warning: This order is important for the focus mode. (rx/of (dch/commit-changes changes) (select-shapes new-selected) + (dwsl/update-layout-positions frames) (memorize-duplicated id-original id-duplicated)))))))))) (defn change-hover-state diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 956d24e1e7..1e68643491 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -11,8 +11,8 @@ [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl] [app.main.data.workspace.changes :as dwc] + [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.state-helpers :as wsh] - [app.main.data.workspace.transforms :as dwt] [beicon.core :as rx] [potok.core :as ptk])) @@ -50,11 +50,11 @@ ptk/WatchEvent (watch [_ state _] (let [objects (wsh/lookup-page-objects state) - ids (->> ids (filter #(get-in objects [% :layout])))] + ids (->> ids (filter (partial ctl/layout? objects)))] (if (d/not-empty? ids) - (let [modif-tree (dwt/create-modif-tree ids (ctm/reflow))] - (rx/of (dwt/set-modifiers modif-tree) - (dwt/apply-modifiers))) + (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow))] + (rx/of (dwm/set-modifiers modif-tree) + (dwm/apply-modifiers))) (rx/empty)))))) ;; TODO LAYOUT: Remove constraints from children diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 1769d41ae7..9ec4c43838 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -14,15 +14,14 @@ [app.common.geom.shapes.flex-layout :as gsl] [app.common.math :as mth] [app.common.pages.changes-builder :as pcb] - [app.common.pages.common :as cpc] [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.modifiers :as ctm] [app.common.types.shape-tree :as ctst] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] - [app.main.data.workspace.comments :as-alias dwcm] - [app.main.data.workspace.guides :as-alias dwg] + [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] @@ -97,269 +96,12 @@ (update state :workspace-local dissoc :transform)))) -;; -- Temporary modifiers ------------------------------------------- - -;; During an interactive transformation of shapes (e.g. when resizing or rotating -;; a group with the mouse), there are a lot of objects that need to be modified -;; (in this case, the group and all its children). -;; -;; To avoid updating the shapes theirselves, and forcing redraw of all components -;; that depend on the "objects" global state, we set a "modifiers" structure, with -;; the changes that need to be applied, and store it in :workspace-modifiers global -;; variable. The viewport reads this and merges it into the objects list it uses to -;; paint the viewport content, redrawing only the objects that have new modifiers. -;; -;; When the interaction is finished (e.g. user releases mouse button), the -;; apply-modifiers event is done, that consolidates all modifiers into the base -;; geometric attributes of the shapes. - -(declare clear-local-transform) - -(declare get-ignore-tree) - -(defn create-modif-tree - [ids modifiers] - (us/verify (s/coll-of uuid?) ids) - (into {} (map #(vector % {:modifiers modifiers})) ids)) - -(defn build-modif-tree - [ids objects get-modifier] - (us/verify (s/coll-of uuid?) ids) - (into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids)) - -(defn build-change-frame-modifiers - [modif-tree objects selected target-frame position] - - (let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) - layout? (get-in objects [target-frame :layout]) - child-set (set (get-in objects [target-frame :shapes])) - drop-index (when layout? (gsl/get-drop-index target-frame objects position)) - - update-frame-modifiers - (fn [modif-tree [original-frame shapes]] - (let [shapes (->> shapes (d/removev #(= target-frame %))) - shapes (cond->> shapes - (and layout? (= original-frame target-frame)) - ;; When movining inside a layout frame remove the shapes that are not immediate children - (filterv #(contains? child-set %)))] - (cond-> modif-tree - (not= original-frame target-frame) - (-> (update-in [original-frame :modifiers] ctm/set-remove-children shapes) - (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index)) - - (and layout? (= original-frame target-frame)) - (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index))))] - - (reduce update-frame-modifiers modif-tree origin-frame-ids))) - -(defn modif->js - [modif-tree objects] - (clj->js (into {} - (map (fn [[k v]] - [(get-in objects [k :name]) v])) - modif-tree))) - -(defn set-modifiers - ([modif-tree] - (set-modifiers modif-tree false)) - - ([modif-tree ignore-constraints] - (set-modifiers modif-tree ignore-constraints false)) - - ([modif-tree ignore-constraints ignore-snap-pixel] - (ptk/reify ::set-modifiers - ptk/UpdateEvent - (update [_ state] - (let [objects - (wsh/lookup-page-objects state) - - snap-pixel? - (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) - - modif-tree - (gsh/set-objects-modifiers modif-tree objects ignore-constraints snap-pixel?)] - - (assoc state :workspace-modifiers modif-tree)))))) - -;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints). -(defn- set-rotation-modifiers - ([angle shapes] - (set-rotation-modifiers angle shapes (-> shapes gsh/selection-rect gsh/center-selrect))) - - ([angle shapes center] - (ptk/reify ::set-rotation-modifiers - ptk/UpdateEvent - (update [_ state] - (let [objects (wsh/lookup-page-objects state) - ids - (->> shapes - (remove #(get % :blocked false)) - (filter #((cpc/editable-attrs (:type %)) :rotation)) - (map :id)) - - get-modifier - (fn [shape] - (ctm/rotation shape center angle)) - - modif-tree - (-> (build-modif-tree ids objects get-modifier) - (gsh/set-objects-modifiers objects false false))] - - (assoc state :workspace-modifiers modif-tree)))))) - -(defn- update-grow-type - [shape old-shape] - (let [auto-width? (= :auto-width (:grow-type shape)) - auto-height? (= :auto-height (:grow-type shape)) - - changed-width? (not (mth/close? (:width shape) (:width old-shape))) - changed-height? (not (mth/close? (:height shape) (:height old-shape))) - - change-to-fixed? (or (and auto-width? (or changed-height? changed-width?)) - (and auto-height? changed-height?))] - (cond-> shape - change-to-fixed? - (assoc :grow-type :fixed)))) - -(defn apply-modifiers - ([] - (apply-modifiers nil)) - - ([{:keys [undo-transation?] :or {undo-transation? true}}] - (ptk/reify ::apply-modifiers - ptk/WatchEvent - (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - object-modifiers (get state :workspace-modifiers) - - ids (or (keys object-modifiers) []) - ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids) - - shapes (map (d/getf objects) ids) - ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes) - (reduce merge {}))] - - (rx/concat - (if undo-transation? - (rx/of (dwu/start-undo-transaction)) - (rx/empty)) - (rx/of (ptk/event ::dwg/move-frame-guides ids-with-children) - (ptk/event ::dwcm/move-frame-comment-threads ids-with-children) - (dch/update-shapes - ids - (fn [shape] - (let [modif (get-in object-modifiers [(:id shape) :modifiers]) - text-shape? (cph/text-shape? shape)] - (-> shape - (gsh/transform-shape modif) - (cond-> text-shape? - (update-grow-type shape))))) - {:reg-objects? true - :ignore-tree ignore-tree - ;; Attributes that can change in the transform. This way we don't have to check - ;; all the attributes - :attrs [:selrect - :points - :x - :y - :width - :height - :content - :transform - :transform-inverse - :rotation - :position-data - :flip-x - :flip-y - :grow-type]}) - (clear-local-transform)) - (if undo-transation? - (rx/of (dwu/commit-undo-transaction)) - (rx/empty)))))))) - -(defn- check-delta - "If the shape is a component instance, check its relative position respect the - root of the component, and see if it changes after applying a transformation." - [shape root transformed-shape transformed-root objects modif-tree] - (let [root - (cond - (:component-root? shape) - shape - - (nil? root) - (cph/get-root-shape objects shape) - - :else root) - - transformed-root - (cond - (:component-root? transformed-shape) - transformed-shape - - (nil? transformed-root) - (as-> (cph/get-root-shape objects transformed-shape) $ - (gsh/transform-shape (merge $ (get modif-tree (:id $))))) - - :else transformed-root) - - shape-delta - (when root - (gpt/point (- (gsh/left-bound shape) (gsh/left-bound root)) - (- (gsh/top-bound shape) (gsh/top-bound root)))) - - transformed-shape-delta - (when transformed-root - (gpt/point (- (gsh/left-bound transformed-shape) (gsh/left-bound transformed-root)) - (- (gsh/top-bound transformed-shape) (gsh/top-bound transformed-root)))) - - ;; There are cases in that the coordinates change slightly (e.g. when - ;; rounding to pixel, or when recalculating text positions in different - ;; zoom levels). To take this into account, we ignore movements smaller - ;; than 1 pixel. - distance (if (and shape-delta transformed-shape-delta) - (gpt/distance-vector shape-delta transformed-shape-delta) - (gpt/point 0 0)) - - ignore-geometry? (and (< (:x distance) 1) (< (:y distance) 1))] - - [root transformed-root ignore-geometry?])) - -(defn- get-ignore-tree - "Retrieves a map with the flag `ignore-geometry?` given a tree of modifiers" - ([modif-tree objects shape] - (get-ignore-tree modif-tree objects shape nil nil {})) - - ([modif-tree objects shape root transformed-root ignore-tree] - (let [children (map (d/getf objects) (:shapes shape)) - - shape-id (:id shape) - transformed-shape (gsh/transform-shape shape (get modif-tree shape-id)) - - [root transformed-root ignore-geometry?] - (check-delta shape root transformed-shape transformed-root objects modif-tree) - - ignore-tree (assoc ignore-tree shape-id ignore-geometry?) - - set-child - (fn [ignore-tree child] - (get-ignore-tree modif-tree objects child root transformed-root ignore-tree))] - - (reduce set-child ignore-tree children)))) - -(defn- clear-local-transform [] - (ptk/reify ::clear-local-transform - ptk/UpdateEvent - (update [_ state] - (-> state - (dissoc :workspace-modifiers) - (dissoc ::current-move-selected))))) - ;; -- Resize -------------------------------------------------------- (defn start-resize "Enter mouse resize mode, until mouse button is released." [handler ids shape] - (letfn [(resize [shape initial layout [point lock? center? point-snap]] + (letfn [(resize [shape objects initial layout [point lock? center? point-snap]] (let [{:keys [width height]} (:selrect shape) {:keys [rotation]} shape @@ -423,18 +165,42 @@ (some? displacement) (gpt/add displacement)) + ;; When the horizontal/vertical scale a flex children with auto/fill + ;; we change it too fixed + layout? (ctl/layout? shape) + layout-child? (ctl/layout-child? objects shape) + auto-width? (ctl/auto-width? shape) + fill-width? (ctl/fill-width? shape) + auto-height? (ctl/auto-height? shape) + fill-height? (ctl/fill-height? shape) + + set-fix-width? + (and (not (mth/close? (:x scalev) 1)) + (or (and (or layout? layout-child?) auto-width?) + (and layout-child? fill-width?))) + + set-fix-height? + (and (not (mth/close? (:y scalev) 1)) + (or (and (or layout? layout-child?) auto-height?) + (and layout-child? fill-height?))) + modifiers (-> (ctm/empty-modifiers) (cond-> displacement (ctm/set-move displacement)) (ctm/set-resize scalev resize-origin shape-transform shape-transform-inverse) + (cond-> set-fix-width? + (ctm/set-change-property :layout-item-h-sizing :fix)) + + (cond-> set-fix-height? + (ctm/set-change-property :layout-item-v-sizing :fix)) + (cond-> scale-text (ctm/set-scale-content (:x scalev)))) - modif-tree (create-modif-tree ids modifiers)] - - (rx/of (set-modifiers modif-tree)))) + modif-tree (dwm/create-modif-tree ids modifiers)] + (rx/of (dwm/set-modifiers modif-tree)))) ;; Unifies the instantaneous proportion lock modifier ;; activated by Shift key and the shapes own proportion @@ -458,6 +224,7 @@ zoom (get-in state [:workspace-local :zoom] 1) objects (wsh/lookup-page-objects state page-id) resizing-shapes (map #(get objects %) ids)] + (rx/concat (->> ms/mouse-position (rx/with-latest-from ms/mouse-position-shift ms/mouse-position-alt) @@ -465,9 +232,9 @@ (rx/switch-map (fn [[point _ _ :as current]] (->> (snap/closest-snap-point page-id resizing-shapes objects layout zoom focus point) (rx/map #(conj current %))))) - (rx/mapcat (partial resize shape initial-position layout)) + (rx/mapcat (partial resize shape objects initial-position layout)) (rx/take-until stoper)) - (rx/of (apply-modifiers) + (rx/of (dwm/apply-modifiers) (finish-transform)))))))) (defn update-dimensions @@ -487,14 +254,14 @@ (fn [shape] (ctm/change-dimensions shape attr value)) modif-tree - (-> (build-modif-tree ids objects get-modifier) + (-> (dwm/build-modif-tree ids objects get-modifier) (gsh/set-objects-modifiers objects false snap-pixel?))] (assoc state :workspace-modifiers modif-tree))) ptk/WatchEvent (watch [_ _ _] - (rx/of (apply-modifiers))))) + (rx/of (dwm/apply-modifiers))))) (defn change-orientation "Change orientation of shapes, from the sidebar options form. @@ -512,14 +279,14 @@ (fn [shape] (ctm/change-orientation-modifiers shape orientation)) modif-tree - (-> (build-modif-tree ids objects get-modifier) + (-> (dwm/build-modif-tree ids objects get-modifier) (gsh/set-objects-modifiers objects false snap-pixel?))] (assoc state :workspace-modifiers modif-tree))) ptk/WatchEvent (watch [_ _ _] - (rx/of (apply-modifiers))))) + (rx/of (dwm/apply-modifiers))))) ;; -- Rotate -------------------------------------------------------- @@ -560,9 +327,9 @@ (rx/map (fn [[[pos mod?] shift?]] (let [delta-angle (calculate-angle pos mod? shift?)] - (set-rotation-modifiers delta-angle shapes group-center)))) + (dwm/set-rotation-modifiers delta-angle shapes group-center)))) (rx/take-until stoper)) - (rx/of (apply-modifiers) + (rx/of (dwm/apply-modifiers) (finish-transform))))))) (defn increase-rotation @@ -576,10 +343,10 @@ objects (wsh/lookup-page-objects state page-id) rotate-shape (fn [shape] (let [delta (- rotation (:rotation shape))] - (set-rotation-modifiers delta [shape])))] + (dwm/set-rotation-modifiers delta [shape])))] (rx/concat (rx/from (->> ids (map #(get objects %)) (map rotate-shape))) - (rx/of (apply-modifiers))))))) + (rx/of (dwm/apply-modifiers))))))) ;; -- Move ---------------------------------------------------------- @@ -705,15 +472,15 @@ (fn [move-vector] (let [position (gpt/add from-position move-vector) target-frame (ctst/top-nested-frame objects position)] - (-> (create-modif-tree ids (ctm/move move-vector)) - (build-change-frame-modifiers objects selected target-frame position) - (set-modifiers))))) + (-> (dwm/create-modif-tree ids (ctm/move move-vector)) + (dwm/build-change-frame-modifiers objects selected target-frame position) + (dwm/set-modifiers))))) (rx/take-until stopper))) (rx/of (dwu/start-undo-transaction) (calculate-frame-for-move ids) - (apply-modifiers {:undo-transation? false}) + (dwm/apply-modifiers {:undo-transation? false}) (finish-transform) (dwu/commit-undo-transaction))))))))) @@ -756,12 +523,12 @@ (rx/merge (->> move-events (rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0)) - (rx/map #(create-modif-tree selected (ctm/move %))) - (rx/map (partial set-modifiers)) + (rx/map #(dwm/create-modif-tree selected (ctm/move %))) + (rx/map (partial dwm/set-modifiers)) (rx/take-until stopper)) (rx/of (move-selected direction shift?))) - (rx/of (apply-modifiers) + (rx/of (dwm/apply-modifiers) (finish-transform)))) (rx/empty)))))) @@ -789,10 +556,10 @@ (or (:y position) (:y bbox))) delta (gpt/subtract pos cpos) - modif-tree (create-modif-tree [id] (ctm/move delta))] + modif-tree (dwm/create-modif-tree [id] (ctm/move delta))] - (rx/of (set-modifiers modif-tree) - (apply-modifiers)))))) + (rx/of (dwm/set-modifiers modif-tree) + (dwm/apply-modifiers)))))) (defn- calculate-frame-for-move [ids] @@ -851,14 +618,14 @@ selrect (gsh/selection-rect shapes) origin (gpt/point (:x selrect) (+ (:y selrect) (/ (:height selrect) 2))) - modif-tree (create-modif-tree + modif-tree (dwm/create-modif-tree selected (-> (ctm/empty-modifiers) (ctm/set-resize (gpt/point -1.0 1.0) origin) (ctm/move (gpt/point (:width selrect) 0))))] - (rx/of (set-modifiers modif-tree true) - (apply-modifiers)))))) + (rx/of (dwm/set-modifiers modif-tree true) + (dwm/apply-modifiers)))))) (defn flip-vertical-selected [] (ptk/reify ::flip-vertical-selected @@ -870,11 +637,11 @@ selrect (gsh/selection-rect shapes) origin (gpt/point (+ (:x selrect) (/ (:width selrect) 2)) (:y selrect)) - modif-tree (create-modif-tree + modif-tree (dwm/create-modif-tree selected (-> (ctm/empty-modifiers) (ctm/set-resize (gpt/point 1.0 -1.0) origin) (ctm/move (gpt/point 0 (:height selrect)))))] - (rx/of (set-modifiers modif-tree true) - (apply-modifiers)))))) + (rx/of (dwm/set-modifiers modif-tree true) + (dwm/apply-modifiers)))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index fe9b1ca489..72e4420d14 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -443,7 +443,8 @@ (l/derived (fn [objects] (->> ids - (some #(-> (cph/get-parent objects %) ctl/layout?)))) + (map (d/getf objects)) + (some (partial ctl/layout-child? objects)))) workspace-page-objects)) (defn get-flex-child-viewer? @@ -452,8 +453,8 @@ (fn [state] (let [objects (wsh/lookup-viewer-objects state page-id)] (into [] - (comp (filter #(= :flex (:layout (cph/get-parent objects %)))) - (map #(get objects %))) + (comp (filter (partial ctl/layout-child? objects)) + (map (d/getf objects))) ids))) st/state =)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index cba05bc1ee..6488fafe01 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -87,6 +87,12 @@ flex-child? (->> selection-parents (some ctl/layout?)) + flex-container? (ctl/layout? shape) + flex-auto-width? (ctl/auto-width? shape) + flex-fill-width? (ctl/fill-width? shape) + flex-auto-height? (ctl/auto-height? shape) + flex-fill-height? (ctl/fill-height? shape) + ;; To show interactively the measures while the user is manipulating ;; the shape with the mouse, generate a copy of the shapes applying ;; the transient transformations. @@ -306,6 +312,7 @@ :placeholder "--" :on-click select-all :on-change on-width-change + :disabled (and (or flex-child? flex-container?) (or flex-auto-width? flex-fill-width?)) :value (:width values)}]] [:div.input-element.height {:title (tr "workspace.options.height")} @@ -314,6 +321,7 @@ :placeholder "--" :on-click select-all :on-change on-height-change + :disabled (and (or flex-child? flex-container?) (or flex-auto-height? flex-fill-height?)) :value (:height values)}]] [:div.lock-size {:class (dom/classnames diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 2896f6d4b5..586e1b3891 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -286,7 +286,7 @@ (when show-frame-outline? [:& outline/shape-outlines - {:objects base-objects + {:objects objects-modified :hover #{(->> @hover-ids (filter #(cph/frame-shape? (get base-objects %))) (remove selected) diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index 869caa8ca5..d338c0a719 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -13,6 +13,44 @@ [app.common.pages.helpers :as cph] [rumext.v2 :as mf])) +;; Helper to debug the bounds when set the "hug" content property +#_(mf/defc debug-bounds + "Debug component to show the auto-layout drop areas" + {::mf/wrap-props false} + [props] + + (let [objects (unchecked-get props "objects") + selected-shapes (unchecked-get props "selected-shapes") + hover-top-frame-id (unchecked-get props "hover-top-frame-id") + + selected-frame + (when (and (= (count selected-shapes) 1) (= :frame (-> selected-shapes first :type))) + (first selected-shapes)) + + shape (or selected-frame (get objects hover-top-frame-id))] + + (when (and shape (:layout shape)) + (let [children (cph/get-immediate-children objects (:id shape)) + layout-data (gsl/calc-layout-data shape children) + + {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} (:layout-padding shape) + pad-top (or pad-top 0) + pad-right (or pad-right 0) + pad-bottom (or pad-bottom 0) + pad-left (or pad-left 0) + + layout-bounds (gsl/layout-content-bounds shape children)] + [:g.debug-layout {:pointer-events "none" + :transform (gsh/transform-str shape)} + + + [:rect {:x (:x layout-bounds) + :y (:y layout-bounds) + :width (:width layout-bounds) + :height (:height layout-bounds) + :style {:stroke "red" + :fill "none"}}]])))) + (mf/defc debug-layout "Debug component to show the auto-layout drop areas" {::mf/wrap-props false} @@ -27,7 +65,7 @@ (first selected-shapes)) shape (or selected-frame (get objects hover-top-frame-id))] - + (when (and shape (:layout shape)) (let [children (cph/get-immediate-children objects (:id shape)) layout-data (gsl/calc-layout-data shape children) diff --git a/frontend/src/app/util/snap_data.cljs b/frontend/src/app/util/snap_data.cljs index 865162aa33..1de3b7a259 100644 --- a/frontend/src/app/util/snap_data.cljs +++ b/frontend/src/app/util/snap_data.cljs @@ -13,6 +13,7 @@ [app.common.pages.diff :as diff] [app.common.pages.helpers :as cph] [app.common.types.shape-tree :as ctst] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.util.geom.grid :as gg] [app.util.geom.snap-points :as snap] @@ -70,7 +71,7 @@ (mapv grid->snap))))) (defn- add-frame - [page-data frame] + [objects page-data frame] (let [frame-id (:id frame) parent-id (:parent-id frame) frame-data (->> (snap/shape-snap-points frame) @@ -79,21 +80,24 @@ :pt %))) grid-x-data (get-grids-snap-points frame :x) grid-y-data (get-grids-snap-points frame :y)] - (-> page-data - ;; Update root frame information - (assoc-in [uuid/zero :objects-data frame-id] frame-data) - (update-in [parent-id :x] (make-insert-tree-data frame-data :x)) - (update-in [parent-id :y] (make-insert-tree-data frame-data :y)) - ;; Update frame information - (assoc-in [frame-id :objects-data frame-id] (d/concat-vec frame-data grid-x-data grid-y-data)) - (update-in [frame-id :x] #(or % (rt/make-tree))) - (update-in [frame-id :y] #(or % (rt/make-tree))) - (update-in [frame-id :x] (make-insert-tree-data (d/concat-vec frame-data grid-x-data) :x)) - (update-in [frame-id :y] (make-insert-tree-data (d/concat-vec frame-data grid-y-data) :y))))) + (cond-> page-data + (not (ctl/layout-child? objects frame)) + + (-> ;; Update root frame information + (assoc-in [uuid/zero :objects-data frame-id] frame-data) + (update-in [parent-id :x] (make-insert-tree-data frame-data :x)) + (update-in [parent-id :y] (make-insert-tree-data frame-data :y)) + + ;; Update frame information + (assoc-in [frame-id :objects-data frame-id] (d/concat-vec frame-data grid-x-data grid-y-data)) + (update-in [frame-id :x] #(or % (rt/make-tree))) + (update-in [frame-id :y] #(or % (rt/make-tree))) + (update-in [frame-id :x] (make-insert-tree-data (d/concat-vec frame-data grid-x-data) :x)) + (update-in [frame-id :y] (make-insert-tree-data (d/concat-vec frame-data grid-y-data) :y)))))) (defn- add-shape - [page-data shape] + [objects page-data shape] (let [frame-id (:frame-id shape) snap-points (snap/shape-snap-points shape) shape-data (->> snap-points @@ -101,11 +105,11 @@ :type :shape :id (:id shape) :pt %)))] - (-> page-data - (assoc-in [frame-id :objects-data (:id shape)] shape-data) - (update-in [frame-id :x] (make-insert-tree-data shape-data :x)) - (update-in [frame-id :y] (make-insert-tree-data shape-data :y))))) - + (cond-> page-data + (not (ctl/layout-child? objects shape)) + (-> (assoc-in [frame-id :objects-data (:id shape)] shape-data) + (update-in [frame-id :x] (make-insert-tree-data shape-data :x)) + (update-in [frame-id :y] (make-insert-tree-data shape-data :y)))))) (defn- add-guide [objects page-data guide] @@ -164,22 +168,22 @@ (update-in [:guides (:axis guide)] (make-delete-tree-data guide-data (:axis guide))))))) (defn- update-frame - [page-data [_ new-frame]] + [objects page-data [_ new-frame]] (let [frame-id (:id new-frame) root-data (get-in page-data [uuid/zero :objects-data frame-id]) frame-data (get-in page-data [frame-id :objects-data frame-id])] - (-> page-data - (update-in [uuid/zero :x] (make-delete-tree-data root-data :x)) - (update-in [uuid/zero :y] (make-delete-tree-data root-data :y)) - (update-in [frame-id :x] (make-delete-tree-data frame-data :x)) - (update-in [frame-id :y] (make-delete-tree-data frame-data :y)) - (add-frame new-frame)))) + (as-> page-data $ + (update-in $ [uuid/zero :x] (make-delete-tree-data root-data :x)) + (update-in $ [uuid/zero :y] (make-delete-tree-data root-data :y)) + (update-in $ [frame-id :x] (make-delete-tree-data frame-data :x)) + (update-in $ [frame-id :y] (make-delete-tree-data frame-data :y)) + (add-frame objects $ new-frame)))) (defn- update-shape - [page-data [old-shape new-shape]] - (-> page-data - (remove-shape old-shape) - (add-shape new-shape))) + [objects page-data [old-shape new-shape]] + (as-> page-data $ + (remove-shape $ old-shape) + (add-shape objects $ new-shape))) (defn- update-guide [objects page-data [old-guide new-guide]] @@ -205,8 +209,8 @@ page-data (as-> {} $ (add-root-frame $) - (reduce add-frame $ frames) - (reduce add-shape $ shapes) + (reduce (partial add-frame objects) $ frames) + (reduce (partial add-shape objects) $ shapes) (reduce (partial add-guide objects) $ guides))] (assoc snap-data (:id page) page-data))) @@ -233,16 +237,16 @@ (diff/calculate-page-diff old-page page snap-attrs)] (as-> page-data $ - (reduce update-shape $ change-frame-shapes) + (reduce (partial update-shape objects) $ change-frame-shapes) (reduce remove-frame $ removed-frames) (reduce remove-shape $ removed-shapes) - (reduce update-frame $ updated-frames) - (reduce update-shape $ updated-shapes) - (reduce add-frame $ new-frames) - (reduce add-shape $ new-shapes) - (reduce remove-guide $ removed-guides) + (reduce (partial update-frame objects) $ updated-frames) + (reduce (partial update-shape objects) $ updated-shapes) + (reduce (partial add-frame objects) $ new-frames) + (reduce (partial add-shape objects) $ new-shapes) ;; Guides functions. Need objects to get its frame data + (reduce remove-guide $ removed-guides) (reduce (partial update-guide objects) $ change-frame-guides) (reduce (partial update-guide objects) $ updated-guides) (reduce (partial add-guide objects) $ new-guides))))) From 7caf4b9136e13ad038fb2641245ee051815c721c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 3 Nov 2022 16:13:58 +0100 Subject: [PATCH 214/682] :sparkles: Removed constraints when layout child --- .../main/ui/workspace/sidebar/options.cljs | 40 ++++++++++--------- .../sidebar/options/menus/constraints.cljs | 1 - .../options/menus/layout_container.cljs | 17 ++++---- .../sidebar/options/menus/measures.cljs | 7 +--- .../sidebar/options/shapes/bool.cljs | 6 ++- .../sidebar/options/shapes/circle.cljs | 5 ++- .../sidebar/options/shapes/frame.cljs | 5 ++- .../sidebar/options/shapes/group.cljs | 3 +- .../sidebar/options/shapes/image.cljs | 5 ++- .../sidebar/options/shapes/multiple.cljs | 6 ++- .../sidebar/options/shapes/path.cljs | 5 ++- .../sidebar/options/shapes/rect.cljs | 6 ++- .../sidebar/options/shapes/svg_raw.cljs | 5 ++- .../sidebar/options/shapes/text.cljs | 8 ++-- 14 files changed, 67 insertions(+), 52 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 5024d3dd97..360151a9c7 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options (:require [app.common.data :as d] + [app.common.geom.shapes :as gsh] [app.main.data.workspace :as udw] [app.main.refs :as refs] [app.main.store :as st] @@ -36,24 +37,27 @@ (mf/defc shape-options {::mf/wrap [#(mf/throttle % 60)]} [{:keys [shape shapes-with-children page-id file-id shared-libs]}] - [:* - (case (:type shape) - :frame [:& frame/options {:shape shape}] - :group [:& group/options {:shape shape :shape-with-children shapes-with-children :file-id file-id :shared-libs shared-libs}] - :text [:& text/options {:shape shape :file-id file-id :shared-libs shared-libs}] - :rect [:& rect/options {:shape shape}] - :circle [:& circle/options {:shape shape}] - :path [:& path/options {:shape shape}] - :image [:& image/options {:shape shape}] - :svg-raw [:& svg-raw/options {:shape shape}] - :bool [:& bool/options {:shape shape}] - nil) - [:& exports-menu - {:ids [(:id shape)] - :values (select-keys shape [:exports]) - :shape shape - :page-id page-id - :file-id file-id}]]) + (let [workspace-modifiers (mf/deref refs/workspace-modifiers) + modifiers (get-in workspace-modifiers [(:id shape) :modifiers]) + shape (gsh/transform-shape shape modifiers)] + [:* + (case (:type shape) + :frame [:& frame/options {:shape shape}] + :group [:& group/options {:shape shape :shape-with-children shapes-with-children :file-id file-id :shared-libs shared-libs}] + :text [:& text/options {:shape shape :file-id file-id :shared-libs shared-libs}] + :rect [:& rect/options {:shape shape}] + :circle [:& circle/options {:shape shape}] + :path [:& path/options {:shape shape}] + :image [:& image/options {:shape shape}] + :svg-raw [:& svg-raw/options {:shape shape}] + :bool [:& bool/options {:shape shape}] + nil) + [:& exports-menu + {:ids [(:id shape)] + :values (select-keys shape [:exports]) + :shape shape + :page-id page-id + :file-id file-id}]])) (mf/defc options-content {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs index 4d22ffa548..a6089f49d0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs @@ -30,7 +30,6 @@ frames (map #(deref (refs/object-by-id (:frame-id %))) old-shapes) shapes (as-> old-shapes $ - #_(map gsh/transform-shape $) (map gsh/translate-to-frame $ frames)) values (let [{:keys [x y]} (-> shapes first :points gsh/points->selrect)] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index 71545cb596..68452f9df7 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -77,18 +77,18 @@ :stretch nil)) :align-self (if is-col? - (case val - :start i/align-self-column-top - :end i/align-self-column-bottom - :center i/align-self-column-center - :stretch i/align-self-column-strech - :baseline i/align-self-column-baseline) (case val :start i/align-self-row-left :end i/align-self-row-right :center i/align-self-row-center :stretch i/align-self-row-strech - :baseline i/align-self-row-baseline)))) + :baseline i/align-self-row-baseline) + (case val + :start i/align-self-column-top + :end i/align-self-column-bottom + :center i/align-self-column-center + :stretch i/align-self-column-strech + :baseline i/align-self-column-baseline)))) (mf/defc direction-btn [{:keys [dir saved-dir set-direction] :as props}] @@ -170,7 +170,8 @@ [{:keys [values on-change-style on-change] :as props}] (let [padding-type (:layout-padding-type values) - rx (if (apply = (vals (:layout-padding values))) + rx (if (and (not (= :multiple (:layout-padding values))) + (apply = (vals (:layout-padding values)))) (:p1 (:layout-padding values)) "--")] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 6488fafe01..5c0f2fa7c0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -69,9 +69,7 @@ ;; -- User/drawing coords (mf/defc measures-menu [{:keys [ids ids-with-children values type all-types shape] :as props}] - (let [workspace-modifiers (mf/deref refs/workspace-modifiers) - - options (if (= type :multiple) + (let [options (if (= type :multiple) (reduce #(union %1 %2) (map #(get type->options %) all-types)) (get type->options type)) @@ -97,9 +95,6 @@ ;; the shape with the mouse, generate a copy of the shapes applying ;; the transient transformations. shapes (as-> old-shapes $ - (map (fn [shape] - (let [modifiers (get-in workspace-modifiers [(:id shape) :modifiers])] - (gsh/transform-shape shape modifiers))) $) (map gsh/translate-to-frame $ frames)) ;; For rotated or stretched shapes, the origin point we show in the menu diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs index 1c45a1bf24..ff84041d0a 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/bool.cljs @@ -41,8 +41,10 @@ :values layout-item-values :is-layout-child? true :shape shape}]) - [:& constraints-menu {:ids ids - :values constraint-values}] + + (when (not is-layout-child?) + [:& constraints-menu {:ids ids + :values constraint-values}]) [:& layer-menu {:ids ids :type type :values layer-values}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs index af0878b7eb..812e3c8d98 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/circle.cljs @@ -43,8 +43,9 @@ :is-layout-child? true :is-layout-container? false :shape shape}]) - [:& constraints-menu {:ids ids - :values constraint-values}] + (when (not is-layout-child?) + [:& constraints-menu {:ids ids + :values constraint-values}]) [:& layer-menu {:ids ids :type type :values layer-values}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 219518ec1a..1fcb34ed6e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -43,8 +43,9 @@ :values measure-values :type type :shape shape}] - [:& constraints-menu {:ids ids - :values constraint-values}] + (when (not is-layout-child?) + [:& constraints-menu {:ids ids + :values constraint-values}]) (when (or layout-active? is-layout-container?) [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}]) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index b81a4a46ab..6f921a4b01 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -62,7 +62,8 @@ :is-layout-container? false :values layout-item-values}]) - [:& constraints-menu {:ids constraint-ids :values constraint-values}] + (when (not is-layout-child?) + [:& constraints-menu {:ids constraint-ids :values constraint-values}]) [:& layer-menu {:type type :ids layer-ids :values layer-values}] (when-not (empty? fill-ids) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs index d2b1a8358a..0f99ca9790 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/image.cljs @@ -44,8 +44,9 @@ :is-layout-child? true :shape shape}]) - [:& constraints-menu {:ids ids - :values constraint-values}] + (when (not is-layout-child?) + [:& constraints-menu {:ids ids + :values constraint-values}]) [:& layer-menu {:ids ids :type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 17f5f70514..06425fd9ed 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -253,6 +253,10 @@ [props] (let [shapes (unchecked-get props "shapes") shapes-with-children (unchecked-get props "shapes-with-children") + + workspace-modifiers (mf/deref refs/workspace-modifiers) + shapes (map #(gsh/transform-shape % (get-in workspace-modifiers [(:id %) :modifiers])) shapes) + page-id (unchecked-get props "page-id") file-id (unchecked-get props "file-id") shared-libs (unchecked-get props "shared-libs") @@ -319,7 +323,7 @@ :is-layout-container? true :values layout-item-values}]) - (when-not (empty? constraint-ids) + (when-not (or (empty? constraint-ids) is-layout-child?) [:& constraints-menu {:ids constraint-ids :values constraint-values}]) (when-not (empty? layer-ids) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs index 22f750df4b..5b1a894264 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/path.cljs @@ -43,8 +43,9 @@ :is-layout-child? true :is-layout-container? false :shape shape}]) - [:& constraints-menu {:ids ids - :values constraint-values}] + (when (not is-layout-child?) + [:& constraints-menu {:ids ids + :values constraint-values}]) [:& layer-menu {:ids ids :type type :values layer-values}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs index 654aad11b8..582f476d84 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/rect.cljs @@ -44,8 +44,10 @@ :values layout-item-values :is-layout-child? true :shape shape}]) - [:& constraints-menu {:ids ids - :values constraint-values}] + + (when (not is-layout-child?) + [:& constraints-menu {:ids ids + :values constraint-values}]) [:& layer-menu {:ids ids :type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index bd7a2c1388..e05bf68d81 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs @@ -120,8 +120,9 @@ :is-layout-child? true :shape shape}]) - [:& constraints-menu {:ids ids - :values constraint-values}] + (when (not is-layout-child?) + [:& constraints-menu {:ids ids + :values constraint-values}]) [:& fill-menu {:ids ids :type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs index 620832b1d6..7c84cdeb57 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/text.cljs @@ -78,9 +78,11 @@ :values layout-item-values :is-layout-child? true :shape shape}]) - [:& constraints-menu - {:ids ids - :values (select-keys shape constraint-attrs)}] + + (when (not is-layout-child?) + [:& constraints-menu + {:ids ids + :values (select-keys shape constraint-attrs)}]) [:& layer-menu {:ids ids :type type From c86d88834e65efa8b956ccaf97dcb68424e23bdb Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 3 Nov 2022 17:44:10 +0100 Subject: [PATCH 215/682] :sparkles: Fix problems moving frames --- common/src/app/common/pages/helpers.cljc | 3 +- common/src/app/common/types/shape_tree.cljc | 29 ++++-- .../app/main/data/workspace/modifiers.cljs | 6 +- .../app/main/data/workspace/transforms.cljs | 93 +++++++++++-------- 4 files changed, 77 insertions(+), 54 deletions(-) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 55b3c571e6..e04291bd28 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -29,7 +29,7 @@ (defn frame-shape? ([objects id] - (= (get-in objects [id :type]) id)) + (frame-shape? (get objects id))) ([{:keys [type]}] (= type :frame))) @@ -467,7 +467,6 @@ (defn selected-with-children [objects selected] - (into selected (mapcat #(get-children-ids objects %)) selected)) diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index 905854d030..d894d77ef4 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -224,16 +224,25 @@ "Search for the top nested frame for positioning shapes when moving or creating. Looks for all the frames in a position and then goes in depth between the top-most and its children to find the target." - [objects position] - (let [frame-ids (all-frames-by-position objects position) - frame-set (set frame-ids)] - (loop [current-id (first frame-ids)] - (let [current-shape (get objects current-id) - child-frame-id (d/seek #(contains? frame-set %) - (-> (:shapes current-shape) reverse))] - (if (nil? child-frame-id) - (or current-id uuid/zero) - (recur child-frame-id)))))) + ([objects position] + (top-nested-frame objects position nil)) + + ([objects position excluded] + (assert (or (nil? excluded) (set? excluded))) + + (let [frame-ids (cond->> (all-frames-by-position objects position) + (some? excluded) + (remove excluded)) + + frame-set (set frame-ids)] + + (loop [current-id (first frame-ids)] + (let [current-shape (get objects current-id) + child-frame-id (d/seek #(contains? frame-set %) + (-> (:shapes current-shape) reverse))] + (if (nil? child-frame-id) + (or current-id uuid/zero) + (recur child-frame-id))))))) (defn top-nested-frame-ids "Search the top nested frame in a list of ids" diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 4861414e34..1e322c83f8 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -16,6 +16,7 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.modifiers :as ctm] + [app.common.types.shape.layout :as ctl] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.comments :as-alias dwcm] [app.main.data.workspace.guides :as-alias dwg] @@ -144,12 +145,11 @@ (into {} (map #(vector % {:modifiers (get-modifier (get objects %))})) ids)) (defn build-change-frame-modifiers - [modif-tree objects selected target-frame position] + [modif-tree objects selected target-frame drop-index] (let [origin-frame-ids (->> selected (group-by #(get-in objects [% :frame-id]))) - layout? (get-in objects [target-frame :layout]) child-set (set (get-in objects [target-frame :shapes])) - drop-index (when layout? (gsl/get-drop-index target-frame objects position)) + layout? (ctl/layout? objects target-frame) update-frame-modifiers (fn [modif-tree [original-frame shapes]] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 9ec4c43838..46aa099152 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -353,7 +353,7 @@ (declare start-move) (declare start-move-duplicate) -(declare calculate-frame-for-move) +(declare move-shapes-to-frame) (declare get-displacement) (defn start-move-selected @@ -414,7 +414,6 @@ (rx/take 1) (rx/map #(start-move from-position)))))) - (defn- start-move ([from-position] (start-move from-position nil)) ([from-position ids] @@ -436,13 +435,18 @@ zoom (get-in state [:workspace-local :zoom] 1) focus (:workspace-focus-selected state) - fix-axis (fn [[position shift?]] - (let [delta (gpt/to-vec from-position position)] - (if shift? - (if (> (mth/abs (:x delta)) (mth/abs (:y delta))) - (gpt/point (:x delta) 0) - (gpt/point 0 (:y delta))) - delta))) + exclude-frames (into #{} + (filter (partial cph/frame-shape? objects)) + (cph/selected-with-children objects selected)) + + fix-axis + (fn [[position shift?]] + (let [delta (gpt/to-vec from-position position)] + (if shift? + (if (> (mth/abs (:x delta)) (mth/abs (:y delta))) + (gpt/point (:x delta) 0) + (gpt/point 0 (:y delta))) + delta))) position (->> ms/mouse-position (rx/with-latest-from ms/mouse-position-shift) @@ -456,33 +460,48 @@ (rx/switch-map (fn [pos] (->> (snap/closest-snap-move page-id shapes objects layout zoom focus pos) - (rx/map #(vector pos %)))))))] + (rx/map #(vector pos %))))))) + + drop-frame (atom nil)] (if (empty? shapes) (rx/of (finish-transform)) - (rx/concat - (rx/merge - (->> position - ;; We ask for the snap position but we continue even if the result is not available - (rx/with-latest vector snap-delta) + (let [move-stream + (->> position + ;; We ask for the snap position but we continue even if the result is not available + (rx/with-latest vector snap-delta) - ;; We try to use the previous snap so we don't have to wait for the result of the new - (rx/map snap/correct-snap-point) + ;; We try to use the previous snap so we don't have to wait for the result of the new + (rx/map snap/correct-snap-point) - (rx/map - (fn [move-vector] - (let [position (gpt/add from-position move-vector) - target-frame (ctst/top-nested-frame objects position)] - (-> (dwm/create-modif-tree ids (ctm/move move-vector)) - (dwm/build-change-frame-modifiers objects selected target-frame position) - (dwm/set-modifiers))))) + (rx/map + (fn [move-vector] + (let [position (gpt/add from-position move-vector) + target-frame (ctst/top-nested-frame objects position exclude-frames) + layout? (ctl/layout? objects target-frame) + drop-index (when layout? (gsl/get-drop-index target-frame objects position))] + [move-vector target-frame drop-index]))) - (rx/take-until stopper))) + (rx/take-until stopper))] - (rx/of (dwu/start-undo-transaction) - (calculate-frame-for-move ids) - (dwm/apply-modifiers {:undo-transation? false}) - (finish-transform) - (dwu/commit-undo-transaction))))))))) + (rx/merge + ;; Temporary modifiers stream + (->> move-stream + (rx/map + (fn [[move-vector target-frame drop-index]] + (-> (dwm/create-modif-tree ids (ctm/move move-vector)) + (dwm/build-change-frame-modifiers objects selected target-frame drop-index) + (dwm/set-modifiers))))) + + ;; Last event will write the modifiers creating the changes + (->> move-stream + (rx/last) + (rx/mapcat + (fn [[move-vector target-frame drop-index]] + (rx/of (dwu/start-undo-transaction) + (move-shapes-to-frame ids target-frame drop-index) + (dwm/apply-modifiers {:undo-transation? false}) + (finish-transform) + (dwu/commit-undo-transaction))))))))))))) (s/def ::direction #{:up :down :right :left}) @@ -561,15 +580,13 @@ (rx/of (dwm/set-modifiers modif-tree) (dwm/apply-modifiers)))))) -(defn- calculate-frame-for-move - [ids] - (ptk/reify ::calculate-frame-for-move +(defn- move-shapes-to-frame + [ids frame-id drop-index] + (ptk/reify ::move-shapes-to-frame ptk/WatchEvent (watch [it state _] - (let [position @ms/mouse-position - page-id (:current-page-id state) + (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - frame-id (ctst/top-nested-frame objects position) layout? (get-in objects [frame-id :layout]) lookup (d/getf objects) @@ -584,14 +601,12 @@ (remove #(and (= (:frame-id %) frame-id) (not= (:parent-id %) frame-id)))) - drop-index (when layout? (gsl/get-drop-index frame-id objects position)) - changes (-> (pcb/empty-changes it page-id) (pcb/with-objects objects) (pcb/change-parent frame-id moving-shapes drop-index))] - (when-not (empty? changes) + (when (and (some? frame-id) (not (empty? changes))) (rx/of (dch/commit-changes changes) (dwc/expand-collapse frame-id))))))) From 861eb283e8f44e55ba4a532a55c12e58c8b0c8c9 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 4 Nov 2022 14:56:51 +0100 Subject: [PATCH 216/682] :sparkles: Flex layout small fixes --- .../src/app/common/geom/shapes/modifiers.cljc | 98 +++++++++++-------- .../common/geom/shapes/pixel_precision.cljc | 12 +-- common/src/app/common/types/modifiers.cljc | 89 ++++++++++------- common/src/app/common/types/shape/layout.cljc | 8 +- .../app/main/data/workspace/modifiers.cljs | 1 - .../app/main/data/workspace/transforms.cljs | 12 +-- .../main/ui/workspace/shapes/path/editor.cljs | 6 +- .../main/ui/workspace/viewport/widgets.cljs | 5 +- frontend/src/debug.cljs | 3 + 9 files changed, 132 insertions(+), 102 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 878c977592..7ec14c2d7f 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -26,7 +26,6 @@ ;; [(get-in objects [k :name]) v])) ;; modif-tree)))) - (defn set-children-modifiers [modif-tree objects parent ignore-constraints snap-pixel?] (let [children (map (d/getf objects) (:shapes parent)) @@ -52,10 +51,9 @@ (= :frame (:type shape))) (defn set-layout-modifiers - ;; TODO LAYOUT: SNAP PIXEL! - [modif-tree objects parent _snap-pixel?] + [modif-tree objects parent process-child?] - (letfn [(process-child [transformed-parent _snap-pixel? modif-tree child] + (letfn [(process-child [transformed-parent modif-tree child] (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) child-modifiers (-> modifiers (ctm/select-child-geometry-modifiers) @@ -88,7 +86,9 @@ transformed-parent (gtr/transform-shape parent modifiers) children (map (d/getf objects) (:shapes transformed-parent)) - modif-tree (reduce (partial process-child transformed-parent _snap-pixel?) modif-tree children) + modif-tree (if process-child? + (reduce (partial process-child transformed-parent) modif-tree children) + modif-tree) children (->> children (map (partial apply-modifiers modif-tree))) layout-data (gcl/calc-layout-data transformed-parent children) @@ -169,16 +169,13 @@ result ;; Frame found, but not layout we return the last layout found (or the id) - (and (and (= :frame (:type parent)) - (not (ctl/auto-width? parent)) - (not (ctl/auto-height? parent))) - (not (:layout parent))) + (and (= :frame (:type parent)) + (not (ctl/layout? parent))) result ;; Layout found. We continue upward but we mark this layout - (and (= :frame (:type parent)) - (:layout parent)) - (:id parent) + (ctl/layout? parent) + (recur (:id parent) (:id parent)) ;; If group or boolean or other type of group we continue with the last result :else @@ -238,42 +235,50 @@ (recur (:parent-id current)))))) (defn- calculate-modifiers - ([objects snap-pixel? ignore-constraints [modif-tree recalculate] shape] - (calculate-modifiers objects snap-pixel? ignore-constraints false [modif-tree recalculate] shape)) + [objects snap-pixel? ignore-constraints [modif-tree recalculate] shape] + (let [shape-id (:id shape) + root? (= uuid/zero shape-id) + modifiers (get-in modif-tree [shape-id :modifiers]) - ([objects snap-pixel? ignore-constraints ignore-auto? [modif-tree recalculate] shape] - (let [shape-id (:id shape) - root? (= uuid/zero shape-id) - modifiers (get-in modif-tree [shape-id :modifiers]) + modifiers (cond-> modifiers + (and (not root?) (ctm/has-geometry? modifiers) snap-pixel?) + (gpp/set-pixel-precision shape)) - modifiers (cond-> modifiers - (and (not root?) (ctm/has-geometry? modifiers) snap-pixel?) - (gpp/set-pixel-precision shape)) + modif-tree (-> modif-tree (assoc-in [shape-id :modifiers] modifiers)) - modif-tree (-> modif-tree (assoc-in [shape-id :modifiers] modifiers)) + has-modifiers? (ctm/child-modifiers? modifiers) + is-layout? (ctl/layout? shape) + is-auto? (or (ctl/auto-height? shape) (ctl/auto-width? shape)) + is-parent? (or (group? shape) (and (frame? shape) (not (ctl/layout? shape)))) - has-modifiers? (ctm/child-modifiers? modifiers) - is-layout? (ctl/layout? shape) - is-auto? (or (ctl/auto-height? shape) (ctl/auto-width? shape)) - is-parent? (or (group? shape) (and (frame? shape) (not (ctl/layout? shape)))) + ;; If the current child is inside the layout we ignore the constraints + is-inside-layout? (inside-layout? objects shape)] - ;; If the current child is inside the layout we ignore the constraints - is-inside-layout? (inside-layout? objects shape)] + [(cond-> modif-tree + (and has-modifiers? is-parent? (not root?)) + (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?) - [(cond-> modif-tree - (and has-modifiers? is-parent? (not root?)) - (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?) + is-layout? + (set-layout-modifiers objects shape true) - is-layout? - (set-layout-modifiers objects shape snap-pixel?) + is-auto? + (set-auto-modifiers objects shape)) - (and (not ignore-auto?) is-auto?) - (set-auto-modifiers objects shape)) + (cond-> recalculate + ;; Auto-width/height can change the positions in the parent so we need to recalculate + is-auto? + (conj (:id shape)))])) - (cond-> recalculate - ;; Auto-width/height can change the positions in the parent so we need to recalculate - (and (not ignore-auto?) is-auto?) - (conj (:id shape)))]))) +(defn- calculate-reflow-layout + [objects snap-pixel? modif-tree shape] + (let [is-layout? (ctl/layout? shape) + is-auto? (or (ctl/auto-height? shape) (ctl/auto-width? shape))] + (cond-> modif-tree + is-layout? + (set-layout-modifiers objects shape false) + + is-auto? + (set-auto-modifiers objects shape)))) (defn set-objects-modifiers [modif-tree objects ignore-constraints snap-pixel?] @@ -284,8 +289,19 @@ (reduce (partial calculate-modifiers objects snap-pixel? ignore-constraints) [modif-tree #{}] shapes-tree) shapes-tree (resolve-tree-sequence recalculate objects) - [modif-tree _] - (reduce (partial calculate-modifiers objects snap-pixel? ignore-constraints true) [modif-tree #{}] shapes-tree)] + + ;; We need to go again and recalculate the layout positions+hug + ;; TODO LAYOUT: How to remove this recalculus? + modif-tree + (->> shapes-tree + reverse + (filter ctl/layout?) + (reduce (partial calculate-reflow-layout objects snap-pixel?) modif-tree )) + + modif-tree + (->> shapes-tree + (filter ctl/layout?) + (reduce (partial calculate-reflow-layout objects snap-pixel?) modif-tree ))] ;;#?(:cljs ;; (.log js/console ">result" (modif->js modif-tree objects))) diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc index 40e32ae72b..03aa4359a7 100644 --- a/common/src/app/common/geom/shapes/pixel_precision.cljc +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -31,10 +31,8 @@ ratio-width (/ target-width curr-width) ratio-height (/ target-height curr-height) scalev (gpt/point ratio-width ratio-height)] - (cond-> modifiers - (or (not (mth/almost-zero? (- ratio-width 1))) - (not (mth/almost-zero? (- ratio-height 1)))) - (ctm/set-resize scalev origin transform transform-inverse)))) + (-> modifiers + (ctm/set-resize scalev origin transform transform-inverse)))) (defn position-pixel-precision [modifiers shape] @@ -43,10 +41,8 @@ corner (gpt/point bounds) target-corner (gpt/round corner) deltav (gpt/to-vec corner target-corner)] - (cond-> modifiers - (or (not (mth/almost-zero? (:x deltav))) - (not (mth/almost-zero? (:y deltav)))) - (ctm/set-move deltav)))) + (-> modifiers + (ctm/set-move deltav)))) (defn set-pixel-precision "Adjust modifiers so they adjust to the pixel grid" diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 25e17df9d9..48710b40cb 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -10,6 +10,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] + [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.text :as txt])) @@ -41,73 +42,89 @@ (defn empty-modifiers [] {}) +(defn move-vec? [vector] + (or (not (mth/almost-zero? (:x vector))) + (not (mth/almost-zero? (:y vector))))) + +(defn resize-vec? [vector] + (or (not (mth/almost-zero? (- (:x vector) 1))) + (not (mth/almost-zero? (- (:y vector) 1))))) + (defn set-move-parent ([modifiers x y] (set-move-parent modifiers (gpt/point x y))) ([modifiers vector] - (-> modifiers - (update :geometry-parent conjv {:type :move :vector vector})))) + (cond-> modifiers + (move-vec? vector) + (update :geometry-parent conjv {:type :move :vector vector})))) (defn set-resize-parent ([modifiers vector origin] - (-> modifiers - (update :geometry-parent conjv {:type :resize + (cond-> modifiers + (resize-vec? vector) + (update :geometry-parent conjv {:type :resize :vector vector :origin origin}))) ([modifiers vector origin transform transform-inverse] - (-> modifiers - (update :geometry-parent conjv {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + (cond-> modifiers + (resize-vec? vector) + (update :geometry-parent conjv {:type :resize + :vector vector + :origin origin + :transform transform + :transform-inverse transform-inverse})))) (defn set-move ([modifiers x y] (set-move modifiers (gpt/point x y))) ([modifiers vector] - (-> modifiers - (update :geometry-child conjv {:type :move :vector vector})))) + (cond-> modifiers + (move-vec? vector) + (update :geometry-child conjv {:type :move :vector vector})))) (defn set-resize ([modifiers vector origin] - (-> modifiers - (update :geometry-child conjv {:type :resize - :vector vector - :origin origin}))) + (cond-> modifiers + (resize-vec? vector) + (update :geometry-child conjv {:type :resize + :vector vector + :origin origin}))) ([modifiers vector origin transform transform-inverse] - (-> modifiers - (update :geometry-child conjv {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + (cond-> modifiers + (resize-vec? vector) + (update :geometry-child conjv {:type :resize + :vector vector + :origin origin + :transform transform + :transform-inverse transform-inverse})))) (defn set-rotation [modifiers center angle] - (-> modifiers - (update :structure-child conjv {:type :rotation - :rotation angle}) - (update :geometry-child conjv {:type :rotation - :center center - :rotation angle}))) + (cond-> modifiers + (not (mth/close? angle 0)) + (-> (update :structure-child conjv {:type :rotation + :rotation angle}) + (update :geometry-child conjv {:type :rotation + :center center + :rotation angle})))) (defn set-remove-children [modifiers shapes] - (-> modifiers - (update :structure-parent conjv {:type :remove-children - :value shapes})) - ) + (cond-> modifiers + (d/not-empty? shapes) + (update :structure-parent conjv {:type :remove-children + :value shapes}))) (defn set-add-children [modifiers shapes index] - (-> modifiers - (update :structure-parent conjv {:type :add-children - :value shapes - :index index}))) + (cond-> modifiers + (d/not-empty? shapes) + (update :structure-parent conjv {:type :add-children + :value shapes + :index index}))) (defn set-reflow [modifiers] diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 87a1ad3fe7..d25dc9844e 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -46,15 +46,14 @@ (s/def ::p4 ::us/safe-number) (s/def ::layout-padding - (s/keys :req-un [::p1] - :opt-un [::p2 ::p3 ::p4])) + (s/keys :opt-un [::p1 ::p2 ::p3 ::p4])) (s/def ::row-gap ::us/safe-number) (s/def ::column-gap ::us/safe-number) (s/def ::layout-type #{:flex :grid}) (s/def ::layout-gap - (s/keys :req-un [::row-gap ::column-gap])) + (s/keys :opt-un [::row-gap ::column-gap])) (s/def ::layout-container-props (s/keys :opt-un [::layout @@ -74,8 +73,7 @@ (s/def ::m3 ::us/safe-number) (s/def ::m4 ::us/safe-number) -(s/def ::layout-item-margin (s/keys :req-un [::m1] - :opt-un [::m2 ::m3 ::m4])) +(s/def ::layout-item-margin (s/keys :opt-un [::m1 ::m2 ::m3 ::m4])) (s/def ::layout-item-margin-type #{:simple :multiple}) (s/def ::layout-item-h-sizing #{:fill :fix :auto}) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 1e322c83f8..9263d97450 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -10,7 +10,6 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.flex-layout :as gsl] [app.common.math :as mth] [app.common.pages.common :as cpc] [app.common.pages.helpers :as cph] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 46aa099152..7ef6eb3cdd 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -460,9 +460,7 @@ (rx/switch-map (fn [pos] (->> (snap/closest-snap-move page-id shapes objects layout zoom focus pos) - (rx/map #(vector pos %))))))) - - drop-frame (atom nil)] + (rx/map #(vector pos %)))))))] (if (empty? shapes) (rx/of (finish-transform)) (let [move-stream @@ -496,7 +494,7 @@ (->> move-stream (rx/last) (rx/mapcat - (fn [[move-vector target-frame drop-index]] + (fn [[_ target-frame drop-index]] (rx/of (dwu/start-undo-transaction) (move-shapes-to-frame ids target-frame drop-index) (dwm/apply-modifiers {:undo-transation? false}) @@ -606,7 +604,7 @@ (pcb/with-objects objects) (pcb/change-parent frame-id moving-shapes drop-index))] - (when (and (some? frame-id) (not (empty? changes))) + (when (and (some? frame-id) (d/not-empty? changes)) (rx/of (dch/commit-changes changes) (dwc/expand-collapse frame-id))))))) @@ -637,7 +635,7 @@ selected (-> (ctm/empty-modifiers) (ctm/set-resize (gpt/point -1.0 1.0) origin) - (ctm/move (gpt/point (:width selrect) 0))))] + (ctm/set-move (gpt/point (:width selrect) 0))))] (rx/of (dwm/set-modifiers modif-tree true) (dwm/apply-modifiers)))))) @@ -656,7 +654,7 @@ selected (-> (ctm/empty-modifiers) (ctm/set-resize (gpt/point 1.0 -1.0) origin) - (ctm/move (gpt/point 0 (:height selrect)))))] + (ctm/set-move (gpt/point 0 (:height selrect)))))] (rx/of (dwm/set-modifiers modif-tree true) (dwm/apply-modifiers)))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index d6f2a1dd17..67b79a46a1 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -6,6 +6,8 @@ (ns app.main.ui.workspace.shapes.path.editor (:require + [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as gsp] [app.common.path.commands :as upc] @@ -308,7 +310,7 @@ :start-path? start-p? :zoom zoom}]]) - (for [position points] + (for [[index position] (d/enumerate points)] (let [show-handler? (fn [[index prefix]] (let [handler-position (upc/handler->point content index prefix)] @@ -322,7 +324,7 @@ pos-handlers (->> pos-handlers (filter show-handler?)) curve? (boolean (seq pos-handlers))] - [:g.path-node + [:g.path-node {:key (dm/str index "-" (:x position) "-" (:y position))} [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")} (for [[index prefix] pos-handlers] (let [handler-position (upc/handler->point content index prefix) diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 9da2e1673a..d78b534633 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -21,6 +21,7 @@ [app.main.ui.workspace.viewport.path-actions :refer [path-actions]] [app.main.ui.workspace.viewport.utils :as vwu] [app.util.dom :as dom] + [debug :refer [debug?]] [rumext.v2 :as mf])) (mf/defc pixel-grid @@ -34,8 +35,8 @@ :pattern-units "userSpaceOnUse"} [:path {:d "M 1 0 L 0 0 0 1" :style {:fill "none" - :stroke "var(--color-info)" - :stroke-opacity "0.2" + :stroke (if (debug? :pixel-grid) "red" "var(--color-info)") + :stroke-opacity (if (debug? :pixel-grid) 1 "0.2") :stroke-width (str (/ 1 zoom))}}]]] [:rect {:x (:x vbox) :y (:y vbox) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 3a3b9f3669..d4856cbda9 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -70,6 +70,9 @@ ;; Enable a widget to show the auto-layout drop-zones :layout-drop-zones + + ;; Makes the pixel grid red so its more visibile + :pixel-grid }) ;; These events are excluded when we activate the :events flag From 7375eed18f7f531a1e6e93213fa3d4cf5b116b8a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 4 Nov 2022 15:50:55 +0100 Subject: [PATCH 217/682] :sparkles: Refactor modifiers --- .../src/app/common/geom/shapes/modifiers.cljc | 259 ++++++++---------- common/src/app/common/pages/helpers.cljc | 4 + common/src/app/common/types/shape/layout.cljc | 16 ++ 3 files changed, 138 insertions(+), 141 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 7ec14c2d7f..3b6a3c61ba 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -26,11 +26,71 @@ ;; [(get-in objects [k :name]) v])) ;; modif-tree)))) -(defn set-children-modifiers - [modif-tree objects parent ignore-constraints snap-pixel?] +(defn resolve-tree-sequence + "Given the ids that have changed search for layout roots to recalculate" + [ids objects] + + (assert (or (nil? ids) (set? ids)) (dm/str "tree sequence from not set: " ids)) + + (letfn [(get-tree-root ;; Finds the tree root for the current id + [id] + + (loop [current id + result id] + (let [shape (get objects current) + parent (get objects (:parent-id shape))] + (cond + (or (not shape) (= uuid/zero current)) + result + + ;; Frame found, but not layout we return the last layout found (or the id) + (and (= :frame (:type parent)) + (not (ctl/layout? parent))) + result + + ;; Layout found. We continue upward but we mark this layout + (ctl/layout? parent) + (recur (:id parent) (:id parent)) + + ;; If group or boolean or other type of group we continue with the last result + :else + (recur (:id parent) result))))) + + (calculate-common-roots ;; Given some roots retrieves the minimum number of tree roots + [result id] + (if (= id uuid/zero) + result + (let [root (get-tree-root id) + + ;; Remove the children from the current root + result + (into #{} (remove #(cph/is-child? objects root %)) result) + + contains-parent? + (some #(cph/is-child? objects % root) result)] + + (cond-> result + (not contains-parent?) + (conj root))))) + + (generate-tree ;; Generate a tree sequence from a given root id + [id] + (->> (tree-seq + #(d/not-empty? (get-in objects [% :shapes])) + #(get-in objects [% :shapes]) + id) + (map #(get objects %))))] + + (let [roots (->> ids (reduce calculate-common-roots #{}))] + (concat + (when (contains? ids uuid/zero) [(get objects uuid/zero)]) + (mapcat generate-tree roots))))) + +(defn- set-children-modifiers + "Propagates the modifiers from a parent too its children applying constraints if necesary" + [modif-tree objects parent transformed-parent ignore-constraints snap-pixel?] (let [children (map (d/getf objects) (:shapes parent)) modifiers (get-in modif-tree [(:id parent) :modifiers]) - transformed-parent (gtr/transform-shape parent modifiers) parent (gtr/transform-shape parent (ctm/select-parent-modifiers modifiers)) set-child @@ -43,32 +103,29 @@ (reduce set-child modif-tree children))) -(defn group? [shape] - (or (= :group (:type shape)) - (= :bool (:type shape)))) - -(defn frame? [shape] - (= :frame (:type shape))) - -(defn set-layout-modifiers - [modif-tree objects parent process-child?] - - (letfn [(process-child [transformed-parent modif-tree child] +(defn- process-layout-children + [modif-tree objects parent transformed-parent] + (letfn [(process-child [modif-tree child] (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) child-modifiers (-> modifiers (ctm/select-child-geometry-modifiers) (gcl/normalize-child-modifiers parent child transformed-parent))] (cond-> modif-tree (not (ctm/empty-modifiers? child-modifiers)) - (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers)))) + (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))] + (let [children (map (d/getf objects) (:shapes transformed-parent))] + (reduce process-child modif-tree children)))) - (apply-modifiers [modif-tree child] +(defn- set-layout-modifiers + [modif-tree objects parent] + + (letfn [(apply-modifiers [modif-tree child] (let [modifiers (get-in modif-tree [(:id child) :modifiers])] (cond-> child (some? modifiers) (gtr/transform-shape modifiers) - (and (some? modifiers) (group? child)) + (and (some? modifiers) (cph/group-like-shape? child)) (gtr/apply-group-modifiers objects modif-tree)))) (set-child-modifiers [parent [layout-line modif-tree] child] @@ -82,19 +139,11 @@ [layout-line modif-tree]))] - (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) - transformed-parent (gtr/transform-shape parent modifiers) - children (map (d/getf objects) (:shapes transformed-parent)) - - modif-tree (if process-child? - (reduce (partial process-child transformed-parent) modif-tree children) - modif-tree) + (let [children (map (d/getf objects) (:shapes parent)) children (->> children (map (partial apply-modifiers modif-tree))) - - layout-data (gcl/calc-layout-data transformed-parent children) + layout-data (gcl/calc-layout-data parent children) children (into [] (cond-> children (:reverse? layout-data) reverse)) max-idx (dec (count children)) - layout-lines (:layout-lines layout-data)] (loop [modif-tree modif-tree @@ -106,20 +155,22 @@ children (subvec children from-idx to-idx) [_ modif-tree] - (reduce (partial set-child-modifiers transformed-parent) [layout-line modif-tree] children)] + (reduce (partial set-child-modifiers parent) [layout-line modif-tree] children)] (recur modif-tree (first pending) (rest pending) to-idx)) modif-tree))))) -(defn set-auto-modifiers +(defn- set-auto-modifiers + "Calculates the modifiers to adjust the bounds for auto-width/auto-height shapes" [modif-tree objects parent] - (letfn [(apply-modifiers [child] + (letfn [(apply-modifiers + [child] (let [modifiers (get-in modif-tree [(:id child) :modifiers])] (cond-> child (some? modifiers) (gtr/transform-shape modifiers) - (and (some? modifiers) (group? child)) + (and (some? modifiers) (cph/group-like-shape? child)) (gtr/apply-group-modifiers objects modif-tree)))) (set-parent-auto-width @@ -137,148 +188,74 @@ (ctm/set-resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))] (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) - transformed-parent (gtr/transform-shape parent modifiers) - - children (->> transformed-parent + children (->> parent :shapes (map (comp apply-modifiers (d/getf objects)))) {auto-width :width auto-height :height} - (when (and (d/not-empty? children) (or (ctl/auto-height? transformed-parent) (ctl/auto-width? transformed-parent))) - (gcl/layout-content-bounds transformed-parent children)) + (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) + (gcl/layout-content-bounds parent children)) modifiers (cond-> modifiers - (and (some? auto-width) (ctl/auto-width? transformed-parent)) - (set-parent-auto-width transformed-parent auto-width) + (and (some? auto-width) (ctl/auto-width? parent)) + (set-parent-auto-width parent auto-width) - (and (some? auto-height) (ctl/auto-height? transformed-parent)) - (set-parent-auto-height transformed-parent auto-height))] + (and (some? auto-height) (ctl/auto-height? parent)) + (set-parent-auto-height parent auto-height))] (assoc-in modif-tree [(:id parent) :modifiers] modifiers)))) -(defn get-tree-root - [id objects] - - (loop [current id - result id] - (let [shape (get objects current) - parent (get objects (:parent-id shape))] - (cond - (or (not shape) (= uuid/zero current)) - result - - ;; Frame found, but not layout we return the last layout found (or the id) - (and (= :frame (:type parent)) - (not (ctl/layout? parent))) - result - - ;; Layout found. We continue upward but we mark this layout - (ctl/layout? parent) - (recur (:id parent) (:id parent)) - - ;; If group or boolean or other type of group we continue with the last result - :else - (recur (:id parent) result))))) - -(defn resolve-tree-sequence - "Given the ids that have changed search for layout roots to recalculate" - [ids objects] - - (assert (or (nil? ids) (set? ids)) (dm/str "tree sequence from not set: " ids)) - - (let [redfn - (fn [result id] - (if (= id uuid/zero) - result - (let [root (get-tree-root id objects) - - ;; Remove the children from the current root - result - (into #{} (remove #(cph/is-child? objects root %)) result) - - contains-parent? - (some #(cph/is-child? objects % root) result)] - - (cond-> result - (not contains-parent?) - (conj root))))) - - generate-tree - (fn [id] - (->> (tree-seq - #(d/not-empty? (get-in objects [% :shapes])) - #(get-in objects [% :shapes]) - id) - - (map #(get objects %)))) - - roots (->> ids (reduce redfn #{}))] - - (concat - (when (contains? ids uuid/zero) [(get objects uuid/zero)]) - (mapcat generate-tree roots)))) - -(defn inside-layout? - [objects shape] - - (loop [current-id (:id shape)] - (let [current (get objects current-id)] - (cond - (or (nil? current) (= current-id (:parent-id current))) - false - - (= :frame (:type current)) - (:layout current) - - :else - (recur (:parent-id current)))))) - -(defn- calculate-modifiers - [objects snap-pixel? ignore-constraints [modif-tree recalculate] shape] - (let [shape-id (:id shape) - root? (= uuid/zero shape-id) - modifiers (get-in modif-tree [shape-id :modifiers]) +(defn- propagate-modifiers + [objects snap-pixel? ignore-constraints [modif-tree recalculate] parent] + (let [parent-id (:id parent) + root? (= uuid/zero parent-id) + modifiers (get-in modif-tree [parent-id :modifiers]) modifiers (cond-> modifiers (and (not root?) (ctm/has-geometry? modifiers) snap-pixel?) - (gpp/set-pixel-precision shape)) + (gpp/set-pixel-precision parent)) - modif-tree (-> modif-tree (assoc-in [shape-id :modifiers] modifiers)) + modif-tree (-> modif-tree (assoc-in [parent-id :modifiers] modifiers)) + + transformed-parent (gtr/transform-shape parent modifiers) has-modifiers? (ctm/child-modifiers? modifiers) - is-layout? (ctl/layout? shape) - is-auto? (or (ctl/auto-height? shape) (ctl/auto-width? shape)) - is-parent? (or (group? shape) (and (frame? shape) (not (ctl/layout? shape)))) + is-layout? (ctl/layout? parent) + is-auto? (or (ctl/auto-height? transformed-parent) (ctl/auto-width? transformed-parent)) + is-parent? (or (cph/group-like-shape? parent) (and (cph/frame-shape? parent) (not (ctl/layout? parent)))) ;; If the current child is inside the layout we ignore the constraints - is-inside-layout? (inside-layout? objects shape)] + is-inside-layout? (ctl/inside-layout? objects parent)] [(cond-> modif-tree (and has-modifiers? is-parent? (not root?)) - (set-children-modifiers objects shape (or ignore-constraints is-inside-layout?) snap-pixel?) + (set-children-modifiers objects parent transformed-parent (or ignore-constraints is-inside-layout?) snap-pixel?) is-layout? - (set-layout-modifiers objects shape true) + (-> (process-layout-children objects parent transformed-parent) + (set-layout-modifiers objects transformed-parent)) is-auto? - (set-auto-modifiers objects shape)) + (set-auto-modifiers objects transformed-parent)) (cond-> recalculate ;; Auto-width/height can change the positions in the parent so we need to recalculate is-auto? - (conj (:id shape)))])) + (conj (:id parent)))])) (defn- calculate-reflow-layout - [objects snap-pixel? modif-tree shape] - (let [is-layout? (ctl/layout? shape) - is-auto? (or (ctl/auto-height? shape) (ctl/auto-width? shape))] + [objects modif-tree parent] + (let [is-layout? (ctl/layout? parent) + is-auto? (or (ctl/auto-height? parent) (ctl/auto-width? parent)) + modifiers (get-in modif-tree [(:id parent) :modifiers]) + transformed-parent (gtr/transform-shape parent modifiers)] (cond-> modif-tree is-layout? - (set-layout-modifiers objects shape false) + (set-layout-modifiers objects transformed-parent) is-auto? - (set-auto-modifiers objects shape)))) + (set-auto-modifiers objects transformed-parent)))) (defn set-objects-modifiers [modif-tree objects ignore-constraints snap-pixel?] @@ -286,22 +263,22 @@ (let [shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects) [modif-tree recalculate] - (reduce (partial calculate-modifiers objects snap-pixel? ignore-constraints) [modif-tree #{}] shapes-tree) + (reduce (partial propagate-modifiers objects snap-pixel? ignore-constraints) [modif-tree #{}] shapes-tree) shapes-tree (resolve-tree-sequence recalculate objects) ;; We need to go again and recalculate the layout positions+hug - ;; TODO LAYOUT: How to remove this recalculus? + ;; TODO LAYOUT: How to remove this recalculation? modif-tree (->> shapes-tree reverse (filter ctl/layout?) - (reduce (partial calculate-reflow-layout objects snap-pixel?) modif-tree )) + (reduce (partial calculate-reflow-layout objects) modif-tree)) modif-tree (->> shapes-tree (filter ctl/layout?) - (reduce (partial calculate-reflow-layout objects snap-pixel?) modif-tree ))] + (reduce (partial calculate-reflow-layout objects) modif-tree ))] ;;#?(:cljs ;; (.log js/console ">result" (modif->js modif-tree objects))) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index e04291bd28..2799639a50 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -47,6 +47,10 @@ [{:keys [type]}] (= type :bool)) +(defn group-like-shape? + [{:keys [type]}] + (or (= :group type) (= :bool type))) + (defn text-shape? [{:keys [type]}] (= type :text)) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index d25dc9844e..b318191c03 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -106,6 +106,22 @@ parent (get objects parent-id)] (layout? parent))) +(defn inside-layout? + "Check if the shape is inside a layout" + [objects shape] + + (loop [current-id (:id shape)] + (let [current (get objects current-id)] + (cond + (or (nil? current) (= current-id (:parent-id current))) + false + + (= :frame (:type current)) + (:layout current) + + :else + (recur (:parent-id current)))))) + (defn wrap? [{:keys [layout-wrap-type]}] (= layout-wrap-type :wrap)) From b5df7bbfc524c309fbe83a8d8e77e1bddc129040 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 4 Nov 2022 15:53:50 +0100 Subject: [PATCH 218/682] :sparkles: Remove constraints when autolayout --- CHANGES.md | 3 +++ frontend/resources/images/icons/auto-flex.svg | 3 --- .../images/icons/auto-row-column.svg | 3 --- .../resources/images/icons/layout-columns.svg | 4 ++-- .../resources/images/icons/layout-rows.svg | 4 ++-- .../app/main/data/workspace/shape_layout.cljs | 20 ++++++++++------ .../main/data/workspace/state_helpers.cljs | 19 ++++++++------- .../src/app/main/data/workspace/texts.cljs | 23 +++++++------------ frontend/src/app/main/ui/shapes/bool.cljs | 5 +--- .../main/ui/workspace/shapes/path/editor.cljs | 5 ++-- 10 files changed, 41 insertions(+), 48 deletions(-) delete mode 100644 frontend/resources/images/icons/auto-flex.svg delete mode 100644 frontend/resources/images/icons/auto-row-column.svg diff --git a/CHANGES.md b/CHANGES.md index 5238ce23a0..8c3d7ac4f9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,8 +4,11 @@ ### :boom: Breaking changes & Deprecations ### :sparkles: New features + +- Adds layout flex functionality for boards - Better overlays interactions on boards inside boards [Taiga #4386](https://tree.taiga.io/project/penpot/us/4386) - Show board miniature in manual overlay setting [Taiga #4475](https://tree.taiga.io/project/penpot/issue/4475) + ### :bug: Bugs fixed - Add title to color bullets [Taiga #4218](https://tree.taiga.io/project/penpot/task/4218) diff --git a/frontend/resources/images/icons/auto-flex.svg b/frontend/resources/images/icons/auto-flex.svg deleted file mode 100644 index 9023c8fcf0..0000000000 --- a/frontend/resources/images/icons/auto-flex.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/resources/images/icons/auto-row-column.svg b/frontend/resources/images/icons/auto-row-column.svg deleted file mode 100644 index 9023c8fcf0..0000000000 --- a/frontend/resources/images/icons/auto-row-column.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/frontend/resources/images/icons/layout-columns.svg b/frontend/resources/images/icons/layout-columns.svg index 4c9381c99c..eb4b7ba55a 100644 --- a/frontend/resources/images/icons/layout-columns.svg +++ b/frontend/resources/images/icons/layout-columns.svg @@ -1,3 +1,3 @@ - - + + diff --git a/frontend/resources/images/icons/layout-rows.svg b/frontend/resources/images/icons/layout-rows.svg index f09a1ced87..9023c8fcf0 100644 --- a/frontend/resources/images/icons/layout-rows.svg +++ b/frontend/resources/images/icons/layout-rows.svg @@ -1,3 +1,3 @@ - - + + diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 1e68643491..c1a94684ad 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -57,17 +57,23 @@ (dwm/apply-modifiers))) (rx/empty)))))) -;; TODO LAYOUT: Remove constraints from children +(defn get-layout-initializer + [type] + (let [initial-layout-data (if (= type :flex) initial-flex-layout initial-grid-layout)] + (fn [shape] + (-> shape + (merge shape initial-layout-data))))) + (defn create-layout [ids type] (ptk/reify ::create-layout ptk/WatchEvent - (watch [_ _ _] - (if (= type :flex) - (rx/of (dwc/update-shapes ids #(merge % initial-flex-layout)) - (update-layout-positions ids)) - (rx/of (dwc/update-shapes ids #(merge % initial-grid-layout)) - (update-layout-positions ids)))))) + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + children-ids (into [] (mapcat #(get-in objects [% :shapes])) ids)] + (rx/of (dwc/update-shapes ids (get-layout-initializer type)) + (update-layout-positions ids) + (dwc/update-shapes children-ids #(dissoc % :constraints-h :constraints-v))))))) (defn remove-layout [ids] diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index c56931cf19..5804e14343 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -8,9 +8,9 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] - [app.common.path.commands :as upc] - [app.common.types.modifiers :as ctm])) + [app.common.path.commands :as upc])) (defn lookup-page ([state] @@ -127,16 +127,15 @@ (defn select-bool-children [parent-id state] (let [objects (lookup-page-objects state) - selected (lookup-selected-raw state) modifiers (:workspace-modifiers state) - children-ids (cph/get-children-ids objects parent-id) - selected-children (into [] (filter selected) children-ids) - - modifiers (select-keys modifiers selected-children) - children (select-keys objects children-ids)] + children + (-> (select-keys objects children-ids) + (d/update-vals + (fn [child] + (cond-> child + (contains? modifiers (:id child)) + (gsh/transform-shape (get-in modifiers [(:id child) :modifiers]))))))] (as-> children $ - ;; TODO LAYOUT: REVIEW THIS - (ctm/merge-modifiers $ modifiers) (d/mapm (set-content-modifiers state) $)))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index dbfb5f453a..06592a1534 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -319,17 +319,14 @@ (watch [_ _ _] (letfn [(update-fn [shape] (let [{:keys [selrect grow-type]} shape - {shape-width :width shape-height :height} selrect - modifier-width (ctm/change-dimensions shape :width new-width) - modifier-height (ctm/change-dimensions shape :height new-height)] - ;; TODO LAYOUT: MEZCLAR ESTOS EN UN UNICO MODIFIER + {shape-width :width shape-height :height} selrect] (cond-> shape (and (not-changed? shape-width new-width) (= grow-type :auto-width)) - (gsh/transform-shape modifier-width) + (gsh/transform-shape (ctm/change-dimensions shape :width new-width)) (and (not-changed? shape-height new-height) (or (= grow-type :auto-height) (= grow-type :auto-width))) - (gsh/transform-shape modifier-height))))] + (gsh/transform-shape (ctm/change-dimensions shape :height new-height)))))] (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false})))))) @@ -346,17 +343,13 @@ (defn apply-text-modifier [shape {:keys [width height position-data]}] - (let [modifier-width (when width (ctm/change-dimensions shape :width width)) - modifier-height (when height (ctm/change-dimensions shape :height height)) - - ;; TODO LAYOUT: MEZCLAR LOS DOS EN UN UNICO MODIFIER - new-shape + (let [new-shape (cond-> shape - (some? modifier-width) - (gsh/transform-shape modifier-width) + (some? width) + (gsh/transform-shape (ctm/change-dimensions shape :width width)) - (some? modifier-height) - (gsh/transform-shape modifier-height) + (some? height) + (gsh/transform-shape (ctm/change-dimensions shape :height height)) (some? position-data) (assoc :position-data position-data)) diff --git a/frontend/src/app/main/ui/shapes/bool.cljs b/frontend/src/app/main/ui/shapes/bool.cljs index 2c00a9684e..d725a00aeb 100644 --- a/frontend/src/app/main/ui/shapes/bool.cljs +++ b/frontend/src/app/main/ui/shapes/bool.cljs @@ -14,7 +14,6 @@ [app.util.object :as obj] [rumext.v2 :as mf])) -;; TODO LAYOUT: REVIEW DYNAMIC CHANGES IN BOOLEANS (defn bool-shape [shape-wrapper] (mf/fnc bool-shape @@ -34,9 +33,7 @@ (:bool-content shape) (some? childs) - (->> childs - #_(d/mapm #(gsh/transform-shape %2)) - (gsh/calc-bool-content shape)))))] + (gsh/calc-bool-content shape childs))))] [:* (when (some? bool-content) diff --git a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs index 67b79a46a1..8a991bf3fc 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -185,8 +185,9 @@ matches (concat (second (:x snap-matches)) (second (:y snap-matches)))] [:g.snap-paths - (for [[from to] matches] - [:line {:x1 (:x from) + (for [[idx [from to]] (d/enumerate matches)] + [:line {:key (dm/str "snap-" idx "-" from "-" to) + :x1 (:x from) :y1 (:y from) :x2 (:x to) :y2 (:y to) From a2e26210d1550296101a9f34d039ecd4c07d8258 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 7 Nov 2022 12:25:22 +0100 Subject: [PATCH 219/682] :sparkles: Renamed modifiers functions --- .../app/common/geom/shapes/constraints.cljc | 13 +- .../geom/shapes/flex_layout/modifiers.cljc | 16 +- .../src/app/common/geom/shapes/modifiers.cljc | 8 +- .../common/geom/shapes/pixel_precision.cljc | 4 +- .../app/common/geom/shapes/transforms.cljc | 2 +- common/src/app/common/types/modifiers.cljc | 270 +++++++++--------- .../test/common_tests/geom_shapes_test.cljc | 14 +- .../app/main/data/workspace/drawing/box.cljs | 6 +- .../main/data/workspace/drawing/common.cljs | 2 +- .../app/main/data/workspace/modifiers.cljs | 8 +- .../app/main/data/workspace/shape_layout.cljs | 2 +- .../src/app/main/data/workspace/texts.cljs | 8 +- .../app/main/data/workspace/transforms.cljs | 32 +-- frontend/src/app/main/render.cljs | 8 +- .../src/app/main/ui/viewer/interactions.cljs | 2 +- .../shapes/frame/dynamic_modifiers.cljs | 4 +- .../shapes/text/viewport_texts_html.cljs | 2 +- 17 files changed, 201 insertions(+), 200 deletions(-) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 2be4becadb..c90cd179b5 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -152,7 +152,7 @@ end-angl (gpt/angle-with-other end-before end-after) target-end (if (mth/close? end-angl 180) (- (gpt/length end-before)) (gpt/length end-before)) disp-vector-end (gpt/subtract end-after (gpt/scale (gpt/unit end-after) target-end))] - (ctm/move disp-vector-end))) + (ctm/move-modifiers disp-vector-end))) (defmethod constraint-modifier :fixed [_ axis child-points-before parent-points-before child-points-after parent-points-after transformed-parent] @@ -174,9 +174,8 @@ resize-angl (gpt/angle-with-other before-vec after-vec) resize-sign (if (mth/close? resize-angl 180) -1 1) - scale (* resize-sign (/ (gpt/length after-vec) (gpt/length before-vec))) - ] - (ctm/resize (get-scale axis scale) c0 (:transform transformed-parent) (:transform-inverse transformed-parent)))) + scale (* resize-sign (/ (gpt/length after-vec) (gpt/length before-vec)))] + (ctm/resize-modifiers (get-scale axis scale) c0 (:transform transformed-parent) (:transform-inverse transformed-parent)))) (defmethod constraint-modifier :center [_ axis child-points-before parent-points-before child-points-after parent-points-after] @@ -185,7 +184,7 @@ center-angl (gpt/angle-with-other center-before center-after) target-center (if (mth/close? center-angl 180) (- (gpt/length center-before)) (gpt/length center-before)) disp-vector-center (gpt/subtract center-after (gpt/scale (gpt/unit center-after) target-center))] - (ctm/move disp-vector-center))) + (ctm/move-modifiers disp-vector-center))) (defmethod constraint-modifier :default [_ _ _ _ _] []) @@ -242,10 +241,10 @@ (cond-> modifiers (not= :scale constraints-h) - (ctm/set-resize (gpt/point scale-x 1) resize-origin transform transform-inverse) + (ctm/resize (gpt/point scale-x 1) resize-origin transform transform-inverse) (not= :scale constraints-v) - (ctm/set-resize (gpt/point 1 scale-y) resize-origin transform transform-inverse)))) + (ctm/resize (gpt/point 1 scale-y) resize-origin transform transform-inverse)))) (defn calc-child-modifiers [parent child modifiers ignore-constraints transformed-parent] diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index fc0b606831..2a9a04e09f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -23,11 +23,11 @@ scale-x (/ (:width child-bb-before) (:width child-bb-after)) scale-y (/ (:height child-bb-before) (:height child-bb-after)) - resize-origin (-> transformed-parent :points first) ;; TODO LAYOUT: IS always the origin?n + resize-origin (-> transformed-parent :points first) ;; TODO LAYOUT: IS always the origin resize-vector (gpt/point scale-x scale-y)] (-> modifiers (ctm/select-child-modifiers) - (ctm/set-resize resize-vector resize-origin transform transform-inverse)))) + (ctm/resize resize-vector resize-origin transform transform-inverse)))) (defn calc-fill-width-data "Calculates the size and modifiers for the width of an auto-fill child" @@ -41,7 +41,7 @@ (let [target-width (max (get-in children-data [(:id child) :child-width]) 0.01) fill-scale (/ target-width child-width)] {:width target-width - :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}) + :modifiers (ctm/resize-modifiers (gpt/point fill-scale 1) child-origin transform transform-inverse)}) (ctl/col? parent) (let [target-width (max (- line-width (ctl/child-width-margin child)) 0.01) @@ -49,7 +49,7 @@ target-width (min max-width target-width) fill-scale (/ target-width child-width)] {:width target-width - :modifiers (ctm/resize (gpt/point fill-scale 1) child-origin transform transform-inverse)}))) + :modifiers (ctm/resize-modifiers (gpt/point fill-scale 1) child-origin transform transform-inverse)}))) (defn calc-fill-height-data "Calculates the size and modifiers for the height of an auto-fill child" @@ -63,7 +63,7 @@ (let [target-height (max (get-in children-data [(:id child) :child-height]) 0.01) fill-scale (/ target-height child-height)] {:height target-height - :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) + :modifiers (ctm/resize-modifiers (gpt/point 1 fill-scale) child-origin transform transform-inverse)}) (ctl/row? parent) (let [target-height (max (- line-height (ctl/child-height-margin child)) 0.01) @@ -71,7 +71,7 @@ target-height (min max-height target-height) fill-scale (/ target-height child-height)] {:height target-height - :modifiers (ctm/resize (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))) + :modifiers (ctm/resize-modifiers (gpt/point 1 fill-scale) child-origin transform transform-inverse)}))) (defn layout-child-modifiers "Calculates the modifiers for the layout" @@ -93,9 +93,9 @@ move-vec (gpt/to-vec child-origin corner-p) modifiers - (-> (ctm/empty-modifiers) + (-> (ctm/empty) (cond-> fill-width (ctm/add-modifiers (:modifiers fill-width))) (cond-> fill-height (ctm/add-modifiers (:modifiers fill-height))) - (ctm/set-move move-vec))] + (ctm/move move-vec))] [modifiers layout-line])) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 3b6a3c61ba..2f365a6a25 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -98,7 +98,7 @@ (let [child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints transformed-parent) child-modifiers (cond-> child-modifiers snap-pixel? (gpp/set-pixel-precision child))] (cond-> modif-tree - (not (ctm/empty-modifiers? child-modifiers)) + (not (ctm/empty? child-modifiers)) (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))] (reduce set-child modif-tree children))) @@ -111,7 +111,7 @@ (ctm/select-child-geometry-modifiers) (gcl/normalize-child-modifiers parent child transformed-parent))] (cond-> modif-tree - (not (ctm/empty-modifiers? child-modifiers)) + (not (ctm/empty? child-modifiers)) (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))] (let [children (map (d/getf objects) (:shapes transformed-parent))] (reduce process-child modif-tree children)))) @@ -178,14 +178,14 @@ (let [origin (-> parent :points first) scale-width (/ auto-width (-> parent :selrect :width) )] (-> modifiers - (ctm/set-resize-parent (gpt/point scale-width 1) origin (:transform parent) (:transform-inverse parent))))) + (ctm/resize-parent (gpt/point scale-width 1) origin (:transform parent) (:transform-inverse parent))))) (set-parent-auto-height [modifiers parent auto-height] (let [origin (-> parent :points first) scale-height (/ auto-height (-> parent :selrect :height) )] (-> modifiers - (ctm/set-resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))] + (ctm/resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))] (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) children (->> parent diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc index 03aa4359a7..297abf99a6 100644 --- a/common/src/app/common/geom/shapes/pixel_precision.cljc +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -32,7 +32,7 @@ ratio-height (/ target-height curr-height) scalev (gpt/point ratio-width ratio-height)] (-> modifiers - (ctm/set-resize scalev origin transform transform-inverse)))) + (ctm/resize scalev origin transform transform-inverse)))) (defn position-pixel-precision [modifiers shape] @@ -42,7 +42,7 @@ target-corner (gpt/round corner) deltav (gpt/to-vec corner target-corner)] (-> modifiers - (ctm/set-move deltav)))) + (ctm/move deltav)))) (defn set-pixel-precision "Adjust modifiers so they adjust to the pixel grid" diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index bdaa878078..3dcf61c896 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -403,7 +403,7 @@ ([shape modifiers] (cond-> shape - (and (some? modifiers) (not (ctm/empty-modifiers? modifiers))) + (and (some? modifiers) (not (ctm/empty? modifiers))) (apply-modifiers modifiers)))) (defn transform-bounds diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 48710b40cb..e6c7943d17 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -5,6 +5,7 @@ ;; Copyright (c) UXBOX Labs SL (ns app.common.types.modifiers + (:refer-clojure :exclude [empty empty?]) (:require [app.common.data :as d] [app.common.geom.matrix :as gmt] @@ -13,7 +14,9 @@ [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.spec :as us] - [app.common.text :as txt])) + [app.common.text :as txt] + #?(:cljs [cljs.core :as c] + :clj [clojure.core :as c]))) ;; --- Modifiers @@ -39,7 +42,7 @@ ;; Public builder API -(defn empty-modifiers [] +(defn empty [] {}) (defn move-vec? [vector] @@ -50,16 +53,16 @@ (or (not (mth/almost-zero? (- (:x vector) 1))) (not (mth/almost-zero? (- (:y vector) 1))))) -(defn set-move-parent +(defn move-parent ([modifiers x y] - (set-move-parent modifiers (gpt/point x y))) + (move-parent modifiers (gpt/point x y))) ([modifiers vector] (cond-> modifiers (move-vec? vector) (update :geometry-parent conjv {:type :move :vector vector})))) -(defn set-resize-parent +(defn resize-parent ([modifiers vector origin] (cond-> modifiers (resize-vec? vector) @@ -75,16 +78,16 @@ :origin origin :transform transform :transform-inverse transform-inverse})))) -(defn set-move +(defn move ([modifiers x y] - (set-move modifiers (gpt/point x y))) + (move modifiers (gpt/point x y))) ([modifiers vector] (cond-> modifiers (move-vec? vector) (update :geometry-child conjv {:type :move :vector vector})))) -(defn set-resize +(defn resize ([modifiers vector origin] (cond-> modifiers (resize-vec? vector) @@ -101,7 +104,7 @@ :transform transform :transform-inverse transform-inverse})))) -(defn set-rotation +(defn rotation [modifiers center angle] (cond-> modifiers (not (mth/close? angle 0)) @@ -111,14 +114,14 @@ :center center :rotation angle})))) -(defn set-remove-children +(defn remove-children [modifiers shapes] (cond-> modifiers (d/not-empty? shapes) (update :structure-parent conjv {:type :remove-children :value shapes}))) -(defn set-add-children +(defn add-children [modifiers shapes index] (cond-> modifiers (d/not-empty? shapes) @@ -126,17 +129,17 @@ :value shapes :index index}))) -(defn set-reflow +(defn reflow [modifiers] (-> modifiers (update :structure-parent conjv {:type :reflow}))) -(defn set-scale-content +(defn scale-content [modifiers value] (-> modifiers (update :structure-child conjv {:type :scale-content :value value}))) -(defn set-change-property +(defn change-property [modifiers property value] (-> modifiers (update :structure-child conjv {:type :change-property @@ -162,94 +165,66 @@ ;; These are convenience methods to create single operation modifiers without the builder -(defn move +(defn move-modifiers ([x y] - (set-move (empty-modifiers) (gpt/point x y))) + (move (empty) (gpt/point x y))) ([vector] - (set-move (empty-modifiers) vector))) + (move (empty) vector))) -(defn move-parent +(defn move-parent-modifiers ([x y] - (set-move-parent (empty-modifiers) (gpt/point x y))) + (move-parent (empty) (gpt/point x y))) ([vector] - (set-move-parent (empty-modifiers) vector))) + (move-parent (empty) vector))) -(defn resize +(defn resize-modifiers ([vector origin] - (set-resize (empty-modifiers) vector origin)) + (resize (empty) vector origin)) ([vector origin transform transform-inverse] - (set-resize (empty-modifiers) vector origin transform transform-inverse))) + (resize (empty) vector origin transform transform-inverse))) -(defn resize-parent +(defn resize-parent-modifiers ([vector origin] - (set-resize-parent (empty-modifiers) vector origin)) + (resize-parent (empty) vector origin)) ([vector origin transform transform-inverse] - (set-resize-parent (empty-modifiers) vector origin transform transform-inverse))) + (resize-parent (empty) vector origin transform transform-inverse))) -(defn rotation +(defn rotation-modifiers [shape center angle] (let [shape-center (gco/center-shape shape) rotation (-> (gmt/matrix) (gmt/rotate angle center) (gmt/rotate (- angle) shape-center))] - (-> (empty-modifiers) - (set-rotation shape-center angle) - (set-move (gpt/transform (gpt/point 0 0) rotation))))) + (-> (empty) + (rotation shape-center angle) + (move (gpt/transform (gpt/point 0 0) rotation))))) -(defn remove-children +(defn remove-children-modifiers [shapes] - (-> (empty-modifiers) - (set-remove-children shapes))) + (-> (empty) + (remove-children shapes))) -(defn add-children +(defn add-children-modifiers [shapes index] - (-> (empty-modifiers) - (set-add-children shapes index))) + (-> (empty) + (add-children shapes index))) -(defn reflow +(defn reflow-modifiers [] - (-> (empty-modifiers) - (set-reflow))) + (-> (empty) + (reflow))) -(defn scale-content +(defn scale-content-modifiers [value] - (-> (empty-modifiers) - (set-scale-content value))) + (-> (empty) + (scale-content value))) -(defn child-modifiers? - [{:keys [geometry-child structure-child]}] - (or (d/not-empty? geometry-child) - (d/not-empty? structure-child))) - -(defn select-child-modifiers - [modifiers] - (select-keys modifiers [:geometry-child :structure-child])) - -(defn select-child-geometry-modifiers - [modifiers] - (select-keys modifiers [:geometry-child])) - -(defn select-parent-modifiers - [modifiers] - (select-keys modifiers [:geometry-parent :structure-parent])) - -(defn select-structure - [modifiers] - (select-keys modifiers [:structure-parent])) - -(defn empty-modifiers? - [modifiers] - (and (empty? (:geometry-child modifiers)) - (empty? (:geometry-parent modifiers)) - (empty? (:structure-parent modifiers)) - (empty? (:structure-child modifiers)))) - -(defn change-dimensions +(defn change-dimensions-modifiers [shape attr value] (us/assert map? shape) (us/assert #{:width :height} attr) @@ -281,7 +256,7 @@ scalev (gpt/divide (gpt/point width height) (gpt/point sr-width sr-height))] - (resize scalev origin shape-transform shape-transform-inv))) + (resize-modifiers scalev origin shape-transform shape-transform-inv))) (defn change-orientation-modifiers [shape orientation] @@ -304,28 +279,56 @@ scalev (gpt/divide (gpt/point new-width new-height) (gpt/point sr-width sr-height))] - (resize scalev origin shape-transform shape-transform-inv))) + (resize-modifiers scalev origin shape-transform shape-transform-inv))) -(defn merge-modifiers - [objects modifiers] +;; Predicates - (let [set-modifier - (fn [objects [id modifiers]] - (-> objects - (d/update-when id merge modifiers)))] - (->> modifiers - (reduce set-modifier objects)))) +(defn empty? + [modifiers] + (and (c/empty? (:geometry-child modifiers)) + (c/empty? (:geometry-parent modifiers)) + (c/empty? (:structure-parent modifiers)) + (c/empty? (:structure-child modifiers)))) + +(defn child-modifiers? + [{:keys [geometry-child structure-child]}] + (or (d/not-empty? geometry-child) + (d/not-empty? structure-child))) (defn only-move? + "Returns true if there are only move operations" [modifier] (or (and (= 1 (-> modifier :geometry-child count)) (= :move (-> modifier :geometry-child first :type))) (and (= 1 (-> modifier :geometry-parent count)) (= :move (-> modifier :geometry-parent first :type))))) -(defn get-frame-add-children - [modif-tree] +(defn has-geometry? + [{:keys [geometry-parent geometry-child]}] + (or (d/not-empty? geometry-parent) + (d/not-empty? geometry-child))) +;; Extract subsets of modifiers + +(defn select-child-modifiers + [modifiers] + (select-keys modifiers [:geometry-child :structure-child])) + +(defn select-child-geometry-modifiers + [modifiers] + (select-keys modifiers [:geometry-child])) + +(defn select-parent-modifiers + [modifiers] + (select-keys modifiers [:geometry-parent :structure-parent])) + +(defn select-structure + [modifiers] + (select-keys modifiers [:structure-parent])) + +(defn added-children-frames + "Returns the frames that have an 'add-children' operation" + [modif-tree] (let [structure-changes (into {} (comp (filter (fn [[_ val]] (-> val :modifiers :structure-parent some?))) @@ -340,7 +343,10 @@ (->> value (map (fn [id] {:frame frame-id :shape id})))))))) structure-changes))) +;; Main transformation functions + (defn modifiers->transform + "Given a set of modifiers returns its transformation matrix" [modifiers] (letfn [(apply-modifier [matrix {:keys [type vector rotation center origin transform transform-inverse] :as modifier}] (case type @@ -360,7 +366,6 @@ matrix) :rotation - ;; TODO LAYOUT: Maybe an issue when no center data (gmt/multiply (-> (gmt/matrix) (gmt/translate center) @@ -373,63 +378,60 @@ (->> modifiers (reduce apply-modifier (gmt/matrix)))))) -(defn scale-text-content - [content value] - - (->> content - (txt/transform-nodes - txt/is-text-node? - (fn [attrs] - (let [font-size (-> (get attrs :font-size 14) - (d/parse-double) - (* value) - (str)) ] - (d/txt-merge attrs {:font-size font-size})))))) - -(defn apply-scale-content - [shape value] - - (cond-> shape - (cph/text-shape? shape) - (update :content scale-text-content value))) - (defn apply-structure-modifiers + "Apply structure changes to a shape" [shape modifiers] - (let [remove-children - (fn [shapes children-to-remove] - (let [remove? (set children-to-remove)] - (d/removev remove? shapes))) + (letfn [(scale-text-content + [content value] - apply-modifier - (fn [shape {:keys [type property value index rotation]}] - (cond-> shape - (= type :rotation) - (update :rotation #(mod (+ % rotation) 360)) + (->> content + (txt/transform-nodes + txt/is-text-node? + (fn [attrs] + (let [font-size (-> (get attrs :font-size 14) + (d/parse-double) + (* value) + (str)) ] + (d/txt-merge attrs {:font-size font-size})))))) - (and (= type :add-children) (some? index)) - (update :shapes - (fn [shapes] - (if (vector? shapes) - (cph/insert-at-index shapes index value) - (d/concat-vec shapes value)))) + (apply-scale-content + [shape value] - (and (= type :add-children) (nil? index)) - (update :shapes d/concat-vec value) + (cond-> shape + (cph/text-shape? shape) + (update :content scale-text-content value)))] + (let [remove-children + (fn [shapes children-to-remove] + (let [remove? (set children-to-remove)] + (d/removev remove? shapes))) - (= type :remove-children) - (update :shapes remove-children value) + apply-modifier + (fn [shape {:keys [type property value index rotation]}] + (cond-> shape + (= type :rotation) + (update :rotation #(mod (+ % rotation) 360)) - (= type :scale-content) - (apply-scale-content value) + (and (= type :add-children) (some? index)) + (update :shapes + (fn [shapes] + (if (vector? shapes) + (cph/insert-at-index shapes index value) + (d/concat-vec shapes value)))) - (= type :change-property) - (assoc property value)))] + (and (= type :add-children) (nil? index)) + (update :shapes d/concat-vec value) + + (= type :remove-children) + (update :shapes remove-children value) + + (= type :scale-content) + (apply-scale-content value) + + (= type :change-property) + (assoc property value)))] + + (as-> shape $ + (reduce apply-modifier $ (:structure-parent modifiers)) + (reduce apply-modifier $ (:structure-child modifiers)))))) - (as-> shape $ - (reduce apply-modifier $ (:structure-parent modifiers)) - (reduce apply-modifier $ (:structure-child modifiers))))) -(defn has-geometry? - [{:keys [geometry-parent geometry-child]}] - (or (d/not-empty? geometry-parent) - (d/not-empty? geometry-child))) diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc index eae43ef1b6..e974774f82 100644 --- a/common/test/common_tests/geom_shapes_test.cljc +++ b/common/test/common_tests/geom_shapes_test.cljc @@ -60,7 +60,7 @@ (t/testing "Transform shape with translation modifiers" (t/are [type] - (let [modifiers (ctm/move (gpt/point 10 -10))] + (let [modifiers (ctm/move-modifiers (gpt/point 10 -10))] (let [shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/is (not= shape-before shape-after)) @@ -92,7 +92,7 @@ (t/testing "Transform shape with resize modifiers" (t/are [type] - (let [modifiers (ctm/resize (gpt/point 2 2) (gpt/point 0 0)) + (let [modifiers (ctm/resize-modifiers (gpt/point 2 2) (gpt/point 0 0)) shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/is (not= shape-before shape-after)) @@ -112,7 +112,7 @@ (t/testing "Transform with empty resize" (t/are [type] - (let [modifiers (ctm/resize (gpt/point 1 1) (gpt/point 0 0)) + (let [modifiers (ctm/resize-modifiers (gpt/point 1 1) (gpt/point 0 0)) shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/are [prop] @@ -123,7 +123,7 @@ (t/testing "Transform with resize=0" (t/are [type] - (let [modifiers (ctm/resize (gpt/point 0 0) (gpt/point 0 0)) + (let [modifiers (ctm/resize-modifiers (gpt/point 0 0) (gpt/point 0 0)) shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] (t/is (> (get-in shape-before [:selrect :width]) @@ -138,7 +138,7 @@ (t/testing "Transform shape with rotation modifiers" (t/are [type] (let [shape-before (create-test-shape type) - modifiers (ctm/rotation shape-before (gsh/center-shape shape-before) 30 ) + modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 30 ) shape-before (assoc shape-before :modifiers modifiers) shape-after (gsh/transform-shape shape-before)] @@ -160,7 +160,7 @@ (t/testing "Transform shape with rotation = 0 should leave equal selrect" (t/are [type] (let [shape-before (create-test-shape type) - modifiers (ctm/rotation shape-before (gsh/center-shape shape-before) 0) + modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 0) shape-after (gsh/transform-shape (assoc shape-before :modifiers modifiers))] (t/are [prop] (t/is (close? (get-in shape-before [:selrect prop]) @@ -170,7 +170,7 @@ (t/testing "Transform shape with invalid selrect fails gracefully" (t/are [type selrect] - (let [modifiers (ctm/move 0 0) + (let [modifiers (ctm/move-modifiers 0 0) shape-before (-> (create-test-shape type {:modifiers modifiers}) (assoc :selrect selrect)) shape-after (gsh/transform-shape shape-before)] diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index ad91a12262..c586af8c4d 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -47,9 +47,9 @@ (-> shape (assoc :click-draw? false) - (gsh/transform-shape (-> (ctm/empty-modifiers) - (ctm/set-resize scalev (gpt/point x y)) - (ctm/set-move movev)))))) + (gsh/transform-shape (-> (ctm/empty) + (ctm/resize scalev (gpt/point x y)) + (ctm/move movev)))))) (defn update-drawing [state initial point lock?] (update-in state [:workspace-drawing :object] resize-shape initial point lock?)) diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index 2016c3c7d9..ad0da25636 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -51,7 +51,7 @@ (and click-draw? (not text?)) (-> (assoc :width min-side :height min-side) - (gsh/transform-shape (ctm/move (- (/ min-side 2)) (- (/ min-side 2))))) + (gsh/transform-shape (ctm/move-modifiers (- (/ min-side 2)) (- (/ min-side 2))))) (and click-draw? text?) (assoc :height 17 :width 4 :grow-type :auto-width) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 9263d97450..b6f6d2464f 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -159,11 +159,11 @@ (filterv #(contains? child-set %)))] (cond-> modif-tree (not= original-frame target-frame) - (-> (update-in [original-frame :modifiers] ctm/set-remove-children shapes) - (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index)) + (-> (update-in [original-frame :modifiers] ctm/remove-children shapes) + (update-in [target-frame :modifiers] ctm/add-children shapes drop-index)) (and layout? (= original-frame target-frame)) - (update-in [target-frame :modifiers] ctm/set-add-children shapes drop-index))))] + (update-in [target-frame :modifiers] ctm/add-children shapes drop-index))))] (reduce update-frame-modifiers modif-tree origin-frame-ids))) @@ -214,7 +214,7 @@ get-modifier (fn [shape] - (ctm/rotation shape center angle)) + (ctm/rotation-modifiers shape center angle)) modif-tree (-> (build-modif-tree ids objects get-modifier) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index c1a94684ad..799cf0497c 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -52,7 +52,7 @@ (let [objects (wsh/lookup-page-objects state) ids (->> ids (filter (partial ctl/layout? objects)))] (if (d/not-empty? ids) - (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow))] + (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))] (rx/of (dwm/set-modifiers modif-tree) (dwm/apply-modifiers))) (rx/empty)))))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 06592a1534..0527184a0b 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -322,11 +322,11 @@ {shape-width :width shape-height :height} selrect] (cond-> shape (and (not-changed? shape-width new-width) (= grow-type :auto-width)) - (gsh/transform-shape (ctm/change-dimensions shape :width new-width)) + (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width new-width)) (and (not-changed? shape-height new-height) (or (= grow-type :auto-height) (= grow-type :auto-width))) - (gsh/transform-shape (ctm/change-dimensions shape :height new-height)))))] + (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height new-height)))))] (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false})))))) @@ -346,10 +346,10 @@ (let [new-shape (cond-> shape (some? width) - (gsh/transform-shape (ctm/change-dimensions shape :width width)) + (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width width)) (some? height) - (gsh/transform-shape (ctm/change-dimensions shape :height height)) + (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height height)) (some? position-data) (assoc :position-data position-data)) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 7ef6eb3cdd..543757c1f7 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -185,19 +185,19 @@ (and layout-child? fill-height?))) modifiers - (-> (ctm/empty-modifiers) + (-> (ctm/empty) (cond-> displacement - (ctm/set-move displacement)) - (ctm/set-resize scalev resize-origin shape-transform shape-transform-inverse) + (ctm/move displacement)) + (ctm/resize scalev resize-origin shape-transform shape-transform-inverse) (cond-> set-fix-width? - (ctm/set-change-property :layout-item-h-sizing :fix)) + (ctm/change-property :layout-item-h-sizing :fix)) (cond-> set-fix-height? - (ctm/set-change-property :layout-item-v-sizing :fix)) + (ctm/change-property :layout-item-v-sizing :fix)) (cond-> scale-text - (ctm/set-scale-content (:x scalev)))) + (ctm/scale-content (:x scalev)))) modif-tree (dwm/create-modif-tree ids modifiers)] (rx/of (dwm/set-modifiers modif-tree)))) @@ -251,7 +251,7 @@ snap-pixel? (and (contains? (:workspace-layout state) :snap-pixel-grid) (int? value)) get-modifier - (fn [shape] (ctm/change-dimensions shape attr value)) + (fn [shape] (ctm/change-dimensions-modifiers shape attr value)) modif-tree (-> (dwm/build-modif-tree ids objects get-modifier) @@ -486,7 +486,7 @@ (->> move-stream (rx/map (fn [[move-vector target-frame drop-index]] - (-> (dwm/create-modif-tree ids (ctm/move move-vector)) + (-> (dwm/create-modif-tree ids (ctm/move-modifiers move-vector)) (dwm/build-change-frame-modifiers objects selected target-frame drop-index) (dwm/set-modifiers))))) @@ -540,7 +540,7 @@ (rx/merge (->> move-events (rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0)) - (rx/map #(dwm/create-modif-tree selected (ctm/move %))) + (rx/map #(dwm/create-modif-tree selected (ctm/move-modifiers %))) (rx/map (partial dwm/set-modifiers)) (rx/take-until stopper)) (rx/of (move-selected direction shift?))) @@ -573,7 +573,7 @@ (or (:y position) (:y bbox))) delta (gpt/subtract pos cpos) - modif-tree (dwm/create-modif-tree [id] (ctm/move delta))] + modif-tree (dwm/create-modif-tree [id] (ctm/move-modifiers delta))] (rx/of (dwm/set-modifiers modif-tree) (dwm/apply-modifiers)))))) @@ -633,9 +633,9 @@ modif-tree (dwm/create-modif-tree selected - (-> (ctm/empty-modifiers) - (ctm/set-resize (gpt/point -1.0 1.0) origin) - (ctm/set-move (gpt/point (:width selrect) 0))))] + (-> (ctm/empty) + (ctm/resize (gpt/point -1.0 1.0) origin) + (ctm/move (gpt/point (:width selrect) 0))))] (rx/of (dwm/set-modifiers modif-tree true) (dwm/apply-modifiers)))))) @@ -652,9 +652,9 @@ modif-tree (dwm/create-modif-tree selected - (-> (ctm/empty-modifiers) - (ctm/set-resize (gpt/point 1.0 -1.0) origin) - (ctm/set-move (gpt/point 0 (:height selrect)))))] + (-> (ctm/empty) + (ctm/resize (gpt/point 1.0 -1.0) origin) + (ctm/move (gpt/point 0 (:height selrect)))))] (rx/of (dwm/set-modifiers modif-tree true) (dwm/apply-modifiers)))))) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index a2627d2dad..c12236ad14 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -187,7 +187,7 @@ mod-ids (cons object-id (cph/get-children-ids objects object-id)) - updt-fn #(update %1 %2 gsh/transform-shape (ctm/move vector))] + updt-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))] (reduce updt-fn objects mod-ids))) @@ -254,14 +254,14 @@ objects (mf/with-memo [frame-id objects vector] - (let [update-fn #(update %1 %2 gsh/transform-shape (ctm/move vector))] + (let [update-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))] (->> children-ids (into [frame-id]) (reduce update-fn objects)))) frame (mf/with-memo [vector] - (gsh/transform-shape frame (ctm/move vector))) + (gsh/transform-shape frame (ctm/move-modifiers vector))) frame (cond-> frame @@ -313,7 +313,7 @@ (mf/deps vector objects group-id) (fn [] (let [children-ids (cons group-id (cph/get-children-ids objects group-id)) - update-fn #(update %1 %2 gsh/transform-shape (ctm/move vector))] + update-fn #(update %1 %2 gsh/transform-shape (ctm/move-modifiers vector))] (reduce update-fn objects children-ids)))) group (get objects group-id) diff --git a/frontend/src/app/main/ui/viewer/interactions.cljs b/frontend/src/app/main/ui/viewer/interactions.cljs index 51b136d113..47bd71143e 100644 --- a/frontend/src/app/main/ui/viewer/interactions.cljs +++ b/frontend/src/app/main/ui/viewer/interactions.cljs @@ -33,7 +33,7 @@ (let [frame-id (:id frame) vector (-> (gpt/point (:x size) (:y size)) (gpt/negate)) - update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move vector))] + update-fn #(d/update-when %1 %2 gsh/transform-shape (ctm/move-modifiers vector))] (->> (cph/get-children-ids objects frame-id) (into [frame-id]) (reduce update-fn objects)))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index b5a34ba654..5b2a0a8008 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -188,7 +188,7 @@ (/ (:height shape) (:height shape')))] ;; Reverse the change in size so we can recalculate the layout (-> modifiers - (ctm/set-resize scalev (-> shape' :points first) (:transform shape') (:transform-inverse shape'))))) + (ctm/resize scalev (-> shape' :points first) (:transform shape') (:transform-inverse shape'))))) (defn use-dynamic-modifiers [objects node modifiers] @@ -205,7 +205,7 @@ (ctm/modifiers->transform modifiers))) modifiers)))) - add-children (mf/use-memo (mf/deps modifiers) #(ctm/get-frame-add-children modifiers)) + add-children (mf/use-memo (mf/deps modifiers) #(ctm/added-children-frames modifiers)) add-children (hooks/use-equal-memo add-children) add-children-prev (hooks/use-previous add-children) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 50e2e6ebc5..96d946f263 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -42,7 +42,7 @@ deltav (gpt/to-vec (gpt/point (:selrect shape')) (gpt/point (:selrect shape)))] - (gsh/transform-shape shape' (ctm/move deltav)))) + (gsh/transform-shape shape' (ctm/move-modifiers deltav)))) (defn process-shape [modifiers {:keys [id] :as shape}] (let [modifier (dm/get-in modifiers [id :modifiers])] From e61e76a074ce4cc6ced5cca22b4046f7a36204cd Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 7 Nov 2022 17:26:28 +0100 Subject: [PATCH 220/682] :sparkles: Fix problems with flipped layouts --- .../app/common/geom/shapes/constraints.cljc | 5 +- .../common/geom/shapes/flex_layout/lines.cljc | 10 ++-- .../geom/shapes/flex_layout/modifiers.cljc | 3 +- .../geom/shapes/flex_layout/positions.cljc | 10 ++++ .../app/common/geom/shapes/transforms.cljc | 1 - common/src/app/common/types/modifiers.cljc | 12 +++-- frontend/src/app/main/data/workspace.cljs | 3 +- .../src/app/main/ui/workspace/viewport.cljs | 10 +++- .../app/main/ui/workspace/viewport/debug.cljs | 51 +++++++++++++++++-- frontend/src/debug.cljs | 3 ++ 10 files changed, 84 insertions(+), 24 deletions(-) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index c90cd179b5..0b9fe0b6f1 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -9,6 +9,7 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.intersect :as gsi] + [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.rect :as gre] [app.common.geom.shapes.transforms :as gst] [app.common.math :as mth] @@ -235,9 +236,7 @@ child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) scale-x (/ (:width child-bb-before) (:width child-bb-after)) scale-y (/ (:height child-bb-before) (:height child-bb-after)) - - ;; TODO LAYOUT: Is the first always the origin? - resize-origin (-> transformed-parent :points first)] + resize-origin (-> transformed-parent :points gpo/origin)] (cond-> modifiers (not= :scale constraints-h) diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index e26894fa68..2a5c255b8f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -22,13 +22,8 @@ [pad-top pad-right pad-bottom pad-left] (if (= layout-padding-type :multiple) [pad-top pad-right pad-bottom pad-left] - [pad-top pad-top pad-top pad-top]) - - ;; Normalize the points to remove flips - ;; TODO LAYOUT: Need function to normalize the points - points (gst/parent-coords-points shape shape)] - - (gpo/pad-points points pad-top pad-right pad-bottom pad-left))) + [pad-top pad-top pad-top pad-top])] + (gpo/pad-points (:points shape) pad-top pad-right pad-bottom pad-left))) (defn init-layout-lines "Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation" @@ -312,5 +307,6 @@ (mapv (partial add-children-resizes shape)))] {:layout-lines layout-lines + :layout-bounds layout-bounds :reverse? reverse?})) diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 2a9a04e09f..039f17f98f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -22,8 +22,7 @@ child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) scale-x (/ (:width child-bb-before) (:width child-bb-after)) scale-y (/ (:height child-bb-before) (:height child-bb-after)) - - resize-origin (-> transformed-parent :points first) ;; TODO LAYOUT: IS always the origin + resize-origin (-> transformed-parent :points gpo/origin) resize-vector (gpt/point scale-x scale-y)] (-> modifiers (ctm/select-child-modifiers) diff --git a/common/src/app/common/geom/shapes/flex_layout/positions.cljc b/common/src/app/common/geom/shapes/flex_layout/positions.cljc index 30437fd16f..d6bc69c21e 100644 --- a/common/src/app/common/geom/shapes/flex_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/positions.cljc @@ -249,6 +249,16 @@ (some? margin-y) (gpt/add (vv margin-y))) + + ;; Fix position when layout is flipped + corner-p + (cond-> corner-p + (:flip-x parent) + (gpt/add (hv child-width)) + + (:flip-y parent) + (gpt/add (vv child-height))) + next-p (cond-> start-p row? diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 3dcf61c896..564f9cf74e 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -311,7 +311,6 @@ (cond-> (some? selrect) (assoc :selrect selrect)) - ;; TODO LAYOUT: Make sure the order of points is alright (cond-> (d/not-empty? points) (assoc :points points)) (assoc :rotation rotation)))) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index e6c7943d17..8b2d274455 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -196,13 +196,17 @@ (defn rotation-modifiers [shape center angle] (let [shape-center (gco/center-shape shape) - rotation (-> (gmt/matrix) - (gmt/rotate angle center) - (gmt/rotate (- angle) shape-center))] + ;; Translation caused by the rotation + move-vec + (gpt/transform + (gpt/point 0 0) + (-> (gmt/matrix) + (gmt/rotate angle center) + (gmt/rotate (- angle) shape-center)))] (-> (empty) (rotation shape-center angle) - (move (gpt/transform (gpt/point 0 0) rotation))))) + (move move-vec)))) (defn remove-children-modifiers [shapes] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 895c5799ea..bedd710657 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1580,8 +1580,7 @@ (ptk/reify ::create-artboard-from-selection ptk/WatchEvent (watch [_ state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) + (let [objects (wsh/lookup-page-objects state) selected (wsh/lookup-selected state) selected-objs (map #(get objects %) selected)] (when (d/not-empty? selected) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 586e1b3891..80217083d2 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -418,10 +418,16 @@ ;; DEBUG LAYOUT DROP-ZONES (when (debug? :layout-drop-zones) + [:& wvd/debug-drop-zones {:selected-shapes selected-shapes + :objects objects-modified + :hover-top-frame-id @hover-top-frame-id + :zoom zoom}]) + + (when (debug? :layout-lines) [:& wvd/debug-layout {:selected-shapes selected-shapes :objects objects-modified - :hover-top-frame-id @hover-top-frame-id}]) - + :hover-top-frame-id @hover-top-frame-id + :zoom zoom}]) (when show-selection-handlers? [:g.selection-handlers {:clipPath "url(#clip-handlers)"} diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index d338c0a719..f1fcecf0ed 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -8,13 +8,17 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.flex-layout :as gsl] + [app.common.geom.shapes.points :as gpo] [app.common.pages.helpers :as cph] + [app.common.types.shape.layout :as ctl] + [cuerdas.core :as str] [rumext.v2 :as mf])) ;; Helper to debug the bounds when set the "hug" content property -#_(mf/defc debug-bounds +#_(mf/defc debug-layout "Debug component to show the auto-layout drop areas" {::mf/wrap-props false} [props] @@ -57,6 +61,47 @@ [props] (let [objects (unchecked-get props "objects") + zoom (unchecked-get props "zoom") + selected-shapes (unchecked-get props "selected-shapes") + hover-top-frame-id (unchecked-get props "hover-top-frame-id") + + selected-frame + (when (and (= (count selected-shapes) 1) (= :frame (-> selected-shapes first :type))) + (first selected-shapes)) + + shape (or selected-frame (get objects hover-top-frame-id))] + + (when (and shape (ctl/layout? shape)) + (let [row? (ctl/row? shape) + col? (ctl/col? shape) + + children (cph/get-immediate-children objects (:id shape)) + layout-data (gsl/calc-layout-data shape children) + + layout-bounds (:layout-bounds layout-data) + xv #(gpo/start-hv layout-bounds %) + yv #(gpo/start-vv layout-bounds %)] + [:g.debug-layout {:pointer-events "none"} + (for [[idx {:keys [start-p line-width line-height layout-gap-row layout-gap-col num-children]}] (d/enumerate (:layout-lines layout-data))] + (let [line-width (if row? (+ line-width (* (dec num-children) layout-gap-row)) line-width) + line-height (if col? (+ line-height (* (dec num-children) layout-gap-col)) line-height) + + points [start-p + (-> start-p (gpt/add (xv line-width))) + (-> start-p (gpt/add (xv line-width)) (gpt/add (yv line-height))) + (-> start-p (gpt/add (yv line-height))) + ]] + [:g.layout-line {:key (dm/str "line-" idx)} + [:polygon {:points (->> points (map #(dm/fmt "%, %" (:x %) (:y %))) (str/join " ")) + :style {:stroke "red" :stroke-width (/ 2 zoom) :stroke-dasharray (dm/str (/ 10 zoom) " " (/ 5 zoom))}}]]))])))) + +(mf/defc debug-drop-zones + "Debug component to show the auto-layout drop areas" + {::mf/wrap-props false} + [props] + + (let [objects (unchecked-get props "objects") + zoom (unchecked-get props "objects") selected-shapes (unchecked-get props "selected-shapes") hover-top-frame-id (unchecked-get props "hover-top-frame-id") @@ -81,8 +126,8 @@ :style {:fill "blue" :fill-opacity 0.3 :stroke "red" - :stroke-width 1 - :stroke-dasharray "3 6"}}] + :stroke-width (/ zoom 1) + :stroke-dasharray (dm/str (/ 3 zoom) " " (/ 6 zoom))}}] [:text {:x (:x drop-area) :y (:y drop-area) :width (:width drop-area) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index d4856cbda9..f64e98f4b0 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -71,6 +71,9 @@ ;; Enable a widget to show the auto-layout drop-zones :layout-drop-zones + ;; Display the layout lines + :layout-lines + ;; Makes the pixel grid red so its more visibile :pixel-grid }) From cdaba395c400011f157d138d1e140975b12808e4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 7 Nov 2022 18:21:36 +0100 Subject: [PATCH 221/682] :sparkles: Small fixes for flex layout --- common/src/app/common/geom/shapes/constraints.cljc | 1 - .../common/geom/shapes/flex_layout/drop_area.cljc | 14 ++------------ .../common/geom/shapes/flex_layout/positions.cljc | 1 - common/src/app/common/pages/helpers.cljc | 6 ------ common/test/common_tests/geom_shapes_test.cljc | 9 ++++----- frontend/src/app/main/data/workspace/shapes.cljs | 3 ++- frontend/src/app/main/render.cljs | 11 +++-------- .../src/app/main/ui/viewer/handoff/render.cljs | 2 -- frontend/src/app/main/ui/viewer/shapes.cljs | 1 - .../app/main/ui/workspace/viewport/selection.cljs | 3 --- 10 files changed, 11 insertions(+), 40 deletions(-) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 0b9fe0b6f1..00b1f700a6 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -81,7 +81,6 @@ cp (gsi/line-line-intersect c3 (gpt/add c3 dir-v) p0 p3)] (gpt/to-vec c3 cp))) - (defn top-vector [child-points parent-points] diff --git a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc index 228e09b133..3b9ff8a58e 100644 --- a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc @@ -16,6 +16,7 @@ [app.common.types.shape.layout :as ctl])) (defn layout-drop-areas + "Retrieve the layout drop areas to move shapes inside layouts" [{:keys [margin-x margin-y] :as frame} layout-data children] (let [col? (ctl/col? frame) @@ -84,14 +85,7 @@ (-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height)) (assoc :index (if reverse? index (inc index))))])) - result (conj result line-area-1 line-area-2) - - ;;line-area - ;;(-> (gsr/make-rect x y width height) - ;; (assoc :index (if reverse? (inc index) index))) - ;;result (conj result line-area) - ;;result (conj result (gsr/make-rect box-x box-y box-width box-height)) - ] + result (conj result line-area-1 line-area-2)] [result parent-rect (+ x width) (+ y height)])) @@ -158,10 +152,6 @@ children (subvec children from-idx (+ from-idx num-children)) - - ;; To debug the lines - ;;result (conj result line-area) - result (first (reduce redfn-child [result line-area] (d/with-next children)))] [result (+ from-idx num-children) (+ x width) (+ y height)]))] diff --git a/common/src/app/common/geom/shapes/flex_layout/positions.cljc b/common/src/app/common/geom/shapes/flex_layout/positions.cljc index d6bc69c21e..ae748d0603 100644 --- a/common/src/app/common/geom/shapes/flex_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/positions.cljc @@ -249,7 +249,6 @@ (some? margin-y) (gpt/add (vv margin-y))) - ;; Fix position when layout is flipped corner-p (cond-> corner-p diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 2799639a50..7bbda08e87 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -33,12 +33,6 @@ ([{:keys [type]}] (= type :frame))) -(defn layout-shape? - ([objects id] - (layout-shape? (get objects id))) - ([{:keys [type layout]}] - (and (= type :frame) layout))) - (defn group-shape? [{:keys [type]}] (= type :group)) diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc index e974774f82..864bf6b8c8 100644 --- a/common/test/common_tests/geom_shapes_test.cljc +++ b/common/test/common_tests/geom_shapes_test.cljc @@ -171,12 +171,11 @@ (t/testing "Transform shape with invalid selrect fails gracefully" (t/are [type selrect] (let [modifiers (ctm/move-modifiers 0 0) - shape-before (-> (create-test-shape type {:modifiers modifiers}) - (assoc :selrect selrect)) - shape-after (gsh/transform-shape shape-before)] + shape-before (-> (create-test-shape type) (assoc :selrect selrect)) + shape-after (gsh/transform-shape shape-before modifiers)] - (t/is (not= (:selrect shape-before) - (:selrect shape-after)))) + (t/is (= (:selrect shape-before) + (:selrect shape-after)))) :rect {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf} :path {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf} diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 2f15ac2268..b36930d585 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -16,6 +16,7 @@ [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.types.shape.interactions :as ctsi] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.main.data.comments :as dc] [app.main.data.workspace.changes :as dch] @@ -148,7 +149,7 @@ layout-ids (->> ids (mapcat (partial cph/get-parent-ids objects)) - (filter (partial cph/layout-shape? objects))) + (filter (partial ctl/layout? objects))) components-v2 (features/active-feature? state :components-v2) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index c12236ad14..41073f6e27 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -81,9 +81,7 @@ [{:keys [shape] :as props}] (let [render-thumbnails? (mf/use-ctx muc/render-thumbnails) - childs (mapv #(get objects %) (:shapes shape)) - ;;shape (gsh/transform-shape shape) - ] + childs (mapv #(get objects %) (:shapes shape))] (if (and render-thumbnails? (some? (:thumbnail shape))) [:& frame/frame-thumbnail {:shape shape :bounds (:children-bounds shape)}] [:& frame-shape {:shape shape :childs childs}]))))) @@ -136,8 +134,7 @@ bool-wrapper (mf/use-memo (mf/deps objects) #(bool-wrapper-factory objects)) frame-wrapper (mf/use-memo (mf/deps objects) #(frame-wrapper-factory objects))] (when (and shape (not (:hidden shape))) - (let [;;shape (gsh/transform-shape shape) - opts #js {:shape shape} + (let [opts #js {:shape shape} svg-raw? (= :svg-raw (:type shape))] (if-not svg-raw? [:> shape-container {:shape shape} @@ -167,9 +164,7 @@ [objects object] (let [shapes (cph/get-immediate-children objects) srect (gsh/selection-rect shapes) - object (merge object (select-keys srect [:x :y :width :height])) - ;; object (gsh/transform-shape object) - ] + object (merge object (select-keys srect [:x :y :width :height]))] (assoc object :fill-color "#f0f0f0"))) (defn adapt-objects-for-shape diff --git a/frontend/src/app/main/ui/viewer/handoff/render.cljs b/frontend/src/app/main/ui/viewer/handoff/render.cljs index 0fc20987ee..d32844940e 100644 --- a/frontend/src/app/main/ui/viewer/handoff/render.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/render.cljs @@ -88,8 +88,6 @@ [props] (let [shape (unchecked-get props "shape") childs (mapv #(get objects %) (:shapes shape)) - ;;shape (gsh/transform-shape shape) - props (-> (obj/create) (obj/merge! props) (obj/merge! #js {:shape shape diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index 2b25220786..e43ff57802 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -350,7 +350,6 @@ [props] (let [shape (obj/get props "shape") childs (mapv #(get objects %) (:shapes shape)) - ;;shape (gsh/transform-shape shape) props (obj/merge! #js {} props #js {:shape shape :childs childs diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index b42fbbc368..0a1cb7821b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -341,7 +341,6 @@ (let [shape (mf/use-memo (mf/deps shapes) #(->> shapes - #_(map gsh/transform-shape) (gsh/selection-rect) (cts/setup-shape))) on-resize @@ -369,7 +368,6 @@ (let [shape (mf/use-memo (mf/deps shapes) #(->> shapes - #_(map gsh/transform-shape) (gsh/selection-rect) (cts/setup-shape)))] @@ -384,7 +382,6 @@ (mf/defc single-handlers [{:keys [shape zoom color disable-handlers] :as props}] (let [shape-id (:id shape) - ;;shape (gsh/transform-shape shape) on-resize (fn [current-position _initial-position event] From c1affe75e19c6f55aa720258b4ffe7f1a0c598a9 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 8 Nov 2022 12:06:28 +0100 Subject: [PATCH 222/682] :sparkles: Fix problem with fixed constraints --- .../app/common/geom/shapes/constraints.cljc | 92 ++++++++++++------- .../app/main/data/workspace/modifiers.cljs | 2 +- 2 files changed, 61 insertions(+), 33 deletions(-) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 00b1f700a6..ef72945c99 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -130,61 +130,89 @@ (defn start-vector [axis child-points parent-points] - ((if (= :x axis) left-vector top-vector) child-points parent-points)) + (let [pos-vector + (cond (= :x axis) left-vector + (= :y axis) top-vector)] + (pos-vector child-points parent-points))) (defn end-vector [axis child-points parent-points] - ((if (= :x axis) right-vector bottom-vector) child-points parent-points)) + (let [pos-vector + (cond (= :x axis) right-vector + (= :y axis) bottom-vector)] + (pos-vector child-points parent-points))) (defn center-vector [axis child-points parent-points] ((if (= :x axis) center-horizontal-vector center-vertical-vector) child-points parent-points)) +(defn displacement + [before-v after-v] + (let [angl (gpt/angle-with-other before-v after-v) + sign (if (mth/close? angl 180) -1 1) + length (* sign (gpt/length before-v))] + (gpt/subtract after-v (gpt/scale (gpt/unit after-v) length)))) + +(defn side-vector + [axis [c0 c1 _ c3]] + (if (= axis :x) + (gpt/to-vec c0 c1) + (gpt/to-vec c0 c3))) + +(defn side-vector-resize + [axis [c0 c1 _ c3] start-vector end-vector] + (if (= axis :x) + (gpt/to-vec (gpt/add c0 start-vector) (gpt/add c1 end-vector)) + (gpt/to-vec (gpt/add c0 start-vector) (gpt/add c3 end-vector)))) ;; Constraint function definitions (defmulti constraint-modifier (fn [type & _] type)) +(defmethod constraint-modifier :start + [_ axis child-points-before parent-points-before child-points-after parent-points-after] + (let [start-before (start-vector axis child-points-before parent-points-before) + start-after (start-vector axis child-points-after parent-points-after)] + (ctm/move-modifiers (displacement start-before start-after)))) + (defmethod constraint-modifier :end [_ axis child-points-before parent-points-before child-points-after parent-points-after] (let [end-before (end-vector axis child-points-before parent-points-before) - end-after (end-vector axis child-points-after parent-points-after) - end-angl (gpt/angle-with-other end-before end-after) - target-end (if (mth/close? end-angl 180) (- (gpt/length end-before)) (gpt/length end-before)) - disp-vector-end (gpt/subtract end-after (gpt/scale (gpt/unit end-after) target-end))] - (ctm/move-modifiers disp-vector-end))) + end-after (end-vector axis child-points-after parent-points-after)] + (ctm/move-modifiers (displacement end-before end-after)))) (defmethod constraint-modifier :fixed [_ axis child-points-before parent-points-before child-points-after parent-points-after transformed-parent] - (let [[c0 c1 _ c4] child-points-after + (let [;; Same as constraint end + end-before (end-vector axis child-points-before parent-points-before) + end-after (end-vector axis child-points-after parent-points-after) + start-before (start-vector axis child-points-before parent-points-before) + start-after (start-vector axis child-points-after parent-points-after) - ;; Same as constraint end - end-before (end-vector axis child-points-before parent-points-before) - end-after (end-vector axis child-points-after parent-points-after) - end-angl (gpt/angle-with-other end-before end-after) - target-end (if (mth/close? end-angl 180) (- (gpt/length end-before)) (gpt/length end-before)) - disp-vector-end (gpt/subtract end-after (gpt/scale (gpt/unit end-after) target-end)) + disp-end (displacement end-before end-after) + disp-start (displacement start-before start-after) - before-vec (if (= axis :x) (gpt/to-vec c0 c1) (gpt/to-vec c0 c4)) - after-vec (if (= axis :x) - (gpt/to-vec c0 (gpt/add c1 disp-vector-end)) - (gpt/to-vec c0 (gpt/add c4 disp-vector-end)) - ) + ;; We get the current axis side and grow it on both side by the end+start displacements + before-vec (side-vector axis child-points-after) + after-vec (side-vector-resize axis child-points-after disp-start disp-end) - resize-angl (gpt/angle-with-other before-vec after-vec) - resize-sign (if (mth/close? resize-angl 180) -1 1) + ;; after-vec will contain the side length of the grown side + ;; we scale the shape by the diference and translate it by the start + ;; displacement (so its left+top position is constant) + scale (/ (gpt/length after-vec) (gpt/length before-vec)) - scale (* resize-sign (/ (gpt/length after-vec) (gpt/length before-vec)))] - (ctm/resize-modifiers (get-scale axis scale) c0 (:transform transformed-parent) (:transform-inverse transformed-parent)))) + resize-origin (first child-points-after) + {:keys [transform transform-inverse]} transformed-parent] + + (-> (ctm/empty) + (ctm/resize (get-scale axis scale) resize-origin transform transform-inverse) + (ctm/move disp-start)))) (defmethod constraint-modifier :center [_ axis child-points-before parent-points-before child-points-after parent-points-after] (let [center-before (center-vector axis child-points-before parent-points-before) - center-after (center-vector axis child-points-after parent-points-after) - center-angl (gpt/angle-with-other center-before center-after) - target-center (if (mth/close? center-angl 180) (- (gpt/length center-before)) (gpt/length center-before)) - disp-vector-center (gpt/subtract center-after (gpt/scale (gpt/unit center-after) target-center))] - (ctm/move-modifiers disp-vector-center))) + center-after (center-vector axis child-points-after parent-points-after)] + (ctm/move-modifiers (displacement center-before center-after)))) (defmethod constraint-modifier :default [_ _ _ _ _] []) @@ -266,11 +294,11 @@ modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child transformed-parent) transformed-child (gst/transform-shape child modifiers) - parent-points-before (:points parent) - child-points-before (bounding-box-parent-transform child parent) - parent-points-after (:points transformed-parent) - child-points-after (bounding-box-parent-transform transformed-child transformed-parent) + parent-points-before (bounding-box-parent-transform parent parent) + child-points-before (bounding-box-parent-transform child parent) + parent-points-after (bounding-box-parent-transform transformed-parent transformed-parent) + child-points-after (bounding-box-parent-transform transformed-child transformed-parent) modifiers-h (constraint-modifier (constraints-h const->type+axis) :x child-points-before parent-points-before diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index b6f6d2464f..f76c36d0f9 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -131,7 +131,7 @@ (update [_ state] (-> state (dissoc :workspace-modifiers) - (dissoc ::current-move-selected))))) + (dissoc :app.main.data.workspace.transforms/current-move-selected))))) (defn create-modif-tree [ids modifiers] From 56efb571be0e0250c386722bd573fcf197460b01 Mon Sep 17 00:00:00 2001 From: Eva Date: Mon, 7 Nov 2022 18:00:26 +0100 Subject: [PATCH 223/682] :sparkles: Add `add flex layout` option in context menu --- frontend/src/app/main/data/workspace.cljs | 34 +-------- .../app/main/data/workspace/selection.cljs | 4 +- .../app/main/data/workspace/shape_layout.cljs | 70 ++++++++++++++----- .../src/app/main/data/workspace/shapes.cljs | 44 ++++++++++-- .../data/workspace/shapes_update_layout.cljs | 28 ++++++++ .../app/main/data/workspace/shortcuts.cljs | 11 ++- .../app/main/ui/workspace/context_menu.cljs | 30 +++++++- frontend/translations/en.po | 8 +++ frontend/translations/es.po | 8 +++ 9 files changed, 177 insertions(+), 60 deletions(-) create mode 100644 frontend/src/app/main/data/workspace/shapes_update_layout.cljs diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index bedd710657..9b801f12ac 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -52,8 +52,8 @@ [app.main.data.workspace.path.shapes-to-path :as dwps] [app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.selection :as dws] - [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.svg-upload :as svg] [app.main.data.workspace.thumbnails :as dwth] @@ -690,7 +690,7 @@ (rx/of (dch/commit-changes changes) (dwco/expand-collapse parent-id) - (dwsl/update-layout-positions [parent-id])))))) + (dwul/update-layout-positions [parent-id])))))) (defn relocate-selected-shapes [parent-id to-index] @@ -1571,36 +1571,6 @@ (rx/of (dch/commit-changes changes)))))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Artboard -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(defn create-artboard-from-selection - [] - (ptk/reify ::create-artboard-from-selection - ptk/WatchEvent - (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - selected (wsh/lookup-selected state) - selected-objs (map #(get objects %) selected)] - (when (d/not-empty? selected) - (let [srect (gsh/selection-rect selected-objs) - frame-id (get-in objects [(first selected) :frame-id]) - parent-id (get-in objects [(first selected) :parent-id]) - shape (-> (cts/make-minimal-shape :frame) - (merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)}) - (assoc :frame-id frame-id :parent-id parent-id) - (cond-> (not= frame-id uuid/zero) - (assoc :fills [] :hide-in-viewer true)) - (cts/setup-rect-selrect))] - (rx/of - (dwu/start-undo-transaction) - (dwsh/add-shape shape) - (dwsh/move-shapes-into-frame (:id shape) selected) - - (dwu/commit-undo-transaction)))))))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Remove graphics ;; TODO: this should be deprecated and removed together with components-v2 diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 4d3dffaa7e..8791777c11 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -21,7 +21,7 @@ [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] - [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.zoom :as dwz] @@ -565,7 +565,7 @@ ;; Warning: This order is important for the focus mode. (rx/of (dch/commit-changes changes) (select-shapes new-selected) - (dwsl/update-layout-positions frames) + (dwul/update-layout-positions frames) (memorize-duplicated id-original id-duplicated)))))))))) (defn change-hover-state diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 799cf0497c..7f2d7d3aec 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -8,10 +8,12 @@ (:require [app.common.data :as d] [app.common.pages.helpers :as cph] - [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dwc] - [app.main.data.workspace.modifiers :as dwm] + [app.main.data.workspace.selection :as dwse] + [app.main.data.workspace.shapes :as dws] + [app.main.data.workspace.shapes-update-layout :as wsul] [app.main.data.workspace.state-helpers :as wsh] [beicon.core :as rx] [potok.core :as ptk])) @@ -44,19 +46,6 @@ (def initial-grid-layout ;; TODO {:layout :grid}) -(defn update-layout-positions - [ids] - (ptk/reify ::update-layout-positions - ptk/WatchEvent - (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - ids (->> ids (filter (partial ctl/layout? objects)))] - (if (d/not-empty? ids) - (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))] - (rx/of (dwm/set-modifiers modif-tree) - (dwm/apply-modifiers))) - (rx/empty)))))) - (defn get-layout-initializer [type] (let [initial-layout-data (if (= type :flex) initial-flex-layout initial-grid-layout)] @@ -72,16 +61,59 @@ (let [objects (wsh/lookup-page-objects state) children-ids (into [] (mapcat #(get-in objects [% :shapes])) ids)] (rx/of (dwc/update-shapes ids (get-layout-initializer type)) - (update-layout-positions ids) + (wsul/update-layout-positions ids) (dwc/update-shapes children-ids #(dissoc % :constraints-h :constraints-v))))))) +(defn create-layout-from-selection + [type] + (ptk/reify ::create-layout-from-selection + ptk/WatchEvent + (watch [_ state _] + + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + selected (wsh/lookup-selected state) + selected-shapes (map (d/getf objects) selected) + single? (= (count selected-shapes) 1) + has-group? (->> selected-shapes (d/seek cph/group-shape?)) + is-group? (and single? has-group?)] + (if is-group? + (let [parent-id (:parent-id (first selected-shapes)) + new-shape-id (uuid/next) + shapes-ids (:shapes (first selected-shapes)) + ordered-ids (into (d/ordered-set) shapes-ids)] + (rx/of (dwse/select-shapes ordered-ids) + (dws/create-artboard-from-selection new-shape-id parent-id) + (create-layout [new-shape-id] type) + (dws/delete-shapes page-id selected))) + + (let [new-shape-id (uuid/next)] + (rx/of (dws/create-artboard-from-selection new-shape-id) + (create-layout [new-shape-id] type)))))))) + (defn remove-layout [ids] (ptk/reify ::remove-layout ptk/WatchEvent (watch [_ _ _] (rx/of (dwc/update-shapes ids #(apply dissoc % layout-keys)) - (update-layout-positions ids))))) + (wsul/update-layout-positions ids))))) + +(defn toogle-layout-flex + [] + (ptk/reify ::toogle-layout-flex + ptk/WatchEvent + (watch [_ state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + selected (wsh/lookup-selected state) + selected-shapes (map (d/getf objects) selected) + single? (= (count selected-shapes) 1) + has-flex-layout? (and single? (= :flex (:layout (first selected-shapes))))] + + (if has-flex-layout? + (rx/of (remove-layout selected)) + (rx/of (create-layout-from-selection :flex))))))) (defn update-layout [ids changes] @@ -89,7 +121,7 @@ ptk/WatchEvent (watch [_ _ _] (rx/of (dwc/update-shapes ids #(d/deep-merge % changes)) - (update-layout-positions ids))))) + (wsul/update-layout-positions ids))))) (defn update-layout-child [ids changes] @@ -100,4 +132,4 @@ parent-ids (->> ids (map #(cph/get-parent-id objects %))) layout-ids (->> ids (filter (comp ctl/layout? (d/getf objects))))] (rx/of (dwc/update-shapes ids #(d/deep-merge (or % {}) changes)) - (update-layout-positions (d/concat-vec layout-ids parent-ids))))))) + (wsul/update-layout-positions (d/concat-vec layout-ids parent-ids))))))) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index b36930d585..ee39ddc8df 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.proportions :as gpr] + [app.common.geom.shapes :as gsh] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] @@ -22,8 +23,9 @@ [app.main.data.workspace.changes :as dch] [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.selection :as dws] - [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.shapes-update-layout :as dwsul] [app.main.data.workspace.state-helpers :as wsh] + [app.main.data.workspace.undo :as dwu] [app.main.features :as features] [app.main.streams :as ms] [beicon.core :as rx] @@ -102,7 +104,7 @@ (rx/concat (rx/of (dch/commit-changes changes) - (dwsl/update-layout-positions [(:parent-id shape)]) + (dwsul/update-layout-positions [(:parent-id shape)]) (when-not no-select? (dws/select-shapes (d/ordered-set id)))) (when (= :text (:type attrs)) @@ -270,9 +272,9 @@ (reduce ctp/remove-flow flows))))))] (rx/of (dc/detach-comment-thread ids) - (dwsl/update-layout-positions all-parents) + (dwsul/update-layout-positions all-parents) (dch/commit-changes changes) - (dwsl/update-layout-positions layout-ids))))))) + (dwsul/update-layout-positions layout-ids))))))) (defn- viewport-center [state] @@ -298,3 +300,37 @@ (assoc :frame-id frame-id) (cts/setup-rect-selrect))] (rx/of (add-shape shape)))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Artboard +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn create-artboard-from-selection + ([] + (create-artboard-from-selection nil)) + ([id] + (create-artboard-from-selection id nil)) + ([id parent-id] + (ptk/reify ::create-artboard-from-selection + ptk/WatchEvent + (watch [_ state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + selected (wsh/lookup-selected state) + selected-objs (map #(get objects %) selected)] + (when (d/not-empty? selected) + (let [srect (gsh/selection-rect selected-objs) + frame-id (get-in objects [(first selected) :frame-id]) + parent-id (or parent-id (get-in objects [(first selected) :parent-id])) + shape (-> (cts/make-minimal-shape :frame) + (merge {:x (:x srect) :y (:y srect) :width (:width srect) :height (:height srect)}) + (cond-> id + (assoc :id id)) + (assoc :frame-id frame-id :parent-id parent-id) + (cond-> (not= frame-id uuid/zero) + (assoc :fills [] :hide-in-viewer true)) + (cts/setup-rect-selrect))] + (rx/of + (dwu/start-undo-transaction) + (add-shape shape) + (move-shapes-into-frame (:id shape) selected) + (dwu/commit-undo-transaction))))))))) diff --git a/frontend/src/app/main/data/workspace/shapes_update_layout.cljs b/frontend/src/app/main/data/workspace/shapes_update_layout.cljs new file mode 100644 index 0000000000..3274d23563 --- /dev/null +++ b/frontend/src/app/main/data/workspace/shapes_update_layout.cljs @@ -0,0 +1,28 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.data.workspace.shapes-update-layout + (:require + [app.common.data :as d] + [app.common.types.modifiers :as ctm] + [app.common.types.shape.layout :as ctl] + [app.main.data.workspace.modifiers :as dwm] + [app.main.data.workspace.state-helpers :as wsh] + [beicon.core :as rx] + [potok.core :as ptk])) + +(defn update-layout-positions + [ids] + (ptk/reify ::update-layout-positions + ptk/WatchEvent + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + ids (->> ids (filter (partial ctl/layout? objects)))] + (if (d/not-empty? ids) + (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))] + (rx/of (dwm/set-modifiers modif-tree) + (dwm/apply-modifiers))) + (rx/empty)))))) diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 4f2728d9f4..31fc056ee5 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -15,6 +15,8 @@ [app.main.data.workspace.drawing :as dwd] [app.main.data.workspace.layers :as dwly] [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.shapes :as dws] [app.main.data.workspace.texts :as dwtxt] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] @@ -204,7 +206,12 @@ :artboard-selection {:tooltip (ds/meta (ds/alt "G")) :command (ds/c-mod "alt+g") :subsections [:modify-layers] - :fn #(st/emit! (dw/create-artboard-from-selection))} + :fn #(st/emit! (dws/create-artboard-from-selection))} + + :toogle-layout-flex {:tooltip (ds/shift "F") + :command "shift+f" + :subsections [:modify-layers] + :fn #(st/emit! (dwsl/toogle-layout-flex))} ;; TOOLS @@ -382,7 +389,7 @@ :show-shortcuts {:tooltip "?" :command "?" :subsections [:main-menu] - :fn #(st/emit! (toggle-layout-flag :shortcuts)) } + :fn #(st/emit! (toggle-layout-flag :shortcuts))} ;; PANELS diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 39b8cfc10b..ab69b38794 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -19,6 +19,8 @@ [app.main.data.workspace.interactions :as dwi] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shortcuts :as sc] [app.main.features :as features] [app.main.refs :as refs] @@ -212,7 +214,7 @@ (let [multiple? (> (count shapes) 1) single? (= (count shapes) 1) - do-create-artboard-from-selection #(st/emit! (dw/create-artboard-from-selection)) + do-create-artboard-from-selection #(st/emit! (dwsh/create-artboard-from-selection)) has-frame? (->> shapes (d/seek cph/frame-shape?)) has-group? (->> shapes (d/seek cph/group-shape?)) @@ -364,6 +366,31 @@ [:& menu-entry {:title (tr "workspace.shape.menu.flow-start") :on-click do-add-flow}]))))) +(mf/defc context-menu-flex + [{:keys [shapes]}] + (let [single? (= (count shapes) 1) + has-frame? (->> shapes (d/seek cph/frame-shape?)) + is-frame? (and single? has-frame?) + is-flex-container? (and is-frame? (= :flex (:layout (first shapes)))) + has-group? (->> shapes (d/seek cph/group-shape?)) + is-group? (and single? has-group?) + ids (->> shapes (map :id)) + add-flex #(st/emit! (dwsl/create-layout-from-selection :flex)) + remove-flex #(st/emit! (dwsl/remove-layout ids))] + (cond + (or (not single?) (and is-frame? (not is-flex-container?)) is-group?) + [:* + [:& menu-separator] + [:& menu-entry {:title (tr "workspace.shape.menu.add-flex") + :shortcut (sc/get-tooltip :toogle-layout-flex) + :on-click add-flex}]] + + is-flex-container? + [:* + [:& menu-separator] + [:& menu-entry {:title (tr "workspace.shape.menu.remove-flex") + :shortcut (sc/get-tooltip :toogle-layout-flex) + :on-click remove-flex}]]))) (mf/defc context-menu-component [{:keys [shapes]}] @@ -517,6 +544,7 @@ [:> context-menu-path props] [:> context-menu-layer-options props] [:> context-menu-prototype props] + [:> context-menu-flex props] [:> context-menu-component props] [:> context-menu-delete props]]))) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 45cd4a44fe..ebf0fefaa0 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -4077,6 +4077,14 @@ msgstr "Update main components" msgid "workspace.shape.menu.update-main" msgstr "Update main component" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.add-flex" +msgstr "Add layout flex" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.remove-flex" +msgstr "Remove layout flex" + msgid "workspace.sidebar.collapse" msgstr "Collapse sidebar" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 00a47ffed8..0e833ab0b3 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -4518,6 +4518,14 @@ msgstr "Actualizar componentes" msgid "workspace.shape.menu.update-main" msgstr "Actualizar componente principal" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.add-flex" +msgstr "Añadir layout flex" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.remove-flex" +msgstr "Eliminar layout flex" + msgid "workspace.sidebar.collapse" msgstr "Cerrar barra lateral" From 39041bb63b1828c3e668cb1109e77c88d60b28f3 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 8 Nov 2022 15:10:01 +0100 Subject: [PATCH 224/682] :sparkles: Fix problem with constraints --- common/src/app/common/geom/shapes/constraints.cljc | 4 +++- .../src/app/main/data/workspace/shape_layout.cljs | 4 ++-- frontend/src/app/main/data/workspace/shapes.cljs | 1 + .../ui/workspace/shapes/frame/thumbnail_render.cljs | 2 ++ .../ui/workspace/sidebar/options/rows/stroke_row.cljs | 11 +++++++---- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index ef72945c99..2ced1cce1f 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -151,7 +151,9 @@ (let [angl (gpt/angle-with-other before-v after-v) sign (if (mth/close? angl 180) -1 1) length (* sign (gpt/length before-v))] - (gpt/subtract after-v (gpt/scale (gpt/unit after-v) length)))) + (if (mth/almost-zero? length) + after-v + (gpt/subtract after-v (gpt/scale (gpt/unit after-v) length))))) (defn side-vector [axis [c0 c1 _ c3]] diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 7f2d7d3aec..d1f171b3d8 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -78,8 +78,8 @@ has-group? (->> selected-shapes (d/seek cph/group-shape?)) is-group? (and single? has-group?)] (if is-group? - (let [parent-id (:parent-id (first selected-shapes)) - new-shape-id (uuid/next) + (let [new-shape-id (uuid/next) + parent-id (:parent-id (first selected-shapes)) shapes-ids (:shapes (first selected-shapes)) ordered-ids (into (d/ordered-set) shapes-ids)] (rx/of (dwse/select-shapes ordered-ids) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index ee39ddc8df..72012c8d6a 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -300,6 +300,7 @@ (assoc :frame-id frame-id) (cts/setup-rect-selrect))] (rx/of (add-shape shape)))))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Artboard ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index 5c06eb06b6..1715b887a2 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -199,6 +199,8 @@ (mf/use-effect (mf/deps disable?) (fn [] + (when (and disable? (not @disable-ref?)) + (rx/push! updates-str :update)) (reset! disable-ref? disable?))) (mf/use-effect diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs index bc804fad9b..fb83ff3c34 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/stroke_row.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.rows.stroke-row (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.hooks :as h] @@ -132,9 +133,10 @@ :on-close (close-caps-select start-caps-state)} [:ul.dropdown.cap-select-dropdown {:style {:top (:top @start-caps-state) :left (:left @start-caps-state)}} - (for [[value label separator] (stroke-cap-names)] + (for [[idx [value label separator]] (d/enumerate (stroke-cap-names))] (let [img (value->img value)] - [:li {:class (dom/classnames :separator separator) + [:li {:key (dm/str "start-cap-" idx) + :class (dom/classnames :separator separator) :on-click #(on-stroke-cap-start-change index value)} (when img [:img {:src (value->img value)}]) label]))]] @@ -151,9 +153,10 @@ :on-close (close-caps-select end-caps-state)} [:ul.dropdown.cap-select-dropdown {:style {:top (:top @end-caps-state) :left (:left @end-caps-state)}} - (for [[value label separator] (stroke-cap-names)] + (for [[idx [value label separator]] (d/enumerate (stroke-cap-names))] (let [img (value->img value)] - [:li {:class (dom/classnames :separator separator) + [:li {:key (dm/str "end-cap-" idx) + :class (dom/classnames :separator separator) :on-click #(on-stroke-cap-end-change index value)} (when img [:img {:src (value->img value)}]) label]))]]])])) From 6e35b5c6b68a4274b0caca979cacd5079ae02b7e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 8 Nov 2022 17:08:41 +0100 Subject: [PATCH 225/682] :sparkles: Fixed problem with new modifiers and text auto-heigh --- common/src/app/common/types/modifiers.cljc | 19 ++++------ .../src/app/main/data/workspace/texts.cljs | 2 +- .../shapes/text/viewport_texts_html.cljs | 35 +++++++++++-------- 3 files changed, 27 insertions(+), 29 deletions(-) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 8b2d274455..2b994d6f69 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -229,7 +229,7 @@ (scale-content value))) (defn change-dimensions-modifiers - [shape attr value] + [{:keys [transform transform-inverse] :as shape} attr value] (us/assert map? shape) (us/assert #{:width :height} attr) (us/assert number? value) @@ -245,22 +245,17 @@ (-> size (assoc :height value) (assoc :width (* value proportion))))) + width (:width new-size) height (:height new-size) - shape-transform (:transform shape) - shape-transform-inv (:transform-inverse shape) - shape-center (gco/center-shape shape) {sr-width :width sr-height :height} (:selrect shape) - origin (cond-> (gpt/point (:selrect shape)) - (some? shape-transform) - (gmt/transform-point-center shape-center shape-transform)) + origin (-> shape :points first) + scalex (/ width sr-width) + scaley (/ height sr-height)] - scalev (gpt/divide (gpt/point width height) - (gpt/point sr-width sr-height))] - - (resize-modifiers scalev origin shape-transform shape-transform-inv))) + (resize-modifiers (gpt/point scalex scaley) origin transform transform-inverse))) (defn change-orientation-modifiers [shape orientation] @@ -437,5 +432,3 @@ (as-> shape $ (reduce apply-modifier $ (:structure-parent modifiers)) (reduce apply-modifier $ (:structure-child modifiers)))))) - - diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 0527184a0b..d4cf5995fe 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -394,7 +394,7 @@ ptk/UpdateEvent (update [_ state] (let [ids (keys (::update-position-data state))] - (update state :workspace-text-modifiers #(apply dissoc % ids)))) + (update state :workspace-text-modifier #(apply dissoc % ids)))) ptk/WatchEvent (watch [_ state _] diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 96d946f263..8dc6f1e828 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -33,7 +33,7 @@ (-> shape (cond-> (some? (meta (:position-data shape))) (with-meta (meta (:position-data shape)))) - (dissoc :position-data :transform :transform-inverse))) + (dissoc :position-data))) (defn fix-position [shape modifier] (let [shape' (-> shape @@ -92,20 +92,26 @@ (defn- update-text-modifier [{:keys [grow-type id]} node] - (p/let [position-data (tsp/calc-position-data id) - props {:position-data position-data} + (ts/raf + #(p/let [position-data (tsp/calc-position-data id) + props {:position-data position-data} - props - (if (contains? #{:auto-height :auto-width} grow-type) - (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size)) - width (mth/ceil width) - height (mth/ceil height)] - (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height))) - (assoc props :width width :height height) - props)) - props)] + props + (if (contains? #{:auto-height :auto-width} grow-type) + (let [{:keys [width height]} (-> (dom/query node ".paragraph-set") (dom/get-client-size)) + width (mth/ceil width) + height (mth/ceil height)] + (if (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height))) + (cond-> props + (= grow-type :auto-width) + (assoc :width width) - (st/emit! (dwt/update-text-modifier id props)))) + (or (= grow-type :auto-height) (= grow-type :auto-width)) + (assoc :height height)) + props)) + props)] + + (st/emit! (dwt/update-text-modifier id props))))) (mf/defc text-container {::mf/wrap-props false @@ -163,10 +169,9 @@ handle-update-modifier (mf/use-callback update-text-modifier) handle-update-shape (mf/use-callback update-text-shape)] - [:* (for [{:keys [id] :as shape} changed-texts] - [:& text-container {:shape (gsh/transform-shape shape) + [:& text-container {:shape shape :on-update (if (some? (get modifiers (:id shape))) handle-update-modifier handle-update-shape) From 4c5e8f42ce376c26e71dcd0cfbae5923156aa5cb Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 10 Nov 2022 14:55:13 +0100 Subject: [PATCH 226/682] :sparkles: Review changes --- .../geom/shapes/flex_layout/bounds.cljc | 11 +- .../geom/shapes/flex_layout/drop_area.cljc | 253 ++++++++++-------- .../common/geom/shapes/flex_layout/lines.cljc | 121 +++++---- .../geom/shapes/flex_layout/positions.cljc | 31 +-- .../src/app/common/geom/shapes/modifiers.cljc | 23 +- common/src/app/common/geom/shapes/rect.cljc | 12 +- .../app/common/geom/shapes/transforms.cljc | 3 +- 7 files changed, 245 insertions(+), 209 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc index e8cd52ce2d..07552e540c 100644 --- a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc @@ -105,9 +105,8 @@ (child-layout-bound-points parent child) points))] - (as-> children $ - (mapcat child-bounds $) - (gco/transform-points $ (gco/center-shape parent) (:transform-inverse parent)) - (gre/squared-points $) - (gpo/pad-points $ (- pad-top) (- pad-right) (- pad-bottom) (- pad-left)) - (gre/points->rect $)))) + (-> (mapcat child-bounds children) + (gco/transform-points (gco/center-shape parent) (:transform-inverse parent)) + (gre/squared-points) + (gpo/pad-points (- pad-top) (- pad-right) (- pad-bottom) (- pad-left)) + (gre/points->rect)))) diff --git a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc index 3b9ff8a58e..00de11f3a5 100644 --- a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc @@ -8,16 +8,71 @@ (:require [app.common.data :as d] [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.flex-layout.lines :as fli] [app.common.geom.shapes.rect :as gsr] [app.common.pages.helpers :as cph] [app.common.types.shape.layout :as ctl])) -(defn layout-drop-areas - "Retrieve the layout drop areas to move shapes inside layouts" - [{:keys [margin-x margin-y] :as frame} layout-data children] +(defn drop-child-areas + [{:keys [transform-inverse] :as frame} parent-rect child index reverse? prev-x prev-y last?] + + (let [col? (ctl/col? frame) + row? (ctl/row? frame) + [layout-gap-row layout-gap-col] (ctl/gaps frame) + + start-p (-> child :points first) + center (gco/center-shape frame) + start-p (gmt/transform-point-center start-p center transform-inverse) + + box-x (:x start-p) + box-y (:y start-p) + box-width (-> child :selrect :width) + box-height (-> child :selrect :height) + + x (if col? (:x parent-rect) prev-x) + y (if row? (:y parent-rect) prev-y) + + width + (cond + (and row? last?) + (- (+ (:x parent-rect) (:width parent-rect)) x) + + col? + (:width parent-rect) + + :else + (+ box-width (- box-x prev-x) (/ layout-gap-row 2))) + + height + (cond + (and col? last?) + (- (+ (:y parent-rect) (:height parent-rect)) y) + + row? + (:height parent-rect) + + :else + (+ box-height (- box-y prev-y) (/ layout-gap-col 2)))] + + (if row? + (let [half-point-width (+ (- box-x x) (/ box-width 2))] + [(gsr/make-rect x y width height) + (-> (gsr/make-rect x y half-point-width height) + (assoc :index (if reverse? (inc index) index))) + (-> (gsr/make-rect (+ x half-point-width) y (- width half-point-width) height) + (assoc :index (if reverse? index (inc index))))]) + (let [half-point-height (+ (- box-y y) (/ box-height 2))] + [(gsr/make-rect x y width height) + (-> (gsr/make-rect x y width half-point-height) + (assoc :index (if reverse? (inc index) index))) + (-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height)) + (assoc :index (if reverse? index (inc index))))])))) + +(defn drop-line-area + [{:keys [transform-inverse margin-x margin-y] :as frame} + {:keys [start-p layout-gap-row layout-gap-col num-children line-width line-height] :as line-data} + prev-x prev-y last?] (let [col? (ctl/col? frame) row? (ctl/row? frame) @@ -25,138 +80,108 @@ h-end? (and row? (ctl/h-end? frame)) v-center? (and col? (ctl/v-center? frame)) v-end? (and row? (ctl/v-end? frame)) - reverse? (:reverse? layout-data) - [layout-gap-row layout-gap-col] (ctl/gaps frame) + center (gco/center-shape frame) + start-p (gmt/transform-point-center start-p center transform-inverse) - children (vec (cond->> (d/enumerate children) - reverse? reverse)) + line-width + (if row? + (:width frame) + (+ line-width margin-x + (if row? (* layout-gap-row (dec num-children)) 0))) - redfn-child - (fn [[result parent-rect prev-x prev-y] [[index child] next]] - (let [prev-x (or prev-x (:x parent-rect)) - prev-y (or prev-y (:y parent-rect)) + line-height + (if col? + (:height frame) + (+ line-height margin-y + (if col? + (* layout-gap-col (dec num-children)) + 0))) - last? (nil? next) + box-x + (- (:x start-p) + (cond + h-center? (/ line-width 2) + h-end? line-width + :else 0)) - start-p (gpt/point (:selrect child)) - start-p (-> start-p - (gmt/transform-point-center (gco/center-shape child) (:transform frame)) - (gmt/transform-point-center (gco/center-shape frame) (:transform-inverse frame))) + box-y + (- (:y start-p) + (cond + v-center? (/ line-height 2) + v-end? line-height + :else 0)) - box-x (:x start-p) - box-y (:y start-p) - box-width (-> child :selrect :width) - box-height (-> child :selrect :height) + x (if row? (:x frame) prev-x) + y (if col? (:y frame) prev-y) - x (if col? (:x parent-rect) prev-x) - y (if row? (:y parent-rect) prev-y) + width (cond + (and col? last?) + (- (+ (:x frame) (:width frame)) x) - width (cond - (and row? last?) - (- (+ (:x parent-rect) (:width parent-rect)) x) + row? + (:width frame) - col? - (:width parent-rect) + :else + (+ line-width (- box-x prev-x) (/ layout-gap-row 2))) - :else - (+ box-width (- box-x prev-x) (/ layout-gap-row 2))) + height (cond + (and row? last?) + (- (+ (:y frame) (:height frame)) y) - height (cond - (and col? last?) - (- (+ (:y parent-rect) (:height parent-rect)) y) + col? + (:height frame) - row? - (:height parent-rect) + :else + (+ line-height (- box-y prev-y) (/ layout-gap-col 2)))] + (gsr/make-rect x y width height))) - :else - (+ box-height (- box-y prev-y) (/ layout-gap-col 2))) +(defn layout-drop-areas + "Retrieve the layout drop areas to move shapes inside layouts" + [frame layout-data children] - [line-area-1 line-area-2] - (if row? - (let [half-point-width (+ (- box-x x) (/ box-width 2))] - [(-> (gsr/make-rect x y half-point-width height) - (assoc :index (if reverse? (inc index) index))) - (-> (gsr/make-rect (+ x half-point-width) y (- width half-point-width) height) - (assoc :index (if reverse? index (inc index))))]) - (let [half-point-height (+ (- box-y y) (/ box-height 2))] - [(-> (gsr/make-rect x y width half-point-height) - (assoc :index (if reverse? (inc index) index))) - (-> (gsr/make-rect x (+ y half-point-height) width (- height half-point-height)) - (assoc :index (if reverse? index (inc index))))])) + (let [reverse? (:reverse? layout-data) + children (vec (cond->> (d/enumerate children) reverse? reverse)) + lines (:layout-lines layout-data)] - result (conj result line-area-1 line-area-2)] + (loop [areas [] + from-idx 0 + prev-line-x (:x frame) + prev-line-y (:y frame) - [result parent-rect (+ x width) (+ y height)])) + current-line (first lines) + lines (rest lines)] - redfn-lines - (fn [[result from-idx prev-x prev-y] [{:keys [start-p layout-gap-row layout-gap-col num-children line-width line-height]} next]] - (let [start-p (gmt/transform-point-center start-p (gco/center-shape frame) (:transform-inverse frame)) + (if (nil? current-line) + areas - prev-x (or prev-x (:x frame)) - prev-y (or prev-y (:y frame)) - last? (nil? next) + (let [line-area (drop-line-area frame current-line prev-line-x prev-line-y (nil? (first lines))) + children (subvec children from-idx (+ from-idx (:num-children current-line))) - line-width - (if row? - (:width frame) - (+ line-width margin-x - (if row? (* layout-gap-row (dec num-children)) 0))) + next-areas + (loop [areas areas + prev-child-x (:x line-area) + prev-child-y (:y line-area) + [index child] (first children) + children (rest children)] - line-height - (if col? - (:height frame) - (+ line-height margin-y - (if col? - (* layout-gap-col (dec num-children)) - 0))) + (if (nil? child) + areas - box-x - (- (:x start-p) - (cond - h-center? (/ line-width 2) - h-end? line-width - :else 0)) + (let [[child-area child-area-start child-area-end] + (drop-child-areas frame line-area child index reverse? prev-child-x prev-child-y (nil? (first children)))] + (recur (conj areas child-area-start child-area-end) + (+ (:x child-area) (:width child-area)) + (+ (:y child-area) (:height child-area)) + (first children) + (rest children)))))] - box-y - (- (:y start-p) - (cond - v-center? (/ line-height 2) - v-end? line-height - :else 0)) - - x (if row? (:x frame) prev-x) - y (if col? (:y frame) prev-y) - - width (cond - (and col? last?) - (- (+ (:x frame) (:width frame)) x) - - row? - (:width frame) - - :else - (+ line-width (- box-x prev-x) (/ layout-gap-row 2))) - - height (cond - (and row? last?) - (- (+ (:y frame) (:height frame)) y) - - col? - (:height frame) - - :else - (+ line-height (- box-y prev-y) (/ layout-gap-col 2))) - - line-area (gsr/make-rect x y width height) - - children (subvec children from-idx (+ from-idx num-children)) - - result (first (reduce redfn-child [result line-area] (d/with-next children)))] - - [result (+ from-idx num-children) (+ x width) (+ y height)]))] - - (first (reduce redfn-lines [[] 0] (d/with-next (:layout-lines layout-data)))))) + (recur next-areas + (+ from-idx (:num-children current-line)) + (+ (:x line-area) (:width line-area)) + (+ (:y line-area) (:height line-area)) + (first lines) + (rest lines))))))) (defn get-drop-index [frame-id objects position] diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index 2a5c255b8f..5b0fafdbca 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -37,71 +37,79 @@ (or row? (not (ctl/auto-height? shape)))) [layout-gap-row layout-gap-col] (ctl/gaps shape) - layout-width (gpo/width-points layout-bounds) - layout-height (gpo/height-points layout-bounds) - calculate-line-data - (fn [[{:keys [line-min-width line-min-height + layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds)] + + (loop [line-data nil + result [] + child (first children) + children (rest children)] + + (if (nil? child) + (cond-> result (some? line-data) (conj line-data)) + + (let [{:keys [line-min-width line-min-height line-max-width line-max-height num-children - children-data] :as line-data} result] child] + children-data]} line-data - (let [child-bounds (gst/parent-coords-points child shape) - child-width (gpo/width-points child-bounds) - child-height (gpo/height-points child-bounds) - child-min-width (ctl/child-min-width child) - child-min-height (ctl/child-min-height child) - child-max-width (ctl/child-max-width child) - child-max-height (ctl/child-max-height child) + child-bounds (gst/parent-coords-points child shape) + child-width (gpo/width-points child-bounds) + child-height (gpo/height-points child-bounds) + child-min-width (ctl/child-min-width child) + child-min-height (ctl/child-min-height child) + child-max-width (ctl/child-max-width child) + child-max-height (ctl/child-max-height child) - [child-margin-top child-margin-right child-margin-bottom child-margin-left] - (ctl/child-margins child) + [child-margin-top child-margin-right child-margin-bottom child-margin-left] + (ctl/child-margins child) - child-margin-width (+ child-margin-left child-margin-right) - child-margin-height (+ child-margin-top child-margin-bottom) + child-margin-width (+ child-margin-left child-margin-right) + child-margin-height (+ child-margin-top child-margin-bottom) - fill-width? (ctl/fill-width? child) - fill-height? (ctl/fill-height? child) + fill-width? (ctl/fill-width? child) + fill-height? (ctl/fill-height? child) - ;; We need this info later to calculate the child resizes when fill - child-data {:id (:id child) - :child-min-width (if fill-width? child-min-width child-width) - :child-min-height (if fill-height? child-min-height child-height) - :child-max-width (if fill-width? child-max-width child-width) - :child-max-height (if fill-height? child-max-height child-height)} + ;; We need this info later to calculate the child resizes when fill + child-data {:id (:id child) + :child-min-width (if fill-width? child-min-width child-width) + :child-min-height (if fill-height? child-min-height child-height) + :child-max-width (if fill-width? child-max-width child-width) + :child-max-height (if fill-height? child-max-height child-height)} - next-min-width (+ child-margin-width (if fill-width? child-min-width child-width)) - next-min-height (+ child-margin-height (if fill-height? child-min-height child-height)) - next-max-width (+ child-margin-width (if fill-width? child-max-width child-width)) - next-max-height (+ child-margin-height (if fill-height? child-max-height child-height)) + next-min-width (+ child-margin-width (if fill-width? child-min-width child-width)) + next-min-height (+ child-margin-height (if fill-height? child-min-height child-height)) + next-max-width (+ child-margin-width (if fill-width? child-max-width child-width)) + next-max-height (+ child-margin-height (if fill-height? child-max-height child-height)) - next-line-min-width (+ line-min-width next-min-width (* layout-gap-row num-children)) - next-line-min-height (+ line-min-height next-min-height (* layout-gap-col num-children))] - (if (and (some? line-data) - (or (not wrap?) - (and row? (<= next-line-min-width layout-width)) - (and col? (<= next-line-min-height layout-height)))) + next-line-min-width (+ line-min-width next-min-width (* layout-gap-row num-children)) + next-line-min-height (+ line-min-height next-min-height (* layout-gap-col num-children))] - [{:line-min-width (if row? (+ line-min-width next-min-width) (max line-min-width next-min-width)) - :line-max-width (if row? (+ line-max-width next-max-width) (max line-max-width next-max-width)) - :line-min-height (if col? (+ line-min-height next-min-height) (max line-min-height next-min-height)) - :line-max-height (if col? (+ line-max-height next-max-height) (max line-max-height next-max-height)) - :num-children (inc num-children) - :children-data (conjv children-data child-data)} - result] + (if (and (some? line-data) + (or (not wrap?) + (and row? (<= next-line-min-width layout-width)) + (and col? (<= next-line-min-height layout-height)))) - [{:line-min-width next-min-width - :line-min-height next-min-height - :line-max-width next-max-width - :line-max-height next-max-height - :num-children 1 - :children-data [child-data]} - (cond-> result (some? line-data) (conj line-data))]))) - - [line-data layout-lines] (reduce calculate-line-data [nil []] children)] - - (cond-> layout-lines (some? line-data) (conj line-data)))) + (recur {:line-min-width (if row? (+ line-min-width next-min-width) (max line-min-width next-min-width)) + :line-max-width (if row? (+ line-max-width next-max-width) (max line-max-width next-max-width)) + :line-min-height (if col? (+ line-min-height next-min-height) (max line-min-height next-min-height)) + :line-max-height (if col? (+ line-max-height next-max-height) (max line-max-height next-max-height)) + :num-children (inc num-children) + :children-data (conjv children-data child-data)} + result + (first children) + (rest children)) + (recur {:line-min-width next-min-width + :line-min-height next-min-height + :line-max-width next-max-width + :line-max-height next-max-height + :num-children 1 + :children-data [child-data]} + (cond-> result (some? line-data) (conj line-data)) + (first children) + (rest children)))))))) (defn add-space-to-items ;; Distributes the remainder space between the lines @@ -162,8 +170,7 @@ (let [start-p (flp/get-start-line parent layout-bounds layout-line base-p total-width total-height num-lines) next-p (flp/get-next-line parent layout-bounds layout-line base-p total-width total-height num-lines)] - [(conj result - (assoc layout-line :start-p start-p)) + [(conj result (assoc layout-line :start-p start-p)) next-p]))] (let [[total-min-width total-min-height total-max-width total-max-height] @@ -303,10 +310,10 @@ layout-lines (->> (init-layout-lines shape children layout-bounds) (add-lines-positions shape layout-bounds) - (mapv (partial add-line-spacing shape layout-bounds)) - (mapv (partial add-children-resizes shape)))] + (into [] + (comp (map (partial add-line-spacing shape layout-bounds)) + (map (partial add-children-resizes shape)))))] {:layout-lines layout-lines :layout-bounds layout-bounds :reverse? reverse?})) - diff --git a/common/src/app/common/geom/shapes/flex_layout/positions.cljc b/common/src/app/common/geom/shapes/flex_layout/positions.cljc index ae748d0603..3c710f6691 100644 --- a/common/src/app/common/geom/shapes/flex_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/positions.cljc @@ -114,24 +114,21 @@ "Cross axis line. It's position is fixed along the different lines" [parent layout-bounds {:keys [line-width line-height num-children]} base-p total-width total-height num-lines] - (let [layout-width (gpo/width-points layout-bounds) - layout-height (gpo/height-points layout-bounds) + (let [layout-width (gpo/width-points layout-bounds) + layout-height (gpo/height-points layout-bounds) [layout-gap-row layout-gap-col] (ctl/gaps parent) - - row? (ctl/row? parent) - col? (ctl/col? parent) - space-between? (ctl/space-between? parent) - space-around? (ctl/space-around? parent) - h-center? (ctl/h-center? parent) - h-end? (ctl/h-end? parent) - v-center? (ctl/v-center? parent) - v-end? (ctl/v-end? parent) - content-stretch? (ctl/content-stretch? parent) - - hv #(gpo/start-hv layout-bounds %) - vv #(gpo/start-vv layout-bounds %) - - children-gap-width (* layout-gap-row (dec num-children)) + row? (ctl/row? parent) + col? (ctl/col? parent) + space-between? (ctl/space-between? parent) + space-around? (ctl/space-around? parent) + h-center? (ctl/h-center? parent) + h-end? (ctl/h-end? parent) + v-center? (ctl/v-center? parent) + v-end? (ctl/v-end? parent) + content-stretch? (ctl/content-stretch? parent) + hv (partial gpo/start-hv layout-bounds) + vv (partial gpo/start-vv layout-bounds) + children-gap-width (* layout-gap-row (dec num-children)) children-gap-height (* layout-gap-col (dec num-children)) line-height diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 2f365a6a25..dc57ac178a 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -14,6 +14,7 @@ [app.common.geom.shapes.pixel-precision :as gpp] [app.common.geom.shapes.transforms :as gtr] [app.common.pages.helpers :as cph] + [app.common.spec :as us] [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid])) @@ -30,7 +31,9 @@ "Given the ids that have changed search for layout roots to recalculate" [ids objects] - (assert (or (nil? ids) (set? ids)) (dm/str "tree sequence from not set: " ids)) + (us/assert! + :expr (or (nil? ids) (set? ids)) + :hint (dm/str "tree sequence from not set: " ids)) (letfn [(get-tree-root ;; Finds the tree root for the current id [id] @@ -76,8 +79,8 @@ (generate-tree ;; Generate a tree sequence from a given root id [id] (->> (tree-seq - #(d/not-empty? (get-in objects [% :shapes])) - #(get-in objects [% :shapes]) + #(d/not-empty? (dm/get-in objects [% :shapes])) + #(dm/get-in objects [% :shapes]) id) (map #(get objects %))))] @@ -90,7 +93,7 @@ "Propagates the modifiers from a parent too its children applying constraints if necesary" [modif-tree objects parent transformed-parent ignore-constraints snap-pixel?] (let [children (map (d/getf objects) (:shapes parent)) - modifiers (get-in modif-tree [(:id parent) :modifiers]) + modifiers (dm/get-in modif-tree [(:id parent) :modifiers]) parent (gtr/transform-shape parent (ctm/select-parent-modifiers modifiers)) set-child @@ -106,7 +109,7 @@ (defn- process-layout-children [modif-tree objects parent transformed-parent] (letfn [(process-child [modif-tree child] - (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) + (let [modifiers (dm/get-in modif-tree [(:id parent) :modifiers]) child-modifiers (-> modifiers (ctm/select-child-geometry-modifiers) (gcl/normalize-child-modifiers parent child transformed-parent))] @@ -120,7 +123,7 @@ [modif-tree objects parent] (letfn [(apply-modifiers [modif-tree child] - (let [modifiers (get-in modif-tree [(:id child) :modifiers])] + (let [modifiers (dm/get-in modif-tree [(:id child) :modifiers])] (cond-> child (some? modifiers) (gtr/transform-shape modifiers) @@ -165,7 +168,7 @@ [modif-tree objects parent] (letfn [(apply-modifiers [child] - (let [modifiers (get-in modif-tree [(:id child) :modifiers])] + (let [modifiers (dm/get-in modif-tree [(:id child) :modifiers])] (cond-> child (some? modifiers) (gtr/transform-shape modifiers) @@ -187,7 +190,7 @@ (-> modifiers (ctm/resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))] - (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) + (let [modifiers (dm/get-in modif-tree [(:id parent) :modifiers]) children (->> parent :shapes (map (comp apply-modifiers (d/getf objects)))) @@ -210,7 +213,7 @@ [objects snap-pixel? ignore-constraints [modif-tree recalculate] parent] (let [parent-id (:id parent) root? (= uuid/zero parent-id) - modifiers (get-in modif-tree [parent-id :modifiers]) + modifiers (dm/get-in modif-tree [parent-id :modifiers]) modifiers (cond-> modifiers (and (not root?) (ctm/has-geometry? modifiers) snap-pixel?) @@ -248,7 +251,7 @@ [objects modif-tree parent] (let [is-layout? (ctl/layout? parent) is-auto? (or (ctl/auto-height? parent) (ctl/auto-width? parent)) - modifiers (get-in modif-tree [(:id parent) :modifiers]) + modifiers (dm/get-in modif-tree [(:id parent) :modifiers]) transformed-parent (gtr/transform-shape parent modifiers)] (cond-> modif-tree is-layout? diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index d523f83213..ad0ff5659d 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -12,10 +12,14 @@ (defn make-rect ([p1 p2] - (let [x1 (min (:x p1) (:x p2)) - y1 (min (:y p1) (:y p2)) - x2 (max (:x p1) (:x p2)) - y2 (max (:y p1) (:y p2))] + (let [xp1 (:x p1) + yp1 (:y p1) + xp2 (:x p2) + yp2 (:y p2) + x1 (min xp1 xp2) + y1 (min yp1 yp2) + x2 (max xp1 xp2) + y2 (max yp1 yp2)] (make-rect x1 y1 (- x2 x1) (- y2 y1)))) ([x y width height] diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 564f9cf74e..45ece704bf 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -14,6 +14,7 @@ [app.common.geom.shapes.path :as gpa] [app.common.geom.shapes.rect :as gpr] [app.common.math :as mth] + [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) @@ -452,7 +453,7 @@ (map (d/getf objects)) (apply-children-modifiers objects modif-tree))] (cond-> parent - (= :group (:type parent)) + (cph/group-shape? parent) (update-group-selrect children)))) (defn get-children-bounds From 7b2f0303e8dddeb0746185b5d704c9dd21565342 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 14 Nov 2022 11:46:33 +0100 Subject: [PATCH 227/682] :sparkles: Fixed problems with masks --- .../src/app/common/geom/shapes/modifiers.cljc | 6 +- .../app/common/geom/shapes/transforms.cljc | 55 ++++++++++++------- common/src/app/common/pages/helpers.cljc | 4 ++ frontend/src/app/main/data/workspace.cljs | 10 +++- .../ui/workspace/viewport/snap_distances.cljs | 33 ++++++----- .../ui/workspace/viewport/snap_points.cljs | 13 +++-- 6 files changed, 75 insertions(+), 46 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index dc57ac178a..5386c05c51 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -128,7 +128,7 @@ (some? modifiers) (gtr/transform-shape modifiers) - (and (some? modifiers) (cph/group-like-shape? child)) + (cph/group-like-shape? child) (gtr/apply-group-modifiers objects modif-tree)))) (set-child-modifiers [parent [layout-line modif-tree] child] @@ -226,13 +226,13 @@ has-modifiers? (ctm/child-modifiers? modifiers) is-layout? (ctl/layout? parent) is-auto? (or (ctl/auto-height? transformed-parent) (ctl/auto-width? transformed-parent)) - is-parent? (or (cph/group-like-shape? parent) (and (cph/frame-shape? parent) (not (ctl/layout? parent)))) + is-parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) ;; If the current child is inside the layout we ignore the constraints is-inside-layout? (ctl/inside-layout? objects parent)] [(cond-> modif-tree - (and has-modifiers? is-parent? (not root?)) + (and (not is-layout?) has-modifiers? is-parent? (not root?)) (set-children-modifiers objects parent transformed-parent (or ignore-constraints is-inside-layout?) snap-pixel?) is-layout? diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 45ece704bf..9928ffee15 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -407,17 +407,19 @@ (apply-modifiers modifiers)))) (defn transform-bounds - [points center modifiers] - (let [transform (ctm/modifiers->transform modifiers)] - (gco/transform-points points center transform))) + ([points modifiers] + (transform-bounds points nil modifiers)) + + ([points center modifiers] + (let [transform (ctm/modifiers->transform modifiers)] + (gco/transform-points points center transform)))) (defn transform-selrect [selrect modifiers] - (let [center (gco/center-selrect selrect)] - (-> selrect - (gpr/rect->points) - (transform-bounds center modifiers) - (gpr/points->selrect)))) + (-> selrect + (gpr/rect->points) + (transform-bounds modifiers) + (gpr/points->selrect))) (defn transform-selrect-matrix [selrect mtx] @@ -434,34 +436,45 @@ (map (comp gpr/points->selrect :points transform-shape)) (gpr/join-selrects))) +(declare apply-group-modifiers) + (defn apply-children-modifiers - [objects modif-tree children] + [objects modif-tree parent-modifiers children] (->> children (map (fn [child] - (let [modifiers (get-in modif-tree [(:id child) :modifiers]) - child (transform-shape child modifiers) - parent? (or (= :group (:type child)) (= :bool (:type child)))] + (let [modifiers (->> (get-in modif-tree [(:id child) :modifiers]) + (ctm/add-modifiers parent-modifiers)) + child (transform-shape child modifiers) + parent? (cph/group-like-shape? child)] (cond-> child parent? - (apply-children-modifiers objects modif-tree))))))) + (apply-group-modifiers objects (assoc-in modif-tree [(:id child) :modifiers] modifiers)))))))) (defn apply-group-modifiers "Apply the modifiers to the group children to calculate its selection rect" - [parent objects modif-tree] + [group objects modif-tree] - (let [children (->> (:shapes parent) + (let [modifiers (get-in modif-tree [(:id group) :modifiers]) + children (->> (:shapes group) (map (d/getf objects)) - (apply-children-modifiers objects modif-tree))] - (cond-> parent - (cph/group-shape? parent) - (update-group-selrect children)))) + (apply-children-modifiers objects modif-tree modifiers))] + (cond + (cph/mask-shape? group) + (update-mask-selrect group children) + + (cph/group-shape? group) + (update-group-selrect group children) + + :else + group))) (defn get-children-bounds [parent objects modif-tree] - (let [children + (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) + children (->> (:shapes parent) (map (d/getf objects)) - (apply-children-modifiers objects modif-tree))] + (apply-children-modifiers objects modif-tree modifiers))] (->> children (mapcat :points) gpr/points->rect))) (defn parent-coords-rect diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 7bbda08e87..76e234f576 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -37,6 +37,10 @@ [{:keys [type]}] (= type :group)) +(defn mask-shape? + [{:keys [type masked-group?]}] + (and (= type :group) masked-group?)) + (defn bool-shape? [{:keys [type]}] (= type :bool)) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 9b801f12ac..eb89167110 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -25,6 +25,7 @@ [app.common.types.pages-list :as ctpl] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.config :as cfg] [app.main.data.comments :as dcm] @@ -686,11 +687,16 @@ shapes-to-detach shapes-to-reroot shapes-to-deroot - ids)] + ids) + + layouts-to-update + (into #{} + (filter (partial ctl/layout? objects)) + (concat [parent-id] (cph/get-parent-ids objects parent-id)))] (rx/of (dch/commit-changes changes) (dwco/expand-collapse parent-id) - (dwul/update-layout-positions [parent-id])))))) + (dwul/update-layout-positions layouts-to-update)))))) (defn relocate-selected-shapes [parent-id to-index] diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs index 95cc5693ce..7854fd8c8a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs @@ -10,6 +10,7 @@ [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.pages.helpers :as cph] + [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] [app.main.ui.formats :as fmt] [app.main.worker :as uw] @@ -272,18 +273,20 @@ frame-id (-> selected-shapes first :frame-id) frame (mf/deref (refs/object-by-id frame-id)) selrect (gsh/selection-rect selected-shapes)] - [:g.distance - [:& shape-distance - {:selrect selrect - :page-id page-id - :frame frame - :zoom zoom - :coord :x - :selected selected}] - [:& shape-distance - {:selrect selrect - :page-id page-id - :frame frame - :zoom zoom - :coord :y - :selected selected}]])) + + (when-not (ctl/layout? frame) + [:g.distance + [:& shape-distance + {:selrect selrect + :page-id page-id + :frame frame + :zoom zoom + :coord :x + :selected selected}] + [:& shape-distance + {:selrect selrect + :page-id page-id + :frame frame + :zoom zoom + :coord :y + :selected selected}]]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs index 8b8892ea99..88fe7bc81b 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs @@ -10,6 +10,7 @@ [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.types.shape.layout :as ctl] [app.main.snap :as snap] [app.util.geom.snap-points :as sp] [beicon.core :as rx] @@ -172,9 +173,11 @@ (and (= type :layout) (= grid :square)) (= type :guide)))) - shapes (if drawing [drawing] shapes)] - [:& snap-feedback {:shapes shapes - :page-id page-id - :remove-snap? remove-snap? - :zoom zoom}])) + shapes (if drawing [drawing] shapes) + frame-id (snap/snap-frame-id shapes)] + (when-not (ctl/layout? objects frame-id) + [:& snap-feedback {:shapes shapes + :page-id page-id + :remove-snap? remove-snap? + :zoom zoom}]))) From efc1b87ab0d054de754ad3dc27c6d361a6f1c22b Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 11 Nov 2022 10:37:09 +0100 Subject: [PATCH 228/682] :sparkles: Performance improvements --- common/src/app/common/geom/matrix.cljc | 7 + common/src/app/common/geom/shapes.cljc | 1 - common/src/app/common/geom/shapes/common.cljc | 24 ++- .../src/app/common/geom/shapes/modifiers.cljc | 54 +++--- .../common/geom/shapes/pixel_precision.cljc | 36 +++- common/src/app/common/geom/shapes/rect.cljc | 7 + .../app/common/geom/shapes/transforms.cljc | 159 +++++++++++------- common/src/app/common/math.cljc | 2 +- common/src/app/common/types/modifiers.cljc | 126 ++++++++++---- .../shapes/frame/dynamic_modifiers.cljs | 5 +- 10 files changed, 292 insertions(+), 129 deletions(-) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index 53f3d17489..b8145090f6 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -263,3 +263,10 @@ matrix (translate-matrix (gpt/negate center)))) point)) + +(defn move? + [{:keys [a b c d _ _]}] + (and (mth/almost-zero? (- a 1)) + (mth/almost-zero? b) + (mth/almost-zero? c) + (mth/almost-zero? (- d 1)))) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 163a29b1cd..8d56036a0b 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -175,7 +175,6 @@ (dm/export gtr/transform-selrect-matrix) (dm/export gtr/transform-bounds) (dm/export gtr/move-position-data) -(dm/export gtr/apply-transform) (dm/export gtr/apply-objects-modifiers) (dm/export gtr/parent-coords-rect) (dm/export gtr/parent-coords-points) diff --git a/common/src/app/common/geom/shapes/common.cljc b/common/src/app/common/geom/shapes/common.cljc index 8cb899f045..710c15e782 100644 --- a/common/src/app/common/geom/shapes/common.cljc +++ b/common/src/app/common/geom/shapes/common.cljc @@ -8,7 +8,8 @@ (:require [app.common.data :as d] [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt])) + [app.common.geom.point :as gpt] + [app.common.geom.shapes.rect :as gpr])) (defn center-rect [{:keys [x y width height]}] @@ -31,6 +32,22 @@ (gpt/point (/ (+ minx maxx) 2.0) (/ (+ miny maxy) 2.0)))) +(defn center-bounds [[a b c d]] + (let [xa (:x a) + ya (:y a) + xb (:x b) + yb (:y b) + xc (:x c) + yc (:y c) + xd (:x d) + yd (:y d) + minx (min xa xb xc xd) + miny (min ya yb yc yd) + maxx (max xa xb xc xd) + maxy (max ya yb yc yd)] + (gpt/point (/ (+ minx maxx) 2.0) + (/ (+ miny maxy) 2.0)))) + (defn center-shape "Calculate the center of the shape." [shape] @@ -49,3 +66,8 @@ (gpt/transform point (gmt/multiply prev matrix post)))] (mapv tr-point points)) points))) + +(defn transform-selrect + [{:keys [x1 y1 x2 y2] :as sr} matrix] + (let [[c1 c2] (transform-points [(gpt/point x1 y1) (gpt/point x2 y2)] matrix)] + (gpr/corners->selrect c1 c2))) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 5386c05c51..4a187191b8 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -91,20 +91,30 @@ (defn- set-children-modifiers "Propagates the modifiers from a parent too its children applying constraints if necesary" - [modif-tree objects parent transformed-parent ignore-constraints snap-pixel?] - (let [children (map (d/getf objects) (:shapes parent)) - modifiers (dm/get-in modif-tree [(:id parent) :modifiers]) - parent (gtr/transform-shape parent (ctm/select-parent-modifiers modifiers)) + [modif-tree objects parent transformed-parent ignore-constraints] + (let [children (:shapes parent) + modifiers (dm/get-in modif-tree [(:id parent) :modifiers])] - set-child - (fn [modif-tree child] - (let [child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints transformed-parent) - child-modifiers (cond-> child-modifiers snap-pixel? (gpp/set-pixel-precision child))] - (cond-> modif-tree - (not (ctm/empty? child-modifiers)) - (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))] + (if (ctm/only-move? modifiers) + ;; Move modifiers don't need to calculate constraints + (loop [modif-tree modif-tree + children (seq children)] + (if-let [current (first children)] + (recur (update-in modif-tree [current :modifiers] ctm/add-modifiers modifiers) + (rest children)) + modif-tree)) - (reduce set-child modif-tree children))) + ;; Check the constraints, then resize + (let [parent (gtr/transform-shape parent (ctm/select-parent-modifiers modifiers))] + (loop [modif-tree modif-tree + children (seq children)] + (if-let [current (first children)] + (let [child-modifiers (gct/calc-child-modifiers parent (get objects current) modifiers ignore-constraints transformed-parent)] + (recur (cond-> modif-tree + (not (ctm/empty? child-modifiers)) + (update-in [current :modifiers] ctm/add-modifiers child-modifiers)) + (rest children))) + modif-tree)))))) (defn- process-layout-children [modif-tree objects parent transformed-parent] @@ -210,17 +220,11 @@ (assoc-in modif-tree [(:id parent) :modifiers] modifiers)))) (defn- propagate-modifiers - [objects snap-pixel? ignore-constraints [modif-tree recalculate] parent] + "Propagate modifiers to its children" + [objects ignore-constraints [modif-tree recalculate] parent] (let [parent-id (:id parent) root? (= uuid/zero parent-id) modifiers (dm/get-in modif-tree [parent-id :modifiers]) - - modifiers (cond-> modifiers - (and (not root?) (ctm/has-geometry? modifiers) snap-pixel?) - (gpp/set-pixel-precision parent)) - - modif-tree (-> modif-tree (assoc-in [parent-id :modifiers] modifiers)) - transformed-parent (gtr/transform-shape parent modifiers) has-modifiers? (ctm/child-modifiers? modifiers) @@ -233,7 +237,7 @@ [(cond-> modif-tree (and (not is-layout?) has-modifiers? is-parent? (not root?)) - (set-children-modifiers objects parent transformed-parent (or ignore-constraints is-inside-layout?) snap-pixel?) + (set-children-modifiers objects parent transformed-parent (or ignore-constraints is-inside-layout?)) is-layout? (-> (process-layout-children objects parent transformed-parent) @@ -266,7 +270,7 @@ (let [shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects) [modif-tree recalculate] - (reduce (partial propagate-modifiers objects snap-pixel? ignore-constraints) [modif-tree #{}] shapes-tree) + (reduce (partial propagate-modifiers objects ignore-constraints) [modif-tree #{}] shapes-tree) shapes-tree (resolve-tree-sequence recalculate objects) @@ -281,7 +285,11 @@ modif-tree (->> shapes-tree (filter ctl/layout?) - (reduce (partial calculate-reflow-layout objects) modif-tree ))] + (reduce (partial calculate-reflow-layout objects) modif-tree)) + + modif-tree + (cond-> modif-tree + snap-pixel? (gpp/adjust-pixel-precision objects))] ;;#?(:cljs ;; (.log js/console ">result" (modif->js modif-tree objects))) diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc index 297abf99a6..93dd26dcd8 100644 --- a/common/src/app/common/geom/shapes/pixel_precision.cljc +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -6,6 +6,8 @@ (ns app.common.geom.shapes.pixel-precision (:require + [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.rect :as gpr] @@ -15,9 +17,8 @@ [app.common.types.modifiers :as ctm])) (defn size-pixel-precision - [modifiers shape] - (let [{:keys [points transform transform-inverse] :as shape} (gtr/transform-shape shape modifiers) - origin (gpo/origin points) + [modifiers {:keys [points transform transform-inverse] :as shape}] + (let [origin (gpo/origin points) curr-width (gpo/width-points points) curr-height (gpo/height-points points) @@ -35,9 +36,8 @@ (ctm/resize scalev origin transform transform-inverse)))) (defn position-pixel-precision - [modifiers shape] - (let [{:keys [points]} (gtr/transform-shape shape modifiers) - bounds (gpr/points->rect points) + [modifiers {:keys [points]}] + (let [bounds (gpr/points->rect points) corner (gpt/point bounds) target-corner (gpt/round corner) deltav (gpt/to-vec corner target-corner)] @@ -47,7 +47,25 @@ (defn set-pixel-precision "Adjust modifiers so they adjust to the pixel grid" [modifiers shape] - - (-> modifiers + (let [move? (ctm/only-move? modifiers)] + (cond-> modifiers + (not move?) (size-pixel-precision shape) - (position-pixel-precision shape))) + + :always + (position-pixel-precision shape)))) + +(defn adjust-pixel-precision + [modif-tree objects] + (let [update-modifiers + (fn [modif-tree shape] + (let [modifiers (dm/get-in modif-tree [(:id shape) :modifiers])] + (if-not (ctm/has-geometry? modifiers) + modif-tree + (let [shape (gtr/transform-shape shape modifiers)] + (-> modif-tree + (update-in [(:id shape) :modifiers] set-pixel-precision shape))))))] + + (->> (keys modif-tree) + (map (d/getf objects)) + (reduce update-modifiers modif-tree)))) diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index ad0ff5659d..be8d5a5b5e 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -192,3 +192,10 @@ (>= (:y1 sr2) (:y1 sr1)) (<= (:y2 sr2) (:y2 sr1)))) +(defn corners->selrect + [p1 p2] + (let [xp1 (:x p1) + xp2 (:x p2) + yp1 (:y p1) + yp2 (:y p2)] + (make-selrect (min xp1 xp2) (min yp1 yp2) (abs (- xp1 xp2)) (abs (- yp1 yp2))))) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 9928ffee15..04ac8fc276 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -188,55 +188,52 @@ "Calculates a matrix that is a series of transformations we have to do to the transformed rectangle so that after applying them the end result is the `shape-path-temp`. This is compose of three transformations: skew, resize and rotation" - ([points-temp points-rec] - (calculate-adjust-matrix points-temp points-rec false false)) + [points-temp points-rec flip-x flip-y] + (let [center (gco/center-bounds points-temp) - ([points-temp points-rec flip-x flip-y] - (let [center (gco/center-points points-temp) + stretch-matrix (gmt/matrix) - stretch-matrix (gmt/matrix) + skew-angle (calculate-skew-angle points-temp) - skew-angle (calculate-skew-angle points-temp) + ;; When one of the axis is flipped we have to reverse the skew + ;; skew-angle (if (neg? (* (:x resize-vector) (:y resize-vector))) (- skew-angle) skew-angle ) + skew-angle (if (and (or flip-x flip-y) + (not (and flip-x flip-y))) (- skew-angle) skew-angle ) + skew-angle (if (mth/nan? skew-angle) 0 skew-angle) - ;; When one of the axis is flipped we have to reverse the skew - ;; skew-angle (if (neg? (* (:x resize-vector) (:y resize-vector))) (- skew-angle) skew-angle ) - skew-angle (if (and (or flip-x flip-y) - (not (and flip-x flip-y))) (- skew-angle) skew-angle ) - skew-angle (if (mth/nan? skew-angle) 0 skew-angle) + stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0)) - stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0)) + h1 (max 1 (calculate-height points-temp)) + h2 (max 1 (calculate-height (gco/transform-points points-rec center stretch-matrix))) + h3 (if-not (mth/almost-zero? h2) (/ h1 h2) 1) + h3 (if (mth/nan? h3) 1 h3) - h1 (max 1 (calculate-height points-temp)) - h2 (max 1 (calculate-height (gco/transform-points points-rec center stretch-matrix))) - h3 (if-not (mth/almost-zero? h2) (/ h1 h2) 1) - h3 (if (mth/nan? h3) 1 h3) + w1 (max 1 (calculate-width points-temp)) + w2 (max 1 (calculate-width (gco/transform-points points-rec center stretch-matrix))) + w3 (if-not (mth/almost-zero? w2) (/ w1 w2) 1) + w3 (if (mth/nan? w3) 1 w3) - w1 (max 1 (calculate-width points-temp)) - w2 (max 1 (calculate-width (gco/transform-points points-rec center stretch-matrix))) - w3 (if-not (mth/almost-zero? w2) (/ w1 w2) 1) - w3 (if (mth/nan? w3) 1 w3) + stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point w3 h3))) - stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point w3 h3))) + rotation-angle (calculate-rotation + center + (gco/transform-points points-rec (gco/center-points points-rec) stretch-matrix) + points-temp + flip-x + flip-y) - rotation-angle (calculate-rotation - center - (gco/transform-points points-rec (gco/center-points points-rec) stretch-matrix) - points-temp - flip-x - flip-y) + stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix) - stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix) - - ;; This is the inverse to be able to remove the transformation - stretch-matrix-inverse - (gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3))) - (gmt/skew-matrix (- skew-angle) 0) - (gmt/rotate-matrix (- rotation-angle)))] - [stretch-matrix stretch-matrix-inverse rotation-angle]))) + ;; This is the inverse to be able to remove the transformation + stretch-matrix-inverse + (gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3))) + (gmt/skew-matrix (- skew-angle) 0) + (gmt/rotate-matrix (- rotation-angle)))] + [stretch-matrix stretch-matrix-inverse rotation-angle])) (defn- adjust-rotated-transform [{:keys [transform transform-inverse flip-x flip-y]} points] - (let [center (gco/center-points points) + (let [center (gco/center-bounds points) points-temp (cond-> points (some? transform-inverse) @@ -280,7 +277,28 @@ (-> (update :flip-y not) (update :rotation -))))) -(defn apply-transform +(defn- apply-transform-move + "Given a new set of points transformed, set up the rectangle so it keeps + its properties. We adjust de x,y,width,height and create a custom transform" + [shape transform-mtx] + (let [bool? (= (:type shape) :bool) + path? (= (:type shape) :path) + points (gco/transform-points (:points shape) transform-mtx) + selrect (gco/transform-selrect (:selrect shape) transform-mtx)] + (-> shape + (cond-> bool? + (update :bool-content gpa/transform-content transform-mtx)) + (cond-> path? + (update :content gpa/transform-content transform-mtx)) + (cond-> (not path?) + (assoc :x (:x selrect) + :y (:y selrect) + :width (:width selrect) + :height (:height selrect))) + (assoc :selrect selrect) + (assoc :points points)))) + +(defn- apply-transform-generic "Given a new set of points transformed, set up the rectangle so it keeps its properties. We adjust de x,y,width,height and create a custom transform" [shape transform-mtx] @@ -303,7 +321,10 @@ (cond-> path? (update :content gpa/transform-content transform-mtx)) (cond-> (not path?) - (-> (merge (select-keys selrect [:x :y :width :height])))) + (assoc :x (:x selrect) + :y (:y selrect) + :width (:width selrect) + :height (:height selrect))) (cond-> transform (-> (assoc :transform transform) (assoc :transform-inverse transform-inverse))) @@ -316,6 +337,14 @@ (assoc :points points)) (assoc :rotation rotation)))) +(defn- apply-transform + "Given a new set of points transformed, set up the rectangle so it keeps + its properties. We adjust de x,y,width,height and create a custom transform" + [shape transform-mtx] + (if (gmt/move? transform-mtx) + (apply-transform-move shape transform-mtx) + (apply-transform-generic shape transform-mtx))) + (defn- update-group-viewbox "Updates the viewbox for groups imported from SVG's" [{:keys [selrect svg-viewbox] :as group} new-selrect] @@ -376,24 +405,6 @@ (assoc :flip-x (-> mask :flip-x)) (assoc :flip-y (-> mask :flip-y))))) -(defn apply-modifiers - [shape modifiers] - (let [transform (ctm/modifiers->transform modifiers)] - (cond-> shape - (and (some? transform) - ;; Never transform the root frame - (not= uuid/zero (:id shape))) - (apply-transform transform) - - :always - (ctm/apply-structure-modifiers modifiers)))) - -(defn apply-objects-modifiers - [objects modifiers] - (letfn [(process-shape [objects [id modifier]] - (d/update-when objects id apply-modifiers (:modifiers modifier)))] - (reduce process-shape objects modifiers))) - (defn transform-shape ([shape] (let [modifiers (:modifiers shape)] @@ -402,9 +413,35 @@ (transform-shape modifiers)))) ([shape modifiers] - (cond-> shape - (and (some? modifiers) (not (ctm/empty? modifiers))) - (apply-modifiers modifiers)))) + (letfn [(apply-modifiers + [shape modifiers] + (if (ctm/empty? modifiers) + shape + (let [transform (ctm/modifiers->transform modifiers)] + (cond-> shape + (and (some? transform) (not= uuid/zero (:id shape))) ;; Never transform the root frame + (apply-transform transform) + + (ctm/has-structure? modifiers) + (ctm/apply-structure-modifiers modifiers)))))] + + (cond-> shape + (and (some? modifiers) (not (ctm/empty? modifiers))) + (apply-modifiers modifiers))))) + +(defn apply-objects-modifiers + [objects modifiers] + + (loop [objects objects + entry (first modifiers) + modifiers (rest modifiers)] + + (if (nil? entry) + objects + (let [[id modifier] entry] + (recur (d/update-when objects id transform-shape (:modifiers modifier)) + (first modifiers) + (rest modifiers)))))) (defn transform-bounds ([points modifiers] @@ -412,7 +449,9 @@ ([points center modifiers] (let [transform (ctm/modifiers->transform modifiers)] - (gco/transform-points points center transform)))) + (cond-> points + (some? transform) + (gco/transform-points center transform))))) (defn transform-selrect [selrect modifiers] diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index fc35d15f34..e5483b1189 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -156,7 +156,7 @@ (if (> num to) to num))) (defn almost-zero? [num] - (< (abs (double num)) 1e-5)) + (< (abs (double num)) 1e-4)) (defonce float-equal-precision 0.001) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 2b994d6f69..61af67564e 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -38,21 +38,79 @@ ;; * rotation ;; * change-properties +;; Private aux functions + (def conjv (fnil conj [])) +(defn- move-vec? [vector] + (or (not (mth/almost-zero? (:x vector))) + (not (mth/almost-zero? (:y vector))))) + +(defn- resize-vec? [vector] + (or (not (mth/almost-zero? (- (:x vector) 1))) + (not (mth/almost-zero? (- (:y vector) 1))))) + + +(defn- mergeable-move? + [op1 op2] + (and (= :move (:type op1)) + (= :move (:type op2)))) + +(defn- mergeable-resize? + [op1 op2] + (and (= :resize (:type op1)) + (= :resize (:type op2)) + + ;; Same transforms + (gmt/close? (or (:transform op1) (gmt/matrix)) (or (:transform op2) (gmt/matrix))) + (gmt/close? (or (:transform-inverse op1) (gmt/matrix)) (or (:transform-inverse op2) (gmt/matrix))) + + ;; Same origin + (gpt/close? (:origin op1) (:origin op2)))) + +(defn- merge-move + [op1 op2] + {:type :move + :vector (gpt/add (:vector op1) (:vector op2))}) + +(defn- merge-resize + [op1 op2] + (let [vector (gpt/point (* (-> op1 :vector :x) (-> op2 :vector :x)) + (* (-> op1 :vector :y) (-> op2 :vector :y)))] + (assoc op1 :vector vector))) + +(defn- maybe-add-move + "Check the last operation to check if we can stack it over the last one" + [operations op] + (if (c/empty? operations) + [op] + (let [head (peek operations)] + (if (mergeable-move? head op) + (let [item (merge-move head op)] + (cond-> (pop operations) + (move-vec? (:vector item)) + (conj item))) + (conj operations op))))) + +(defn- maybe-add-resize + "Check the last operation to check if we can stack it over the last one" + [operations op] + + (if (c/empty? operations) + [op] + (let [head (peek operations)] + (if (mergeable-resize? head op) + (let [item (merge-resize head op)] + (cond-> (pop operations) + (resize-vec? (:vector item)) + (conj item))) + (conj operations op))))) + ;; Public builder API (defn empty [] {}) -(defn move-vec? [vector] - (or (not (mth/almost-zero? (:x vector))) - (not (mth/almost-zero? (:y vector))))) - -(defn resize-vec? [vector] - (or (not (mth/almost-zero? (- (:x vector) 1))) - (not (mth/almost-zero? (- (:y vector) 1))))) - (defn move-parent ([modifiers x y] (move-parent modifiers (gpt/point x y))) @@ -66,18 +124,18 @@ ([modifiers vector origin] (cond-> modifiers (resize-vec? vector) - (update :geometry-parent conjv {:type :resize - :vector vector - :origin origin}))) + (update :geometry-parent maybe-add-resize {:type :resize + :vector vector + :origin origin}))) ([modifiers vector origin transform transform-inverse] (cond-> modifiers (resize-vec? vector) - (update :geometry-parent conjv {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + (update :geometry-parent maybe-add-resize {:type :resize + :vector vector + :origin origin + :transform transform + :transform-inverse transform-inverse})))) (defn move ([modifiers x y] (move modifiers (gpt/point x y))) @@ -85,24 +143,24 @@ ([modifiers vector] (cond-> modifiers (move-vec? vector) - (update :geometry-child conjv {:type :move :vector vector})))) + (update :geometry-child maybe-add-move {:type :move :vector vector})))) (defn resize ([modifiers vector origin] (cond-> modifiers (resize-vec? vector) - (update :geometry-child conjv {:type :resize - :vector vector - :origin origin}))) + (update :geometry-child maybe-add-resize {:type :resize + :vector vector + :origin origin}))) ([modifiers vector origin transform transform-inverse] (cond-> modifiers (resize-vec? vector) - (update :geometry-child conjv {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + (update :geometry-child maybe-add-resize {:type :resize + :vector vector + :origin origin + :transform transform + :transform-inverse transform-inverse})))) (defn rotation [modifiers center angle] @@ -296,17 +354,20 @@ (defn only-move? "Returns true if there are only move operations" - [modifier] - (or (and (= 1 (-> modifier :geometry-child count)) - (= :move (-> modifier :geometry-child first :type))) - (and (= 1 (-> modifier :geometry-parent count)) - (= :move (-> modifier :geometry-parent first :type))))) + [{:keys [geometry-child geometry-parent]}] + (and (every? #(= :move (:type %)) geometry-child) + (every? #(= :move (:type %)) geometry-parent))) (defn has-geometry? [{:keys [geometry-parent geometry-child]}] (or (d/not-empty? geometry-parent) (d/not-empty? geometry-child))) +(defn has-structure? + [{:keys [structure-parent structure-child]}] + (or (d/not-empty? structure-parent) + (d/not-empty? structure-child))) + ;; Extract subsets of modifiers (defn select-child-modifiers @@ -374,8 +435,9 @@ (let [modifiers (if (d/not-empty? (:geometry-parent modifiers)) (d/concat-vec (:geometry-parent modifiers) (:geometry-child modifiers)) (:geometry-child modifiers))] - (->> modifiers - (reduce apply-modifier (gmt/matrix)))))) + (when (d/not-empty? modifiers) + (->> modifiers + (reduce apply-modifier (gmt/matrix))))))) (defn apply-structure-modifiers "Apply structure changes to a shape" diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index 5b2a0a8008..c4df18c29f 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -200,8 +200,8 @@ (when (some? modifiers) (d/mapm (fn [id {modifiers :modifiers}] (let [shape (get objects id) - text? (= :text (:type shape)) - modifiers (cond-> modifiers text? (adapt-text-modifiers shape))] + adapt-text? (and (= :text (:type shape)) (not (ctm/only-move? modifiers))) + modifiers (cond-> modifiers adapt-text? (adapt-text-modifiers shape))] (ctm/modifiers->transform modifiers))) modifiers)))) @@ -214,6 +214,7 @@ (mf/deps transforms) (fn [] (->> (keys transforms) + (filter #(some? (get transforms %))) (mapv (d/getf objects))))) prev-shapes (mf/use-var nil) From 32756db1c19d3361a384cb2812ef68385bea1708 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 15 Nov 2022 18:17:11 +0100 Subject: [PATCH 229/682] :sparkles: Redone the calculus of sizing auto --- .../src/app/common/geom/shapes/modifiers.cljc | 163 ++++++++++-------- .../app/common/geom/shapes/transforms.cljc | 55 +++--- common/src/app/common/types/modifiers.cljc | 6 +- 3 files changed, 124 insertions(+), 100 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 4a187191b8..3c77f4a6fc 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -135,10 +135,10 @@ (letfn [(apply-modifiers [modif-tree child] (let [modifiers (dm/get-in modif-tree [(:id child) :modifiers])] (cond-> child - (some? modifiers) + (and (not (cph/group-shape? child)) (some? modifiers)) (gtr/transform-shape modifiers) - (cph/group-like-shape? child) + (cph/group-shape? child) (gtr/apply-group-modifiers objects modif-tree)))) (set-child-modifiers [parent [layout-line modif-tree] child] @@ -152,7 +152,7 @@ [layout-line modif-tree]))] - (let [children (map (d/getf objects) (:shapes parent)) + (let [children (map (d/getf objects) (:shapes parent)) children (->> children (map (partial apply-modifiers modif-tree))) layout-data (gcl/calc-layout-data parent children) children (into [] (cond-> children (:reverse? layout-data) reverse)) @@ -173,119 +173,136 @@ modif-tree))))) -(defn- set-auto-modifiers +(defn- calc-auto-modifiers "Calculates the modifiers to adjust the bounds for auto-width/auto-height shapes" - [modif-tree objects parent] - (letfn [(apply-modifiers - [child] - (let [modifiers (dm/get-in modif-tree [(:id child) :modifiers])] - (cond-> child - (some? modifiers) - (gtr/transform-shape modifiers) - - (and (some? modifiers) (cph/group-like-shape? child)) - (gtr/apply-group-modifiers objects modif-tree)))) - - (set-parent-auto-width - [modifiers parent auto-width] + [objects parent] + (letfn [(set-parent-auto-width + [modifiers auto-width] (let [origin (-> parent :points first) scale-width (/ auto-width (-> parent :selrect :width) )] (-> modifiers (ctm/resize-parent (gpt/point scale-width 1) origin (:transform parent) (:transform-inverse parent))))) (set-parent-auto-height - [modifiers parent auto-height] + [modifiers auto-height] (let [origin (-> parent :points first) scale-height (/ auto-height (-> parent :selrect :height) )] (-> modifiers (ctm/resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))] - (let [modifiers (dm/get-in modif-tree [(:id parent) :modifiers]) - children (->> parent - :shapes - (map (comp apply-modifiers (d/getf objects)))) + (let [children (->> parent :shapes (map (d/getf objects))) {auto-width :width auto-height :height} (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) - (gcl/layout-content-bounds parent children)) + (gcl/layout-content-bounds parent children))] - modifiers - (cond-> modifiers - (and (some? auto-width) (ctl/auto-width? parent)) - (set-parent-auto-width parent auto-width) + (cond-> (ctm/empty) + (and (some? auto-width) (ctl/auto-width? parent)) + (set-parent-auto-width auto-width) - (and (some? auto-height) (ctl/auto-height? parent)) - (set-parent-auto-height parent auto-height))] - - (assoc-in modif-tree [(:id parent) :modifiers] modifiers)))) + (and (some? auto-height) (ctl/auto-height? parent)) + (set-parent-auto-height auto-height))))) (defn- propagate-modifiers "Propagate modifiers to its children" - [objects ignore-constraints [modif-tree recalculate] parent] + [objects ignore-constraints [modif-tree autolayouts] parent] (let [parent-id (:id parent) root? (= uuid/zero parent-id) modifiers (dm/get-in modif-tree [parent-id :modifiers]) transformed-parent (gtr/transform-shape parent modifiers) has-modifiers? (ctm/child-modifiers? modifiers) - is-layout? (ctl/layout? parent) - is-auto? (or (ctl/auto-height? transformed-parent) (ctl/auto-width? transformed-parent)) - is-parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) + layout? (ctl/layout? parent) + auto? (or (ctl/auto-height? transformed-parent) (ctl/auto-width? transformed-parent)) + parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) ;; If the current child is inside the layout we ignore the constraints - is-inside-layout? (ctl/inside-layout? objects parent)] + inside-layout? (ctl/inside-layout? objects parent)] [(cond-> modif-tree - (and (not is-layout?) has-modifiers? is-parent? (not root?)) - (set-children-modifiers objects parent transformed-parent (or ignore-constraints is-inside-layout?)) + (and (not layout?) has-modifiers? parent? (not root?)) + (set-children-modifiers objects parent transformed-parent (or ignore-constraints inside-layout?)) - is-layout? + layout? (-> (process-layout-children objects parent transformed-parent) - (set-layout-modifiers objects transformed-parent)) + (set-layout-modifiers objects transformed-parent))) - is-auto? - (set-auto-modifiers objects transformed-parent)) + ;; Auto-width/height can change the positions in the parent so we need to recalculate + (cond-> autolayouts auto? (conj (:id parent)))])) - (cond-> recalculate - ;; Auto-width/height can change the positions in the parent so we need to recalculate - is-auto? - (conj (:id parent)))])) +(defn- apply-structure-modifiers + [objects modif-tree] + (letfn [(apply-shape [objects [id {:keys [modifiers]}]] + (if (ctm/has-structure? modifiers) + (let [shape (get objects id)] + (update objects id ctm/apply-structure-modifiers modifiers)) + objects))] + (reduce apply-shape objects modif-tree))) -(defn- calculate-reflow-layout - [objects modif-tree parent] - (let [is-layout? (ctl/layout? parent) - is-auto? (or (ctl/auto-height? parent) (ctl/auto-width? parent)) - modifiers (dm/get-in modif-tree [(:id parent) :modifiers]) - transformed-parent (gtr/transform-shape parent modifiers)] - (cond-> modif-tree - is-layout? - (set-layout-modifiers objects transformed-parent) +(defn- apply-partial-objects-modifiers + [objects tree-seq modif-tree] - is-auto? - (set-auto-modifiers objects transformed-parent)))) + (letfn [(apply-shape [objects {:keys [id] :as shape}] + (if (cph/group-shape? shape) + (let [children (cph/get-children objects id)] + (assoc objects id + (cond + (cph/mask-shape? shape) + (gtr/update-mask-selrect shape children) + + :else + (gtr/update-group-selrect shape children)))) + + (let [modifiers (get-in modif-tree [id :modifiers]) + object (cond-> shape + (some? modifiers) + (gtr/transform-shape modifiers))] + (assoc objects id object))))] + + (reduce apply-shape objects (reverse tree-seq)))) + +(defn merge-modif-tree + [modif-tree other-tree] + (reduce (fn [modif-tree [id {:keys [modifiers]}]] + (update-in modif-tree [id :modifiers] ctm/add-modifiers modifiers)) + modif-tree + other-tree)) + +(defn sizing-auto-modifiers + "Recalculates the layouts to adjust the sizing: auto new sizes" + [modif-tree sizing-auto-layouts objects ignore-constraints] + (loop [modif-tree modif-tree + sizing-auto-layouts (reverse sizing-auto-layouts)] + (if-let [current (first sizing-auto-layouts)] + (let [parent-base (get objects current) + tree-seq (resolve-tree-sequence #{current} objects) + + ;; Apply the current stack of transformations so we can calculate the auto-layouts + objects (apply-partial-objects-modifiers objects tree-seq modif-tree) + + resize-modif-tree + {current {:modifiers (calc-auto-modifiers objects parent-base)}} + + tree-seq (resolve-tree-sequence #{current} objects) + + [resize-modif-tree _] + (reduce (partial propagate-modifiers objects ignore-constraints) [resize-modif-tree #{}] tree-seq) + + modif-tree (merge-modif-tree modif-tree resize-modif-tree)] + (recur modif-tree (rest sizing-auto-layouts))) + modif-tree))) (defn set-objects-modifiers [modif-tree objects ignore-constraints snap-pixel?] - (let [shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects) + (let [objects (apply-structure-modifiers objects modif-tree) + shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects) - [modif-tree recalculate] + [modif-tree sizing-auto-layouts] (reduce (partial propagate-modifiers objects ignore-constraints) [modif-tree #{}] shapes-tree) - shapes-tree (resolve-tree-sequence recalculate objects) - - ;; We need to go again and recalculate the layout positions+hug - ;; TODO LAYOUT: How to remove this recalculation? - modif-tree - (->> shapes-tree - reverse - (filter ctl/layout?) - (reduce (partial calculate-reflow-layout objects) modif-tree)) - - modif-tree - (->> shapes-tree - (filter ctl/layout?) - (reduce (partial calculate-reflow-layout objects) modif-tree)) + ;; Calculate hug layouts positions + modif-tree (sizing-auto-modifiers modif-tree sizing-auto-layouts objects ignore-constraints) modif-tree (cond-> modif-tree diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 04ac8fc276..f2c63f5df6 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -369,6 +369,9 @@ ;; Points for every shape inside the group points (->> children (mapcat :points)) + ;; Fixed problem with empty groups. Should not happen (but it does) + points (if (empty? points) (:points group) points) + ;; Invert to get the points minus the transforms applied to the group base-points (gco/transform-points points shape-center (:transform-inverse group (gmt/matrix))) @@ -478,43 +481,43 @@ (declare apply-group-modifiers) (defn apply-children-modifiers - [objects modif-tree parent-modifiers children] + [objects modif-tree parent-modifiers children propagate?] (->> children (map (fn [child] - (let [modifiers (->> (get-in modif-tree [(:id child) :modifiers]) - (ctm/add-modifiers parent-modifiers)) + (let [modifiers (cond-> (get-in modif-tree [(:id child) :modifiers]) + propagate? (ctm/add-modifiers parent-modifiers)) child (transform-shape child modifiers) - parent? (cph/group-like-shape? child)] + parent? (cph/group-like-shape? child) + + modif-tree + (cond-> modif-tree + propagate? + (assoc-in [(:id child) :modifiers] modifiers))] + (cond-> child parent? - (apply-group-modifiers objects (assoc-in modif-tree [(:id child) :modifiers] modifiers)))))))) + (apply-group-modifiers objects modif-tree propagate?))))))) (defn apply-group-modifiers "Apply the modifiers to the group children to calculate its selection rect" - [group objects modif-tree] + ([group objects modif-tree] + (apply-group-modifiers group objects modif-tree true)) - (let [modifiers (get-in modif-tree [(:id group) :modifiers]) - children (->> (:shapes group) - (map (d/getf objects)) - (apply-children-modifiers objects modif-tree modifiers))] - (cond - (cph/mask-shape? group) - (update-mask-selrect group children) + ([group objects modif-tree propagate?] + (let [modifiers (get-in modif-tree [(:id group) :modifiers]) + children + (as-> (:shapes group) $ + (map (d/getf objects) $) + (apply-children-modifiers objects modif-tree modifiers $ propagate?))] + (cond + (cph/mask-shape? group) + (update-mask-selrect group children) - (cph/group-shape? group) - (update-group-selrect group children) + (cph/group-shape? group) + (update-group-selrect group children) - :else - group))) - -(defn get-children-bounds - [parent objects modif-tree] - (let [modifiers (get-in modif-tree [(:id parent) :modifiers]) - children - (->> (:shapes parent) - (map (d/getf objects)) - (apply-children-modifiers objects modif-tree modifiers))] - (->> children (mapcat :points) gpr/points->rect))) + :else + group)))) (defn parent-coords-rect [child parent] diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 61af67564e..d95d0d31fe 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -384,7 +384,11 @@ (defn select-structure [modifiers] - (select-keys modifiers [:structure-parent])) + (select-keys modifiers [:structure-parent :structure-child])) + +(defn select-geometry + [modifiers] + (select-keys modifiers [:geometry-parent :geometry-child])) (defn added-children-frames "Returns the frames that have an 'add-children' operation" From afa6a97693a2beafff2e0fcdfe0c37e3fe2b8f38 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 17 Nov 2022 09:18:51 +0100 Subject: [PATCH 230/682] :sparkles: Fixes problem with bool shapes --- common/src/app/common/geom/shapes.cljc | 4 ++- common/src/app/common/geom/shapes/bool.cljc | 14 ---------- .../src/app/common/geom/shapes/modifiers.cljc | 26 +++++++++++-------- .../app/common/geom/shapes/transforms.cljc | 18 +++++++++++++ common/src/app/common/pages/changes.cljc | 3 +-- .../src/app/common/pages/changes_builder.cljc | 6 ++--- .../app/main/data/workspace/modifiers.cljs | 16 +++++++++++- 7 files changed, 54 insertions(+), 33 deletions(-) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 8d56036a0b..54c07a5701 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -160,6 +160,7 @@ (dm/export gpr/join-selrects) (dm/export gpr/contains-selrect?) (dm/export gpr/contains-point?) +(dm/export gpr/close-selrect?) (dm/export gtr/move) (dm/export gtr/absolute-move) @@ -170,6 +171,7 @@ (dm/export gtr/calculate-adjust-matrix) (dm/export gtr/update-group-selrect) (dm/export gtr/update-mask-selrect) +(dm/export gtr/update-bool-selrect) (dm/export gtr/transform-shape) (dm/export gtr/transform-selrect) (dm/export gtr/transform-selrect-matrix) @@ -194,7 +196,7 @@ (dm/export gin/rect-contains-shape?) ;; Bool -(dm/export gsb/update-bool-selrect) + (dm/export gsb/calc-bool-content) ;; Constraints diff --git a/common/src/app/common/geom/shapes/bool.cljc b/common/src/app/common/geom/shapes/bool.cljc index f404e680c8..a3a4645440 100644 --- a/common/src/app/common/geom/shapes/bool.cljc +++ b/common/src/app/common/geom/shapes/bool.cljc @@ -7,8 +7,6 @@ (ns app.common.geom.shapes.bool (:require [app.common.data :as d] - [app.common.geom.shapes.path :as gsp] - [app.common.geom.shapes.transforms :as gtr] [app.common.path.bool :as pb] [app.common.path.shapes-to-path :as stp])) @@ -25,17 +23,5 @@ (into [] extract-content-xf (:shapes shape))] (pb/content-bool (:bool-type shape) shapes-content))) -(defn update-bool-selrect - "Calculates the selrect+points for the boolean shape" - [shape children objects] - (let [bool-content (calc-bool-content shape objects) - shape (assoc shape :bool-content bool-content) - [points selrect] (gsp/content->points+selrect shape bool-content)] - - (if (and (some? selrect) (d/not-empty? points)) - (-> shape - (assoc :selrect selrect) - (assoc :points points)) - (gtr/update-group-selrect shape children)))) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 3c77f4a6fc..36265536ab 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -133,13 +133,17 @@ [modif-tree objects parent] (letfn [(apply-modifiers [modif-tree child] - (let [modifiers (dm/get-in modif-tree [(:id child) :modifiers])] - (cond-> child - (and (not (cph/group-shape? child)) (some? modifiers)) - (gtr/transform-shape modifiers) + (let [modifiers (-> (dm/get-in modif-tree [(:id child) :modifiers]) + (ctm/select-geometry))] + (cond + (cph/group-like-shape? child) + (gtr/apply-group-modifiers child objects modif-tree) - (cph/group-shape? child) - (gtr/apply-group-modifiers objects modif-tree)))) + (some? modifiers) + (gtr/transform-shape child modifiers) + + :else + child))) (set-child-modifiers [parent [layout-line modif-tree] child] (let [[modifiers layout-line] @@ -208,7 +212,8 @@ [objects ignore-constraints [modif-tree autolayouts] parent] (let [parent-id (:id parent) root? (= uuid/zero parent-id) - modifiers (dm/get-in modif-tree [parent-id :modifiers]) + modifiers (-> (dm/get-in modif-tree [parent-id :modifiers]) + (ctm/select-geometry)) transformed-parent (gtr/transform-shape parent modifiers) has-modifiers? (ctm/child-modifiers? modifiers) @@ -233,10 +238,9 @@ (defn- apply-structure-modifiers [objects modif-tree] (letfn [(apply-shape [objects [id {:keys [modifiers]}]] - (if (ctm/has-structure? modifiers) - (let [shape (get objects id)] - (update objects id ctm/apply-structure-modifiers modifiers)) - objects))] + (cond-> objects + (ctm/has-structure? modifiers) + (update id ctm/apply-structure-modifiers modifiers)))] (reduce apply-shape objects modif-tree))) (defn- apply-partial-objects-modifiers diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index f2c63f5df6..e8c942fd63 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -10,6 +10,7 @@ [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] + [app.common.geom.shapes.bool :as gshb] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.path :as gpa] [app.common.geom.shapes.rect :as gpr] @@ -408,6 +409,20 @@ (assoc :flip-x (-> mask :flip-x)) (assoc :flip-y (-> mask :flip-y))))) +(defn update-bool-selrect + "Calculates the selrect+points for the boolean shape" + [shape children objects] + + (let [bool-content (gshb/calc-bool-content shape objects) + shape (assoc shape :bool-content bool-content) + [points selrect] (gpa/content->points+selrect shape bool-content)] + + (if (and (some? selrect) (d/not-empty? points)) + (-> shape + (assoc :selrect selrect) + (assoc :points points)) + (update-group-selrect shape children)))) + (defn transform-shape ([shape] (let [modifiers (:modifiers shape)] @@ -513,6 +528,9 @@ (cph/mask-shape? group) (update-mask-selrect group children) + (cph/bool-shape? group) + (transform-shape group modifiers) + (cph/group-shape? group) (update-group-selrect group children) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 9e60acfd0d..cee62de190 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -11,7 +11,6 @@ [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.bool :as gshb] [app.common.math :as mth] [app.common.pages.common :refer [component-sync-attrs]] [app.common.pages.helpers :as cph] @@ -170,7 +169,7 @@ group (= :bool (:type group)) - (gshb/update-bool-selrect group children objects) + (gsh/update-bool-selrect group children objects) (:masked-group? group) (set-mask-selrect group children) diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 52eae30f86..77e232f3bd 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -12,8 +12,6 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.bool :as gshb] - [app.common.geom.shapes.rect :as gshr] [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.helpers :as cph] @@ -419,7 +417,7 @@ (every? #(apply gpt/close? %) (d/zip old-val new-val)) (= attr :selrect) - (gshr/close-selrect? old-val new-val) + (gsh/close-selrect? old-val new-val) :else (= old-val new-val))] @@ -438,7 +436,7 @@ nil ;; so it does not need resize (= (:type parent) :bool) - (gshb/update-bool-selrect parent children objects) + (gsh/update-bool-selrect parent children objects) (= (:type parent) :group) (if (:masked-group? parent) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index f76c36d0f9..9b3766e595 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -150,6 +150,19 @@ child-set (set (get-in objects [target-frame :shapes])) layout? (ctl/layout? objects target-frame) + set-parent-ids + (fn [modif-tree shapes target-frame] + (reduce + (fn [modif-tree id] + (update-in + modif-tree + [id :modifiers] + #(-> % + (ctm/change-property :frame-id target-frame) + (ctm/change-property :parent-id target-frame)))) + modif-tree + shapes)) + update-frame-modifiers (fn [modif-tree [original-frame shapes]] (let [shapes (->> shapes (d/removev #(= target-frame %))) @@ -160,7 +173,8 @@ (cond-> modif-tree (not= original-frame target-frame) (-> (update-in [original-frame :modifiers] ctm/remove-children shapes) - (update-in [target-frame :modifiers] ctm/add-children shapes drop-index)) + (update-in [target-frame :modifiers] ctm/add-children shapes drop-index) + (set-parent-ids shapes target-frame)) (and layout? (= original-frame target-frame)) (update-in [target-frame :modifiers] ctm/add-children shapes drop-index))))] From 2a2b5c7dba9dc78ccb1b0b25032d6cd39c52450a Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 16 Nov 2022 12:54:16 +0100 Subject: [PATCH 231/682] :sparkles: Add code block to layout elements --- common/src/app/common/types/shape/layout.cljc | 6 +- .../app/main/data/workspace/shape_layout.cljs | 17 +- frontend/src/app/main/refs.cljs | 6 +- .../app/main/ui/components/code_block.cljs | 2 +- frontend/src/app/main/ui/formats.cljs | 35 +++- .../ui/viewer/handoff/attributes/layout.cljs | 16 +- .../handoff/attributes/layout_flex.cljs | 22 +-- .../attributes/layout_flex_element.cljs | 160 ++++++++++-------- .../src/app/main/ui/viewer/handoff/code.cljs | 29 ++-- .../app/main/ui/workspace/context_menu.cljs | 4 +- .../sidebar/options/menus/layout_item.cljs | 11 +- frontend/src/app/util/code_gen.cljs | 112 +++++++++--- 12 files changed, 272 insertions(+), 148 deletions(-) diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index b318191c03..c8b0e0c36d 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -23,12 +23,12 @@ ;; ITEMS ;; :layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} ;; :layout-item-margin-type ;; :simple :multiple -;; :layout-item-h-sizing ;; :fill :fix :auto -;; :layout-item-v-sizing ;; :fill :fix :auto +;; :layout-item-h-sizing ;; :fill :fix :auto +;; :layout-item-v-sizing ;; :fill :fix :auto ;; :layout-item-max-h ;; num ;; :layout-item-min-h ;; num ;; :layout-item-max-w ;; num -;; :layout-item-min-w +;; :layout-item-min-w ;; num (s/def ::layout #{:flex :grid}) (s/def ::layout-flex-dir #{:row :reverse-row :column :reverse-column}) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index d1f171b3d8..3e7d2a9ac5 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -104,16 +104,19 @@ (ptk/reify ::toogle-layout-flex ptk/WatchEvent (watch [_ state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - selected (wsh/lookup-selected state) - selected-shapes (map (d/getf objects) selected) - single? (= (count selected-shapes) 1) - has-flex-layout? (and single? (= :flex (:layout (first selected-shapes))))] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + selected (wsh/lookup-selected state) + selected-shapes (map (d/getf objects) selected) + single? (= (count selected-shapes) 1) + has-flex-layout? (and single? (= :flex (:layout (first selected-shapes)))) + is-frame? (and single? (= :frame (:type (first selected-shapes))))] (if has-flex-layout? (rx/of (remove-layout selected)) - (rx/of (create-layout-from-selection :flex))))))) + (if is-frame? + (rx/of (create-layout [(first selected)] :flex)) + (rx/of (create-layout-from-selection :flex)))))))) (defn update-layout [ids changes] diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 72e4420d14..3ff68f8fd1 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -447,14 +447,14 @@ (some (partial ctl/layout-child? objects)))) workspace-page-objects)) -(defn get-flex-child-viewer? +(defn get-flex-child-viewer [ids page-id] (l/derived (fn [state] (let [objects (wsh/lookup-viewer-objects state page-id)] (into [] - (comp (filter (partial ctl/layout-child? objects)) - (map (d/getf objects))) + (comp (map (d/getf objects)) + (filter (partial ctl/layout-child? objects))) ids))) st/state =)) diff --git a/frontend/src/app/main/ui/components/code_block.cljs b/frontend/src/app/main/ui/components/code_block.cljs index a446e83ca2..2f8ded61e0 100644 --- a/frontend/src/app/main/ui/components/code_block.cljs +++ b/frontend/src/app/main/ui/components/code_block.cljs @@ -14,7 +14,7 @@ (mf/use-effect (mf/deps code type block-ref) (fn [] - (hljs/highlightBlock (mf/ref-val block-ref)))) + (hljs/highlightElement (mf/ref-val block-ref)))) [:pre.code-display {:class type :ref block-ref} code])) diff --git a/frontend/src/app/main/ui/formats.cljs b/frontend/src/app/main/ui/formats.cljs index c90ec87ab6..cb02882b7c 100644 --- a/frontend/src/app/main/ui/formats.cljs +++ b/frontend/src/app/main/ui/formats.cljs @@ -8,7 +8,8 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.math :as mth])) + [app.common.math :as mth] + [cuerdas.core :as str])) (defn format-percent ([value] @@ -60,4 +61,34 @@ {:p1 p1 :p2 p2 :p3 p3} :else - {:p1 p1 :p2 p2 :p3 p3}))) \ No newline at end of file + {:p1 p1 :p2 p2 :p3 p3}))) + +(defn format-size [type value shape] + (let [sizing (if (= type :width) + (:layout-item-h-sizing shape) + (:layout-item-v-sizing shape))] + (if (= sizing :fill) + "100%" + (str (format-pixels value))))) + +(defn format-padding + [padding-values type] + (let [new-padding (if (= :margin type) + {:m1 0 :m2 0 :m3 0 :m4 0} + {:p1 0 :p2 0 :p3 0 :p4 0}) + merged-padding (merge new-padding padding-values) + short-hand (format-padding-margin-shorthand (vals merged-padding)) + parsed-values (map #(str/fmt "%spx" %) (vals short-hand))] + (str/join " " parsed-values))) + +(defn format-margin + [margin-values] + (format-padding margin-values :margin)) + +(defn format-gap + [gap-values] + (let [row-gap (:row-gap gap-values) + column-gap (:column-gap gap-values)] + (if (= row-gap column-gap) + (str/fmt "%spx" row-gap) + (str/fmt "%spx %spx" row-gap column-gap)))) \ No newline at end of file diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs index a3068085c4..63b5dcbbd1 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout.cljs @@ -23,29 +23,31 @@ :rx "border-radius" :r1 "border-radius"} :format {:rotation #(str/fmt "rotate(%sdeg)" %) - :r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)} + :r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %) + :width (partial fmt/format-size :width) + :height (partial fmt/format-size :height)} :multi {:r1 [:r1 :r2 :r3 :r4]}}) (defn copy-data ([shape] (apply copy-data shape properties)) - ([shape & properties] + ([shape & properties] (cg/generate-css-props shape properties params))) (mf/defc layout-block [{:keys [shape]}] (let [selrect (:selrect shape) - {:keys [width height x y]} selrect] + {:keys [x y]} selrect] [:* [:div.attributes-unit-row [:div.attributes-label (tr "handoff.attributes.layout.width")] - [:div.attributes-value (fmt/format-pixels width)] - [:& copy-button {:data (copy-data selrect :width)}]] + [:div.attributes-value (fmt/format-size :width (:width shape) shape)] + [:& copy-button {:data (copy-data shape :width)}]] [:div.attributes-unit-row [:div.attributes-label (tr "handoff.attributes.layout.height")] - [:div.attributes-value (fmt/format-pixels height)] - [:& copy-button {:data (copy-data selrect :height)}]] + [:div.attributes-value (fmt/format-size :height (:height shape) shape)] + [:& copy-button {:data (copy-data shape :height)}]] (when (not= (:x shape) 0) [:div.attributes-unit-row diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex.cljs index 2c7e61b734..4a539b85c4 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex.cljs @@ -13,20 +13,6 @@ [cuerdas.core :as str] [rumext.v2 :as mf])) -(defn format-gap - [gap-values] - (let [row-gap (:row-gap gap-values) - column-gap (:column-gap gap-values)] - (if (= row-gap column-gap) - (str/fmt "%spx" row-gap) - (str/fmt "%spx %spx" row-gap column-gap)))) - -(defn format-padding - [padding-values] - (let [short-hand (fm/format-padding-margin-shorthand (vals padding-values)) - parsed-values (map #(str/fmt "%spx" %) (vals short-hand))] - (str/join " " parsed-values))) - (def properties [:layout :layout-flex-dir :layout-align-items @@ -49,7 +35,7 @@ :layout-flex-dir "flex-direction" :layout-align-items "align-items" :layout-justify-content "justify-content" - :layout-wrap-type "wrap" + :layout-wrap-type "flex-wrap" :layout-gap "gap" :layout-padding "padding"} :format {:layout name @@ -57,8 +43,8 @@ :layout-align-items name :layout-justify-content name :layout-wrap-type name - :layout-gap format-gap - :layout-padding format-padding}}) + :layout-gap fm/format-gap + :layout-padding fm/format-padding}}) (def layout-align-content-params {:props [:layout-align-content] @@ -109,7 +95,7 @@ [:& copy-button {:data (copy-data shape :layout-justify-content)}]] [:div.attributes-unit-row - [:div.attributes-label "Wrap"] + [:div.attributes-label "Flex wrap"] [:div.attributes-value (str/capital (d/name (:layout-wrap-type shape)))] [:& copy-button {:data (copy-data shape :layout-wrap-type)}]] diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs index 31d7da405b..9cdb46a02f 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs @@ -10,7 +10,7 @@ [app.main.refs :as refs] [app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.formats :as fmt] - [app.main.ui.hooks :as hooks] + [app.main.ui.viewer.handoff.code :as cd] [app.util.code-gen :as cg] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -18,41 +18,31 @@ (defn format-margin [margin-values] - (let [short-hand (fmt/format-padding-margin-shorthand (vals margin-values)) + (let [short-hand (fmt/format-padding-margin-shorthand (vals margin-values)) parsed-values (map #(str/fmt "%spx" %) (vals short-hand))] (str/join " " parsed-values))) -(def properties [:layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} - :layout-item-h-sizing ;; :fill-width :fix-width :auto-width - :layout-item-v-sizing ;; :fill-height :fix-height :auto-height - :layout-item-max-h ;; num - :layout-item-min-h ;; num - :layout-item-max-w ;; num - :layout-item-min-w ;; num - :layout-item-align-self ;; :start :end :center :strech :baseline - ]) - +(def properties [:layout-item-margin ;; {:m1 0 :m2 0 :m3 0 :m4 0} + :layout-item-max-h ;; num + :layout-item-min-h ;; num + :layout-item-max-w ;; num + :layout-item-min-w ;; num + :layout-item-align-self]) ;; :start :end :center (def layout-flex-item-params {:props [:layout-item-margin - :layout-item-h-sizing - :layout-item-v-sizing :layout-item-max-h :layout-item-min-h :layout-item-max-w :layout-item-min-w :layout-item-align-self] :to-prop {:layout-item-margin "margin" - :layout-item-h-sizing "width" - :layout-item-v-sizing "height" :layout-item-align-self "align-self" - :layout-item-max-h "max. height" - :layout-item-min-h "min. height" - :layout-item-max-w "max. width" - :layout-item-min-w "min. width"} + :layout-item-max-h "max-height" + :layout-item-min-h "min-height" + :layout-item-max-w "max-width" + :layout-item-min-w "min-width"} :format {:layout-item-margin format-margin - :layout-item-h-sizing name - :layout-item-v-sizing name :layout-item-align-self name}}) (defn copy-data @@ -69,65 +59,97 @@ (for [[k v] values] [:span.items {:key (str type "-" k "-" v)} v "px"])])) +(defn manage-sizing + [value type] + (let [ref-value-h {:fill "Width 100%" + :fix "Fixed width" + :auto "Fit content"} + ref-value-v {:fill "Height 100%" + :fix "Fixed height" + :auto "Fit content"}] + (if (= :h type) + (ref-value-h value) + (ref-value-v value)))) + (mf/defc layout-element-block [{:keys [shape]}] - [:* - [:div.attributes-unit-row - [:div.attributes-label "Width"] - [:div.attributes-value (str/capital (d/name (:layout-item-h-sizing shape)))] - [:& copy-button {:data (copy-data shape :layout-item-h-sizing)}]] + (let [old-margin (:layout-item-margin shape) + new-margin {:m1 0 :m2 0 :m3 0 :m4 0} + merged-margin (merge new-margin old-margin) + shape (assoc shape :layout-item-margin merged-margin)] - [:div.attributes-unit-row - [:div.attributes-label "Height"] - [:div.attributes-value (str/capital (d/name (:layout-item-v-sizing shape)))] - [:& copy-button {:data (copy-data shape :layout-item-v-sizing)}]] + [:* + (when (:layout-item-align-self shape) + [:div.attributes-unit-row + [:div.attributes-label "Align self"] + [:div.attributes-value (str/capital (d/name (:layout-item-align-self shape)))] + [:& copy-button {:data (copy-data shape :layout-item-align-self)}]]) - [:div.attributes-unit-row - [:div.attributes-label "Align self"] - [:div.attributes-value (str/capital (d/name (:layout-item-align-self shape)))] - [:& copy-button {:data (copy-data shape :layout-item-align-self)}]] + (when (:layout-item-margin shape) + [:div.attributes-unit-row + [:div.attributes-label "Margin"] + [:& manage-margin {:margin merged-margin :type "margin"}] + [:& copy-button {:data (copy-data shape :layout-item-margin)}]]) + + (when (:layout-item-h-sizing shape) + [:div.attributes-unit-row + [:div.attributes-label "Horizontal sizing"] + [:div.attributes-value (manage-sizing (:layout-item-h-sizing shape) :h)] + [:& copy-button {:data (copy-data shape :layout-item-h-sizing)}]]) - [:div.attributes-unit-row - [:div.attributes-label "Margin"] - [:& manage-margin {:margin (:layout-item-margin shape) :type "margin"}] - [:& copy-button {:data (copy-data shape :layout-item-margin)}]] + (when (:layout-item-v-sizing shape) + [:div.attributes-unit-row + [:div.attributes-label "Vertical sizing"] + [:div.attributes-value (manage-sizing (:layout-item-v-sizing shape) :v)] + [:& copy-button {:data (copy-data shape :layout-item-v-sizing)}]]) + + (when (= :fill (:layout-item-h-sizing shape)) + [:* + (when (some? (:layout-item-max-w shape)) + [:div.attributes-unit-row + [:div.attributes-label "Max. width"] + [:div.attributes-value (fmt/format-pixels (:layout-item-max-w shape))] + [:& copy-button {:data (copy-data shape :layout-item-max-w)}]]) - [:div.attributes-unit-row - [:div.attributes-label "Max. width"] - [:div.attributes-value (fmt/format-pixels (:layout-item-max-w shape))] - [:& copy-button {:data (copy-data shape :layout-item-max-w)}]] + (when (some? (:layout-item-min-w shape)) + [:div.attributes-unit-row + [:div.attributes-label "Min. width"] + [:div.attributes-value (fmt/format-pixels (:layout-item-min-w shape))] + [:& copy-button {:data (copy-data shape :layout-item-min-w)}]])]) - [:div.attributes-unit-row - [:div.attributes-label "Min. width"] - [:div.attributes-value (fmt/format-pixels (:layout-item-min-w shape))] - [:& copy-button {:data (copy-data shape :layout-item-min-w)}]] + (when (= :fill (:layout-item-v-sizing shape)) + [:* + (when (:layout-item-max-h shape) + [:div.attributes-unit-row + [:div.attributes-label "Max. height"] + [:div.attributes-value (fmt/format-pixels (:layout-item-max-h shape))] + [:& copy-button {:data (copy-data shape :layout-item-max-h)}]]) - [:div.attributes-unit-row - [:div.attributes-label "Max. height"] - [:div.attributes-value (fmt/format-pixels (:layout-item-max-h shape))] - [:& copy-button {:data (copy-data shape :layout-item-max-h)}]] - - [:div.attributes-unit-row - [:div.attributes-label "Min. height"] - [:div.attributes-value (fmt/format-pixels (:layout-item-min-w shape))] - [:& copy-button {:data (copy-data shape :layout-item-min-h)}]]]) - -(defn get-flex-elements [page-id shapes] - (let [ids (mapv :id shapes) - ids (hooks/use-equal-memo ids) - get-layout-children-refs (mf/use-memo (mf/deps ids page-id) #(refs/get-flex-child-viewer? ids page-id))] - - (mf/deref get-layout-children-refs))) + (when (:layout-item-min-h shape) + [:div.attributes-unit-row + [:div.attributes-label "Min. height"] + [:div.attributes-value (fmt/format-pixels (:layout-item-min-h shape))] + [:& copy-button {:data (copy-data shape :layout-item-min-h)}]])])])) (mf/defc layout-flex-element-panel [{:keys [shapes]}] - (let [route (mf/deref refs/route) - page-id (:page-id (:query-params route)) - shapes (get-flex-elements page-id shapes)] - (when (and (= (count shapes) 1) (seq shapes)) + (let [route (mf/deref refs/route) + page-id (:page-id (:query-params route)) + mod-shapes (cd/get-flex-elements page-id shapes) + shape (first mod-shapes) + has-margin? (some? (:layout-item-margin shape)) + has-values? (or (some? (:layout-item-max-w shape)) + (some? (:layout-item-max-h shape)) + (some? (:layout-item-min-w shape)) + (some? (:layout-item-min-h shape))) + has-align? (some? (:layout-item-align-self shape)) + has-sizing? (or (some? (:layout-item-h-sizing shape)) + (some? (:layout-item-w-sizing shape))) + must-show (or has-margin? has-values? has-align? has-sizing?)] + (when (and (= (count mod-shapes) 1) must-show) [:div.attributes-block [:div.attributes-block-title [:div.attributes-block-title-text "Flex element"] - [:& copy-button {:data (copy-data (first shapes))}]] + [:& copy-button {:data (copy-data shape)}]] - [:& layout-element-block {:shape (first shapes)}]]))) + [:& layout-element-block {:shape shape}]]))) diff --git a/frontend/src/app/main/ui/viewer/handoff/code.cljs b/frontend/src/app/main/ui/viewer/handoff/code.cljs index ad035dd7cd..6213719bcf 100644 --- a/frontend/src/app/main/ui/viewer/handoff/code.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/code.cljs @@ -9,9 +9,11 @@ ["js-beautify" :as beautify] [app.common.geom.shapes :as gsh] [app.main.data.events :as ev] + [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.code-block :refer [code-block]] [app.main.ui.components.copy-button :refer [copy-button]] + [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] [app.util.code-gen :as cg] [app.util.dom :as dom] @@ -40,13 +42,23 @@ (cond-> code (= type "svg") (beautify/html #js {"indent_size" 2})))) +(defn get-flex-elements [page-id shapes] + (let [ids (mapv :id shapes) + ids (hooks/use-equal-memo ids) + get-layout-children-refs (mf/use-memo (mf/deps ids page-id) #(refs/get-flex-child-viewer ids page-id))] + + (mf/deref get-layout-children-refs))) + (mf/defc code [{:keys [shapes frame on-expand]}] - (let [style-type (mf/use-state "css") + (let [style-type (mf/use-state "css") markup-type (mf/use-state "svg") - shapes (->> shapes - (map #(gsh/translate-to-frame % frame))) - + shapes (->> shapes + (map #(gsh/translate-to-frame % frame))) + route (mf/deref refs/route) + page-id (:page-id (:query-params route)) + flex-items (get-flex-elements page-id shapes) + shapes (map #(assoc % :flex-items flex-items) shapes) style-code (-> (cg/generate-style-code @style-type shapes) (format-code "css")) @@ -67,15 +79,14 @@ (fn [] (st/emit! (ptk/event ::ev/event {::ev/name "copy-handoff-style" - :type @style-type})))) - ] + :type @style-type}))))] [:div.element-options [:div.code-block [:div.code-row-lang "CSS" [:button.expand-button - {:on-click on-expand } + {:on-click on-expand} i/full-screen] [:& copy-button {:data style-code @@ -96,6 +107,4 @@ :on-copied on-markup-copied}]] [:div.code-row-display [:& code-block {:type @markup-type - :code markup-code}]]] - - ])) + :code markup-code}]]]])) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index ab69b38794..81bf006b5b 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -375,7 +375,9 @@ has-group? (->> shapes (d/seek cph/group-shape?)) is-group? (and single? has-group?) ids (->> shapes (map :id)) - add-flex #(st/emit! (dwsl/create-layout-from-selection :flex)) + add-flex #(st/emit! (if is-frame? + (dwsl/create-layout ids :flex) + (dwsl/create-layout-from-selection :flex))) remove-flex #(st/emit! (dwsl/remove-layout ids))] (cond (or (not single?) (and is-frame? (not is-flex-container?)) is-group?) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index 97aff08791..48474df03e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -34,7 +34,14 @@ (mf/defc margin-section [{:keys [values change-margin-style on-margin-change] :as props}] - (let [margin-type (or (:layout-item-margin-type values) :simple)] + (let [margin-type (or (:layout-item-margin-type values) :simple) + margins (if (nil? (:layout-item-margin values)) + {:m1 0 :m2 0 :m3 0 :m4 0} + (:layout-item-margin values)) + rx (if (and (not (= :multiple (:layout-item-margin-type values))) + (apply = (vals margins))) + (:m1 margins) + "--")] [:div.margin-row [:div.margin-icons @@ -59,7 +66,7 @@ {:placeholder "--" :on-click #(dom/select-target %) :on-change (partial on-margin-change :simple) - :value (or (-> values :layout-item-margin :m1) 0)}]]] + :value rx}]]] (= margin-type :multiple) (for [num [:m1 :m2 :m3 :m4]] diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index 804d9af4bc..ed506d985c 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -19,6 +19,11 @@ (if (= style :inner-shadow) "inset " "") (str/fmt "%spx %spx %spx %spx %s" offset-x offset-y blur spread css-color)))) +(defn format-gap + [{row-gap :row-gap column-gap :column-gap}] + (if (= row-gap column-gap) + (str/fmt "%spx" row-gap) + (str/fmt "%spx %spx" row-gap column-gap))) (defn format-fill-color [_ shape] (let [color {:color (:fill-color shape) @@ -37,27 +42,50 @@ (str/format "%spx %s %s" width style (uc/color->background color))))) (def styles-data - {:layout {:props [:width :height :x :y :radius :rx :r1] - :to-prop {:x "left" - :y "top" - :rotation "transform" - :rx "border-radius" - :r1 "border-radius"} - :format {:rotation #(str/fmt "rotate(%sdeg)" %) - :r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %)} - :multi {:r1 [:r1 :r2 :r3 :r4]}} - :fill {:props [:fill-color :fill-color-gradient] - :to-prop {:fill-color "background" :fill-color-gradient "background"} - :format {:fill-color format-fill-color :fill-color-gradient format-fill-color}} - :stroke {:props [:stroke-style] - :to-prop {:stroke-style "border"} - :format {:stroke-style format-stroke}} - :shadow {:props [:shadow] - :to-prop {:shadow :box-shadow} - :format {:shadow #(str/join ", " (map shadow->css %1))}} - :blur {:props [:blur] - :to-prop {:blur "filter"} - :format {:blur #(str/fmt "blur(%spx)" (:value %))}}}) + {:layout {:props [:width :height :x :y :radius :rx :r1] + :to-prop {:x "left" + :y "top" + :rotation "transform" + :rx "border-radius" + :r1 "border-radius"} + :format {:rotation #(str/fmt "rotate(%sdeg)" %) + :r1 #(apply str/fmt "%spx, %spx, %spx, %spx" %) + :width (partial fmt/format-size :width) + :height (partial fmt/format-size :height)} + :multi {:r1 [:r1 :r2 :r3 :r4]}} + :fill {:props [:fill-color :fill-color-gradient] + :to-prop {:fill-color "background" :fill-color-gradient "background"} + :format {:fill-color format-fill-color :fill-color-gradient format-fill-color}} + :stroke {:props [:stroke-style] + :to-prop {:stroke-style "border"} + :format {:stroke-style format-stroke}} + :shadow {:props [:shadow] + :to-prop {:shadow :box-shadow} + :format {:shadow #(str/join ", " (map shadow->css %1))}} + :blur {:props [:blur] + :to-prop {:blur "filter"} + :format {:blur #(str/fmt "blur(%spx)" (:value %))}} + :layout-flex {:props [:layout + :layout-align-items + :layout-flex-dir + :layout-justify-content + :layout-gap + :layout-padding + :layout-wrap-type] + :to-prop {:layout "display" + :layout-flex-dir "flex-direction" + :layout-align-items "align-items" + :layout-justify-content "justify-content" + :layout-wrap-type "flex-wrap" + :layout-gap "gap" + :layout-padding "padding"} + :format {:layout name + :layout-flex-dir name + :layout-align-items name + :layout-justify-content name + :layout-wrap-type name + :layout-gap format-gap + :layout-padding fmt/format-padding}}}) (def style-text {:props [:fill-color @@ -78,6 +106,26 @@ :text-transform name :fill-color format-fill-color}}) +(def layout-flex-item-params + {:props [:layout-item-margin + :layout-item-max-h + :layout-item-min-h + :layout-item-max-w + :layout-item-min-w + :layout-item-align-self] + :to-prop {:layout-item-margin "margin" + :layout-item-max-h "max-height" + :layout-item-min-h "min-height" + :layout-item-max-w "max-width" + :layout-item-min-w "min-width" + :layout-item-align-self "align-self"} + :format {:layout-item-margin fmt/format-margin + :layout-item-max-h #(str % "px") + :layout-item-min-h #(str % "px") + :layout-item-max-w #(str % "px") + :layout-item-min-w #(str % "px") + :layout-item-align-self name}}) + (defn generate-css-props ([values properties] (generate-css-props values properties nil)) @@ -126,10 +174,24 @@ (str/join "\n"))))) (defn shape->properties [shape] - (let [props (->> styles-data vals (mapcat :props)) - to-prop (->> styles-data vals (map :to-prop) (reduce merge)) - format (->> styles-data vals (map :format) (reduce merge)) - multi (->> styles-data vals (map :multi) (reduce merge))] + (let [;; This property is added in an earlier step (code.cljs), + ;; it will come with a vector of flex-items if any. + ;; If there are none it will continue as usual. + flex-items (:flex-items shape) + + props (->> styles-data vals (mapcat :props)) + to-prop (->> styles-data vals (map :to-prop) (reduce merge)) + format (->> styles-data vals (map :format) (reduce merge)) + multi (->> styles-data vals (map :multi) (reduce merge)) + props (if (seq flex-items) + (concat props (:props layout-flex-item-params)) + props) + to-prop (if (seq flex-items) + (merge to-prop (:to-prop layout-flex-item-params)) + to-prop) + format (if (seq flex-items) + (merge format (:format layout-flex-item-params)) + format)] (generate-css-props shape props {:to-prop to-prop :format format :multi multi From e3616ea2b5e6c4412516fff19e85c239b99d91ce Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 16 Nov 2022 13:18:40 +0100 Subject: [PATCH 232/682] :wrench: Update highlight.js library --- frontend/package.json | 2 +- frontend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index fd2b8aa2cf..4463f90d3b 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -56,7 +56,7 @@ "@sentry/tracing": "^6.17.4", "date-fns": "^2.28.0", "draft-js": "^0.11.7", - "highlight.js": "^11.5.1", + "highlight.js": "^11.6.0", "js-beautify": "^1.14.4", "jszip": "^3.10.0", "luxon": "^3.0.1", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1ca534e0df..847d2c770f 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2493,10 +2493,10 @@ hasha@^2.2.0: is-stream "^1.0.1" pinkie-promise "^2.0.0" -highlight.js@^11.5.1: - version "11.5.1" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.5.1.tgz#027c24e4509e2f4dcd00b4a6dda542ce0a1f7aea" - integrity sha512-LKzHqnxr4CrD2YsNoIf/o5nJ09j4yi/GcH5BnYz9UnVpZdS4ucMgvP61TDty5xJcFGRjnH4DpujkS9bHT3hq0Q== +highlight.js@^11.6.0: + version "11.6.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.6.0.tgz#a50e9da05763f1bb0c1322c8f4f755242cff3f5a" + integrity sha512-ig1eqDzJaB0pqEvlPVIpSSyMaO92bH1N2rJpLMN/nX396wTpDA4Eq0uK+7I/2XG17pFaaKE0kjV/XPeGt7Evjw== hmac-drbg@^1.0.1: version "1.0.1" From ce7852329ab90cec857773ee882d335546358445 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 11 Nov 2022 16:12:13 +0100 Subject: [PATCH 233/682] :tada: Add the ability to create components in bulk from images --- frontend/src/app/main/data/workspace.cljs | 59 +-------- .../src/app/main/data/workspace/media.cljs | 124 +++++++++++++++++- .../src/app/main/data/workspace/shapes.cljs | 11 +- .../main/data/workspace/state_helpers.cljs | 7 + .../app/main/ui/workspace/sidebar/assets.cljs | 39 +++++- 5 files changed, 170 insertions(+), 70 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index eb89167110..0a3d96d432 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -14,7 +14,6 @@ [app.common.geom.proportions :as gpr] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.rect :as gpsr] - [app.common.logging :as log] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] @@ -56,7 +55,6 @@ [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] - [app.main.data.workspace.svg-upload :as svg] [app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] @@ -1604,58 +1602,6 @@ (update [_ state] (dissoc state :remove-graphics)))) -(defn- create-shapes-svg - [file-id objects pos media-obj] - (let [path (cfg/resolve-file-media media-obj) - - upload-images - (fn [svg-data] - (->> (svg/upload-images svg-data file-id) - (rx/map #(assoc svg-data :image-data %)))) - - process-svg - (fn [svg-data] - (let [[shape children] - (svg/create-svg-shapes svg-data pos objects uuid/zero #{} false)] - [shape children]))] - - (->> (http/send! {:method :get :uri path :mode :no-cors}) - (rx/map :body) - (rx/map #(vector (:name media-obj) %)) - (rx/merge-map dwm/svg->clj) - (rx/merge-map upload-images) - (rx/map process-svg) - (rx/catch ; When error downloading media-obj, skip it and continue with next one - #(log/error :hint "error downloading file" - :path path - :name (:name media-obj) - :cause %))))) - -(defn- create-shapes-img - [pos {:keys [name width height id mtype] :as media-obj}] - (let [group-shape (cts/make-shape :group - {:x (:x pos) - :y (:y pos) - :width width - :height height} - {:name name - :frame-id uuid/zero - :parent-id uuid/zero}) - - img-shape (cts/make-shape :image - {:x (:x pos) - :y (:y pos) - :width width - :height height - :metadata {:id id - :width width - :height height - :mtype mtype}} - {:name name - :frame-id uuid/zero - :parent-id (:id group-shape)})] - (rx/of [group-shape [img-shape]]))) - (defn- remove-graphic [it file-data page [index [media-obj pos]]] (let [process-shapes @@ -1689,8 +1635,9 @@ (dch/commit-changes changes))) shapes (if (= (:mtype media-obj) "image/svg+xml") - (create-shapes-svg (:id file-data) (:objects page) pos media-obj) - (create-shapes-img pos media-obj))] + (->> (dwm/load-and-parse-svg media-obj) + (rx/mapcat (partial dwm/create-shapes-svg (:id file-data) (:objects page) pos))) + (dwm/create-shapes-img pos media-obj))] (rx/concat (rx/of (update-remove-graphics index)) diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index 3ff712de57..b94125f8f7 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -7,11 +7,20 @@ (ns app.main.data.workspace.media (:require [app.common.exceptions :as ex] + [app.common.logging :as log] + [app.common.pages.changes-builder :as pcb] [app.common.spec :as us] + [app.common.types.container :as ctn] + [app.common.types.shape :as cts] + [app.common.types.shape-tree :as ctst] + [app.common.uuid :as uuid] + [app.config :as cfg] [app.main.data.media :as dmm] [app.main.data.messages :as dm] + [app.main.data.workspace.changes :as dch] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.svg-upload :as svg] [app.main.repo :as rp] [app.main.store :as st] @@ -202,7 +211,6 @@ :on-image #(st/emit! (dwl/add-media %)))] (process-media-objects params))) - ;; TODO: it is really need handle SVG here, looks like it already ;; handled separately (defn upload-media-workspace @@ -216,6 +224,120 @@ ;; --- Upload File Media objects +(defn load-and-parse-svg + "Load the contents of a media-obj of type svg, and parse it + into a clojure structure." + [media-obj] + (let [path (cfg/resolve-file-media media-obj)] + (->> (http/send! {:method :get :uri path :mode :no-cors}) + (rx/map :body) + (rx/map #(vector (:name media-obj) %)) + (rx/merge-map svg->clj) + (rx/catch ; When error downloading media-obj, skip it and continue with next one + #(log/error :msg (str "Error downloading " (:name media-obj) " from " path) + :hint (ex-message %) + :error %))))) + +(defn create-shapes-svg + "Convert svg elements into penpot shapes." + [file-id objects pos svg-data] + (let [upload-images + (fn [svg-data] + (->> (svg/upload-images svg-data file-id) + (rx/map #(assoc svg-data :image-data %)))) + + process-svg + (fn [svg-data] + (let [[shape children] + (svg/create-svg-shapes svg-data pos objects uuid/zero #{} false)] + [shape children]))] + + (->> (upload-images svg-data) + (rx/map process-svg)))) + +(defn create-shapes-img + "Convert a media object that contains a bitmap image into shapes, + one shape of type :image and one group that contains it." + [pos {:keys [name width height id mtype] :as media-obj}] + (let [group-shape (cts/make-shape :group + {:x (:x pos) + :y (:y pos) + :width width + :height height} + {:name name + :frame-id uuid/zero + :parent-id uuid/zero}) + + img-shape (cts/make-shape :image + {:x (:x pos) + :y (:y pos) + :width width + :height height + :metadata {:id id + :width width + :height height + :mtype mtype}} + {:name name + :frame-id uuid/zero + :parent-id (:id group-shape)})] + (rx/of [group-shape [img-shape]]))) + +(defn- add-shapes-and-component + [it file-data page name [shape children]] + (let [page' (reduce #(ctst/add-shape (:id %2) %2 %1 uuid/zero (:parent-id %2) nil false) + page + (cons shape children)) + + shape' (ctn/get-shape page' (:id shape)) + + [component-shape component-shapes updated-shapes] + (ctn/make-component-shape shape' (:objects page') (:id file-data) true) + + changes (-> (pcb/empty-changes it) + (pcb/with-page page') + (pcb/with-objects (:objects page')) + (pcb/with-library-data file-data) + (pcb/add-objects (cons shape children)) + (pcb/add-component (:id component-shape) + "" + name + component-shapes + updated-shapes + (:id shape) + (:id page)))] + + (dch/commit-changes changes))) + +(defn- process-img-component + [media-obj] + (ptk/reify ::process-img-component + ptk/WatchEvent + (watch [it state _] + (let [file-data (wsh/get-local-file state) + page (wsh/lookup-page state) + pos (wsh/viewport-center state)] + (->> (create-shapes-img pos media-obj) + (rx/map (partial add-shapes-and-component it file-data page (:name media-obj)))))))) + +(defn- process-svg-component + [svg-data] + (ptk/reify ::process-svg-component + ptk/WatchEvent + (watch [it state _] + (let [file-data (wsh/get-local-file state) + page (wsh/lookup-page state) + pos (wsh/viewport-center state)] + (->> (create-shapes-svg (:id file-data) (:objects page) pos svg-data) + (rx/map (partial add-shapes-and-component it file-data page (:name svg-data)))))))) + +(defn upload-media-components + [params] + (let [params (assoc params + :local? false + :on-image #(st/emit! (process-img-component %)) + :on-svg #(st/emit! (process-svg-component %)))] + (process-media-objects params))) + (s/def ::object-id ::us/uuid) (s/def ::clone-media-objects-params diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 72012c8d6a..93f7b8264c 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -276,11 +276,6 @@ (dch/commit-changes changes) (dwsul/update-layout-positions layout-ids))))))) -(defn- viewport-center - [state] - (let [{:keys [x y width height]} (get-in state [:workspace-local :vbox])] - [(+ x (/ width 2)) (+ y (/ height 2))])) - (defn create-and-add-shape [type frame-x frame-y data] (ptk/reify ::create-and-add-shape @@ -288,9 +283,9 @@ (watch [_ state _] (let [{:keys [width height]} data - [vbc-x vbc-y] (viewport-center state) - x (:x data (- vbc-x (/ width 2))) - y (:y data (- vbc-y (/ height 2))) + vbc (wsh/viewport-center state) + x (:x data (- (:x vbc) (/ width 2))) + y (:y data (- (:y vbc) (/ height 2))) page-id (:current-page-id state) frame-id (-> (wsh/lookup-page-objects state page-id) (ctst/top-nested-frame {:x frame-x :y frame-y})) diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index 5804e14343..f1a8acc35b 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] [app.common.path.commands :as upc])) @@ -139,3 +140,9 @@ (as-> children $ (d/mapm (set-content-modifiers state) $)))) + +(defn viewport-center + [state] + (let [{:keys [x y width height]} (get-in state [:workspace-local :vbox])] + (gpt/point (+ x (/ width 2)) (+ y (/ height 2))))) + diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index fc12a7da32..d77fb28e15 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -552,8 +552,9 @@ (mf/defc components-box [{:keys [file-id local? components listing-thumbs? open? reverse-sort? open-groups selected-assets on-asset-click on-assets-delete on-clear-selection] :as props}] - (let [state (mf/use-state {:renaming nil - :component-id nil}) + (let [input-ref (mf/use-ref nil) + state (mf/use-state {:renaming nil + :component-id nil}) menu-state (mf/use-state auto-pos-menu-state) @@ -566,6 +567,24 @@ groups (group-assets components reverse-sort?) + components-v2 (mf/use-ctx ctx/components-v2) + + add-component + (mf/use-fn + (fn [] + #(st/emit! (dwl/set-assets-box-open file-id :components true)) + (dom/click (mf/ref-val input-ref)))) + + on-file-selected + (mf/use-fn + (mf/deps file-id) + (fn [blobs] + (let [params {:file-id file-id + :blobs (seq blobs)}] + (st/emit! (dwm/upload-media-components params) + (ptk/event ::ev/event {::ev/name "add-asset-to-library" + :asset-type "components"}))))) + on-duplicate (mf/use-fn (mf/deps @state) @@ -694,6 +713,16 @@ :box :components :assets-count (count components) :open? open?} + (when local? + [:& asset-section-block {:role :title-button} + (when components-v2 + [:div.assets-button {:on-click add-component} + i/plus + [:& file-uploader {:accept cm/str-image-types + :multi true + :ref input-ref + :on-selected on-file-selected}]])]) + [:& asset-section-block {:role :content} [:& components-group {:file-id file-id :prefix "" @@ -899,9 +928,9 @@ (mf/defc graphics-box [{:keys [file-id project-id local? objects listing-thumbs? open? open-groups selected-assets reverse-sort? on-asset-click on-assets-delete on-clear-selection] :as props}] - (let [input-ref (mf/use-ref nil) - state (mf/use-state {:renaming nil - :object-id nil}) + (let [input-ref (mf/use-ref nil) + state (mf/use-state {:renaming nil + :object-id nil}) menu-state (mf/use-state auto-pos-menu-state) From 848f5125d8c481c98ce6682f64caab254e3922fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 15 Nov 2022 17:08:01 +0100 Subject: [PATCH 234/682] :sparkles: Improve show main component menu --- frontend/src/app/main/data/workspace.cljs | 78 +++++++++++++------ .../app/main/ui/workspace/context_menu.cljs | 5 +- .../sidebar/options/menus/component.cljs | 5 +- 3 files changed, 63 insertions(+), 25 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index eb89167110..767bcf910e 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -20,6 +20,7 @@ [app.common.spec :as us] [app.common.text :as txt] [app.common.transit :as t] + [app.common.types.components-list :as ctkl] [app.common.types.container :as ctn] [app.common.types.file :as ctf] [app.common.types.pages-list :as ctpl] @@ -989,28 +990,6 @@ :graphics #{} :colors #{} :typographies #{}})))) -(defn go-to-component - [component-id] - (ptk/reify ::go-to-component - IDeref - (-deref [_] {:layout :assets}) - - ptk/WatchEvent - (watch [_ state _] - (let [project-id (get-in state [:workspace-project :id]) - file-id (get-in state [:workspace-file :id]) - page-id (get state :current-page-id) - pparams {:file-id file-id :project-id project-id} - qparams {:page-id page-id :layout :assets}] - (rx/of (rt/nav :workspace pparams qparams) - (dwl/set-assets-box-open file-id :library true) - (dwl/set-assets-box-open file-id :components true) - (select-single-asset component-id :components)))) - ptk/EffectEvent - (effect [_ _ _] - (let [wrapper-id (str "component-shape-id-" component-id)] - (tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id))))))) - (defn go-to-main-instance [page-id shape-id on-page-selected] (us/verify ::us/uuid page-id) @@ -1026,7 +1005,8 @@ (let [project-id (:current-project-id state) file-id (:current-file-id state) pparams {:file-id file-id :project-id project-id} - qparams {:page-id page-id :layout :assets}] + qparams {:page-id page-id}] + ;; qparams {:page-id page-id :layout :assets}] (rx/merge (rx/of (rt/nav :workspace pparams qparams)) (->> stream @@ -1036,6 +1016,58 @@ (on-page-selected) (rx/of (dws/select-shapes (lks/set shape-id))))))))))))) +(defn go-to-component + [component-id] + (ptk/reify ::go-to-component + IDeref + (-deref [_] {:layout :assets}) + + ptk/WatchEvent + (watch [_ state _] + (let [components-v2 (features/active-feature? state :components-v2)] + (if components-v2 + (let [file-data (wsh/get-local-file state) + component (ctkl/get-component file-data component-id) + main-instance-id (:main-instance-id component) + main-instance-page (:main-instance-page component)] + (rx/of (go-to-main-instance main-instance-page main-instance-id identity))) + (let [project-id (get-in state [:workspace-project :id]) + file-id (get-in state [:workspace-file :id]) + page-id (get state :current-page-id) + pparams {:file-id file-id :project-id project-id} + qparams {:page-id page-id :layout :assets}] + (rx/of (rt/nav :workspace pparams qparams) + (dwl/set-assets-box-open file-id :library true) + (dwl/set-assets-box-open file-id :components true) + (select-single-asset component-id :components)))))) + + ptk/EffectEvent + (effect [_ state _] + (let [components-v2 (features/active-feature? state :components-v2) + wrapper-id (str "component-shape-id-" component-id)] + (when-not components-v2 + (tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id)))))))) + +(defn show-component-in-assets + [component-id] + (ptk/reify ::show-component-in-assets + ptk/WatchEvent + (watch [_ state _] + (let [project-id (get-in state [:workspace-project :id]) + file-id (get-in state [:workspace-file :id]) + page-id (get state :current-page-id) + pparams {:file-id file-id :project-id project-id} + qparams {:page-id page-id :layout :assets}] + (rx/of (rt/nav :workspace pparams qparams) + (dwl/set-assets-box-open file-id :library true) + (dwl/set-assets-box-open file-id :components true) + (select-single-asset component-id :components)))) + + ptk/EffectEvent + (effect [_ _ _] + (let [wrapper-id (str "component-shape-id-" component-id)] + (tm/schedule-on-idle #(dom/scroll-into-view-if-needed! (dom/get-element wrapper-id))))))) + (def go-to-file (ptk/reify ::go-to-file ptk/WatchEvent diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 81bf006b5b..d6f6136b97 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -424,6 +424,9 @@ do-detach-component-in-bulk #(st/emit! dwl/detach-selected-components) do-reset-component #(st/emit! (dwl/reset-component shape-id)) do-show-component #(st/emit! (dw/go-to-component component-id)) + do-show-in-assets #(st/emit! (if components-v2 + (dw/show-component-in-assets component-id) + (dw/go-to-component component-id))) do-navigate-component-file #(st/emit! (dwl/nav-to-component-file component-file)) do-update-component #(st/emit! (dwl/update-component-sync shape-id component-file)) do-update-component-in-bulk #(st/emit! (dwl/update-component-in-bulk component-shapes component-file)) @@ -474,7 +477,7 @@ [:& menu-separator] (if main-component? [:& menu-entry {:title (tr "workspace.shape.menu.show-in-assets") - :on-click do-show-component}] + :on-click do-show-in-assets}] (if local-component? (if is-dangling? [:* diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 91d2ab9c77..db16601de1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -78,6 +78,9 @@ :on-accept do-update-component})) do-show-component #(st/emit! (dw/go-to-component component-id)) + do-show-in-assets #(st/emit! (if components-v2 + (dw/show-component-in-assets component-id) + (dw/go-to-component component-id))) do-navigate-component-file #(st/emit! (dwl/nav-to-component-file library-id))] (when show? [:div.element-set @@ -99,7 +102,7 @@ :show (:menu-open @local) :options (if main-instance? - [[(tr "workspace.shape.menu.show-in-assets") do-show-component]] + [[(tr "workspace.shape.menu.show-in-assets") do-show-in-assets]] (if local-component? (if is-dangling? [[(tr "workspace.shape.menu.detach-instance") do-detach-component] From 831839080fa934a04cd957df9e32808014df3598 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 17 Nov 2022 15:31:25 +0100 Subject: [PATCH 235/682] :tada: When deleting a shape inside a component copy, just hide it --- common/src/app/common/types/component.cljc | 11 + frontend/src/app/main/data/workspace.cljs | 63 +--- .../src/app/main/data/workspace/shapes.cljs | 348 +++++++++++------- 3 files changed, 240 insertions(+), 182 deletions(-) diff --git a/common/src/app/common/types/component.cljc b/common/src/app/common/types/component.cljc index 6d20c9ef3b..d84befc448 100644 --- a/common/src/app/common/types/component.cljc +++ b/common/src/app/common/types/component.cljc @@ -23,7 +23,13 @@ (or (= (:shape-ref shape-inst) (:id shape-main)) (= (:shape-ref shape-inst) (:shape-ref shape-main))))) +(defn main-instance? + "Check if this shape is the root of the main instance of some component." + [shape] + (some? (:main-instance? shape))) + (defn is-main-instance? + "Check if this shape is the root of the main instance of the given component." [shape-id page-id component] (and (= shape-id (:main-instance-id component)) (= page-id (:main-instance-page component)))) @@ -38,3 +44,8 @@ (and (some? (:component-id shape)) (= (:component-file shape) library-id))) +(defn in-component-instance? + "Check if the shape is inside a component instance." + [shape] + (some? (:shape-ref shape))) + diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 4fb86995dd..e927d1ec40 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -842,63 +842,6 @@ (rx/of (dch/update-shapes selected #(assoc % :proportion-lock true))) (rx/of (dch/update-shapes selected #(update % :proportion-lock not)))))))) -;; --- Update Shape Flags - -(defn update-shape-flags - [ids {:keys [blocked hidden] :as flags}] - (us/verify (s/coll-of ::us/uuid) ids) - (us/assert ::shape-attrs flags) - (ptk/reify ::update-shape-flags - ptk/WatchEvent - (watch [_ state _] - (let [update-fn - (fn [obj] - (cond-> obj - (boolean? blocked) (assoc :blocked blocked) - (boolean? hidden) (assoc :hidden hidden))) - objects (wsh/lookup-page-objects state) - ids (into ids (->> ids (mapcat #(cph/get-children-ids objects %))))] - (rx/of (dch/update-shapes ids update-fn)))))) - -(defn toggle-visibility-selected - [] - (ptk/reify ::toggle-visibility-selected - ptk/WatchEvent - (watch [_ state _] - (let [selected (wsh/lookup-selected state)] - (rx/of (dch/update-shapes selected #(update % :hidden not))))))) - -(defn toggle-lock-selected - [] - (ptk/reify ::toggle-lock-selected - ptk/WatchEvent - (watch [_ state _] - (let [selected (wsh/lookup-selected state)] - (rx/of (dch/update-shapes selected #(update % :blocked not))))))) - -(defn toggle-file-thumbnail-selected - [] - (ptk/reify ::toggle-file-thumbnail-selected - ptk/WatchEvent - (watch [_ state _] - (let [selected (wsh/lookup-selected state) - pages (-> state :workspace-data :pages-index vals) - get-frames (fn [{:keys [objects id] :as page}] - (->> (ctst/get-frames objects) - (sequence - (comp (filter :use-for-thumbnail?) - (map :id) - (remove selected) - (map (partial vector id))))))] - - (rx/concat - (rx/from - (->> (mapcat get-frames pages) - (d/group-by first second) - (map (fn [[page-id frame-ids]] - (dch/update-shapes frame-ids #(dissoc % :use-for-thumbnail?) {:page-id page-id}))))) - (rx/of (dch/update-shapes selected #(update % :use-for-thumbnail? not)))))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Navigation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -1755,6 +1698,12 @@ (dm/export dwh/highlight-shape) (dm/export dwh/dehighlight-shape) +;; Shape flags +(dm/export dwsh/update-shape-flags) +(dm/export dwsh/toggle-visibility-selected) +(dm/export dwsh/toggle-lock-selected) +(dm/export dwsh/toggle-file-thumbnail-selected) + ;; Groups (dm/export dwg/mask-group) (dm/export dwg/unmask-group) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 93f7b8264c..49bfe4b1ca 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -13,6 +13,7 @@ [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.types.component :as ctk] [app.common.types.page :as ctp] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] @@ -133,6 +134,9 @@ (rx/of (dch/commit-changes changes)) (rx/empty)))))) +(declare real-delete-shapes) +(declare update-shape-flags) + (defn delete-shapes ([ids] (delete-shapes nil ids)) ([page-id ids] @@ -140,141 +144,175 @@ (ptk/reify ::delete-shapes ptk/WatchEvent (watch [it state _] - (let [file-id (:current-file-id state) - page-id (or page-id (:current-page-id state)) - file (wsh/get-file state file-id) - page (wsh/lookup-page state page-id) - objects (wsh/lookup-page-objects state page-id) - - ids (cph/clean-loops objects ids) - lookup (d/getf objects) - - layout-ids (->> ids - (mapcat (partial cph/get-parent-ids objects)) - (filter (partial ctl/layout? objects))) + (let [file-id (:current-file-id state) + page-id (or page-id (:current-page-id state)) + file (wsh/get-file state file-id) + page (wsh/lookup-page state page-id) + objects (wsh/lookup-page-objects state page-id) components-v2 (features/active-feature? state :components-v2) - groups-to-unmask - (reduce (fn [group-ids id] - ;; When the shape to delete is the mask of a masked group, - ;; the mask condition must be removed, and it must be - ;; converted to a normal group. - (let [obj (lookup id) - parent (lookup (:parent-id obj))] - (if (and (:masked-group? parent) - (= id (first (:shapes parent)))) - (conj group-ids (:id parent)) - group-ids))) - #{} - ids) + ids (cph/clean-loops objects ids) - interacting-shapes - (filter (fn [shape] - ;; If any of the deleted shapes is the destination of - ;; some interaction, this must be deleted, too. - (let [interactions (:interactions shape)] - (some #(and (ctsi/has-destination %) - (contains? ids (:destination %))) - interactions))) - (vals objects)) + in-component-copy? + (fn [shape-id] + ;; Look for shapes that are inside a component copy, but are + ;; not the root. In this case, they must not be deleted, + ;; but hidden (to be able to recover them more easily). + (let [shape (get objects shape-id) + component-shape (cph/get-component-shape objects shape)] + (and (ctk/in-component-instance? shape) + (not= shape component-shape) + (not (ctk/main-instance? component-shape))))) - ;; If any of the deleted shapes is a frame with guides - guides (into {} - (comp (map second) - (remove #(contains? ids (:frame-id %))) - (map (juxt :id identity))) - (dm/get-in page [:options :guides])) - - starting-flows - (filter (fn [flow] - ;; If any of the deleted is a frame that starts a flow, - ;; this must be deleted, too. - (contains? ids (:starting-frame flow))) - (-> page :options :flows)) - - all-parents - (reduce (fn [res id] - ;; All parents of any deleted shape must be resized. - (into res (cph/get-parent-ids objects id))) - (d/ordered-set) - ids) - - all-children - (->> ids ;; Children of deleted shapes must be also deleted. - (reduce (fn [res id] - (into res (cph/get-children-ids objects id))) - []) - (reverse) - (into (d/ordered-set))) - - find-all-empty-parents - (fn recursive-find-empty-parents [empty-parents] - (let [all-ids (into empty-parents ids) - contains? (partial contains? all-ids) - xform (comp (map lookup) - (filter cph/group-shape?) - (remove #(->> (:shapes %) (remove contains?) seq)) - (map :id)) - parents (into #{} xform all-parents)] - (if (= empty-parents parents) - empty-parents - (recursive-find-empty-parents parents)))) - - empty-parents - ;; Any parent whose children are all deleted, must be deleted too. - (into (d/ordered-set) (find-all-empty-parents #{})) - - components-to-delete + [ids-to-delete ids-to-hide] (if components-v2 - (reduce (fn [components id] - (let [shape (get objects id)] - (if (and (= (:component-file shape) file-id) ;; Main instances should exist only in local file - (:main-instance? shape)) ;; but check anyway - (conj components (:component-id shape)) - components))) - [] - (into ids all-children)) - []) + (loop [ids-seq (seq ids) + ids-to-delete [] + ids-to-hide []] + (let [id (first ids-seq)] + (if (nil? id) + [ids-to-delete ids-to-hide] + (if (in-component-copy? id) + (recur (rest ids-seq) + ids-to-delete + (conj ids-to-hide id)) + (recur (rest ids-seq) + (conj ids-to-delete id) + ids-to-hide))))) + [ids []])] - changes (-> (pcb/empty-changes it page-id) - (pcb/with-page page) - (pcb/with-objects objects) - (pcb/with-library-data file) - (pcb/set-page-option :guides guides)) + (rx/concat (rx/of (update-shape-flags ids-to-hide {:hidden true})) + (real-delete-shapes file page objects ids-to-delete it components-v2))))))) - changes (reduce (fn [changes component-id] - ;; It's important to delete the component before the main instance, because we - ;; need to store the instance position if we want to restore it later. - (pcb/delete-component changes component-id components-v2)) - changes - components-to-delete) +(defn- real-delete-shapes + [file page objects ids it components-v2] + (let [lookup (d/getf objects) - changes (-> changes - (pcb/remove-objects all-children) - (pcb/remove-objects ids) - (pcb/remove-objects empty-parents) - (pcb/resize-parents all-parents) - (pcb/update-shapes groups-to-unmask - (fn [shape] - (assoc shape :masked-group? false))) - (pcb/update-shapes (map :id interacting-shapes) - (fn [shape] - (d/update-when shape :interactions - (fn [interactions] - (into [] - (remove #(and (ctsi/has-destination %) - (contains? ids (:destination %)))) - interactions))))) - (cond-> (seq starting-flows) - (pcb/update-page-option :flows (fn [flows] - (->> (map :id starting-flows) - (reduce ctp/remove-flow flows))))))] + layout-ids (->> ids + (mapcat (partial cph/get-parent-ids objects)) + (filter (partial ctl/layout? objects))) - (rx/of (dc/detach-comment-thread ids) - (dwsul/update-layout-positions all-parents) - (dch/commit-changes changes) - (dwsul/update-layout-positions layout-ids))))))) + groups-to-unmask + (reduce (fn [group-ids id] + ;; When the shape to delete is the mask of a masked group, + ;; the mask condition must be removed, and it must be + ;; converted to a normal group. + (let [obj (lookup id) + parent (lookup (:parent-id obj))] + (if (and (:masked-group? parent) + (= id (first (:shapes parent)))) + (conj group-ids (:id parent)) + group-ids))) + #{} + ids) + + interacting-shapes + (filter (fn [shape] + ;; If any of the deleted shapes is the destination of + ;; some interaction, this must be deleted, too. + (let [interactions (:interactions shape)] + (some #(and (ctsi/has-destination %) + (contains? ids (:destination %))) + interactions))) + (vals objects)) + + ;; If any of the deleted shapes is a frame with guides + guides (into {} + (comp (map second) + (remove #(contains? ids (:frame-id %))) + (map (juxt :id identity))) + (dm/get-in page [:options :guides])) + + starting-flows + (filter (fn [flow] + ;; If any of the deleted is a frame that starts a flow, + ;; this must be deleted, too. + (contains? ids (:starting-frame flow))) + (-> page :options :flows)) + + all-parents + (reduce (fn [res id] + ;; All parents of any deleted shape must be resized. + (into res (cph/get-parent-ids objects id))) + (d/ordered-set) + ids) + + all-children + (->> ids ;; Children of deleted shapes must be also deleted. + (reduce (fn [res id] + (into res (cph/get-children-ids objects id))) + []) + (reverse) + (into (d/ordered-set))) + + find-all-empty-parents + (fn recursive-find-empty-parents [empty-parents] + (let [all-ids (into empty-parents ids) + contains? (partial contains? all-ids) + xform (comp (map lookup) + (filter cph/group-shape?) + (remove #(->> (:shapes %) (remove contains?) seq)) + (map :id)) + parents (into #{} xform all-parents)] + (if (= empty-parents parents) + empty-parents + (recursive-find-empty-parents parents)))) + + empty-parents + ;; Any parent whose children are all deleted, must be deleted too. + (into (d/ordered-set) (find-all-empty-parents #{})) + + components-to-delete + (if components-v2 + (reduce (fn [components id] + (let [shape (get objects id)] + (if (and (= (:component-file shape) (:id file)) ;; Main instances should exist only in local file + (:main-instance? shape)) ;; but check anyway + (conj components (:component-id shape)) + components))) + [] + (into ids all-children)) + []) + + changes (-> (pcb/empty-changes it (:id page)) + (pcb/with-page page) + (pcb/with-objects objects) + (pcb/with-library-data file) + (pcb/set-page-option :guides guides)) + + changes (reduce (fn [changes component-id] + ;; It's important to delete the component before the main instance, because we + ;; need to store the instance position if we want to restore it later. + (pcb/delete-component changes component-id components-v2)) + changes + components-to-delete) + + changes (-> changes + (pcb/remove-objects all-children) + (pcb/remove-objects ids) + (pcb/remove-objects empty-parents) + (pcb/resize-parents all-parents) + (pcb/update-shapes groups-to-unmask + (fn [shape] + (assoc shape :masked-group? false))) + (pcb/update-shapes (map :id interacting-shapes) + (fn [shape] + (d/update-when shape :interactions + (fn [interactions] + (into [] + (remove #(and (ctsi/has-destination %) + (contains? ids (:destination %)))) + interactions))))) + (cond-> (seq starting-flows) + (pcb/update-page-option :flows (fn [flows] + (->> (map :id starting-flows) + (reduce ctp/remove-flow flows))))))] + + (rx/of (dc/detach-comment-thread ids) + (dwsul/update-layout-positions all-parents) + (dch/commit-changes changes) + (dwsul/update-layout-positions layout-ids)))) (defn create-and-add-shape [type frame-x frame-y data] @@ -330,3 +368,63 @@ (add-shape shape) (move-shapes-into-frame (:id shape) selected) (dwu/commit-undo-transaction))))))))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Shape Flags +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn update-shape-flags + [ids {:keys [blocked hidden] :as flags}] + (us/verify (s/coll-of ::us/uuid) ids) + (us/assert ::shape-attrs flags) + (ptk/reify ::update-shape-flags + ptk/WatchEvent + (watch [_ state _] + (let [update-fn + (fn [obj] + (cond-> obj + (boolean? blocked) (assoc :blocked blocked) + (boolean? hidden) (assoc :hidden hidden))) + objects (wsh/lookup-page-objects state) + ids (into ids (->> ids (mapcat #(cph/get-children-ids objects %))))] + (rx/of (dch/update-shapes ids update-fn)))))) + +(defn toggle-visibility-selected + [] + (ptk/reify ::toggle-visibility-selected + ptk/WatchEvent + (watch [_ state _] + (let [selected (wsh/lookup-selected state)] + (rx/of (dch/update-shapes selected #(update % :hidden not))))))) + +(defn toggle-lock-selected + [] + (ptk/reify ::toggle-lock-selected + ptk/WatchEvent + (watch [_ state _] + (let [selected (wsh/lookup-selected state)] + (rx/of (dch/update-shapes selected #(update % :blocked not))))))) + +(defn toggle-file-thumbnail-selected + [] + (ptk/reify ::toggle-file-thumbnail-selected + ptk/WatchEvent + (watch [_ state _] + (let [selected (wsh/lookup-selected state) + pages (-> state :workspace-data :pages-index vals) + get-frames (fn [{:keys [objects id] :as page}] + (->> (ctst/get-frames objects) + (sequence + (comp (filter :use-for-thumbnail?) + (map :id) + (remove selected) + (map (partial vector id))))))] + + (rx/concat + (rx/from + (->> (mapcat get-frames pages) + (d/group-by first second) + (map (fn [[page-id frame-ids]] + (dch/update-shapes frame-ids #(dissoc % :use-for-thumbnail?) {:page-id page-id}))))) + (rx/of (dch/update-shapes selected #(update % :use-for-thumbnail? not)))))))) + From af03f720b0eafcf40d280e38d86fd32364a0b8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 18 Nov 2022 16:13:52 +0100 Subject: [PATCH 236/682] :sparkles: Allow to reset changes from a deleted component --- .../main/data/workspace/libraries_helpers.cljs | 6 ++++++ .../src/app/main/ui/workspace/context_menu.cljs | 10 ++++------ .../sidebar/options/menus/component.cljs | 16 +++++++--------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 565cd1b934..681ec9c34e 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -18,6 +18,7 @@ [app.common.types.color :as ctc] [app.common.types.component :as ctk] [app.common.types.container :as ctn] + [app.common.types.file :as ctf] [app.common.types.shape-tree :as ctst] [app.common.types.typography :as cty] [app.main.data.workspace.groups :as dwg] @@ -453,6 +454,11 @@ component (cph/get-component libraries (:component-file shape-inst) (:component-id shape-inst)) + component (or component + (and reset? + (ctf/get-deleted-component + (get-in libraries [(:component-file shape-inst) :data]) + (:component-id shape-inst)))) shape-main (when component (ctn/get-shape component (:shape-ref shape-inst))) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index d6f6136b97..1fab9587a0 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -484,9 +484,8 @@ [:& menu-entry {:title (tr "workspace.shape.menu.detach-instance") :shortcut (sc/get-tooltip :detach-component) :on-click do-detach-component}] - ;; This is commented due this functionality is not yet available - ;; [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides") - ;; :on-click do-reset-component}] + [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides") + :on-click do-reset-component}] (when components-v2 [:& menu-entry {:title (tr "workspace.shape.menu.restore-main") :on-click do-restore-component}])] @@ -505,9 +504,8 @@ [:& menu-entry {:title (tr "workspace.shape.menu.detach-instance") :shortcut (sc/get-tooltip :detach-component) :on-click do-detach-component}] - ;; This is commented due this functionality is not yet available - ;; [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides") - ;; :on-click do-reset-component}] + [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides") + :on-click do-reset-component}] (when components-v2 [:& menu-entry {:title (tr "workspace.shape.menu.restore-main") :on-click do-restore-component}])] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index db16601de1..392b135eaa 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -106,23 +106,21 @@ (if local-component? (if is-dangling? [[(tr "workspace.shape.menu.detach-instance") do-detach-component] - ;; [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] + [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] (when components-v2 [(tr "workspace.shape.menu.restore-main") do-restore-component])] [[(tr "workspace.shape.menu.detach-instance") do-detach-component] - ;; [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] + [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] [(tr "workspace.shape.menu.update-main") do-update-component] [(tr "workspace.shape.menu.show-main") do-show-component]]) - + (if is-dangling? [[(tr "workspace.shape.menu.detach-instance") do-detach-component] - ;; [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] + [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] (when components-v2 [(tr "workspace.shape.menu.restore-main") do-restore-component])] [[(tr "workspace.shape.menu.detach-instance") do-detach-component] - ;; [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] - ;; [(tr "workspace.shape.menu.update-main") _do-update-remote-component] - [(tr "workspace.shape.menu.go-main") do-navigate-component-file] - ])))}]]]]]))) - + [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] + [(tr "workspace.shape.menu.update-main") _do-update-remote-component] + [(tr "workspace.shape.menu.go-main") do-navigate-component-file]])))}]]]]]))) From 08399ebac168be17fc5b9e2da7b67b6e155a1042 Mon Sep 17 00:00:00 2001 From: Prithvi Tharun Date: Mon, 21 Nov 2022 19:31:33 +0530 Subject: [PATCH 237/682] Error message updated Updated to be accurate and concise. --- frontend/translations/en.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index ebf0fefaa0..b0135f0eb8 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -813,7 +813,7 @@ msgstr "Unknown token" #: src/app/main/ui/auth/login.cljs msgid "errors.wrong-credentials" -msgstr "Username or password seems to be wrong." +msgstr "Email or password is incorrect." #: src/app/main/ui/settings/password.cljs msgid "errors.wrong-old-password" From 6565655ac357acbe8a8371dc5985b9239036e46c Mon Sep 17 00:00:00 2001 From: Prithvi Tharun Date: Tue, 22 Nov 2022 11:18:35 +0530 Subject: [PATCH 238/682] :bug: Fix the corners icon name Closes #2581 --- frontend/translations/en.po | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index b0135f0eb8..5176b36aed 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3621,7 +3621,7 @@ msgstr "All corners" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.radius.single-corners" -msgstr "Single corners" +msgstr "Individual corners" msgid "workspace.options.recent-fonts" msgstr "Recent" From baf9124304afbd0b924c2f0e1b1aaa9fe592b8e4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 22 Nov 2022 12:05:59 +0100 Subject: [PATCH 239/682] :bug: Fix problem with texts in viewer --- common/src/app/common/geom/shapes/transforms.cljc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index e8c942fd63..43b4dad21d 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -43,11 +43,12 @@ (defn move-position-data [position-data dx dy] - (cond->> position-data - (d/num? dx dy) - (mapv #(-> % - (update :x + dx) - (update :y + dy))))) + (when (some? position-data) + (cond->> position-data + (d/num? dx dy) + (mapv #(-> % + (update :x + dx) + (update :y + dy)))))) (defn move "Move the shape relatively to its current @@ -284,6 +285,8 @@ [shape transform-mtx] (let [bool? (= (:type shape) :bool) path? (= (:type shape) :path) + text? (= (:type shape) :text) + {dx :x dy :y} (gpt/transform (gpt/point) transform-mtx) points (gco/transform-points (:points shape) transform-mtx) selrect (gco/transform-selrect (:selrect shape) transform-mtx)] (-> shape @@ -291,6 +294,8 @@ (update :bool-content gpa/transform-content transform-mtx)) (cond-> path? (update :content gpa/transform-content transform-mtx)) + (cond-> text? + (update :position-data move-position-data dx dy)) (cond-> (not path?) (assoc :x (:x selrect) :y (:y selrect) From 2a46989ec91bcbe0481d9b7c3467f78f6b7f24ae Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 22 Nov 2022 12:06:12 +0100 Subject: [PATCH 240/682] :bug: Fix problem with flex direction --- common/src/app/common/geom/shapes/flex_layout/drop_area.cljc | 4 ++-- common/src/app/common/geom/shapes/flex_layout/lines.cljc | 2 +- common/src/app/common/geom/shapes/modifiers.cljc | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc index 00de11f3a5..b68057a179 100644 --- a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc @@ -141,7 +141,7 @@ [frame layout-data children] (let [reverse? (:reverse? layout-data) - children (vec (cond->> (d/enumerate children) reverse? reverse)) + children (vec (cond->> (d/enumerate children) (not reverse?) reverse)) lines (:layout-lines layout-data)] (loop [areas [] @@ -169,7 +169,7 @@ areas (let [[child-area child-area-start child-area-end] - (drop-child-areas frame line-area child index reverse? prev-child-x prev-child-y (nil? (first children)))] + (drop-child-areas frame line-area child index (not reverse?) prev-child-x prev-child-y (nil? (first children)))] (recur (conj areas child-area-start child-area-end) (+ (:x child-area) (:width child-area)) (+ (:y child-area) (:height child-area)) diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index 5b0fafdbca..41dea20ea0 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -304,7 +304,7 @@ (let [layout-bounds (layout-bounds shape) reverse? (ctl/reverse? shape) - children (cond->> children reverse? reverse) + children (cond->> children (not reverse?) reverse) ;; Creates the layout lines information layout-lines diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 36265536ab..c23e07258e 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -159,7 +159,7 @@ (let [children (map (d/getf objects) (:shapes parent)) children (->> children (map (partial apply-modifiers modif-tree))) layout-data (gcl/calc-layout-data parent children) - children (into [] (cond-> children (:reverse? layout-data) reverse)) + children (into [] (cond-> children (not (:reverse? layout-data)) reverse)) max-idx (dec (count children)) layout-lines (:layout-lines layout-data)] From bbf95434d854d91b3a0ee54cff81c34f2cdae26e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Nov 2022 08:06:01 +0100 Subject: [PATCH 241/682] :tada: Add lazy loading and storage/pointer-map support on workspace This also rewrites the workspace load process making it a bit more efficient independently if lazy loading is used. --- backend/src/app/rpc/commands/files.clj | 94 +++-- backend/src/app/rpc/commands/viewer.clj | 2 +- backend/src/app/rpc/helpers.clj | 14 +- backend/src/app/rpc/queries/files.clj | 9 +- common/src/app/common/transit.cljc | 21 +- common/src/app/common/types/shape_tree.cljc | 6 - frontend/src/app/main/data/workspace.cljs | 352 ++++++++++++------ .../src/app/main/data/workspace/changes.cljs | 2 +- .../src/app/main/data/workspace/common.cljs | 13 - .../app/main/data/workspace/persistence.cljs | 116 +----- frontend/src/app/main/refs.cljs | 3 + frontend/src/app/main/repo.cljs | 1 + frontend/src/app/main/ui/workspace.cljs | 8 +- frontend/src/app/worker/impl.cljs | 36 +- frontend/src/app/worker/selection.cljs | 48 ++- frontend/src/app/worker/snaps.cljs | 11 +- 16 files changed, 387 insertions(+), 349 deletions(-) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 2d354f63d8..1d02eace18 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -17,7 +17,6 @@ [app.common.types.shape-tree :as ctt] [app.db :as db] [app.db.sql :as sql] - [app.rpc :as-alias rpc] [app.rpc.commands.files.thumbnails :as-alias thumbs] [app.rpc.cond :as-alias cond] [app.rpc.doc :as-alias doc] @@ -56,6 +55,11 @@ (s/def ::search-term ::us/string) (s/def ::team-id ::us/uuid) +;; --- HELPERS + +(def long-cache-duration + (dt/duration {:days 7})) + (defn decode-row [{:keys [data changes features] :as row}] (when row @@ -208,6 +212,25 @@ ;; QUERY COMMANDS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- handle-file-features + [{:keys [features] :as file} client-features] + (when (and (contains? features "components/v2") + (not (contains? client-features "components/v2"))) + (ex/raise :type :restriction + :code :feature-mismatch + :feature "components/v2" + :hint "file has 'components/v2' feature enabled but frontend didn't specifies it")) + + (cond-> file + (and (contains? client-features "components/v2") + (not (contains? features "components/v2"))) + (update :data ctf/migrate-to-components-v2) + + (and (contains? features "storage/pointer-map") + (not (contains? client-features "storage/pointer-map"))) + (process-pointers deref))) + + ;; --- COMMAND QUERY: get-file (by id) (defn get-file @@ -216,27 +239,10 @@ (check-features-compatibility! client-features) (binding [pmap/*load-fn* (partial load-pointer conn id)] - (let [file (->> (db/get-by-id conn :file id) - (decode-row) - (pmg/migrate-file)) - features (:features file) - file (cond-> file - (and (contains? client-features "components/v2") - (not (contains? features "components/v2"))) - (update :data ctf/migrate-to-components-v2) - - (and (contains? features "storage/pointer-map") - (not (contains? client-features "storage/pointer-map"))) - (process-pointers deref))] - - (when (and (contains? features "components/v2") - (not (contains? client-features "components/v2"))) - (ex/raise :type :restriction - :code :feature-mismatch - :feature "components/v2" - :hint "file has 'components/v2' feature enabled but frontend didn't specifies it")) - - file))) + (-> (db/get-by-id conn :file id) + (decode-row) + (pmg/migrate-file) + (handle-file-features client-features)))) (defn- get-minimal-file [{:keys [pool] :as cfg} id] @@ -264,6 +270,26 @@ (vary-meta file assoc ::cond/key (get-file-etag file)))))) +;; --- COMMAND QUERY: get-file-fragment (by id) + +(defn- get-file-fragment + [conn file-id fragment-id] + (some-> (db/get conn :file-data-fragment {:file-id file-id :id fragment-id}) + (update :content blob/decode))) + +(s/def ::fragment-id ::us/uuid) +(s/def ::get-file-fragment + (s/keys :req-un [::file-id ::fragment-id ::profile-id])) + +(sv/defmethod ::get-file-fragment + "Retrieve a file by its ID. Only authenticated users." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id file-id fragment-id] :as params}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id file-id) + (-> (get-file-fragment conn file-id fragment-id) + (rph/with-http-cache long-cache-duration)))) + ;; --- COMMAND QUERY: get-file-object-thumbnails (defn get-object-thumbnails @@ -484,6 +510,7 @@ ) SELECT l.id, l.data, + l.features, l.project_id, l.created_at, l.modified_at, @@ -495,22 +522,27 @@ WHERE l.deleted_at IS NULL OR l.deleted_at > now();") (defn get-file-libraries - [conn is-indirect file-id] - (let [xform (comp - (map #(assoc % :is-indirect is-indirect)) - (map decode-row))] - (into #{} xform (db/exec! conn [sql:file-libraries file-id])))) + [conn file-id client-features] + (check-features-compatibility! client-features) + (->> (db/exec! conn [sql:file-libraries file-id]) + (mapv (fn [{:keys [id] :as row}] + (binding [pmap/*load-fn* (partial load-pointer conn id)] + (-> (decode-row row) + (assoc :is-indirect false) + (update :data dissoc :pages-index) + (handle-file-features client-features))))))) (s/def ::get-file-libraries - (s/keys :req-un [::profile-id ::file-id])) + (s/keys :req-un [::profile-id ::file-id] + :opt-un [::features])) (sv/defmethod ::get-file-libraries "Get libraries used by the specified file." {::doc/added "1.17"} - [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id features] :as params}] (with-open [conn (db/open pool)] (check-read-permissions! conn profile-id file-id) - (get-file-libraries conn false file-id))) + (get-file-libraries conn file-id features))) ;; --- COMMAND QUERY: Files that use this File library @@ -607,7 +639,7 @@ (with-open [conn (db/open pool)] (check-read-permissions! conn profile-id file-id) (-> (get-file-thumbnail conn file-id revn) - (with-meta {::rpc/transform-response (rph/http-cache {:max-age (* 1000 60 60)})})))) + (rph/with-http-cache long-cache-duration)))) ;; --- COMMAND QUERY: get-file-data-for-thumbnail diff --git a/backend/src/app/rpc/commands/viewer.clj b/backend/src/app/rpc/commands/viewer.clj index 2d77a7c222..11f1cd75b7 100644 --- a/backend/src/app/rpc/commands/viewer.clj +++ b/backend/src/app/rpc/commands/viewer.clj @@ -26,7 +26,7 @@ (let [file (files/get-file conn file-id features) thumbnails (files/get-object-thumbnails conn file-id) project (get-project conn (:project-id file)) - libs (files/get-file-libraries conn false file-id) + libs (files/get-file-libraries conn file-id features) users (comments/get-file-comments-users conn file-id profile-id) links (->> (db/query conn :share-link {:file-id file-id}) diff --git a/backend/src/app/rpc/helpers.clj b/backend/src/app/rpc/helpers.clj index 2dda8203b6..d68282c087 100644 --- a/backend/src/app/rpc/helpers.clj +++ b/backend/src/app/rpc/helpers.clj @@ -44,13 +44,6 @@ [o] (if (wrapped? o) @o o)) -(defn http-cache - [{:keys [max-age]}] - (fn [_ response] - (let [exp (if (integer? max-age) max-age (inst-ms max-age)) - val (dm/fmt "max-age=%" (int (/ exp 1000.0)))] - (update response :headers assoc "cache-control" val)))) - (defn with-header "Add a http header to the RPC result." [mdw key val] @@ -66,3 +59,10 @@ [mdw hook-fn] (vary-meta mdw update ::rpc/before-complete-fns conj hook-fn)) +(defn with-http-cache + [mdw max-age] + (vary-meta mdw update ::rpc/response-transform-fns conj + (fn [_ response] + (let [exp (if (integer? max-age) max-age (inst-ms max-age)) + val (dm/fmt "max-age=%" (int (/ exp 1000.0)))] + (update response :headers assoc "cache-control" val))))) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 3b7e8052a3..58025328f9 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -8,10 +8,9 @@ (:require [app.common.spec :as us] [app.db :as db] - [app.rpc :as-alias rpc] [app.rpc.commands.files :as cmd.files] [app.rpc.doc :as-alias doc] - [app.rpc.helpers :as rpch] + [app.rpc.helpers :as rph] [app.rpc.queries.projects :as projects] [app.rpc.queries.teams :as teams] [app.util.services :as sv] @@ -127,10 +126,10 @@ (sv/defmethod ::file-libraries {::doc/added "1.3" ::doc/deprecated "1.17"} - [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id features] :as params}] (with-open [conn (db/open pool)] (cmd.files/check-read-permissions! conn profile-id file-id) - (cmd.files/get-file-libraries conn false file-id))) + (cmd.files/get-file-libraries conn file-id features))) ;; --- Query: Files that use this File library @@ -169,4 +168,4 @@ (with-open [conn (db/open pool)] (cmd.files/check-read-permissions! conn profile-id file-id) (-> (cmd.files/get-file-thumbnail conn file-id revn) - (with-meta {::rpc/transform-response (rpch/http-cache {:max-age (* 1000 60 60)})})))) + (rph/with-http-cache cmd.files/long-cache-duration)))) diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc index 1c08c8594c..ad0ba80d18 100644 --- a/common/src/app/common/transit.cljc +++ b/common/src/app/common/transit.cljc @@ -35,7 +35,24 @@ ;; A generic pointer; mainly used for deserialize backend pointer-map ;; instances that serializes to pointer but may in other ways. -(defrecord Pointer [id]) +(deftype Pointer [id metadata] + #?@(:clj + [clojure.lang.IObj + (meta [_] metadata) + (withMeta [_ meta] (Pointer. id meta)) + clojure.lang.IDeref + (deref [_] id)] + :cljs + [cljs.core/IMeta + (-meta [_] metadata) + cljs.core/IWithMeta + (-with-meta [_ meta] (Pointer. id meta)) + cljs.core/IDeref + (-deref [_] id)])) + +(defn pointer? + [o] + (instance? Pointer o)) ;; --- HELPERS @@ -140,7 +157,7 @@ {:id "penpot/pointer" :class Pointer :rfn (fn [[id meta]] - (Pointer. id meta {}))} + (Pointer. id meta))} #?(:clj {:id "m" diff --git a/common/src/app/common/types/shape_tree.cljc b/common/src/app/common/types/shape_tree.cljc index d894d77ef4..6304264a86 100644 --- a/common/src/app/common/types/shape_tree.cljc +++ b/common/src/app/common/types/shape_tree.cljc @@ -278,12 +278,6 @@ [objects] (with-meta objects {::index-frames (get-frames (with-meta objects nil))})) -(defn start-object-indices - [file] - (letfn [(process-index [page-index page-id] - (update-in page-index [page-id :objects] start-page-index))] - (update file :pages-index #(reduce process-index % (keys %))))) - (defn update-object-indices [file page-id] (update-in file [:pages-index page-id :objects] update-page-index)) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index e927d1ec40..0f16dd7d84 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -9,6 +9,7 @@ [app.common.attrs :as attrs] [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.files.features :as ffeat] [app.common.geom.align :as gal] [app.common.geom.point :as gpt] [app.common.geom.proportions :as gpr] @@ -27,16 +28,16 @@ [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] - [app.config :as cfg] + [app.config :as cf] [app.main.data.comments :as dcm] [app.main.data.events :as ev] + [app.main.data.fonts :as df] [app.main.data.messages :as msg] [app.main.data.modal :as modal] [app.main.data.users :as du] [app.main.data.workspace.bool :as dwb] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwco] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.drawing :as dwd] [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.fix-bool-contents :as fbc] @@ -64,6 +65,7 @@ [app.main.features :as features] [app.main.repo :as rp] [app.main.streams :as ms] + [app.main.worker :as uw] [app.util.dom :as dom] [app.util.globals :as ug] [app.util.http :as http] @@ -77,26 +79,27 @@ [linked.core :as lks] [potok.core :as ptk])) -(s/def ::shape-attrs ::cts/shape-attrs) -(s/def ::set-of-string - (s/every string? :kind set?)) +(def default-workspace-local {:zoom 1}) + +(s/def ::layout-name (s/nilable ::us/keyword)) +(s/def ::coll-of-uuids (s/coll-of ::us/uuid)) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Workspace Initialization ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(declare file-initialized) -(declare remove-graphics) +(declare ^:private workspace-initialized) +(declare ^:private remove-graphics) +(declare ^:private libraries-fetched) ;; --- Initialize Workspace -(def default-workspace-local - {:zoom 1}) -(defn initialize +(defn initialize-layout [lname] - (us/verify (s/nilable ::us/keyword) lname) - (ptk/reify ::initialize + (us/assert! ::layout-name lname) + (ptk/reify ::initialize-layout ptk/UpdateEvent (update [_ state] (-> state @@ -109,10 +112,168 @@ (rx/of (layout/ensure-layout lname)) (rx/of (layout/ensure-layout :layers)))))) +(defn- workspace-initialized + [] + (ptk/reify ::workspace-initialized + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc :workspace-undo {}) + (assoc :workspace-ready? true))) + + ptk/WatchEvent + (watch [_ state _] + (let [file (:workspace-file state) + has-graphics? (-> file :data :media seq) + components-v2 (features/active-feature? state :components-v2)] + (rx/merge + (rx/of (fbc/fix-bool-contents)) + (if (and has-graphics? components-v2) + (rx/of (remove-graphics (:id file) (:name file))) + (rx/empty))))))) + +(defn- workspace-data-loaded + [data] + (ptk/reify ::workspace-data-loaded + ptk/UpdateEvent + (update [_ state] + (let [data (d/removem (comp t/pointer? val) data)] + (assoc state :workspace-data data))))) + +(defn- workspace-data-pointers-loaded + [pdata] + (ptk/reify ::workspace-data-pointers-loaded + ptk/UpdateEvent + (update [_ state] + (update state :workspace-data merge pdata)))) + +(defn- bundle-fetched + [features [{:keys [id data] :as file} thumbnails project users comments-users]] + (letfn [(resolve-pointer [[key pointer]] + (->> (rp/cmd! :get-file-fragment {:file-id id :fragment-id @pointer}) + (rx/map :content) + (rx/map #(vector key %)))) + (resolve-pointers [in-to coll] + (->> (rx/from (seq coll)) + (rx/merge-map resolve-pointer) + (rx/reduce conj in-to)))] + + (ptk/reify ::bundle-fetched + ptk/UpdateEvent + (update [_ state] + (-> state + (assoc :workspace-thumbnails thumbnails) + (assoc :workspace-file (dissoc file :data)) + (assoc :workspace-project project) + (assoc :current-team-id (:team-id project)) + (assoc :users (d/index-by :id users)) + (assoc :current-file-comments-users (d/index-by :id comments-users)))) + + ptk/WatchEvent + (watch [_ _ stream] + (let [team-id (:team-id project) + stoper (rx/filter (ptk/type? ::bundle-fetched) stream)] + (->> (rx/merge + ;; Initialize notifications & load team fonts + (rx/of (dwn/initialize team-id id) + (df/load-team-fonts team-id)) + + ;; Load all pages, independently if they are pointers or already + ;; resolved values. + (->> (rx/from (seq (:pages-index data))) + (rx/merge-map + (fn [[_ page :as kp]] + (if (t/pointer? page) + (resolve-pointer kp) + (rx/of kp)))) + (rx/merge-map + (fn [[id page]] + (let [page (update page :objects ctst/start-page-index)] + (->> (uw/ask! {:cmd :initialize-page-index :page page}) + (rx/map (constantly [id page])))))) + (rx/reduce conj {}) + (rx/map (fn [pages-index] + (-> data + (assoc :pages-index pages-index) + (workspace-data-loaded))))) + + ;; Once workspace data is loaded, proceed asynchronously load + ;; the local library and all referenced libraries, without + ;; blocking the main workspace load process. + (->> stream + (rx/filter (ptk/type? ::workspace-data-loaded)) + (rx/take 1) + (rx/merge-map + (fn [_] + (rx/merge + (rx/of (workspace-initialized)) + + (->> data + (filter (comp t/pointer? val)) + (resolve-pointers {}) + (rx/map workspace-data-pointers-loaded)) + + (->> (rp/cmd! :get-file-libraries {:file-id id :features features}) + (rx/mapcat identity) + (rx/merge-map + (fn [file] + (->> (filter (comp t/pointer? val) file) + (resolve-pointers file)))) + (rx/reduce conj []) + (rx/map libraries-fetched))))))) + + (rx/take-until stoper))))))) + +(defn- libraries-fetched + [libraries] + (ptk/reify ::libraries-fetched + ptk/UpdateEvent + (update [_ state] + (assoc state :workspace-libraries (d/index-by :id libraries))) + + ptk/WatchEvent + (watch [_ state _] + (let [ignore-until (-> state :workspace-file :ignore-sync-until) + file-id (-> state :workspace-file :id) + needs-update? (some #(and (> (:modified-at %) (:synced-at %)) + (or (not ignore-until) + (> (:modified-at %) ignore-until))) + libraries)] + (when needs-update? + (rx/of (dwl/notify-sync-file file-id))))))) + +(defn- fetch-bundle + [project-id file-id] + (ptk/reify ::fetch-bundle + ptk/WatchEvent + (watch [_ state stream] + (let [features (cond-> ffeat/enabled + (features/active-feature? state :components-v2) + (conj "components/v2") + + ;; We still put the feature here and not in the + ;; ffeat/enabled var because the pointers map is only + ;; supported on workspace bundle fetching mechanism. + :always + (conj "storage/pointer-map")) + + ;; WTF is this? + share-id (-> state :viewer-local :share-id) + stoper (rx/filter (ptk/type? ::fetch-bundle) stream)] + + (->> (rx/zip (rp/cmd! :get-file {:id file-id :features features}) + (rp/cmd! :get-file-object-thumbnails {:file-id file-id}) + (rp/query! :project {:id project-id}) + (rp/query! :team-users {:file-id file-id}) + (rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id})) + (rx/take 1) + (rx/map (partial bundle-fetched features)) + (rx/take-until stoper)))))) + (defn initialize-file [project-id file-id] - (us/verify ::us/uuid project-id) - (us/verify ::us/uuid file-id) + (us/assert! ::us/uuid project-id) + (us/assert! ::us/uuid file-id) (ptk/reify ::initialize-file ptk/UpdateEvent @@ -123,80 +284,16 @@ :workspace-presence {})) ptk/WatchEvent - (watch [_ _ stream] - (rx/merge - (rx/of (dwp/fetch-bundle project-id file-id) - (dcm/retrieve-comment-threads file-id)) - - ;; Initialize notifications (websocket connection) and the file persistence - (->> stream - (rx/filter (ptk/type? ::dwp/bundle-fetched)) - (rx/take 1) - (rx/map deref) - (rx/mapcat - (fn [bundle] - (rx/merge - (rx/of (dwc/initialize-indices bundle)) - - (->> (rx/of bundle) - (rx/mapcat - (fn [bundle] - (let [file (-> bundle :file-raw t/decode-str) - bundle (assoc bundle :file file) - team-id (dm/get-in bundle [:project :team-id])] - (rx/merge - (rx/of (dwn/initialize team-id file-id) - (dwp/initialize-file-persistence file-id)) - (->> stream - (rx/filter #(= ::dwc/index-initialized %)) - (rx/take 1) - (rx/map #(file-initialized bundle)))))))))))))) + (watch [_ _ _] + (rx/of (dcm/retrieve-comment-threads file-id) + (dwp/initialize-file-persistence file-id) + (fetch-bundle project-id file-id))) ptk/EffectEvent (effect [_ _ _] (let [name (str "workspace-" file-id)] (unchecked-set ug/global "name" name))))) -(defn- file-initialized - [{:keys [file thumbnails users project libraries file-comments-users] :as bundle}] - (ptk/reify ::file-initialized - ptk/UpdateEvent - (update [_ state] - (assoc state - :current-team-id (:team-id project) - :users (d/index-by :id users) - :workspace-undo {} - :workspace-project project - :workspace-file (assoc file :initialized true) - :workspace-thumbnails thumbnails - :workspace-data (-> (:data file) - (ctst/start-object-indices) - ;; DEBUG: Uncomment this to try out migrations in local without changing - ;; the version number - #_(assoc :version 17) - #_(app.common.pages.migrations/migrate-data 19)) - :workspace-libraries (d/index-by :id libraries) - :current-file-comments-users (d/index-by :id file-comments-users))) - - ptk/WatchEvent - (watch [_ state _] - (let [file-id (:id file) - ignore-until (:ignore-sync-until file) - some-graphics? (some? (-> file :data :media)) - needs-update? (some #(and (> (:modified-at %) (:synced-at %)) - (or (not ignore-until) - (> (:modified-at %) ignore-until))) - libraries) - components-v2 (features/active-feature? state :components-v2)] - (rx/merge - (rx/of (fbc/fix-bool-contents)) - (if (and some-graphics? components-v2) - (rx/of (remove-graphics (:id file) (:name file))) - (rx/empty)) - (if needs-update? - (rx/of (dwl/notify-sync-file file-id)) - (rx/empty))))))) - (defn finalize-file [_project-id file-id] (ptk/reify ::finalize-file @@ -209,6 +306,7 @@ :workspace-editor-state :workspace-file :workspace-libraries + :workspace-ready? :workspace-media-objects :workspace-persistence :workspace-presence @@ -224,55 +322,73 @@ (rx/observe-on :async)))))) (declare go-to-page) +(declare ^:private preload-data-uris) (defn initialize-page [page-id] - (us/assert ::us/uuid page-id) + (us/assert! ::us/uuid page-id) (ptk/reify ::initialize-page - ptk/WatchEvent - (watch [_ state _] - (if (contains? (get-in state [:workspace-data :pages-index]) page-id) - (rx/of (dwp/preload-data-uris) - (dwth/watch-state-changes) - (dwl/watch-component-changes)) - (let [default-page-id (get-in state [:workspace-data :pages 0])] - (rx/of (go-to-page default-page-id))))) - ptk/UpdateEvent (update [_ state] - (if-let [{:keys [id] :as page} (get-in state [:workspace-data :pages-index page-id])] - ;; we maintain a cache of page state for user convenience with - ;; the exception of the selection; when user abandon the - ;; current page, the selection is lost - (let [local (-> state - (get-in [:workspace-cache id] default-workspace-local) - (assoc :selected (d/ordered-set)))] + (if-let [{:keys [id] :as page} (dm/get-in state [:workspace-data :pages-index page-id])] + ;; we maintain a cache of page state for user convenience with the exception of the + ;; selection; when user abandon the current page, the selection is lost + (let [local (dm/get-in state [:workspace-cache id] default-workspace-local)] (-> state (assoc :current-page-id id) - (assoc :trimmed-page (dm/select-keys page [:id :name])) - (assoc :workspace-local local) + (assoc :workspace-local (assoc local :selected (d/ordered-set))) + (assoc :workspace-trimmed-page (dm/select-keys page [:id :name])) + + ;; FIXME: this should be done on `initialize-layout` (?) (update :workspace-layout layout/load-layout-flags) (update :workspace-global layout/load-layout-state) + (update :workspace-global assoc :background-color (-> page :options :background)) (update-in [:route :params :query] assoc :page-id (dm/str id)))) - state)))) + + state)) + + ptk/WatchEvent + (watch [_ state _] + (let [pindex (-> state :workspace-data :pages-index)] + (if (contains? pindex page-id) + (rx/of (preload-data-uris page-id) + (dwth/watch-state-changes) + (dwl/watch-component-changes)) + (let [page-id (dm/get-in state [:workspace-data :pages 0])] + (rx/of (go-to-page page-id)))))))) (defn finalize-page [page-id] - (us/assert ::us/uuid page-id) + (us/assert! ::us/uuid page-id) (ptk/reify ::finalize-page ptk/UpdateEvent (update [_ state] (let [local (-> (:workspace-local state) - (dissoc :edition - :edit-path - :selected)) - exit-workspace? (not= :workspace (get-in state [:route :data :name]))] - (cond-> (assoc-in state [:workspace-cache page-id] local) - :always - (dissoc :current-page-id :workspace-local :trimmed-page :workspace-focus-selected) - exit-workspace? - (dissoc :workspace-drawing)))))) + (dissoc :edition :edit-path :selected)) + exit? (not= :workspace (dm/get-in state [:route :data :name])) + state (-> state + (update :workspace-cache assoc page-id local) + (dissoc :current-page-id :workspace-local :workspace-trimmed-page :workspace-focus-selected))] + + (cond-> state + exit? (dissoc :workspace-drawing)))))) + +(defn- preload-data-uris + "Preloads the image data so it's ready when necessary" + [page-id] + (ptk/reify ::preload-data-uris + ptk/EffectEvent + (effect [_ state _] + (let [xform (comp (map second) + (keep (fn [{:keys [metadata fill-image]}] + (cond + (some? metadata) (cf/resolve-file-media metadata) + (some? fill-image) (cf/resolve-file-media fill-image))))) + uris (into #{} xform (wsh/lookup-page-objects state page-id))] + + (->> (rx/from uris) + (rx/subs #(http/fetch-data-uri % false))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Workspace Page CRUD @@ -426,7 +542,7 @@ (defn update-shape [id attrs] (us/verify ::us/uuid id) - (us/verify ::shape-attrs attrs) + (us/verify ::cts/shape-attrs attrs) (ptk/reify ::update-shape ptk/WatchEvent (watch [_ _ _] @@ -452,7 +568,7 @@ (defn update-selected-shapes [attrs] - (us/verify ::shape-attrs attrs) + (us/verify ::cts/shape-attrs attrs) (ptk/reify ::update-selected-shapes ptk/WatchEvent (watch [_ state _] @@ -868,7 +984,7 @@ qparams {:page-id page-id}] (rx/of (rt/nav' :workspace pparams qparams)))))) ([page-id] - (us/verify ::us/uuid page-id) + (us/assert! ::us/uuid page-id) (ptk/reify ::go-to-page-2 ptk/WatchEvent (watch [_ state _] @@ -1135,7 +1251,7 @@ (prepare-object [objects selected+children {:keys [type] :as obj}] (let [obj (maybe-translate obj objects selected+children)] (if (= type :image) - (let [url (cfg/resolve-file-media (:metadata obj))] + (let [url (cf/resolve-file-media (:metadata obj))] (->> (http/send! {:method :get :uri url :response-type :blob}) @@ -1466,7 +1582,7 @@ (defn paste-text [text] - (us/assert string? text) + (us/assert! (string? text) "expected string as first argument") (ptk/reify ::paste-text ptk/WatchEvent (watch [_ state _] @@ -1496,7 +1612,7 @@ ;; TODO: why not implement it in terms of upload-media-workspace? (defn- paste-svg [text] - (us/assert string? text) + (us/assert! (string? text) "expected string as first argument") (ptk/reify ::paste-svg ptk/WatchEvent (watch [_ state _] diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index 0b68bd1b78..b00c2904e7 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -78,7 +78,7 @@ ptk/EffectEvent (effect [_ state _] (doseq [[page-id changes] (::update-changes state)] - (uw/ask! {:cmd :update-page-indices + (uw/ask! {:cmd :update-page-index :page-id page-id :changes changes}))))) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index bfddb12ba7..a9df53834e 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -9,7 +9,6 @@ [app.common.logging :as log] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.undo :as dwu] - [app.main.worker :as uw] [beicon.core :as rx] [potok.core :as ptk])) @@ -27,18 +26,6 @@ (defn interrupt? [e] (= e :interrupt)) -;; --- Selection Index Handling - -(defn initialize-indices - [{:keys [file-raw] :as bundle}] - (ptk/reify ::setup-selection-index - ptk/WatchEvent - (watch [_ _ _] - (let [msg {:cmd :initialize-indices - :file-raw file-raw}] - (->> (uw/ask! msg) - (rx/map (constantly ::index-initialized))))))) - ;; These functions should've been in `src/app/main/data/workspace/undo.cljs` but doing that causes ;; a circular dependency with `src/app/main/data/workspace/changes.cljs` (def undo diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 4a85fcae1a..47c80be58c 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -8,24 +8,17 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.files.features :as ffeat] [app.common.logging :as log] [app.common.pages :as cp] [app.common.pages.changes-spec :as pcs] [app.common.spec :as us] - [app.common.types.file :as ctf] [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] - [app.config :as cf] - [app.main.data.dashboard :as dd] - [app.main.data.fonts :as df] [app.main.data.workspace.changes :as dch] - [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.features :as features] [app.main.repo :as rp] [app.main.store :as st] - [app.util.http :as http] [app.util.router :as rt] [app.util.time :as dt] [beicon.core :as rx] @@ -156,9 +149,10 @@ (->> (rp/cmd! :update-file params) (rx/mapcat (fn [lagged] (log/debug :hint "changes persisted" :lagged (count lagged)) - (let [lagged (cond->> lagged - (= #{sid} (into #{} (map :session-id) lagged)) - (map #(assoc % :changes []))) + (let [lagged-updates + (cond->> lagged + (= #{sid} (into #{} (map :session-id) lagged)) + (map #(assoc % :changes []))) frame-updates (-> (group-by :page-id changes) @@ -166,10 +160,10 @@ (rx/merge (->> (rx/from frame-updates) - (rx/flat-map (fn [[page-id frames]] - (->> frames (map #(vector page-id %))))) + (rx/mapcat (fn [[page-id frames]] + (->> frames (map #(vector page-id %))))) (rx/map (fn [[page-id frame-id]] (dwt/update-thumbnail (:id file) page-id frame-id)))) - (->> (rx/of lagged) + (->> (rx/of lagged-updates) (rx/mapcat seq) (rx/map #(shapes-changes-persisted file-id %))))))) (rx/catch (fn [cause] @@ -179,7 +173,6 @@ (rx/of (rt/assign-exception cause))) (rx/throw cause)))))))))) - (defn persist-synchronous-changes [{:keys [file-id changes]}] (us/verify ::us/uuid file-id) @@ -202,7 +195,6 @@ (->> (rp/mutation :update-file params) (rx/ignore))))))) - (defn update-persistence-status [{:keys [status reason]}] (ptk/reify ::update-persistence-status @@ -215,6 +207,7 @@ :status status :updated-at (dt/now))))))) +(s/def ::revn ::us/integer) (s/def ::shapes-changes-persisted (s/keys :req-un [::revn ::pcs/changes])) @@ -223,8 +216,8 @@ (defn shapes-changes-persisted [file-id {:keys [revn changes] :as params}] - (us/verify ::us/uuid file-id) - (us/verify ::shapes-changes-persisted params) + (us/verify! ::us/uuid file-id) + (us/verify! ::shapes-changes-persisted params) (ptk/reify ::changes-persisted ptk/UpdateEvent (update [_ state] @@ -249,94 +242,5 @@ (update-in [:workspace-libraries file-id :data] cp/process-changes changes))))))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Data Fetching & Uploading -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; --- Specs -(s/def ::id ::us/uuid) -(s/def ::profile-id ::us/uuid) -(s/def ::name string?) -(s/def ::type keyword?) -(s/def ::file-id ::us/uuid) -(s/def ::created-at ::us/inst) -(s/def ::modified-at ::us/inst) -(s/def ::version ::us/integer) -(s/def ::revn ::us/integer) -(s/def ::ordering ::us/integer) -(s/def ::data ::ctf/data) - -(s/def ::file ::dd/file) -(s/def ::project ::dd/project) -(s/def ::page - (s/keys :req-un [::id - ::name - ::file-id - ::revn - ::created-at - ::modified-at - ::ordering - ::data])) - -(declare fetch-libraries-content) -(declare bundle-fetched) - -(defn fetch-bundle - [project-id file-id] - (ptk/reify ::fetch-bundle - ptk/WatchEvent - (watch [_ state _] - (let [share-id (-> state :viewer-local :share-id) - features (cond-> ffeat/enabled - (features/active-feature? state :components-v2) - (conj "components/v2"))] - (->> (rx/zip (rp/cmd! :get-raw-file {:id file-id :features features}) - (rp/cmd! :get-file-object-thumbnails {:file-id file-id}) - (rp/query! :team-users {:file-id file-id}) - (rp/query! :project {:id project-id}) - (rp/cmd! :get-file-libraries {:file-id file-id}) - (rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id})) - (rx/take 1) - (rx/map (fn [[file-raw thumbnails users project libraries file-comments-users]] - {:file-raw file-raw - :thumbnails thumbnails - :users users - :project project - :libraries libraries - :file-comments-users file-comments-users})) - (rx/mapcat (fn [{:keys [project] :as bundle}] - (rx/of (ptk/data-event ::bundle-fetched bundle) - (df/load-team-fonts (:team-id project)))))))))) - -;; --- Helpers - -(defn purge-page - "Remove page and all related stuff from the state." - [state id] - (-> state - (update-in [:workspace-file :pages] #(filterv (partial not= id) %)) - (update :workspace-pages dissoc id))) - -(defn preload-data-uris - "Preloads the image data so it's ready when necessary" - [] - (ptk/reify ::preload-data-uris - ptk/WatchEvent - (watch [_ state _] - (let [extract-urls - (fn [{:keys [metadata fill-image]}] - (cond - (some? metadata) - [(cf/resolve-file-media metadata)] - - (some? fill-image) - [(cf/resolve-file-media fill-image)])) - - uris (into #{} - (comp (mapcat extract-urls) - (filter some?)) - (vals (wsh/lookup-page-objects state)))] - (->> (rx/from uris) - (rx/merge-map #(http/fetch-data-uri % false)) - (rx/ignore)))))) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 3ff68f8fd1..9711aeefb3 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -101,6 +101,9 @@ (def workspace-drawing (l/derived :workspace-drawing st/state)) +(def workspace-ready? + (l/derived :workspace-ready? st/state)) + ;; TODO: rename to workspace-selected (?) ;; Don't use directly from components, this is a proxy to improve performance of selected-shapes (def ^:private selected-shapes-data diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 3e6cba60f5..d0876afbad 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -15,6 +15,7 @@ (derive :get-file ::query) (derive :get-file-object-thumbnails ::query) (derive :get-file-libraries ::query) +(derive :get-file-fragment ::query) (defn handle-response [{:keys [status body] :as response}] diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index bfcca5b485..05e5e57831 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -90,7 +90,7 @@ :selected selected :layout layout}]])])) -(def trimmed-page-ref (l/derived :trimmed-page st/state =)) +(def trimmed-page-ref (l/derived :workspace-trimmed-page st/state =)) (mf/defc workspace-page [{:keys [file layout page-id wglobal] :as props}] @@ -121,6 +121,7 @@ project (mf/deref refs/workspace-project) layout (mf/deref refs/workspace-layout) wglobal (mf/deref refs/workspace-global) + ready? (mf/deref refs/workspace-ready?) components-v2 (features/use-feature :components-v2) @@ -128,7 +129,7 @@ ;; Setting the layout preset by its name (mf/with-effect [layout-name] - (st/emit! (dw/initialize layout-name))) + (st/emit! (dw/initialize-layout layout-name))) (mf/with-effect [project-id file-id] (st/emit! (dw/initialize-file project-id file-id)) @@ -160,8 +161,7 @@ [:& context-menu] - (if (and (and file project) - (:initialized file)) + (if ready? [:& workspace-page {:key (dm/str "page-" page-id) :page-id page-id :file file diff --git a/frontend/src/app/worker/impl.cljs b/frontend/src/app/worker/impl.cljs index 4e19add0e2..fa2dddef33 100644 --- a/frontend/src/app/worker/impl.cljs +++ b/frontend/src/app/worker/impl.cljs @@ -9,7 +9,6 @@ [app.common.data.macros :as dm] [app.common.logging :as log] [app.common.pages.changes :as ch] - [app.common.transit :as t] [app.config :as cf] [okulary.core :as l])) @@ -31,29 +30,24 @@ [message] message) -(defmethod handler :initialize-indices - [{:keys [file-raw] :as message}] +(defmethod handler :initialize-page-index + [{:keys [page] :as message}] + (swap! state update :pages-index assoc (:id page) page) + (handler (assoc message :cmd :selection/initialize-page-index)) + (handler (assoc message :cmd :snaps/initialize-page-index))) - (let [data (-> (t/decode-str file-raw) :data) - message (assoc message :data data)] - (reset! state data) - (handler (assoc message :cmd :selection/initialize-index)) - (handler (assoc message :cmd :snaps/initialize-index)))) - -(defmethod handler :update-page-indices +(defmethod handler :update-page-index [{:keys [page-id changes] :as message}] - (let [old-page (dm/get-in @state [:pages-index page-id])] - (swap! state ch/process-changes changes false) - - (let [new-page (dm/get-in @state [:pages-index page-id]) - message (assoc message - :old-page old-page - :new-page new-page)] - (handler (-> message - (assoc :cmd :selection/update-index))) - (handler (-> message - (assoc :cmd :snaps/update-index)))))) + (let [old-page (dm/get-in @state [:pages-index page-id]) + new-page (-> state + (swap! ch/process-changes changes false) + (dm/get-in [:pages-index page-id])) + message (assoc message + :old-page old-page + :new-page new-page)] + (handler (assoc message :cmd :selection/update-page-index)) + (handler (assoc message :cmd :snaps/update-page-index)))) (defmethod handler :configure [{:keys [key val]}] diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs index 05e73e4638..1d592c7f9b 100644 --- a/frontend/src/app/worker/selection.cljs +++ b/frontend/src/app/worker/selection.cljs @@ -153,40 +153,34 @@ result))) -(defmethod impl/handler :selection/initialize-index - [{:keys [data] :as message}] - (letfn [(index-page [state page] - (let [id (:id page) - objects (:objects page)] - (assoc state id (create-index objects)))) - - (update-state [state] - (reduce index-page state (vals (:pages-index data))))] - (swap! state update-state) +(defmethod impl/handler :selection/initialize-page-index + [{:keys [page] :as message}] + (letfn [(add-page [state {:keys [id objects] :as page}] + (assoc state id (create-index objects)))] + (swap! state add-page page) nil)) -(defmethod impl/handler :selection/update-index +(defmethod impl/handler :selection/update-page-index [{:keys [page-id old-page new-page] :as message}] - (let [old-objects (:objects old-page) - new-objects (:objects new-page) - update-page-index - (fn [index] - (let [old-bounds (:bounds index) - new-bounds (objects-bounds new-objects)] + (swap! state update page-id + (fn [index] + (let [old-objects (:objects old-page) + new-objects (:objects new-page) + old-bounds (:bounds index) + new-bounds (objects-bounds new-objects)] - ;; If the new bounds are contained within the old bounds we can - ;; update the index. - ;; Otherwise we need to re-create it - (if (and (some? index) - (gsh/contains-selrect? old-bounds new-bounds)) - (update-index index old-objects new-objects) - (create-index new-objects))))] - (swap! state update page-id update-page-index)) + ;; If the new bounds are contained within the old bounds + ;; we can update the index. Otherwise we need to + ;; re-create it. + (if (and (some? index) + (gsh/contains-selrect? old-bounds new-bounds)) + (update-index index old-objects new-objects) + (create-index new-objects))))) nil) (defmethod impl/handler :selection/query [{:keys [page-id rect frame-id full-frame? include-frames? ignore-groups? clip-children?] - :or {full-frame? false include-frames? false clip-children? true} :as message}] + :or {full-frame? false include-frames? false clip-children? true} + :as message}] (when-let [index (get @state page-id)] (query-index index rect frame-id full-frame? include-frames? ignore-groups? clip-children?))) - diff --git a/frontend/src/app/worker/snaps.cljs b/frontend/src/app/worker/snaps.cljs index f96cba02a1..9c99779cbb 100644 --- a/frontend/src/app/worker/snaps.cljs +++ b/frontend/src/app/worker/snaps.cljs @@ -13,15 +13,12 @@ (defonce state (l/atom {})) ;; Public API -(defmethod impl/handler :snaps/initialize-index - [{:keys [data] :as message}] - - (let [pages (vals (:pages-index data))] - (reset! state (reduce sd/add-page (sd/make-snap-data) pages))) - +(defmethod impl/handler :snaps/initialize-page-index + [{:keys [page] :as message}] + (swap! state sd/add-page page) nil) -(defmethod impl/handler :snaps/update-index +(defmethod impl/handler :snaps/update-page-index [{:keys [old-page new-page] :as message}] (swap! state sd/update-page old-page new-page) nil) From fbd042d4ee91823290ba71188feac73314139219 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Nov 2022 08:06:15 +0100 Subject: [PATCH 242/682] :bug: Remove not working chrono-units on duration constructor --- backend/src/app/util/time.clj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/backend/src/app/util/time.clj b/backend/src/app/util/time.clj index 69da4c847f..8c9e4f0994 100644 --- a/backend/src/app/util/time.clj +++ b/backend/src/app/util/time.clj @@ -39,9 +39,7 @@ :seconds ChronoUnit/SECONDS :minutes ChronoUnit/MINUTES :hours ChronoUnit/HOURS - :days ChronoUnit/DAYS - :weeks ChronoUnit/WEEKS - :months ChronoUnit/MONTHS))) + :days ChronoUnit/DAYS))) ;; --- DURATION From c72be4ae2a33254cae4775df7cc22589a8a6ee50 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Nov 2022 08:08:18 +0100 Subject: [PATCH 243/682] :arrow_up: Update redis and postgresql on devenv docker --- docker/devenv/docker-compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml index b06f5e0bb6..f16c80f220 100644 --- a/docker/devenv/docker-compose.yaml +++ b/docker/devenv/docker-compose.yaml @@ -97,7 +97,7 @@ services: # - "8080:8080" postgres: - image: postgres:13 + image: postgres:15 command: postgres -c config_file=/etc/postgresql.conf restart: always stop_signal: SIGINT @@ -112,7 +112,7 @@ services: - postgres_data:/var/lib/postgresql/data redis: - image: redis:5.0.7 + image: redis:7 hostname: "penpot-devenv-redis" container_name: "penpot-devenv-redis" restart: always From ccb7c466bf28e6e29fabb91a84168591f3c35b38 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sat, 19 Nov 2022 11:02:34 +0100 Subject: [PATCH 244/682] :tada: Add lazy loading and storage/pointer-map support on viewer --- backend/src/app/rpc/commands/files.clj | 25 ++++--- backend/src/app/rpc/commands/viewer.clj | 68 +++++++++---------- backend/src/app/srepl/main.clj | 5 ++ .../test/backend_tests/rpc_viewer_test.clj | 2 - frontend/src/app/main/data/viewer.cljs | 65 +++++++++++------- frontend/src/app/main/data/workspace.cljs | 3 +- .../app/main/ui/workspace/shapes/frame.cljs | 2 +- 7 files changed, 98 insertions(+), 72 deletions(-) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 1d02eace18..29bb36a9c1 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -116,6 +116,7 @@ :can-edit (or is-owner is-admin can-edit) :can-read true :is-logged (some? profile-id)}))) + ([conn profile-id file-id share-id] (let [perms (get-permissions conn profile-id file-id) ldata (retrieve-share-link conn file-id share-id)] @@ -128,6 +129,7 @@ (some? perms) perms (some? ldata) {:type :share-link :can-read true + :pages (:pages ldata) :is-logged (some? profile-id) :who-comment (:who-comment ldata) :who-inspect (:who-inspect ldata)})))) @@ -212,7 +214,7 @@ ;; QUERY COMMANDS ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- handle-file-features +(defn handle-file-features [{:keys [features] :as file} client-features] (when (and (contains? features "components/v2") (not (contains? client-features "components/v2"))) @@ -244,11 +246,11 @@ (pmg/migrate-file) (handle-file-features client-features)))) -(defn- get-minimal-file +(defn get-minimal-file [{:keys [pool] :as cfg} id] (db/get pool :file {:id id} {:columns [:id :modified-at :revn]})) -(defn- get-file-etag +(defn get-file-etag [{:keys [modified-at revn]}] (str (dt/format-instant modified-at :iso) "-" revn)) @@ -277,18 +279,23 @@ (some-> (db/get conn :file-data-fragment {:file-id file-id :id fragment-id}) (update :content blob/decode))) +(s/def ::share-id ::us/uuid) (s/def ::fragment-id ::us/uuid) + (s/def ::get-file-fragment - (s/keys :req-un [::file-id ::fragment-id ::profile-id])) + (s/keys :req-un [::file-id ::fragment-id] + :opt-un [::share-id ::profile-id])) (sv/defmethod ::get-file-fragment "Retrieve a file by its ID. Only authenticated users." - {::doc/added "1.17"} - [{:keys [pool] :as cfg} {:keys [profile-id file-id fragment-id] :as params}] + {::doc/added "1.17" + :auth false} + [{:keys [pool] :as cfg} {:keys [profile-id file-id fragment-id share-id] :as params}] (with-open [conn (db/open pool)] - (check-read-permissions! conn profile-id file-id) - (-> (get-file-fragment conn file-id fragment-id) - (rph/with-http-cache long-cache-duration)))) + (let [perms (get-permissions conn profile-id file-id share-id)] + (check-read-permissions! perms) + (-> (get-file-fragment conn file-id fragment-id) + (rph/with-http-cache long-cache-duration))))) ;; --- COMMAND QUERY: get-file-object-thumbnails diff --git a/backend/src/app/rpc/commands/viewer.clj b/backend/src/app/rpc/commands/viewer.clj index 11f1cd75b7..68cd69f888 100644 --- a/backend/src/app/rpc/commands/viewer.clj +++ b/backend/src/app/rpc/commands/viewer.clj @@ -10,6 +10,7 @@ [app.db :as db] [app.rpc.commands.comments :as comments] [app.rpc.commands.files :as files] + [app.rpc.cond :as-alias cond] [app.rpc.doc :as-alias doc] [app.rpc.queries.share-link :as slnk] [app.util.services :as sv] @@ -23,57 +24,52 @@ (defn- get-bundle [conn file-id profile-id features] - (let [file (files/get-file conn file-id features) - thumbnails (files/get-object-thumbnails conn file-id) - project (get-project conn (:project-id file)) - libs (files/get-file-libraries conn file-id features) - users (comments/get-file-comments-users conn file-id profile-id) + (let [file (files/get-file conn file-id features) + project (get-project conn (:project-id file)) + libs (files/get-file-libraries conn file-id features) + users (comments/get-file-comments-users conn file-id profile-id) - links (->> (db/query conn :share-link {:file-id file-id}) - (mapv slnk/decode-share-link-row)) + links (->> (db/query conn :share-link {:file-id file-id}) + (mapv slnk/decode-share-link-row)) - fonts (db/query conn :team-font-variant - {:team-id (:team-id project) - :deleted-at nil})] - {:file (assoc file :thumbnails thumbnails) + fonts (db/query conn :team-font-variant + {:team-id (:team-id project) + :deleted-at nil})] + + {:file file :users users :fonts fonts :project project :share-links links :libraries libs})) +(defn- remove-not-allowed-pages + [data allowed] + (-> data + (update :pages (fn [pages] (filterv #(contains? allowed %) pages))) + (update :pages-index select-keys allowed))) + (defn get-view-only-bundle [conn {:keys [profile-id file-id share-id features] :as params}] - (let [slink (slnk/retrieve-share-link conn file-id share-id) - perms (files/get-permissions conn profile-id file-id share-id) - thumbs (files/get-object-thumbnails conn file-id) + (let [perms (files/get-permissions conn profile-id file-id share-id) bundle (-> (get-bundle conn file-id profile-id features) - (assoc :permissions perms) - (assoc-in [:file :thumbnails] thumbs))] + (assoc :permissions perms))] ;; When we have neither profile nor share, we just return a not ;; found response to the user. - (when (and (not profile-id) - (not slink)) + (when-not perms (ex/raise :type :not-found - :code :object-not-found)) + :code :object-not-found + :hint "object not found")) - ;; When we have only profile, we need to check read permissions - ;; on file. - (when (and profile-id (not slink)) - (files/check-read-permissions! conn profile-id file-id)) + (update bundle :file + (fn [file] + (cond-> file + (= :share-link (:type perms)) + (update :data remove-not-allowed-pages (:pages perms)) - (cond-> bundle - (some? slink) - (assoc :share slink) - - (and (some? slink) - (not (contains? (:flags slink) "view-all-pages"))) - (update-in [:file :data] (fn [data] - (let [allowed-pages (:pages slink)] - (-> data - (update :pages (fn [pages] (filterv #(contains? allowed-pages %) pages))) - (update :pages-index (fn [index] (select-keys index allowed-pages)))))))))) + :always + (update :data select-keys [:id :options :pages :pages-index])))))) (s/def ::get-view-only-bundle (s/keys :req-un [::files/file-id] @@ -83,8 +79,10 @@ (sv/defmethod ::get-view-only-bundle {:auth false + ::cond/get-object #(files/get-minimal-file %1 (:file-id %2)) + ::cond/key-fn files/get-file-etag + ::cond/reuse-key? true ::doc/added "1.17"} [{:keys [pool]} params] (with-open [conn (db/open pool)] (get-view-only-bundle conn params))) - diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index 224a623331..5a85ab684a 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -137,3 +137,8 @@ :id id :update-fn update-file :save? save?))) + +(defn enable-storage-features-on-file! + [system & {:as params}] + (enable-objects-map-feature-on-file! system params) + (enable-pointer-map-feature-on-file! system params)) diff --git a/backend/test/backend_tests/rpc_viewer_test.clj b/backend/test/backend_tests/rpc_viewer_test.clj index baaa416edb..6b0ded158a 100644 --- a/backend/test/backend_tests/rpc_viewer_test.clj +++ b/backend/test/backend_tests/rpc_viewer_test.clj @@ -88,7 +88,6 @@ (t/is (nil? (:error out))) (let [result (:result out)] - (t/is (contains? result :share)) (t/is (contains? result :file)) (t/is (contains? result :project))))) @@ -103,7 +102,6 @@ ;; (th/print-result! out) (let [result (:result out)] (t/is (contains? result :file)) - (t/is (contains? result :share)) (t/is (contains? result :project))))) )) diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index 26252f042f..4ae8f12630 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -8,18 +8,18 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.files.features :as ffeat] [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.transit :as t] [app.common.types.shape-tree :as ctt] [app.common.types.shape.interactions :as ctsi] [app.main.data.comments :as dcm] [app.main.data.fonts :as df] - [app.main.data.messages :as msg] [app.main.features :as features] [app.main.repo :as rp] [app.util.globals :as ug] - [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -99,40 +99,58 @@ ;; --- Data Fetching -(s/def ::fetch-bundle-params +(s/def ::fetch-bundle (s/keys :req-un [::page-id ::file-id] :opt-un [::share-id])) -(defn fetch-bundle +(defn- fetch-bundle [{:keys [file-id share-id] :as params}] - (us/assert ::fetch-bundle-params params) - (ptk/reify ::fetch-file + (us/assert! ::fetch-bundle params) + + (ptk/reify ::fetch-bundle ptk/WatchEvent (watch [_ state _] - (let [components-v2 (features/active-feature? state :components-v2) - params' (cond-> {:file-id file-id} - (uuid? share-id) - (assoc :share-id share-id) + (let [features (cond-> ffeat/enabled + (features/active-feature? state :components-v2) + (conj "components/v2") - :always - (assoc :components-v2 components-v2))] + :always + (conj "storage/pointer-map")) + params' (cond-> {:file-id file-id :features features} + (uuid? share-id) + (assoc :share-id share-id)) + + resolve (fn [[key pointer]] + (let [params {:file-id file-id :fragment-id @pointer} + params (cond-> params + (uuid? share-id) + (assoc :share-id share-id))] + (->> (rp/cmd! :get-file-fragment params) + (rx/map :content) + (rx/map #(vector key %)))))] (->> (rp/query! :view-only-bundle params') - (rx/mapcat - (fn [{:keys [fonts] :as bundle}] - (->> (rx/of (df/fonts-fetched fonts) - (bundle-fetched (merge bundle params)))))) - (rx/catch (fn [err] - (if (and (= (:type err) :restriction) - (= (:code err) :feature-disabled)) - (rx/of (msg/error (tr "errors.components-v2") {:timeout nil})) - (rx/throw err))))))))) + (rx/mapcat + (fn [bundle] + (->> (rx/from (-> bundle :file :data :pages-index seq)) + (rx/merge-map + (fn [[_ page :as kp]] + (if (t/pointer? page) + (resolve kp) + (rx/of kp)))) + (rx/reduce conj {}) + (rx/map (fn [pages-index] + (update-in bundle [:file :data] assoc :pages-index pages-index)))))) + (rx/mapcat + (fn [{:keys [fonts] :as bundle}] + (rx/of (df/fonts-fetched fonts) + (bundle-fetched (merge bundle params)))))))))) (declare go-to-frame-auto) (defn bundle-fetched - [{:keys [project file share-links libraries users permissions] :as bundle}] - (let [pages (->> (get-in file [:data :pages]) + [{:keys [project file share-links libraries users permissions thumbnails] :as bundle}] + (let [pages (->> (dm/get-in file [:data :pages]) (map (fn [page-id] (let [data (get-in file [:data :pages-index page-id])] [page-id (assoc data @@ -150,6 +168,7 @@ :permissions permissions :project project :pages pages + :thumbnails thumbnails :file file}))) ptk/WatchEvent diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 0f16dd7d84..0753c90fef 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -215,11 +215,10 @@ (->> (rp/cmd! :get-file-libraries {:file-id id :features features}) (rx/mapcat identity) - (rx/merge-map + (rx/mapcat (fn [file] (->> (filter (comp t/pointer? val) file) (resolve-pointers file)))) - (rx/reduce conj []) (rx/map libraries-fetched))))))) (rx/take-until stoper))))))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 1de768035e..2ac96c6616 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -100,7 +100,7 @@ ;; when `true` we've called the mount for the frame rendered? (mf/use-var false) - disable-thumbnail? (d/not-empty? (dm/get-in modifiers [(:id shape) :modifiers])) + disable-thumbnail? (d/not-empty? (dm/get-in modifiers [frame-id :modifiers])) [on-load-frame-dom render-frame? thumbnail-renderer] (ftr/use-render-thumbnail page-id shape node-ref rendered? disable-thumbnail? @force-render) From 1b9dea01e2f44114c3f2ce4e5f8d20a6382dde63 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 21 Nov 2022 15:59:25 +0100 Subject: [PATCH 245/682] :fire: Remove unused d/update-vals function Already available as clojure.core/update-vals --- backend/src/app/rpc/commands/files.clj | 2 +- common/src/app/common/data.cljc | 7 -- common/src/app/common/pages/helpers.cljc | 4 +- common/src/app/common/pages/indices.cljc | 3 +- common/src/app/common/pages/migrations.cljc | 68 +++++++++---------- .../app/main/data/workspace/persistence.cljs | 3 +- .../main/data/workspace/state_helpers.cljs | 2 +- .../shapes/text/viewport_texts_html.cljs | 2 +- frontend/src/app/worker/import.cljs | 2 +- 9 files changed, 42 insertions(+), 51 deletions(-) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 29bb36a9c1..9b3a6bbc97 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -409,7 +409,7 @@ "Given the page data, removes the `:thumbnail` prop from all shapes." [page] - (update page :objects d/update-vals #(dissoc % :thumbnail))) + (update page :objects update-vals #(dissoc % :thumbnail))) (defn get-page [conn {:keys [file-id page-id object-id features]}] diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 8dd84f3eee..c89794e9d8 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -232,13 +232,6 @@ ([mfn coll] (into {} (mapm mfn) coll))) -(defn update-vals - "m f => {k (f v) ...} - Given a map m and a function f of 1-argument, returns a new map where the keys of m - are mapped to result of applying f to the corresponding values of m." - [m f] - (c/update-vals m f)) - (defn removev "Returns a vector of the items in coll for which (fn item) returns logical false" [fn coll] diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 76e234f576..3428d47974 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -411,7 +411,7 @@ cur (-> (or (get objects frame-id) (transient {})) (assoc! id shape))] (assoc! objects frame-id cur)))] - (d/update-vals + (update-vals (->> objects (reduce process-shape (transient {})) (persistent!)) @@ -432,7 +432,7 @@ (update shape :shapes #(filterv selected+parents %)))] (-> (select-keys objects selected+parents) - (d/update-vals remove-children)))) + (update-vals remove-children)))) (defn is-child? [objects parent-id candidate-child-id] diff --git a/common/src/app/common/pages/indices.cljc b/common/src/app/common/pages/indices.cljc index 9a7a94b6ac..7a5e8ef1ae 100644 --- a/common/src/app/common/pages/indices.cljc +++ b/common/src/app/common/pages/indices.cljc @@ -6,7 +6,6 @@ (ns app.common.pages.indices (:require - [app.common.data :as d] [app.common.pages.helpers :as cph] [app.common.uuid :as uuid])) @@ -54,4 +53,4 @@ (mapcat get-clip-parents)) parents)))] (-> parents-index - (d/update-vals retrieve-clips)))) + (update-vals retrieve-clips)))) diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index 6ff873c8e9..effbee310e 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -54,9 +54,9 @@ (into [] shapes) shapes)))) (update-page [page] - (update page :objects d/update-vals update-object))] + (update page :objects update-vals update-object))] - (update data :pages-index d/update-vals update-page))) + (update data :pages-index update-vals update-page))) ;; Changes paths formats (defmethod migrate 3 @@ -105,9 +105,9 @@ (fix-empty-points))) (update-page [page] - (update page :objects d/update-vals update-object))] + (update page :objects update-vals update-object))] - (update data :pages-index d/update-vals update-page))) + (update data :pages-index update-vals update-page))) ;; We did rollback version 4 migration. ;; Keep this in order to remember the next version to be 5 @@ -123,9 +123,9 @@ object)) (update-page [page] - (update page :objects d/update-vals update-object))] + (update page :objects update-vals update-object))] - (update data :pages-index d/update-vals update-page))) + (update data :pages-index update-vals update-page))) (defmethod migrate 6 [data] @@ -147,11 +147,11 @@ shape)) (update-container [container] - (update container :objects d/update-vals fix-line-paths))] + (update container :objects update-vals fix-line-paths))] (-> data - (update :pages-index d/update-vals update-container) - (update :components d/update-vals update-container)))) + (update :pages-index update-vals update-container) + (update :components update-vals update-container)))) ;; Remove interactions pointing to deleted frames (defmethod migrate 7 @@ -162,9 +162,9 @@ (filterv #(get-in page [:objects (:destination %)]) interactions)))) (update-page [page] - (update page :objects d/update-vals (partial update-object page)))] + (update page :objects update-vals (partial update-object page)))] - (update data :pages-index d/update-vals update-page))) + (update data :pages-index update-vals update-page))) ;; Remove groups without any shape, both in pages and components @@ -205,8 +205,8 @@ (assoc container :objects objects)))))] (-> data - (update :pages-index d/update-vals clean-container) - (update :components d/update-vals clean-container)))) + (update :pages-index update-vals clean-container) + (update :components update-vals clean-container)))) (defmethod migrate 9 [data] @@ -240,7 +240,7 @@ [data] (letfn [(update-page [page] (d/update-in-when page [:objects uuid/zero] dissoc :points :selrect))] - (update data :pages-index d/update-vals update-page))) + (update data :pages-index update-vals update-page))) (defmethod migrate 11 [data] @@ -252,9 +252,9 @@ (update-page [page] (update page :objects (fn [objects] - (d/update-vals objects (partial update-object objects)))))] + (update-vals objects (partial update-object objects)))))] - (update data :pages-index d/update-vals update-page))) + (update data :pages-index update-vals update-page))) (defmethod migrate 12 [data] @@ -264,9 +264,9 @@ (assoc :size nil))) (update-page [page] - (d/update-in-when page [:options :saved-grids] d/update-vals update-grid))] + (d/update-in-when page [:options :saved-grids] update-vals update-grid))] - (update data :pages-index d/update-vals update-page))) + (update data :pages-index update-vals update-page))) ;; Add rx and ry to images (defmethod migrate 13 @@ -284,9 +284,9 @@ (fix-radius))) (update-page [page] - (update page :objects d/update-vals update-object))] + (update page :objects update-vals update-object))] - (update data :pages-index d/update-vals update-page))) + (update data :pages-index update-vals update-page))) (defmethod migrate 14 [data] @@ -314,8 +314,8 @@ (assoc container :objects objects))))] (-> data - (update :pages-index d/update-vals update-container) - (update :components d/update-vals update-container)))) + (update :pages-index update-vals update-container) + (update :components update-vals update-container)))) (defmethod migrate 15 [data] data) @@ -361,11 +361,11 @@ (assign-fills))) (update-container [container] - (update container :objects d/update-vals update-object))] + (update container :objects update-vals update-object))] (-> data - (update :pages-index d/update-vals update-container) - (update :components d/update-vals update-container)))) + (update :pages-index update-vals update-container) + (update :components update-vals update-container)))) (defmethod migrate 17 [data] @@ -390,11 +390,11 @@ (assoc :fills []))) (update-container [container] - (update container :objects d/update-vals update-object))] + (update container :objects update-vals update-object))] (-> data - (update :pages-index d/update-vals update-container) - (update :components d/update-vals update-container)))) + (update :pages-index update-vals update-container) + (update :components update-vals update-container)))) ;;Remove position-data to solve a bug with the text positioning (defmethod migrate 18 @@ -405,11 +405,11 @@ (dissoc :position-data))) (update-container [container] - (update container :objects d/update-vals update-object))] + (update container :objects update-vals update-object))] (-> data - (update :pages-index d/update-vals update-container) - (update :components d/update-vals update-container)))) + (update :pages-index update-vals update-container) + (update :components update-vals update-container)))) (defmethod migrate 19 [data] @@ -421,11 +421,11 @@ (dissoc :position-data))) (update-container [container] - (update container :objects d/update-vals update-object))] + (update container :objects update-vals update-object))] (-> data - (update :pages-index d/update-vals update-container) - (update :components d/update-vals update-container)))) + (update :pages-index update-vals update-container) + (update :components update-vals update-container)))) ;; TODO: pending to do a migration for delete already not used fill ;; and stroke props. This should be done for >1.14.x version. diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 47c80be58c..2025486733 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -6,7 +6,6 @@ (ns app.main.data.workspace.persistence (:require - [app.common.data :as d] [app.common.data.macros :as dm] [app.common.logging :as log] [app.common.pages :as cp] @@ -156,7 +155,7 @@ frame-updates (-> (group-by :page-id changes) - (d/update-vals #(into #{} (mapcat :frames) %)))] + (update-vals #(into #{} (mapcat :frames) %)))] (rx/merge (->> (rx/from frame-updates) diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index f1a8acc35b..e8bee24ecd 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -132,7 +132,7 @@ children-ids (cph/get-children-ids objects parent-id) children (-> (select-keys objects children-ids) - (d/update-vals + (update-vals (fn [child] (cond-> child (contains? modifiers (:id child)) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 8dc6f1e828..e13e2fcede 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -242,7 +242,7 @@ text-shapes (mf/use-memo (mf/deps text-shapes modifiers) - #(d/update-vals text-shapes (partial process-shape modifiers))) + #(update-vals text-shapes (partial process-shape modifiers))) editing-shape (get text-shapes edition) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index a58f4e0545..cdccc632ee 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -350,7 +350,7 @@ (mapv #(update % :starting-frame resolve))) guides (-> (get-in page-data [:options :guides]) - (d/update-vals #(update % :frame-id resolve))) + (update-vals #(update % :frame-id resolve))) page-data (-> page-data (d/assoc-in-when [:options :flows] flows) From 9334138510b4da9c361d58da94eaf13191dfcec6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 21 Nov 2022 16:12:24 +0100 Subject: [PATCH 246/682] :bug: Fix persistence loop on dev code hot reload --- frontend/src/app/main/data/workspace.cljs | 5 +---- frontend/src/app/main/data/workspace/persistence.cljs | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 0753c90fef..a63cf23c56 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -315,10 +315,7 @@ ptk/WatchEvent (watch [_ _ _] - (rx/merge - (rx/of (dwn/finalize file-id)) - (->> (rx/of ::dwp/finalize) - (rx/observe-on :async)))))) + (rx/of (dwn/finalize file-id))))) (declare go-to-page) (declare ^:private preload-data-uris) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 2025486733..a18acb48c4 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -40,7 +40,7 @@ ptk/WatchEvent (watch [_ _ stream] (log/debug :hint "initialize persistence") - (let [stoper (rx/filter #(= ::finalize %) stream) + (let [stoper (rx/filter (ptk/type? ::initialize-persistence) stream) commits (l/atom []) local-file? From 87ebb2e24cb6fbcc1c3d22d31cae58d2e3a2edee Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 21 Nov 2022 17:29:37 +0100 Subject: [PATCH 247/682] :arrow_up: Update yetti dependency to v9.11 --- backend/deps.edn | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/deps.edn b/backend/deps.edn index f2bcb7eca0..cb45fff872 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -22,8 +22,8 @@ java-http-clj/java-http-clj {:mvn/version "0.4.3"} funcool/yetti - {:git/tag "v9.10" - :git/sha "9744349" + {:git/tag "v9.11" + :git/sha "6f9197a" :git/url "https://github.com/funcool/yetti.git" :exclusions [org.slf4j/slf4j-api]} From 8a6f1d82e56d85c4c95c44af8abcfefb71a61119 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 22 Nov 2022 15:38:04 +0100 Subject: [PATCH 248/682] :bug: Fix min size when creating shapes --- .../src/app/main/data/workspace/drawing/common.cljs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index ad0da25636..7c58476241 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -48,19 +48,19 @@ (cond-> shape (not click-draw?) (-> (assoc :grow-type :fixed)) - + (and click-draw? (not text?)) (-> (assoc :width min-side :height min-side) + (cts/setup-rect-selrect) (gsh/transform-shape (ctm/move-modifiers (- (/ min-side 2)) (- (/ min-side 2))))) (and click-draw? text?) - (assoc :height 17 :width 4 :grow-type :auto-width) - - click-draw? - (cts/setup-rect-selrect) + (-> (assoc :height 17 :width 4 :grow-type :auto-width) + (cts/setup-rect-selrect)) :always (dissoc :initialized? :click-draw?))] + ;; Add & select the created shape to the workspace (rx/concat (if (= :text (:type shape)) From 7ffdf21657baff21a3de358da9ed95dad87665e7 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 22 Nov 2022 12:29:58 +0100 Subject: [PATCH 249/682] :bug: Fix paste shapes into layout --- frontend/src/app/main/data/workspace.cljs | 4 +++- frontend/src/app/main/data/workspace/texts.cljs | 2 -- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index e927d1ec40..cd3d707e1e 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -55,6 +55,7 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes-update-layout :as dwul] + [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.transforms :as dwt] @@ -1441,7 +1442,8 @@ (into (d/ordered-set)))] (rx/of (dch/commit-changes changes) - (dws/select-shapes selected))))] + (dws/select-shapes selected) + (dwul/update-layout-positions [frame-id]))))] (ptk/reify ::paste-shape ptk/WatchEvent diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index d4cf5995fe..f9f1a45974 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -358,11 +358,9 @@ (gpt/subtract (gpt/point (:selrect new-shape)) (gpt/point (:selrect shape))) - new-shape (update new-shape :position-data gsh/move-position-data (:x delta-move) (:y delta-move))] - new-shape)) (defn update-text-modifier From 54fd836dd402a673992eec917087f422e3da0335 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 22 Nov 2022 16:41:44 +0100 Subject: [PATCH 250/682] :bug: Fix problem when dragging layout children with frames --- common/src/app/common/pages/helpers.cljc | 5 +++++ .../src/app/main/data/workspace/transforms.cljs | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 76e234f576..b9bf67b7ec 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -109,6 +109,11 @@ (recur (conj result parent-id) parent-id) result)))) +(defn get-siblings-ids + [objects id] + (let [parent (get-parent objects id)] + (into [] (->> (:shapes parent) (remove #(= % id)))))) + (defn get-frame "Get the frame that contains the shape. If the shape is already a frame, get itself. If no shape is provided, returns the root frame." diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 2658720efd..be5bf599c4 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -435,9 +435,15 @@ zoom (get-in state [:workspace-local :zoom] 1) focus (:workspace-focus-selected state) - exclude-frames (into #{} - (filter (partial cph/frame-shape? objects)) - (cph/selected-with-children objects selected)) + exclude-frames + (into #{} + (filter (partial cph/frame-shape? objects)) + (cph/selected-with-children objects selected)) + + exclude-frames-siblings + (into exclude-frames + (mapcat (partial cph/get-siblings-ids objects)) + selected) fix-axis (fn [[position shift?]] @@ -471,9 +477,12 @@ ;; We try to use the previous snap so we don't have to wait for the result of the new (rx/map snap/correct-snap-point) + (rx/with-latest vector ms/mouse-position-mod) + (rx/map - (fn [move-vector] + (fn [[move-vector mod?]] (let [position (gpt/add from-position move-vector) + exclude-frames (if mod? exclude-frames exclude-frames-siblings) target-frame (ctst/top-nested-frame objects position exclude-frames) layout? (ctl/layout? objects target-frame) drop-index (when layout? (gsl/get-drop-index target-frame objects position))] From 441e14234959eea222546e8bbfe04e17070003c5 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 22 Nov 2022 17:08:23 +0100 Subject: [PATCH 251/682] :bug: Fix reflow layout when changes in paths and texts --- frontend/src/app/main/data/workspace.cljs | 1 - .../app/main/data/workspace/modifiers.cljs | 42 +++++++++++++++++- .../app/main/data/workspace/path/edition.cljs | 22 ++++++---- .../data/workspace/shapes_update_layout.cljs | 16 +++---- .../src/app/main/data/workspace/texts.cljs | 43 +++++++++++++------ .../main/ui/workspace/shapes/path/common.cljs | 7 ++- .../shapes/text/viewport_texts_html.cljs | 6 ++- 7 files changed, 100 insertions(+), 37 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index cd3d707e1e..c486811860 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -55,7 +55,6 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shapes-update-layout :as dwul] - [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.transforms :as dwt] diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 9b3766e595..2f153442bd 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -15,6 +15,7 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.types.modifiers :as ctm] + [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.comments :as-alias dwcm] @@ -188,6 +189,42 @@ [(get-in objects [k :name]) v])) modif-tree))) +(defn apply-text-modifiers + [objects text-modifiers] + (letfn [(apply-text-modifier + [shape {:keys [width height]}] + (cond-> shape + (some? width) + (assoc :width width) + + (some? height) + (assoc :height height) + + (or (some? width) (some? height)) + (cts/setup-rect-selrect)))] + (loop [modifiers (seq text-modifiers) + result objects] + (if (empty? modifiers) + result + (let [[id text-modifier] (first modifiers)] + (recur (rest modifiers) + (update objects id apply-text-modifier text-modifier))))))) + +#_(defn apply-path-modifiers + [objects path-modifiers] + (letfn [(apply-path-modifier + [shape {:keys [content-modifiers]}] + (let [shape (update shape :content upc/apply-content-modifiers content-modifiers) + [points selrect] (helpers/content->points+selrect shape (:content shape))] + (assoc shape :selrect selrect :points points)))] + (loop [modifiers (seq path-modifiers) + result objects] + (if (empty? modifiers) + result + (let [[id path-modifier] (first modifiers)] + (recur (rest modifiers) + (update objects id apply-path-modifier path-modifier))))))) + (defn set-modifiers ([modif-tree] (set-modifiers modif-tree false)) @@ -206,7 +243,10 @@ (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) modif-tree - (gsh/set-objects-modifiers modif-tree objects ignore-constraints snap-pixel?)] + (as-> objects $ + (apply-text-modifiers $ (get state :workspace-text-modifier)) + ;;(apply-path-modifiers $ (get-in state [:workspace-local :edit-path])) + (gsh/set-objects-modifiers modif-tree $ ignore-constraints snap-pixel?))] (assoc state :workspace-modifiers modif-tree)))))) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 7ecf800c8f..a5b4a30a5b 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -21,6 +21,7 @@ [app.main.data.workspace.path.state :as st] [app.main.data.workspace.path.streams :as streams] [app.main.data.workspace.path.undo :as undo] + [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] [app.util.path.tools :as upt] @@ -297,22 +298,25 @@ ptk/WatchEvent (watch [_ state stream] - (let [mode (get-in state [:workspace-local :edit-path id :edit-mode])] + (let [mode (get-in state [:workspace-local :edit-path id :edit-mode]) + stopper (->> stream (rx/filter (ptk/type? ::start-path-edit))) + interrupt (->> stream (rx/filter #(= % :interrupt)) (rx/take 1))] (rx/concat (rx/of (undo/start-path-undo)) (rx/of (drawing/change-edit-mode mode)) - (->> stream - (rx/take-until (->> stream (rx/filter (ptk/type? ::start-path-edit)))) - (rx/filter #(= % :interrupt)) - (rx/take 1) - (rx/map #(stop-path-edit)))))))) + (->> interrupt + (rx/map #(stop-path-edit id)) + (rx/take-until stopper))))))) -(defn stop-path-edit [] +(defn stop-path-edit [id] (ptk/reify ::stop-path-edit ptk/UpdateEvent (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (update state :workspace-local dissoc :edit-path id))))) + (update state :workspace-local dissoc :edit-path id)) + + ptk/WatchEvent + (watch [_ _ _] + (rx/of (dwul/update-layout-positions [id]))))) (defn split-segments [{:keys [from-p to-p t]}] diff --git a/frontend/src/app/main/data/workspace/shapes_update_layout.cljs b/frontend/src/app/main/data/workspace/shapes_update_layout.cljs index 3274d23563..710fd10c69 100644 --- a/frontend/src/app/main/data/workspace/shapes_update_layout.cljs +++ b/frontend/src/app/main/data/workspace/shapes_update_layout.cljs @@ -8,9 +8,7 @@ (:require [app.common.data :as d] [app.common.types.modifiers :as ctm] - [app.common.types.shape.layout :as ctl] [app.main.data.workspace.modifiers :as dwm] - [app.main.data.workspace.state-helpers :as wsh] [beicon.core :as rx] [potok.core :as ptk])) @@ -18,11 +16,9 @@ [ids] (ptk/reify ::update-layout-positions ptk/WatchEvent - (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - ids (->> ids (filter (partial ctl/layout? objects)))] - (if (d/not-empty? ids) - (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))] - (rx/of (dwm/set-modifiers modif-tree) - (dwm/apply-modifiers))) - (rx/empty)))))) + (watch [_ _ _] + (if (d/not-empty? ids) + (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))] + (rx/of (dwm/set-modifiers modif-tree) + (dwm/apply-modifiers))) + (rx/empty))))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index f9f1a45974..b0ff747d6c 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -14,11 +14,13 @@ [app.common.pages.helpers :as cph] [app.common.text :as txt] [app.common.types.modifiers :as ctm] + [app.common.types.shape :as cts] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shapes :as dwsh] + [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.util.router :as rt] @@ -75,7 +77,8 @@ (dch/update-shapes [id] (fn [shape] (-> shape (assoc :content content) - (merge modifiers)))) + (merge modifiers) + (cts/setup-rect-selrect)))) (dwu/commit-undo-transaction))))) (when (some? id) @@ -316,19 +319,24 @@ [id new-width new-height] (ptk/reify ::resize-text ptk/WatchEvent - (watch [_ _ _] - (letfn [(update-fn [shape] - (let [{:keys [selrect grow-type]} shape - {shape-width :width shape-height :height} selrect] - (cond-> shape - (and (not-changed? shape-width new-width) (= grow-type :auto-width)) - (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width new-width)) + (watch [_ state _] + (let [shape (wsh/lookup-shape state id)] + (letfn [(update-fn [shape] + (let [{:keys [selrect grow-type]} shape + {shape-width :width shape-height :height} selrect] + (cond-> shape + (and (not-changed? shape-width new-width) (= grow-type :auto-width)) + (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width new-width)) - (and (not-changed? shape-height new-height) - (or (= grow-type :auto-height) (= grow-type :auto-width))) - (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height new-height)))))] + (and (not-changed? shape-height new-height) + (or (= grow-type :auto-height) (= grow-type :auto-width))) + (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height new-height)))))] - (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false})))))) + (when (or (and (not-changed? (:width shape) new-width) (= (:grow-type shape) :auto-width)) + (and (not-changed? (:height shape) new-height) + (or (= (:grow-type shape) :auto-height) (= (:grow-type shape) :auto-width)))) + (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false}) + (dwul/update-layout-positions [id])))))))) (defn save-font [data] @@ -368,7 +376,16 @@ (ptk/reify ::update-text-modifier ptk/UpdateEvent (update [_ state] - (update-in state [:workspace-text-modifier id] (fnil merge {}) props)))) + (update-in state [:workspace-text-modifier id] (fnil merge {}) props)) + + ptk/WatchEvent + (watch [_ state _] + (let [shape (wsh/lookup-shape state id)] + (when (or (and (some? (:width props)) + (not (mth/close? (:width props) (:width shape)))) + (and (some? (:height props)) + (not (mth/close? (:height props) (:height shape))))) + (rx/of (dwul/update-layout-positions [id]))))))) (defn clean-text-modifier [id] diff --git a/frontend/src/app/main/ui/workspace/shapes/path/common.cljs b/frontend/src/app/main/ui/workspace/shapes/path/common.cljs index c88f5e7b58..8c6215304b 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/common.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/common.cljs @@ -28,9 +28,12 @@ (let [selfn #(get-in % [:edit-path id])] #(l/derived selfn refs/workspace-local)))) +(defn content-modifiers-ref + [id] + (l/derived #(get-in % [:edit-path id :content-modifiers]) refs/workspace-local)) + (defn make-content-modifiers-ref [id] (mf/use-memo (mf/deps id) - (let [selfn #(get-in % [:edit-path id :content-modifiers])] - #(l/derived selfn refs/workspace-local)))) + #(content-modifiers-ref id))) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 8dc6f1e828..fa5d69693b 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -155,7 +155,11 @@ (not (identical? old-shape new-shape)) (not= (dissoc old-shape :migrate) (dissoc new-shape :migrate))) - (not= new-modifiers old-modifiers) + (and (not= new-modifiers old-modifiers) + (or (nil? new-modifiers) + (nil? old-modifiers) + (not (ctm/only-move? new-modifiers)) + (not (ctm/only-move? old-modifiers)))) ;; When the position data is nil we force to recalculate (:migrate new-shape)))) From 461e5cb3761329747c91ae3c8abadaca1ae13eac Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 15 Nov 2022 18:36:47 +0100 Subject: [PATCH 252/682] :sparkles: Add several improvements to handoff --- CHANGES.md | 1 + .../styles/main/partials/color-bullet.scss | 2 +- frontend/src/app/main/ui/measurements.cljs | 4 +- .../ui/viewer/handoff/attributes/text.cljs | 62 ++++++++----------- .../main/ui/viewer/handoff/right_sidebar.cljs | 14 +---- frontend/translations/en.po | 3 + frontend/translations/es.po | 3 + 7 files changed, 39 insertions(+), 50 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f3b1c6a822..6c2f5e0388 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Adds layout flex functionality for boards - Better overlays interactions on boards inside boards [Taiga #4386](https://tree.taiga.io/project/penpot/us/4386) - Show board miniature in manual overlay setting [Taiga #4475](https://tree.taiga.io/project/penpot/issue/4475) +- Handoff visual improvements [Taiga #3124](https://tree.taiga.io/project/penpot/us/3124) ### :bug: Bugs fixed diff --git a/frontend/resources/styles/main/partials/color-bullet.scss b/frontend/resources/styles/main/partials/color-bullet.scss index b7b3e8cdb0..7b1c988008 100644 --- a/frontend/resources/styles/main/partials/color-bullet.scss +++ b/frontend/resources/styles/main/partials/color-bullet.scss @@ -182,7 +182,7 @@ ul.palette-menu .color-bullet { } } -.color-data .color-bullet.is-not-library-color { +.color-bullet.is-not-library-color { border-radius: $br-small; & .color-bullet-wrapper { diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs index 3cd60d6de3..19752f0f99 100644 --- a/frontend/src/app/main/ui/measurements.cljs +++ b/frontend/src/app/main/ui/measurements.cljs @@ -19,7 +19,7 @@ ;; CONSTANTS ;; ------------------------------------------------ -(def font-size 10) +(def font-size 11) (def selection-rect-width 1) (def select-color "var(--color-select)") @@ -40,7 +40,7 @@ (def distance-color "var(--color-distance)") (def distance-text-color "var(--color-white)") (def distance-border-radius 2) -(def distance-pill-width 40) +(def distance-pill-width 50) (def distance-pill-height 16) (def distance-line-stroke 1) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs index b536610deb..283d7ee1a5 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/text.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.viewer.handoff.attributes.text (:require + [app.common.data :as d] [app.common.text :as txt] [app.main.fonts :as fonts] [app.main.store :as st] @@ -14,6 +15,7 @@ [app.util.code-gen :as cg] [app.util.color :as uc] [app.util.i18n :refer [tr]] + [app.util.strings :as ust] [cuerdas.core :as str] [okulary.core :as l] [rumext.v2 :as mf])) @@ -30,6 +32,11 @@ (get-in state [:viewer-libraries file-id :data :typographies]))] #(l/derived get-library st/state))) +(defn format-number [number] + (-> number + d/parse-double + (ust/format-precision 2))) + (def properties [:fill-color :fill-color-gradient :font-family @@ -52,9 +59,9 @@ :fill-color-gradient "color"} :format {:font-family #(str "'" % "'") :font-style #(str "'" % "'") - :font-size #(str % "px") - :line-height #(str % "px") - :letter-spacing #(str % "px") + :font-size #(str (format-number %) "px") + :line-height #(format-number %) + :letter-spacing #(str (format-number %) "px") :text-decoration name :text-transform name :fill-color #(-> %2 shape->color uc/color->background) @@ -66,7 +73,7 @@ ([style & properties] (cg/generate-css-props style properties params))) -(mf/defc typography-block [{:keys [text style full-style]}] +(mf/defc typography-block [{:keys [text style]}] (let [typography-library-ref (mf/use-memo (mf/deps (:typography-ref-file style)) (make-typographies-library-ref (:typography-ref-file style))) @@ -86,21 +93,18 @@ {:style {:font-family (:font-family typography) :font-weight (:font-weight typography) :font-style (:font-style typography)}} - (tr "workspace.assets.typography.sample")]] + (tr "workspace.assets.typography.text-styles")]] [:div.typography-entry-name (:name typography)] [:& copy-button {:data (copy-style-data typography)}]] [:div.attributes-typography-row [:div.typography-sample - {:style {:font-family (:font-family full-style) - :font-weight (:font-weight full-style) - :font-style (:font-style full-style)}} - (tr "workspace.assets.typography.sample")] + {:style {:font-family (:font-family style) + :font-weight (:font-weight style) + :font-style (:font-style style)}} + (tr "workspace.assets.typography.text-styles")] [:& copy-button {:data (copy-style-data style)}]]) - [:div.attributes-content-row - [:pre.attributes-content (str/trim text)] - [:& copy-button {:data (str/trim text)}]] (when (:fills style) (for [fill (:fills style)] @@ -124,19 +128,19 @@ (when (:font-size style) [:div.attributes-unit-row [:div.attributes-label (tr "handoff.attributes.typography.font-size")] - [:div.attributes-value (str (:font-size style)) "px"] + [:div.attributes-value (str (format-number (:font-size style))) "px"] [:& copy-button {:data (copy-style-data style :font-size)}]]) (when (:line-height style) [:div.attributes-unit-row [:div.attributes-label (tr "handoff.attributes.typography.line-height")] - [:div.attributes-value (str (:line-height style)) "px"] + [:div.attributes-value (format-number (:line-height style))] [:& copy-button {:data (copy-style-data style :line-height)}]]) (when (:letter-spacing style) [:div.attributes-unit-row [:div.attributes-label (tr "handoff.attributes.typography.letter-spacing")] - [:div.attributes-value (str (:letter-spacing style)) "px"] + [:div.attributes-value (str (format-number (:letter-spacing style))) "px"] [:& copy-button {:data (copy-style-data style :letter-spacing)}]]) (when (:text-decoration style) @@ -158,16 +162,12 @@ ;; handoff.attributes.typography.text-transform.titlecase ;; handoff.attributes.typography.text-transform.uppercase [:div.attributes-value (->> style :text-transform (str "handoff.attributes.typography.text-transform.") (tr))] - [:& copy-button {:data (copy-style-data style :text-transform)}]])])) + [:& copy-button {:data (copy-style-data style :text-transform)}]]) + [:div.attributes-content-row + [:pre.attributes-content (str/trim text)] + [:& copy-button {:data (str/trim text)}]]])) -(defn- remove-equal-values - [m1 m2] - (if (and (map? m1) (map? m2) (not (nil? m1)) (not (nil? m2))) - (->> m1 - (remove (fn [[k v]] (= (k m2) v))) - (into {})) - m1)) (mf/defc text-block [{:keys [shape]}] (let [style-text-blocks (->> (keys txt/default-text-attrs) @@ -175,18 +175,10 @@ (remove (fn [[_ text]] (str/empty? (str/trim text)))) (mapv (fn [[style text]] (vector (merge txt/default-text-attrs style) text))))] - (for [[idx [full-style text]] (map-indexed vector style-text-blocks)] - (let [previous-style (first (nth style-text-blocks (dec idx) nil)) - style (remove-equal-values full-style previous-style) - - ;; If the color is set we need to add opacity otherwise the display will not work - style (cond-> style - (:fill-color style) - (assoc :fill-opacity (:fill-opacity full-style)))] - [:& typography-block {:shape shape - :full-style full-style - :style style - :text text}])))) + (for [[_ [full-style text]] (map-indexed vector style-text-blocks)] + [:& typography-block {:shape shape + :style full-style + :text text}]))) (mf/defc text-panel [{:keys [shapes]}] diff --git a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs index d40599c1cd..f2b2288b99 100644 --- a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.viewer.handoff.right-sidebar (:require - [app.common.data :as d] [app.main.ui.components.shape-icon :as si] [app.main.ui.components.tab-container :refer [tab-container tab-element]] [app.main.ui.icons :as i] @@ -22,16 +21,7 @@ section (mf/use-state :info #_:code) shapes (resolve-shapes (:objects page) selected) - first-shape (first shapes) - - selected-type (or (:type first-shape) :not-found) - selected-type (if (= selected-type :group) - (if (some? (:component-id first-shape)) - :component - (if (:masked-group? first-shape) - :mask - :group)) - selected-type)] + first-shape (first shapes)] [:aside.settings-bar.settings-bar-right {:class (when @expanded "expanded")} [:div.settings-bar-inside @@ -57,7 +47,7 @@ ;; handoff.tabs.code.selected.rect ;; handoff.tabs.code.selected.svg-raw ;; handoff.tabs.code.selected.text - [:span.tool-window-bar-title (->> selected-type d/name (str "handoff.tabs.code.selected.") (tr))]])] + [:span.tool-window-bar-title (:name first-shape)]])] [:div.tool-window-content [:& tab-container {:on-change-tab #(do (reset! expanded false) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 5176b36aed..ff785bab8d 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2699,6 +2699,9 @@ msgstr "Line Height" msgid "workspace.assets.typography.sample" msgstr "Ag" +msgid "workspace.assets.typography.text-styles" +msgstr "Text styles" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.assets.typography.text-transform" msgstr "Text Transform" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 0e833ab0b3..52cb5c0b77 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -3061,6 +3061,9 @@ msgstr "Interlineado" msgid "workspace.assets.typography.sample" msgstr "Ag" +msgid "workspace.assets.typography.text-styles" +msgstr "Estilos de texto" + #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.assets.typography.text-transform" msgstr "Transformar texto" From cc60cfc86d633e1fe83ff9e3dac65a0fe0e5af1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 16 Nov 2022 14:21:45 +0100 Subject: [PATCH 253/682] :sparkles: Enhance modal of convert graphics into components --- .../resources/styles/main/partials/modal.scss | 61 +++++++++++++++++++ frontend/src/app/main/data/workspace.cljs | 44 ++++++++++--- frontend/src/app/main/ui/workspace.cljs | 35 ++++++++--- frontend/src/app/util/dom.cljs | 4 ++ frontend/translations/en.po | 30 +++++++++ frontend/translations/es.po | 33 ++++++++++ 6 files changed, 191 insertions(+), 16 deletions(-) diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index cbfc24e1b8..d76703f51f 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -1849,6 +1849,41 @@ } .remove-graphics-dialog { + .modal-content { + padding-top: 16px; + } + + h2 { + font-size: $fs18; + } + + p { + font-size: $fs12; + color: $color-gray-30; + + &:last-child { + margin-bottom: 0; + } + + &.progress-message { + color: $color-info; + } + + &.error-message { + color: $color-black; + + & svg { + width: 16px; + height: 16px; + fill: $color-danger; + margin-right: 0.5rem; + position: relative; + bottom: -4px; + transform: rotate(45deg); + } + } + } + .close { border: 1px solid $color-gray-30; background: $color-canvas; @@ -1861,6 +1896,32 @@ background: $color-gray-20; } } + + .button-primary { + background: $color-primary; + border: 1px solid $color-primary; + border-radius: 3px; + color: $color-black; + cursor: pointer; + padding: 0.5rem 1rem; + + &:hover { + background: $color-primary-dark; + } + } + + .button-secondary { + border: 1px solid $color-gray-30; + background: $color-white; + border-radius: 3px; + padding: 0.5rem 1rem; + cursor: pointer; + margin-right: 8px; + + &:hover { + background: $color-gray-20; + } + } } //- LOGIN diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index c658a3c9f3..d8c4a9cc55 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -15,6 +15,7 @@ [app.common.geom.proportions :as gpr] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.rect :as gpsr] + [app.common.logging :as log] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] [app.common.spec :as us] @@ -123,8 +124,8 @@ ptk/WatchEvent (watch [_ state _] - (let [file (:workspace-file state) - has-graphics? (-> file :data :media seq) + (let [file (:workspace-data state) + has-graphics? (-> file :media seq) components-v2 (features/active-feature? state :components-v2)] (rx/merge (rx/of (fbc/fix-bool-contents)) @@ -1674,7 +1675,9 @@ ptk/UpdateEvent (update [_ state] (assoc state :remove-graphics {:total total - :current nil})))) + :current nil + :error false + :completed false})))) (defn- update-remove-graphics [current] @@ -1683,12 +1686,31 @@ (update [_ state] (assoc-in state [:remove-graphics :current] current)))) +(defn- error-in-remove-graphics + [] + (ptk/reify ::error-in-remove-graphics + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:remove-graphics :error] true)))) + +(defn clear-remove-graphics + [] + (ptk/reify ::clear-remove-graphics + ptk/UpdateEvent + (update [_ state] + (dissoc state :remove-graphics)))) + (defn- complete-remove-graphics [] (ptk/reify ::complete-remove-graphics ptk/UpdateEvent (update [_ state] - (dissoc state :remove-graphics)))) + (assoc-in state [:remove-graphics :completed] true)) + + ptk/WatchEvent + (watch [_ state _] + (when-not (get-in state [:remove-graphics :error]) + (rx/of (modal/hide)))))) (defn- remove-graphic [it file-data page [index [media-obj pos]]] @@ -1727,9 +1749,14 @@ (rx/mapcat (partial dwm/create-shapes-svg (:id file-data) (:objects page) pos))) (dwm/create-shapes-img pos media-obj))] - (rx/concat - (rx/of (update-remove-graphics index)) - (rx/map process-shapes shapes)))) + (->> (rx/concat + (rx/of (update-remove-graphics index)) + (rx/map process-shapes shapes)) + (rx/catch #(do + (log/error :msg (str "Error removing " (:name media-obj)) + :hint (ex-message %) + :error %) + (rx/of (error-in-remove-graphics))))))) (defn- remove-graphics [file-id file-name] @@ -1766,8 +1793,7 @@ (pcb/add-page (:id page) page))))) (rx/mapcat (partial remove-graphic it file-data' page) (rx/from (d/enumerate (d/zip media shape-grid)))) - (rx/of (modal/hide) - (complete-remove-graphics))))))) + (rx/of (complete-remove-graphics))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Exports diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 05e5e57831..1231a82922 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -174,14 +174,35 @@ ::mf/register-as :remove-graphics-dialog} [{:keys [] :as ctx}] (let [remove-state (mf/deref refs/remove-graphics) - close #(modal/hide!)] + close #(modal/hide!) + reload-file #(dom/reload-current-window)] + (mf/use-effect + (fn [] + #(st/emit! (dw/clear-remove-graphics)))) [:div.modal-overlay [:div.modal-container.remove-graphics-dialog [:div.modal-header [:div.modal-header-title - [:h2 (str "Updating " (:file-name ctx) "...")]] - [:div.modal-close-button - {:on-click close} i/close]] - [:div.modal-content - [:p (str "Converting " (:current remove-state) " / " (:total remove-state))]]]])) - + [:h2 (tr "workspace.remove-graphics.title" (:file-name ctx))]] + (when (and (:completed remove-state) (:error remove-state)) + [:div.modal-close-button + {:on-click close} i/close])] + (if-not (and (:completed remove-state) (:error remove-state)) + [:div.modal-content + [:p (tr "workspace.remove-graphics.text1")] + [:p (tr "workspace.remove-graphics.text2")] + [:p.progress-message (tr "workspace.remove-graphics.progress" + (:current remove-state) + (:total remove-state))]] + [:* + [:div.modal-content + [:p.error-message [:span i/close] (tr "workspace.remove-graphics.error-msg")] + [:p (tr "workspace.remove-graphics.error-hint")]] + [:div.modal-footer + [:div.action-buttons + [:input.button-secondary {:type "button" + :value (tr "labels.close") + :on-click close}] + [:input.button-primary {:type "button" + :value (tr "labels.reload-file") + :on-click reload-file}]]]])]])) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index d24e681afe..60c62f8633 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -596,6 +596,10 @@ [] (.back (.-history js/window))) +(defn reload-current-window + [] + (.reload (.-location js/window))) + (defn animate! ([item keyframes duration] (animate! item keyframes duration nil)) ([item keyframes duration onfinish] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index ff785bab8d..6c77b236f3 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1351,6 +1351,10 @@ msgstr "Projects" msgid "labels.release-notes" msgstr "Release notes" +#: src/app/main/ui/workspace.cljs +msgid "labels.reload-file" +msgstr "Reload file" + #: src/app/main/ui/workspace/libraries.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.remove" msgstr "Remove" @@ -3915,6 +3919,32 @@ msgstr "Separate nodes (%s)" msgid "workspace.path.actions.snap-nodes" msgstr "Snap nodes (%s)" +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.title" +msgstr "Updating %s..." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.text1" +msgstr "Library Graphics are Components from now on, which will make them much more powerful." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.text2" +msgstr "This update is a one time action." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.error-msg" +msgstr "Some graphics could not be updated." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.error-hint" +msgstr "" +"To try it again, you can reload this file. If the problem persists, we suggest you to take" +" a look at the list and consider to delete broken graphics." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.progress" +msgstr "Converting %s/%s" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.back" msgstr "Send to back" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 52cb5c0b77..537142d3af 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1518,6 +1518,10 @@ msgstr "Reciente" msgid "labels.release-notes" msgstr "Notas de versión" +#: src/app/main/ui/workspace.cljs +msgid "labels.reload-file" +msgstr "Recargar archivo" + #: src/app/main/ui/workspace/libraries.cljs, #: src/app/main/ui/dashboard/team.cljs msgid "labels.remove" @@ -4341,6 +4345,35 @@ msgstr "Separar nodos (%s)" msgid "workspace.path.actions.snap-nodes" msgstr "Alinear nodos (%s)" +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.title" +msgstr "Actualizando %s..." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.text1" +msgstr "" +"Desde ahora los gráficos de la librería serán componentes, lo cual" +" los hará mucho más potentes." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.text2" +msgstr "Esta actualización sólo ocurrirá una vez." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.error-msg" +msgstr "Algunos gráficos no han podido ser actualizados." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.error-hint" +msgstr "" +"Para intentarlo de nuevo, puedes recargar este archivo. Si el problema" +" persiste, te sugerimos que compruebes la lista y consideres borrar los" +" gráficos que estén mal." + +#: src/app/main/ui/workspace.cljs +msgid "workspace.remove-graphics.progress" +msgstr "Convirtiendo %s/%s" + #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.back" msgstr "Enviar al fondo" From b4e4a5cab48d4e0419b73f5fd0005f6482615a7b Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Tue, 22 Nov 2022 11:42:46 +0100 Subject: [PATCH 254/682] :bug: Fix show board miniature in manual overlay setting --- .../resources/styles/main/partials/workspace.scss | 10 +++++++++- frontend/src/app/main/ui/shapes/custom_stroke.cljs | 4 ++-- frontend/src/app/main/ui/shapes/frame.cljs | 2 +- .../ui/workspace/shapes/frame/thumbnail_render.cljs | 12 +++++++----- 4 files changed, 19 insertions(+), 9 deletions(-) diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 1a1dec9054..53ecfb202f 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -261,9 +261,17 @@ $height-palette-max: 80px; } .render-shapes { + height: 100%; position: absolute; width: 100%; - height: 100%; + } + + .frame-thumbnail-wrapper { + .fills, + .strokes, + .frame-clip-def { + opacity: 0; + } } .viewport-controls { diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 484a74d8e1..dfbda44d06 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -418,7 +418,7 @@ elem-name (obj/get child "type") position (or (obj/get props "position") 0) render-id (or (obj/get props "render-id") (mf/use-ctx muc/render-id))] - [:g {:id (dm/fmt "fills-%" (:id shape))} + [:g.fills {:id (dm/fmt "fills-%" (:id shape))} [:> elem-name (build-fill-props shape child position render-id)]])) (mf/defc shape-strokes @@ -442,7 +442,7 @@ [:* (when (d/not-empty? (:strokes shape)) - [:> :g stroke-props + [:> :g.strokes stroke-props (for [[index value] (-> (d/enumerate (:strokes shape)) reverse)] (let [props (build-stroke-props index child value render-id) shape (assoc value :points (:points shape))] diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 00d043f8f0..54763d8faa 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -37,7 +37,7 @@ :height height :transform transform})) path? (some? (.-d props))] - [:clipPath {:id (frame-clip-id shape render-id) :class "frame-clip"} + [:clipPath.frame-clip-def {:id (frame-clip-id shape render-id) :class "frame-clip"} (if path? [:> :path props] [:> :rect props])]))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index 1715b887a2..5f13ae5fbc 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -30,7 +30,6 @@ (let [canvas-context (.getContext canvas-node "2d") canvas-width (.-width canvas-node) canvas-height (.-height canvas-node)] - (.clearRect canvas-context 0 0 canvas-width canvas-height) (.drawImage canvas-context img-node 0 0 canvas-width canvas-height) true)) @@ -92,6 +91,8 @@ ;; State variable to select whether we show the image thumbnail or the canvas thumbnail show-frame-thumbnail (mf/use-state (some? thumbnail-data)) + disable-fills? (or @show-frame-thumbnail (some? @image-url)) + on-image-load (mf/use-callback (fn [] @@ -99,8 +100,6 @@ #(let [canvas-node (mf/ref-val frame-canvas-ref) img-node (mf/ref-val frame-image-ref)] (when (draw-thumbnail-canvas! canvas-node img-node) - (reset! image-url nil) - (when @show-frame-thumbnail (reset! show-frame-thumbnail false)) ;; If we don't have the thumbnail data saved (normally the first load) we update the data @@ -229,8 +228,11 @@ (some? thumbnail-data) (assoc :thumbnail thumbnail-data))}]) - - [:foreignObject {:x x :y y :width width :height height} + [:foreignObject {:x x + :y y + :width width + :height height + :opacity (when disable-fills? 0)} [:canvas.thumbnail-canvas {:key (dm/str "thumbnail-canvas-" (:id shape)) :ref frame-canvas-ref From 32746a59606f12f7ca4bc2e58e5a23f0c34086d0 Mon Sep 17 00:00:00 2001 From: Eva Date: Mon, 21 Nov 2022 11:59:13 +0100 Subject: [PATCH 255/682] :bug: Fix some layout errors --- .../partials/sidebar-element-options.scss | 9 ++- .../app/main/data/workspace/shape_layout.cljs | 64 ++++++++++++++----- .../src/app/main/data/workspace/shapes.cljs | 7 +- .../app/main/ui/workspace/context_menu.cljs | 2 +- .../options/menus/layout_container.cljs | 8 ++- .../sidebar/options/shapes/group.cljs | 24 ++++--- .../sidebar/options/shapes/multiple.cljs | 4 +- 7 files changed, 81 insertions(+), 37 deletions(-) diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 7e956838d2..1f1c28f303 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -1610,6 +1610,7 @@ .btn-wrapper { display: flex; width: 100%; + max-width: 185px; .direction, .wrap-type, .align-items-style, @@ -1652,7 +1653,7 @@ border: none; cursor: pointer; border-right: 1px solid $color-gray-60; - padding: 4px; + padding: 2px; &.reverse-row { svg { transform: rotate(180deg); @@ -1687,13 +1688,17 @@ align-items: center; justify-content: center; height: 21px; - width: 21px; + width: 17px; svg { height: 0.7rem; width: 0.7rem; } } + .wrap { + padding: 1px; + } + .gap-group { display: flex; margin-top: 3px; diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 3e7d2a9ac5..d718078878 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -6,11 +6,13 @@ (ns app.main.data.workspace.shape-layout (:require + [app.common.colors :as clr] [app.common.data :as d] [app.common.pages.helpers :as cph] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dwc] + [app.main.data.workspace.colors :as cl] [app.main.data.workspace.selection :as dwse] [app.main.data.workspace.shapes :as dws] [app.main.data.workspace.shapes-update-layout :as wsul] @@ -53,9 +55,9 @@ (-> shape (merge shape initial-layout-data))))) -(defn create-layout +(defn create-layout-from-id [ids type] - (ptk/reify ::create-layout + (ptk/reify ::create-layout-from-id ptk/WatchEvent (watch [_ state _] (let [objects (wsh/lookup-page-objects state) @@ -81,15 +83,27 @@ (let [new-shape-id (uuid/next) parent-id (:parent-id (first selected-shapes)) shapes-ids (:shapes (first selected-shapes)) - ordered-ids (into (d/ordered-set) shapes-ids)] - (rx/of (dwse/select-shapes ordered-ids) - (dws/create-artboard-from-selection new-shape-id parent-id) - (create-layout [new-shape-id] type) - (dws/delete-shapes page-id selected))) + ordered-ids (into (d/ordered-set) shapes-ids) + undo-id (uuid/next)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dwse/select-shapes ordered-ids) + (dws/create-artboard-from-selection new-shape-id parent-id) + (cl/remove-all-fills [new-shape-id] {:color clr/black + :opacity 1}) + (create-layout-from-id [new-shape-id] type) + (dws/delete-shapes page-id selected) + (dwu/commit-undo-transaction undo-id))) - (let [new-shape-id (uuid/next)] - (rx/of (dws/create-artboard-from-selection new-shape-id) - (create-layout [new-shape-id] type)))))))) + (let [new-shape-id (uuid/next) + undo-id (uuid/next)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dws/create-artboard-from-selection new-shape-id) + (cl/remove-all-fills [new-shape-id] {:color clr/black + :opacity 1}) + (create-layout-from-id [new-shape-id] type) + (dwu/commit-undo-transaction undo-id)))))))) (defn remove-layout [ids] @@ -99,6 +113,29 @@ (rx/of (dwc/update-shapes ids #(apply dissoc % layout-keys)) (wsul/update-layout-positions ids))))) +(defn create-layout + [] + (ptk/reify ::create-layout + ptk/WatchEvent + (watch [_ state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + selected (wsh/lookup-selected state) + selected-shapes (map (d/getf objects) selected) + single? (= (count selected-shapes) 1) + is-frame? (= :frame (:type (first selected-shapes))) + undo-id (uuid/next)] + + (if (and single? is-frame?) + (rx/of + (dwu/start-undo-transaction undo-id) + (create-layout-from-id [(first selected)] :flex) + (dwu/commit-undo-transaction undo-id)) + (rx/of + (dwu/start-undo-transaction undo-id) + (create-layout-from-selection :flex) + (dwu/commit-undo-transaction undo-id))))))) + (defn toogle-layout-flex [] (ptk/reify ::toogle-layout-flex @@ -109,14 +146,11 @@ selected (wsh/lookup-selected state) selected-shapes (map (d/getf objects) selected) single? (= (count selected-shapes) 1) - has-flex-layout? (and single? (= :flex (:layout (first selected-shapes)))) - is-frame? (and single? (= :frame (:type (first selected-shapes))))] + has-flex-layout? (and single? (ctl/layout? objects (:id (first selected-shapes))))] (if has-flex-layout? (rx/of (remove-layout selected)) - (if is-frame? - (rx/of (create-layout [(first selected)] :flex)) - (rx/of (create-layout-from-selection :flex)))))))) + (rx/of (create-layout))))))) (defn update-layout [ids changes] diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 49bfe4b1ca..4143b31520 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -362,12 +362,13 @@ (assoc :frame-id frame-id :parent-id parent-id) (cond-> (not= frame-id uuid/zero) (assoc :fills [] :hide-in-viewer true)) - (cts/setup-rect-selrect))] + (cts/setup-rect-selrect)) + undo-id (uuid/next)] (rx/of - (dwu/start-undo-transaction) + (dwu/start-undo-transaction undo-id) (add-shape shape) (move-shapes-into-frame (:id shape) selected) - (dwu/commit-undo-transaction))))))))) + (dwu/commit-undo-transaction undo-id))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Shape Flags diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 1fab9587a0..00d120793f 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -376,7 +376,7 @@ is-group? (and single? has-group?) ids (->> shapes (map :id)) add-flex #(st/emit! (if is-frame? - (dwsl/create-layout ids :flex) + (dwsl/create-layout-from-id ids :flex) (dwsl/create-layout-from-selection :flex))) remove-flex #(st/emit! (dwsl/remove-layout ids))] (cond diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index 68452f9df7..e8111f373b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -267,8 +267,10 @@ layout-type (:layout values) on-add-layout - (fn [type] - (st/emit! (dwsl/create-layout ids type))) + (fn [_] + (st/emit! (dwsl/create-layout)) + (reset! open? true)) + on-remove-layout (fn [_] @@ -376,7 +378,7 @@ :active (= :grid layout-type))} "Grid"]] [:button.remove-layout {:on-click on-remove-layout} i/minus]] - [:button.add-page {:on-click #(on-add-layout :flex)} i/close])]] + [:button.add-page {:on-click on-add-layout} i/close])]] (when (:layout values) (if (= :flex layout-type) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index 6f921a4b01..1876d74d32 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.shapes.group (:require [app.common.data :as d] + [app.main.features :as features] [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]] @@ -14,6 +15,7 @@ [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-menu]] + [app.main.ui.workspace.sidebar.options.menus.layout-container :refer [layout-container-flex-attrs layout-container-menu]] [app.main.ui.workspace.sidebar.options.menus.layout-item :refer [layout-item-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] @@ -27,15 +29,16 @@ {::mf/wrap [mf/memo] ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - shape-with-children (unchecked-get props "shape-with-children") - shared-libs (unchecked-get props "shared-libs") - objects (->> shape-with-children (group-by :id) (d/mapm (fn [_ v] (first v)))) - file-id (unchecked-get props "file-id") - - ids [(:id shape)] - is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) - is-layout-child? (mf/deref is-layout-child-ref) + (let [shape (unchecked-get props "shape") + shape-with-children (unchecked-get props "shape-with-children") + shared-libs (unchecked-get props "shared-libs") + objects (->> shape-with-children (group-by :id) (d/mapm (fn [_ v] (first v)))) + file-id (unchecked-get props "file-id") + layout-active? (features/use-feature :auto-layout) + layout-container-values (select-keys shape layout-container-flex-attrs) + ids [(:id shape)] + is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) + is-layout-child? (mf/deref is-layout-child-ref) type :group [measure-ids measure-values] (get-attrs [shape] objects :measure) @@ -53,7 +56,8 @@ [:div.options [:& measures-menu {:type type :ids measure-ids :values measure-values :shape shape}] [:& component-menu {:ids comp-ids :values comp-values :shape-name (:name shape)}] - + (when layout-active? + [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}]) (when is-layout-child? [:& layout-item-menu {:type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index 06425fd9ed..fd7281e326 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -288,7 +288,6 @@ stroke-ids stroke-values text-ids text-values exports-ids exports-values - layout-container-ids layout-container-values layout-item-ids layout-item-values] (mf/use-memo (mf/deps objects-no-measures) @@ -312,8 +311,7 @@ (when-not (empty? measure-ids) [:& measures-menu {:type type :all-types all-types :ids measure-ids :values measure-values :shape shapes}]) - (when (:layout layout-container-values) - [:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values}]) + [:& layout-container-menu {:type type :ids [] :values []}] (when is-layout-child? [:& layout-item-menu From 694d90d485cc916ff104f1e845d1e6453ddacbaf Mon Sep 17 00:00:00 2001 From: Eva Date: Tue, 22 Nov 2022 14:36:45 +0100 Subject: [PATCH 256/682] :sparkles: Add id functionality to undo transactions --- frontend/src/app/main/data/workspace.cljs | 7 +- .../main/data/workspace/drawing/common.cljs | 2 +- .../app/main/data/workspace/interactions.cljs | 7 +- .../app/main/data/workspace/libraries.cljs | 29 +- .../app/main/data/workspace/modifiers.cljs | 8 +- .../app/main/data/workspace/shape_layout.cljs | 9 +- .../src/app/main/data/workspace/texts.cljs | 2 +- .../app/main/data/workspace/transforms.cljs | 6 +- .../src/app/main/data/workspace/undo.cljs | 22 +- .../app/main/ui/workspace/colorpicker.cljs | 4 +- .../app/main/ui/workspace/sidebar/assets.cljs | 257 ++++++++++-------- .../sidebar/options/menus/measures.cljs | 8 +- .../sidebar/options/menus/shadow.cljs | 4 +- .../ui/workspace/sidebar/options/page.cljs | 4 +- .../ui/workspace/viewport/pixel_overlay.cljs | 4 +- 15 files changed, 208 insertions(+), 165 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index d8c4a9cc55..24e9413777 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1601,11 +1601,12 @@ :width width :height height :grow-type (if (> (count text) 100) :auto-height :auto-width) - :content (as-content text)})] - (rx/of (dwu/start-undo-transaction) + :content (as-content text)}) + undo-id (uuid/next)] + (rx/of (dwu/start-undo-transaction undo-id) (dws/deselect-all) (dwsh/add-shape shape) - (dwu/commit-undo-transaction)))))) + (dwu/commit-undo-transaction undo-id)))))) ;; TODO: why not implement it in terms of upload-media-workspace? (defn- paste-svg diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index 7c58476241..796fe86203 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -64,7 +64,7 @@ ;; Add & select the created shape to the workspace (rx/concat (if (= :text (:type shape)) - (rx/of (dwu/start-undo-transaction)) + (rx/of (dwu/start-undo-transaction (:id shape))) (rx/empty)) (rx/of (dwsh/add-shape shape {:no-select? (= tool :curve)})) diff --git a/frontend/src/app/main/data/workspace/interactions.cljs b/frontend/src/app/main/data/workspace/interactions.cljs index ca33c05e80..ce22d5aec5 100644 --- a/frontend/src/app/main/data/workspace/interactions.cljs +++ b/frontend/src/app/main/data/workspace/interactions.cljs @@ -245,10 +245,11 @@ (ctsi/set-action-type :navigate) :always - (ctsi/set-destination (:id target-frame))))] + (ctsi/set-destination (:id target-frame)))) + undo-id (uuid/next)] (rx/of - (dwu/start-undo-transaction) + (dwu/start-undo-transaction undo-id) (when (:hide-in-viewer target-frame) ; If the target frame is hidden, we need to unhide it so @@ -274,7 +275,7 @@ :else (update-interaction shape index change-interaction)) - (dwu/commit-undo-transaction)))))) + (dwu/commit-undo-transaction undo-id)))))) ;; --- Overlays diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 98a176d247..7a028d0ac6 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -152,11 +152,13 @@ color (assoc color :path path :name name) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) - (pcb/update-color color))] - (rx/of (dwu/start-undo-transaction) + (pcb/update-color color)) + + undo-id (uuid/next)] + (rx/of (dwu/start-undo-transaction undo-id) (dch/commit-changes changes) (sync-file (:current-file-id state) file-id :colors (:id color)) - (dwu/commit-undo-transaction)))) + (dwu/commit-undo-transaction undo-id)))) (defn update-color [color file-id] @@ -256,11 +258,12 @@ typography (extract-path-if-missing typography) changes (-> (pcb/empty-changes it) (pcb/with-library-data data) - (pcb/update-typography typography))] - (rx/of (dwu/start-undo-transaction) + (pcb/update-typography typography)) + undo-id (uuid/next)] + (rx/of (dwu/start-undo-transaction undo-id) (dch/commit-changes changes) (sync-file (:current-file-id state) file-id :typographies (:id typography)) - (dwu/commit-undo-transaction)))) + (dwu/commit-undo-transaction undo-id)))) (defn update-typography [typography file-id] @@ -646,24 +649,26 @@ (watch [_ state _] (let [current-file-id (:current-file-id state) page (wsh/lookup-page state) - shape (ctn/get-shape page shape-id)] + shape (ctn/get-shape page shape-id) + undo-id (uuid/next)] (rx/of - (dwu/start-undo-transaction) + (dwu/start-undo-transaction undo-id) (update-component shape-id) (sync-file current-file-id file-id :components (:component-id shape)) (when (not= current-file-id file-id) (sync-file file-id file-id :components (:component-id shape))) - (dwu/commit-undo-transaction)))))) + (dwu/commit-undo-transaction undo-id)))))) (defn update-component-in-bulk [shapes file-id] (ptk/reify ::update-component-in-bulk ptk/WatchEvent (watch [_ _ _] - (rx/concat - (rx/of (dwu/start-undo-transaction)) + (let [undo-id (uuid/next)] + (rx/concat + (rx/of (dwu/start-undo-transaction undo-id)) (rx/map #(update-component-sync (:id %) file-id) (rx/from shapes)) - (rx/of (dwu/commit-undo-transaction)))))) + (rx/of (dwu/commit-undo-transaction undo-id))))))) (declare sync-file-2nd-stage) diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index 2f153442bd..f6de2ab145 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -17,6 +17,7 @@ [app.common.types.modifiers :as ctm] [app.common.types.shape :as cts] [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.comments :as-alias dwcm] [app.main.data.workspace.guides :as-alias dwg] @@ -293,11 +294,12 @@ shapes (map (d/getf objects) ids) ignore-tree (->> (map #(get-ignore-tree object-modifiers objects %) shapes) - (reduce merge {}))] + (reduce merge {})) + undo-id (uuid/next)] (rx/concat (if undo-transation? - (rx/of (dwu/start-undo-transaction)) + (rx/of (dwu/start-undo-transaction undo-id)) (rx/empty)) (rx/of (ptk/event ::dwg/move-frame-guides ids-with-children) (ptk/event ::dwcm/move-frame-comment-threads ids-with-children) @@ -333,5 +335,5 @@ ]}) (clear-local-transform)) (if undo-transation? - (rx/of (dwu/commit-undo-transaction)) + (rx/of (dwu/commit-undo-transaction undo-id)) (rx/empty)))))))) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index d718078878..3e14d82279 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -17,6 +17,7 @@ [app.main.data.workspace.shapes :as dws] [app.main.data.workspace.shapes-update-layout :as wsul] [app.main.data.workspace.state-helpers :as wsh] + [app.main.data.workspace.undo :as dwu] [beicon.core :as rx] [potok.core :as ptk])) @@ -110,8 +111,12 @@ (ptk/reify ::remove-layout ptk/WatchEvent (watch [_ _ _] - (rx/of (dwc/update-shapes ids #(apply dissoc % layout-keys)) - (wsul/update-layout-positions ids))))) + (let [undo-id (uuid/next)] + (rx/of + (dwu/start-undo-transaction undo-id) + (dwc/update-shapes ids #(apply dissoc % layout-keys)) + (wsul/update-layout-positions ids) + (dwu/commit-undo-transaction undo-id)))))) (defn create-layout [] diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index b0ff747d6c..f0694b253f 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -79,7 +79,7 @@ (assoc :content content) (merge modifiers) (cts/setup-rect-selrect)))) - (dwu/commit-undo-transaction))))) + (dwu/commit-undo-transaction (:id shape)))))) (when (some? id) (rx/of (dws/deselect-shape id) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index be5bf599c4..96088ec36e 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -19,6 +19,7 @@ [app.common.types.modifiers :as ctm] [app.common.types.shape-tree :as ctst] [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] [app.main.data.workspace.modifiers :as dwm] @@ -504,11 +505,12 @@ (rx/last) (rx/mapcat (fn [[_ target-frame drop-index]] - (rx/of (dwu/start-undo-transaction) + (let [undo-id (uuid/next)] + (rx/of (dwu/start-undo-transaction undo-id) (move-shapes-to-frame ids target-frame drop-index) (dwm/apply-modifiers {:undo-transation? false}) (finish-transform) - (dwu/commit-undo-transaction))))))))))))) + (dwu/commit-undo-transaction undo-id)))))))))))))) (s/def ::direction #{:up :down :right :left}) diff --git a/frontend/src/app/main/data/workspace/undo.cljs b/frontend/src/app/main/data/workspace/undo.cljs index 008900f38d..b4dfd7a122 100644 --- a/frontend/src/app/main/data/workspace/undo.cljs +++ b/frontend/src/app/main/data/workspace/undo.cljs @@ -73,28 +73,34 @@ (def empty-tx {:undo-changes [] :redo-changes []}) -(defn start-undo-transaction [] +(defn start-undo-transaction [id] (ptk/reify ::start-undo-transaction ptk/UpdateEvent (update [_ state] ;; We commit the old transaction before starting the new one - (let [current-tx (get-in state [:workspace-undo :transaction])] + (let [current-tx (get-in state [:workspace-undo :transaction]) + pending-tx (get-in state [:workspace-undo :transactions-pending])] (cond-> state - (nil? current-tx) (assoc-in [:workspace-undo :transaction] empty-tx)))))) + (nil? current-tx) (assoc-in [:workspace-undo :transaction] empty-tx) + (nil? pending-tx) (assoc-in [:workspace-undo :transactions-pending] #{id}) + (some? pending-tx) (update-in [:workspace-undo :transactions-pending] conj id)))))) (defn discard-undo-transaction [] (ptk/reify ::discard-undo-transaction ptk/UpdateEvent (update [_ state] - (update state :workspace-undo dissoc :transaction)))) + (update state :workspace-undo dissoc :transaction :transactions-pending)))) -(defn commit-undo-transaction [] +(defn commit-undo-transaction [id] (ptk/reify ::commit-undo-transaction ptk/UpdateEvent (update [_ state] - (-> state - (add-undo-entry (get-in state [:workspace-undo :transaction])) - (update :workspace-undo dissoc :transaction))))) + (let [state (update-in state [:workspace-undo :transactions-pending] disj id)] + (if (empty? (get-in state [:workspace-undo :transactions-pending])) + (-> state + (add-undo-entry (get-in state [:workspace-undo :transaction])) + (update :workspace-undo dissoc :transaction)) + state))))) (def pop-undo-into-transaction (ptk/reify ::last-undo-into-transaction diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index e0ae0d9ad8..6fd51c630e 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -108,13 +108,13 @@ (mf/use-fn (fn [] (reset! drag? true) - (st/emit! (dwu/start-undo-transaction)))) + (st/emit! (dwu/start-undo-transaction (mf/ref-val node-ref))))) on-finish-drag (mf/use-fn (fn [] (reset! drag? false) - (st/emit! (dwu/commit-undo-transaction))))] + (st/emit! (dwu/commit-undo-transaction (mf/ref-val node-ref)))))] ;; Initialize colorpicker state (mf/with-effect [] diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index d77fb28e15..ef04cfc3ab 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -12,6 +12,7 @@ [app.common.pages.helpers :as cph] [app.common.spec :as us] [app.common.text :as txt] + [app.common.uuid :as uuid] [app.config :as cf] [app.main.data.events :as ev] [app.main.data.modal :as modal] @@ -181,13 +182,14 @@ (defn- create-assets-group [rename components-to-group group-name] - (st/emit! (dwu/start-undo-transaction)) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) (apply st/emit! (->> components-to-group (map #(rename (:id %) (add-group % group-name))))) - (st/emit! (dwu/commit-undo-transaction))) + (st/emit! (dwu/commit-undo-transaction undo-id)))) (defn- on-drop-asset [event asset dragging? selected-assets selected-assets-full selected-assets-paths rename] @@ -589,23 +591,25 @@ (mf/use-fn (mf/deps @state) (fn [] - (if (empty? selected-components) + (let [undo-id (uuid/next)] + (if (empty? selected-components) (st/emit! (dwl/duplicate-component {:id (:component-id @state)})) (do - (st/emit! (dwu/start-undo-transaction)) + (st/emit! (dwu/start-undo-transaction undo-id)) (apply st/emit! (map #(dwl/duplicate-component {:id %}) selected-components)) - (st/emit! (dwu/commit-undo-transaction)))))) + (st/emit! (dwu/commit-undo-transaction undo-id))))))) on-delete (mf/use-fn (mf/deps @state file-id multi-components? multi-assets?) (fn [] - (if (or multi-components? multi-assets?) + (let [undo-id (uuid/next)] + (if (or multi-components? multi-assets?) (on-assets-delete) - (st/emit! (dwu/start-undo-transaction) + (st/emit! (dwu/start-undo-transaction undo-id) (dwl/delete-component {:id (:component-id @state)}) (dwl/sync-file file-id file-id :components (:component-id @state)) - (dwu/commit-undo-transaction))))) + (dwu/commit-undo-transaction undo-id)))))) on-rename (mf/use-fn @@ -646,30 +650,32 @@ (mf/deps components selected-components on-clear-selection) (fn [group-name] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> components - (filter #(if multi-components? - (contains? selected-components (:id %)) - (= (:component-id @state) (:id %)))) - (map #(dwl/rename-component - (:id %) - (add-group % group-name))))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> components + (filter #(if multi-components? + (contains? selected-components (:id %)) + (= (:component-id @state) (:id %)))) + (map #(dwl/rename-component + (:id %) + (add-group % group-name))))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) rename-group (mf/use-fn (mf/deps components) (fn [path last-path] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> components - (filter #(str/starts-with? (:path %) path)) - (map #(dwl/rename-component - (:id %) - (rename-group % path last-path))))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> components + (filter #(str/starts-with? (:path %) path)) + (map #(dwl/rename-component + (:id %) + (rename-group % path last-path))))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) on-group (mf/use-fn @@ -692,14 +698,15 @@ (mf/deps components) (fn [path] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> components - (filter #(str/starts-with? (:path %) path)) - (map #(dwl/rename-component - (:id %) - (ungroup % path))))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> components + (filter #(str/starts-with? (:path %) path)) + (map #(dwl/rename-component + (:id %) + (ungroup % path))))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) on-drag-start (mf/use-fn @@ -1015,30 +1022,32 @@ (mf/deps objects selected-objects on-clear-selection) (fn [group-name] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> objects - (filter #(if multi-objects? - (contains? selected-objects (:id %)) - (= (:object-id @state) (:id %)))) - (map #(dwl/rename-media - (:id %) - (add-group % group-name))))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> objects + (filter #(if multi-objects? + (contains? selected-objects (:id %)) + (= (:object-id @state) (:id %)))) + (map #(dwl/rename-media + (:id %) + (add-group % group-name))))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) rename-group (mf/use-fn (mf/deps objects) (fn [path last-path] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> objects - (filter #(str/starts-with? (:path %) path)) - (map #(dwl/rename-media - (:id %) - (rename-group % path last-path))))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> objects + (filter #(str/starts-with? (:path %) path)) + (map #(dwl/rename-media + (:id %) + (rename-group % path last-path))))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) on-group (mf/use-fn @@ -1060,14 +1069,15 @@ (mf/deps objects) (fn [path] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> objects - (filter #(str/starts-with? (:path %) path)) - (map #(dwl/rename-media - (:id %) - (ungroup % path))))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> objects + (filter #(str/starts-with? (:path %) path)) + (map #(dwl/rename-media + (:id %) + (ungroup % path))))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) on-drag-start (mf/use-fn @@ -1175,10 +1185,11 @@ (fn [] (if (or multi-colors? multi-assets?) (on-assets-delete) - (st/emit! (dwu/start-undo-transaction) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id) (dwl/delete-color color) (dwl/sync-file file-id file-id :colors (:id color)) - (dwu/commit-undo-transaction))))) + (dwu/commit-undo-transaction undo-id)))))) rename-color-clicked (fn [event] @@ -1436,7 +1447,8 @@ (fn [color-id] (fn [group-name] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) (apply st/emit! (->> colors (filter #(if multi-colors? @@ -1446,22 +1458,23 @@ (assoc % :name (add-group % group-name)) file-id)))) - (st/emit! (dwu/commit-undo-transaction))))) + (st/emit! (dwu/commit-undo-transaction undo-id)))))) rename-group (mf/use-fn (mf/deps colors) (fn [path last-path] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> colors - (filter #(str/starts-with? (:path %) path)) - (map #(dwl/update-color - (assoc % :name - (rename-group % path last-path)) - file-id)))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> colors + (filter #(str/starts-with? (:path %) path)) + (map #(dwl/update-color + (assoc % :name + (rename-group % path last-path)) + file-id)))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) on-group (mf/use-fn @@ -1484,15 +1497,16 @@ (mf/deps colors) (fn [path] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> colors - (filter #(str/starts-with? (:path %) path)) - (map #(dwl/update-color - (assoc % :name - (ungroup % path)) - file-id)))) - (st/emit! (dwu/commit-undo-transaction))))] + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> colors + (filter #(str/starts-with? (:path %) path)) + (map #(dwl/update-color + (assoc % :name + (ungroup % path)) + file-id)))) + (st/emit! (dwu/commit-undo-transaction undo-id)))))] [:& asset-section {:file-id file-id :title (tr "workspace.assets.colors") @@ -1724,32 +1738,34 @@ (mf/deps typographies selected-typographies on-clear-selection file-id) (fn [group-name] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> typographies - (filter #(if multi-typographies? - (contains? selected-typographies (:id %)) - (= (:id @state) (:id %)))) - (map #(dwl/update-typography - (assoc % :name - (add-group % group-name)) - file-id)))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> typographies + (filter #(if multi-typographies? + (contains? selected-typographies (:id %)) + (= (:id @state) (:id %)))) + (map #(dwl/update-typography + (assoc % :name + (add-group % group-name)) + file-id)))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) rename-group (mf/use-fn (mf/deps typographies) (fn [path last-path] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> typographies - (filter #(str/starts-with? (:path %) path)) - (map #(dwl/update-typography - (assoc % :name - (rename-group % path last-path)) - file-id)))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> typographies + (filter #(str/starts-with? (:path %) path)) + (map #(dwl/update-typography + (assoc % :name + (rename-group % path last-path)) + file-id)))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) on-group (mf/use-fn @@ -1771,15 +1787,16 @@ (mf/deps typographies) (fn [path] (on-clear-selection) - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! - (->> typographies - (filter #(str/starts-with? (:path %) path)) - (map #(dwl/rename-typography - file-id - (:id %) - (ungroup % path))))) - (st/emit! (dwu/commit-undo-transaction)))) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) + (apply st/emit! + (->> typographies + (filter #(str/starts-with? (:path %) path)) + (map #(dwl/rename-typography + file-id + (:id %) + (ungroup % path))))) + (st/emit! (dwu/commit-undo-transaction undo-id))))) on-context-menu (mf/use-fn @@ -1808,12 +1825,13 @@ (mf/use-fn (mf/deps @state multi-typographies? multi-assets?) (fn [] - (if (or multi-typographies? multi-assets?) - (on-assets-delete) - (st/emit! (dwu/start-undo-transaction) - (dwl/delete-typography (:id @state)) - (dwl/sync-file file-id file-id :typographies (:id @state)) - (dwu/commit-undo-transaction))))) + (let [undo-id (uuid/next)] + (if (or multi-typographies? multi-assets?) + (on-assets-delete) + (st/emit! (dwu/start-undo-transaction undo-id) + (dwl/delete-typography (:id @state)) + (dwl/sync-file file-id file-id :typographies (:id @state)) + (dwu/commit-undo-transaction undo-id)))))) editing-id (or (:rename-typography local-data) (:edit-typography local-data))] @@ -2045,7 +2063,8 @@ (mf/use-fn (mf/deps selected-assets) (fn [] - (st/emit! (dwu/start-undo-transaction)) + (let [undo-id (uuid/next)] + (st/emit! (dwu/start-undo-transaction undo-id)) (apply st/emit! (map #(dwl/delete-component {:id %}) (:components selected-assets))) (apply st/emit! (map #(dwl/delete-media {:id %}) @@ -2058,7 +2077,7 @@ (d/not-empty? (:colors selected-assets)) (d/not-empty? (:typographies selected-assets))) (st/emit! (dwl/sync-file (:id file) (:id file)))) - (st/emit! (dwu/commit-undo-transaction))))] + (st/emit! (dwu/commit-undo-transaction undo-id)))))] [:div.tool-window {:on-context-menu #(dom/prevent-default %) :on-click unselect-all} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs index 5c0f2fa7c0..d5a02c5d42 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/measures.cljs @@ -10,6 +10,7 @@ [app.common.geom.shapes :as gsh] [app.common.types.shape.layout :as ctl] [app.common.types.shape.radius :as ctsr] + [app.common.uuid :as uuid] [app.main.constants :refer [size-presets]] [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dch] @@ -248,9 +249,10 @@ (mf/use-callback (mf/deps ids) (fn [event] - (let [value (-> event dom/get-target dom/checked?)] + (let [value (-> event dom/get-target dom/checked?) + undo-id (uuid/next)] (do - (st/emit! (dwu/start-undo-transaction) + (st/emit! (dwu/start-undo-transaction undo-id) (dch/update-shapes ids (fn [shape] (assoc shape :hide-in-viewer (not value))))) (when-not value @@ -258,7 +260,7 @@ ;; interactions that navigate to it. (apply st/emit! (map #(dwi/remove-all-interactions-nav-to %) ids))) - (st/emit! (dwu/commit-undo-transaction)))))) + (st/emit! (dwu/commit-undo-transaction undo-id)))))) select-all #(-> % (dom/get-target) (.select))] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs index 66f0e6f814..31e8b8a478 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs @@ -192,8 +192,8 @@ :disable-gradient true :on-change (update-color index) :on-detach (detach-color index) - :on-open #(st/emit! (dwu/start-undo-transaction)) - :on-close #(st/emit! (dwu/commit-undo-transaction))}]]]])) + :on-open #(st/emit! (dwu/start-undo-transaction :color-row)) + :on-close #(st/emit! (dwu/commit-undo-transaction :color-row))}]]]])) (mf/defc shadow-menu [{:keys [ids type values] :as props}] (let [on-remove-all-shadows diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs index 6bb27a3f23..dd9341be88 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs @@ -27,11 +27,11 @@ on-open (fn [] - (st/emit! (dwu/start-undo-transaction))) + (st/emit! (dwu/start-undo-transaction :options))) on-close (fn [] - (st/emit! (dwu/commit-undo-transaction)))] + (st/emit! (dwu/commit-undo-transaction :options)))] [:div.element-set [:div.element-set-title (tr "workspace.options.canvas-background")] diff --git a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs index cf0f384ebf..214c9b599d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs @@ -114,7 +114,7 @@ (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (st/emit! (dwu/start-undo-transaction) + (st/emit! (dwu/start-undo-transaction :mouse-down-picker) (dwc/pick-color-select true (kbd/shift? event))))) handle-mouse-up-picker @@ -122,7 +122,7 @@ (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (st/emit! (dwu/commit-undo-transaction) + (st/emit! (dwu/commit-undo-transaction :mouse-down-picker) (dwc/stop-picker)) (modal/disallow-click-outside!))) From 69bb4654c9f6b72e833039098333184d0eed9883 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 23 Nov 2022 13:03:37 +0100 Subject: [PATCH 257/682] :sparkles: Improve transforms performance --- common/src/app/common/attrs.cljc | 4 +- common/src/app/common/geom/matrix.cljc | 45 ++++--- common/src/app/common/geom/point.cljc | 4 + common/src/app/common/geom/shapes.cljc | 12 +- .../app/common/geom/shapes/constraints.cljc | 93 +++++++------- .../geom/shapes/flex_layout/drop_area.cljc | 37 +++--- .../common/geom/shapes/flex_layout/lines.cljc | 26 ++-- .../geom/shapes/flex_layout/modifiers.cljc | 42 ++++--- .../geom/shapes/flex_layout/positions.cljc | 21 ++-- .../src/app/common/geom/shapes/modifiers.cljc | 117 ++++++++++-------- .../common/geom/shapes/pixel_precision.cljc | 32 ++--- common/src/app/common/geom/shapes/points.cljc | 54 +++++++- .../app/common/geom/shapes/transforms.cljc | 26 ++-- common/src/app/common/types/modifiers.cljc | 45 ++++--- frontend/src/app/main/data/workspace.cljs | 13 +- .../src/app/main/data/workspace/shapes.cljs | 4 +- .../shapes/text/viewport_texts_html.cljs | 9 +- .../src/app/main/ui/workspace/viewport.cljs | 6 + .../app/main/ui/workspace/viewport/debug.cljs | 56 ++++++++- frontend/src/debug.cljs | 3 + 20 files changed, 402 insertions(+), 247 deletions(-) diff --git a/common/src/app/common/attrs.cljc b/common/src/app/common/attrs.cljc index 7409172feb..7bc8d5001e 100644 --- a/common/src/app/common/attrs.cljc +++ b/common/src/app/common/attrs.cljc @@ -6,7 +6,7 @@ (ns app.common.attrs (:require - [app.common.geom.shapes.transforms :as gst] + [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth])) (defn- get-attr @@ -24,7 +24,7 @@ value (if-let [points (:points obj)] (if (not= points :multiple) - (let [rect (gst/selection-rect [obj])] + (let [rect (gtr/selection-rect [obj])] (if (= attr :ox) (:x rect) (:y rect))) :multiple) (get obj attr ::unset))) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index b8145090f6..ac7443f2d5 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -91,27 +91,34 @@ (defn multiply ([^Matrix m1 ^Matrix m2] - (let [m1a (.-a m1) - m1b (.-b m1) - m1c (.-c m1) - m1d (.-d m1) - m1e (.-e m1) - m1f (.-f m1) + (cond + ;; nil matrixes are equivalent to unit-matrix + (and (nil? m1) (nil? m2)) (matrix) + (nil? m1) m2 + (nil? m2) m1 - m2a (.-a m2) - m2b (.-b m2) - m2c (.-c m2) - m2d (.-d m2) - m2e (.-e m2) - m2f (.-f m2)] + :else + (let [m1a (.-a m1) + m1b (.-b m1) + m1c (.-c m1) + m1d (.-d m1) + m1e (.-e m1) + m1f (.-f m1) - (Matrix. - (+ (* m1a m2a) (* m1c m2b)) - (+ (* m1b m2a) (* m1d m2b)) - (+ (* m1a m2c) (* m1c m2d)) - (+ (* m1b m2c) (* m1d m2d)) - (+ (* m1a m2e) (* m1c m2f) m1e) - (+ (* m1b m2e) (* m1d m2f) m1f)))) + m2a (.-a m2) + m2b (.-b m2) + m2c (.-c m2) + m2d (.-d m2) + m2e (.-e m2) + m2f (.-f m2)] + + (Matrix. + (+ (* m1a m2a) (* m1c m2b)) + (+ (* m1b m2a) (* m1d m2b)) + (+ (* m1a m2c) (* m1c m2d)) + (+ (* m1b m2c) (* m1d m2d)) + (+ (* m1a m2e) (* m1c m2f) m1e) + (+ (* m1b m2e) (* m1d m2f) m1f))))) ([m1 m2 & others] (reduce multiply (multiply m1 m2) others))) diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 5b17160d9f..36b0c9fadb 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -189,6 +189,10 @@ (defn angle-sign [v1 v2] (if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1)) +(defn signed-angle-with-other + [v1 v2] + (* (angle-sign v1 v2) (angle-with-other v1 v2))) + (defn update-angle "Update the angle of the point." [p angle] diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 54c07a5701..b4aa9ef50f 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -13,7 +13,7 @@ [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.corners :as gsc] - [app.common.geom.shapes.intersect :as gin] + [app.common.geom.shapes.intersect :as gsi] [app.common.geom.shapes.modifiers :as gsm] [app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.rect :as gpr] @@ -178,8 +178,6 @@ (dm/export gtr/transform-bounds) (dm/export gtr/move-position-data) (dm/export gtr/apply-objects-modifiers) -(dm/export gtr/parent-coords-rect) -(dm/export gtr/parent-coords-points) ;; Constratins (dm/export gct/calc-child-modifiers) @@ -190,10 +188,10 @@ (dm/export gsp/open-path?) ;; Intersection -(dm/export gin/overlaps?) -(dm/export gin/has-point?) -(dm/export gin/has-point-rect?) -(dm/export gin/rect-contains-shape?) +(dm/export gsi/overlaps?) +(dm/export gsi/has-point?) +(dm/export gsi/has-point-rect?) +(dm/export gsi/rect-contains-shape?) ;; Bool diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 2ced1cce1f..b5e0602146 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -6,12 +6,11 @@ (ns app.common.geom.shapes.constraints (:require + [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.intersect :as gsi] [app.common.geom.shapes.points :as gpo] - [app.common.geom.shapes.rect :as gre] - [app.common.geom.shapes.transforms :as gst] + [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth] [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) @@ -184,7 +183,7 @@ (ctm/move-modifiers (displacement end-before end-after)))) (defmethod constraint-modifier :fixed - [_ axis child-points-before parent-points-before child-points-after parent-points-after transformed-parent] + [_ axis child-points-before parent-points-before child-points-after parent-points-after {:keys [transform transform-inverse]} modifiers] (let [;; Same as constraint end end-before (end-vector axis child-points-before parent-points-before) end-after (end-vector axis child-points-after parent-points-after) @@ -203,11 +202,16 @@ ;; displacement (so its left+top position is constant) scale (/ (gpt/length after-vec) (gpt/length before-vec)) - resize-origin (first child-points-after) - {:keys [transform transform-inverse]} transformed-parent] + resize-origin (gpo/origin child-points-after) + modif-transform (ctm/modifiers->transform modifiers) + modif-transform-inverse (gmt/inverse modif-transform) + resize-transform (gmt/multiply modif-transform transform) + resize-transform-inverse (gmt/multiply transform-inverse modif-transform-inverse) + + resize-vector (get-scale axis scale)] (-> (ctm/empty) - (ctm/resize (get-scale axis scale) resize-origin transform transform-inverse) + (ctm/resize resize-vector resize-origin resize-transform resize-transform-inverse) (ctm/move disp-start)))) (defmethod constraint-modifier :center @@ -245,37 +249,36 @@ :top :scale))) -(defn bounding-box-parent-transform - "Returns a bounding box for the child in the same coordinate system - as the parent. - Returns a points array" - [child parent] - (-> child - :points - (gco/transform-points (:transform-inverse parent)) - (gre/points->rect) - (gre/rect->points) ;; Restore to points so we can transform them - (gco/transform-points (:transform parent)))) - (defn normalize-modifiers "Before aplying constraints we need to remove the deformation caused by the resizing of the parent" - [constraints-h constraints-v modifiers child parent transformed-child {:keys [transform transform-inverse] :as transformed-parent}] + [constraints-h constraints-v modifiers child {:keys [transform transform-inverse] :as parent} transformed-child-bounds transformed-parent-bounds] - (let [child-bb-before (gst/parent-coords-rect child parent) - child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) - scale-x (/ (:width child-bb-before) (:width child-bb-after)) - scale-y (/ (:height child-bb-before) (:height child-bb-after)) - resize-origin (-> transformed-parent :points gpo/origin)] + (let [child-bb-before (gpo/parent-coords-bounds (:points child) (:points parent)) + child-bb-after (gpo/parent-coords-bounds transformed-child-bounds transformed-parent-bounds) - (cond-> modifiers - (not= :scale constraints-h) - (ctm/resize (gpt/point scale-x 1) resize-origin transform transform-inverse) + scale-x (if (= :scale constraints-h) + 1 + (/ (gpo/width-points child-bb-before) (gpo/width-points child-bb-after))) - (not= :scale constraints-v) - (ctm/resize (gpt/point 1 scale-y) resize-origin transform transform-inverse)))) + scale-y (if (= :scale constraints-v) + 1 + (/ (gpo/height-points child-bb-before) (gpo/height-points child-bb-after))) + + resize-vector (gpt/point scale-x scale-y) + modif-transform (ctm/modifiers->transform modifiers) + modif-transform-inverse (gmt/inverse modif-transform) + resize-transform (gmt/multiply modif-transform transform) + resize-transform-inverse (gmt/multiply transform-inverse modif-transform-inverse) + resize-origin (gpo/origin transformed-child-bounds)] + (-> modifiers + (ctm/resize + resize-vector + resize-origin + resize-transform + resize-transform-inverse)))) (defn calc-child-modifiers - [parent child modifiers ignore-constraints transformed-parent] + [parent child modifiers ignore-constraints parent-bounds transformed-parent-bounds] (let [modifiers (ctm/select-child-modifiers modifiers) @@ -292,26 +295,24 @@ (if (and (= :scale constraints-h) (= :scale constraints-v)) modifiers - (let [transformed-child (gst/transform-shape child (ctm/select-child-modifiers modifiers)) - modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child transformed-parent) + (let [child-bounds (:points child) + modifiers (ctm/select-child-modifiers modifiers) + transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) + modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child-bounds transformed-parent-bounds) + transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) - transformed-child (gst/transform-shape child modifiers) - - parent-points-before (bounding-box-parent-transform parent parent) - child-points-before (bounding-box-parent-transform child parent) - parent-points-after (bounding-box-parent-transform transformed-parent transformed-parent) - child-points-after (bounding-box-parent-transform transformed-child transformed-parent) + child-points-before (gpo/parent-coords-bounds child-bounds parent-bounds) + child-points-after (gpo/parent-coords-bounds transformed-child-bounds transformed-parent-bounds) modifiers-h (constraint-modifier (constraints-h const->type+axis) :x - child-points-before parent-points-before - child-points-after parent-points-after - transformed-parent) + child-points-before parent-bounds + child-points-after transformed-parent-bounds + parent modifiers) modifiers-v (constraint-modifier (constraints-v const->type+axis) :y - child-points-before parent-points-before - child-points-after parent-points-after - transformed-parent)] - + child-points-before parent-bounds + child-points-after transformed-parent-bounds + parent modifiers)] (-> modifiers (ctm/add-modifiers modifiers-h) (ctm/add-modifiers modifiers-v)))))) diff --git a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc index b68057a179..8a5c7a5621 100644 --- a/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/drop_area.cljc @@ -10,25 +10,24 @@ [app.common.geom.matrix :as gmt] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.flex-layout.lines :as fli] + [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.rect :as gsr] [app.common.pages.helpers :as cph] [app.common.types.shape.layout :as ctl])) (defn drop-child-areas - [{:keys [transform-inverse] :as frame} parent-rect child index reverse? prev-x prev-y last?] + [frame parent-rect child-bounds index reverse? prev-x prev-y last?] (let [col? (ctl/col? frame) row? (ctl/row? frame) [layout-gap-row layout-gap-col] (ctl/gaps frame) - start-p (-> child :points first) - center (gco/center-shape frame) - start-p (gmt/transform-point-center start-p center transform-inverse) + start-p (gpo/origin child-bounds) box-x (:x start-p) box-y (:y start-p) - box-width (-> child :selrect :width) - box-height (-> child :selrect :height) + box-width (gpo/width-points child-bounds) + box-height (gpo/height-points child-bounds) x (if col? (:x parent-rect) prev-x) y (if row? (:y parent-rect) prev-y) @@ -148,47 +147,45 @@ from-idx 0 prev-line-x (:x frame) prev-line-y (:y frame) + lines (seq lines)] - current-line (first lines) - lines (rest lines)] - - (if (nil? current-line) + (if (empty? lines) areas - (let [line-area (drop-line-area frame current-line prev-line-x prev-line-y (nil? (first lines))) + (let [current-line (first lines) + line-area (drop-line-area frame current-line prev-line-x prev-line-y (empty? (rest lines))) children (subvec children from-idx (+ from-idx (:num-children current-line))) next-areas (loop [areas areas prev-child-x (:x line-area) prev-child-y (:y line-area) - [index child] (first children) - children (rest children)] + children (seq children)] - (if (nil? child) + (if (empty? children) areas - (let [[child-area child-area-start child-area-end] - (drop-child-areas frame line-area child index (not reverse?) prev-child-x prev-child-y (nil? (first children)))] + (let [[index [child-bounds _]] (first children) + [child-area child-area-start child-area-end] + (drop-child-areas frame line-area child-bounds index (not reverse?) prev-child-x prev-child-y (empty? (rest children)))] (recur (conj areas child-area-start child-area-end) (+ (:x child-area) (:width child-area)) (+ (:y child-area) (:height child-area)) - (first children) (rest children)))))] (recur next-areas (+ from-idx (:num-children current-line)) (+ (:x line-area) (:width line-area)) (+ (:y line-area) (:height line-area)) - (first lines) (rest lines))))))) (defn get-drop-index [frame-id objects position] (let [frame (get objects frame-id) position (gmt/transform-point-center position (gco/center-shape frame) (:transform-inverse frame)) - children (cph/get-immediate-children objects frame-id) - layout-data (fli/calc-layout-data frame children) + children (->> (cph/get-immediate-children objects frame-id) + (map #(vector (gpo/parent-coords-bounds (:points %) (:points frame)) %))) + layout-data (fli/calc-layout-data frame children (:points frame)) drop-areas (layout-drop-areas frame layout-data children) area (d/seek #(gsr/contains-point? % position) drop-areas)] (:index area))) diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index 41dea20ea0..664b91737e 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -9,21 +9,20 @@ [app.common.data :as d] [app.common.geom.shapes.flex-layout.positions :as flp] [app.common.geom.shapes.points :as gpo] - [app.common.geom.shapes.transforms :as gst] [app.common.math :as mth] [app.common.types.shape.layout :as ctl])) (def conjv (fnil conj [])) (defn layout-bounds - [{:keys [layout-padding layout-padding-type] :as shape}] + [{:keys [layout-padding layout-padding-type] :as shape} shape-bounds] (let [;; Add padding to the bounds {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding [pad-top pad-right pad-bottom pad-left] (if (= layout-padding-type :multiple) [pad-top pad-right pad-bottom pad-left] [pad-top pad-top pad-top pad-top])] - (gpo/pad-points (:points shape) pad-top pad-right pad-bottom pad-left))) + (gpo/pad-points shape-bounds pad-top pad-right pad-bottom pad-left))) (defn init-layout-lines "Calculates the lines basic data and accumulated values. The positions will be calculated in a different operation" @@ -43,18 +42,17 @@ (loop [line-data nil result [] - child (first children) - children (rest children)] + children (seq children)] - (if (nil? child) + (if (empty? children) (cond-> result (some? line-data) (conj line-data)) - (let [{:keys [line-min-width line-min-height + (let [[child-bounds child] (first children) + {:keys [line-min-width line-min-height line-max-width line-max-height num-children children-data]} line-data - child-bounds (gst/parent-coords-points child shape) child-width (gpo/width-points child-bounds) child-height (gpo/height-points child-bounds) child-min-width (ctl/child-min-width child) @@ -98,7 +96,6 @@ :num-children (inc num-children) :children-data (conjv children-data child-data)} result - (first children) (rest children)) (recur {:line-min-width next-min-width @@ -108,7 +105,6 @@ :num-children 1 :children-data [child-data]} (cond-> result (some? line-data) (conj line-data)) - (first children) (rest children)))))))) (defn add-space-to-items @@ -300,9 +296,9 @@ (defn calc-layout-data "Digest the layout data to pass it to the constrains" - [shape children] + [shape children shape-bounds] - (let [layout-bounds (layout-bounds shape) + (let [layout-bounds (layout-bounds shape shape-bounds) reverse? (ctl/reverse? shape) children (cond->> children (not reverse?) reverse) @@ -310,10 +306,10 @@ layout-lines (->> (init-layout-lines shape children layout-bounds) (add-lines-positions shape layout-bounds) - (into [] - (comp (map (partial add-line-spacing shape layout-bounds)) - (map (partial add-children-resizes shape)))))] + (into [] (comp (map (partial add-line-spacing shape layout-bounds)) + (map (partial add-children-resizes shape)))))] {:layout-lines layout-lines :layout-bounds layout-bounds :reverse? reverse?})) + diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 039f17f98f..406a31927e 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -6,27 +6,43 @@ (ns app.common.geom.shapes.flex-layout.modifiers (:require + [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.flex-layout.positions :as fpo] [app.common.geom.shapes.points :as gpo] - [app.common.geom.shapes.transforms :as gst] + [app.common.geom.shapes.transforms :as gtr] [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl])) (defn normalize-child-modifiers "Apply the modifiers and then normalized them against the parent coordinates" - [modifiers parent child {:keys [transform transform-inverse] :as transformed-parent}] + [modifiers {:keys [transform transform-inverse] :as parent} child transformed-parent-bounds] + + (let [child-bounds (:points child) + parent-bounds (:points parent) + + transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) + + child-bb-before (gpo/parent-coords-bounds child-bounds parent-bounds) + child-bb-after (gpo/parent-coords-bounds transformed-child-bounds transformed-parent-bounds) + + scale-x (/ (gpo/width-points child-bb-before) (gpo/width-points child-bb-after)) + scale-y (/ (gpo/height-points child-bb-before) (gpo/height-points child-bb-after)) + + resize-vector (gpt/point scale-x scale-y) + modif-transform (ctm/modifiers->transform modifiers) + modif-transform-inverse (gmt/inverse modif-transform) + resize-transform (gmt/multiply modif-transform transform) + resize-transform-inverse (gmt/multiply transform-inverse modif-transform-inverse) + resize-origin (gpo/origin transformed-child-bounds)] - (let [transformed-child (gst/transform-shape child modifiers) - child-bb-before (gst/parent-coords-rect child parent) - child-bb-after (gst/parent-coords-rect transformed-child transformed-parent) - scale-x (/ (:width child-bb-before) (:width child-bb-after)) - scale-y (/ (:height child-bb-before) (:height child-bb-after)) - resize-origin (-> transformed-parent :points gpo/origin) - resize-vector (gpt/point scale-x scale-y)] (-> modifiers (ctm/select-child-modifiers) - (ctm/resize resize-vector resize-origin transform transform-inverse)))) + (ctm/resize + resize-vector + resize-origin + resize-transform + resize-transform-inverse)))) (defn calc-fill-width-data "Calculates the size and modifiers for the width of an auto-fill child" @@ -74,10 +90,8 @@ (defn layout-child-modifiers "Calculates the modifiers for the layout" - [parent child layout-line] - (let [child-bounds (gst/parent-coords-points child parent) - - child-origin (gpo/origin child-bounds) + [parent child child-bounds layout-line] + (let [child-origin (gpo/origin child-bounds) child-width (gpo/width-points child-bounds) child-height (gpo/height-points child-bounds) diff --git a/common/src/app/common/geom/shapes/flex_layout/positions.cljc b/common/src/app/common/geom/shapes/flex_layout/positions.cljc index 3c710f6691..cd5cff837b 100644 --- a/common/src/app/common/geom/shapes/flex_layout/positions.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/positions.cljc @@ -167,7 +167,7 @@ "Calculates the position for the current shape given the layout-data context" [parent child child-width child-height - {:keys [start-p layout-gap-row layout-gap-col margin-x margin-y line-height line-width] :as layout-data}] + {:keys [start-p layout-gap-row layout-gap-col margin-x margin-y line-height line-width layout-bounds] :as layout-data}] (let [row? (ctl/row? parent) col? (ctl/col? parent) @@ -193,9 +193,8 @@ [margin-top margin-right margin-bottom margin-left] (ctl/child-margins child) - points (:points parent) - hv (partial gpo/start-hv points) - vv (partial gpo/start-vv points) + hv (partial gpo/start-hv layout-bounds) + vv (partial gpo/start-vv layout-bounds) corner-p (cond-> start-p @@ -247,13 +246,13 @@ (gpt/add (vv margin-y))) ;; Fix position when layout is flipped - corner-p - (cond-> corner-p - (:flip-x parent) - (gpt/add (hv child-width)) - - (:flip-y parent) - (gpt/add (vv child-height))) + ;;corner-p + ;;(cond-> corner-p + ;; (:flip-x parent) + ;; (gpt/add (hv child-width)) + ;; + ;; (:flip-y parent) + ;; (gpt/add (vv child-height))) next-p (cond-> start-p diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index c23e07258e..5b022868e7 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -12,6 +12,7 @@ [app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.flex-layout :as gcl] [app.common.geom.shapes.pixel-precision :as gpp] + [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.transforms :as gtr] [app.common.pages.helpers :as cph] [app.common.spec :as us] @@ -91,12 +92,12 @@ (defn- set-children-modifiers "Propagates the modifiers from a parent too its children applying constraints if necesary" - [modif-tree objects parent transformed-parent ignore-constraints] + [modif-tree objects parent transformed-parent-bounds ignore-constraints] (let [children (:shapes parent) modifiers (dm/get-in modif-tree [(:id parent) :modifiers])] + ;; Move modifiers don't need to calculate constraints (if (ctm/only-move? modifiers) - ;; Move modifiers don't need to calculate constraints (loop [modif-tree modif-tree children (seq children)] (if-let [current (first children)] @@ -105,49 +106,65 @@ modif-tree)) ;; Check the constraints, then resize - (let [parent (gtr/transform-shape parent (ctm/select-parent-modifiers modifiers))] + (let [parent-bounds (gtr/transform-bounds (:points parent) (ctm/select-parent-modifiers modifiers))] (loop [modif-tree modif-tree children (seq children)] - (if-let [current (first children)] - (let [child-modifiers (gct/calc-child-modifiers parent (get objects current) modifiers ignore-constraints transformed-parent)] + (if (empty? children) + modif-tree + (let [child-id (first children) + child (get objects child-id) + child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints parent-bounds @transformed-parent-bounds)] (recur (cond-> modif-tree (not (ctm/empty? child-modifiers)) - (update-in [current :modifiers] ctm/add-modifiers child-modifiers)) - (rest children))) - modif-tree)))))) + (update-in [child-id :modifiers] ctm/add-modifiers child-modifiers)) + (rest children))))))))) (defn- process-layout-children - [modif-tree objects parent transformed-parent] + [modif-tree objects parent transformed-parent-bounds] (letfn [(process-child [modif-tree child] (let [modifiers (dm/get-in modif-tree [(:id parent) :modifiers]) child-modifiers (-> modifiers (ctm/select-child-geometry-modifiers) - (gcl/normalize-child-modifiers parent child transformed-parent))] + (gcl/normalize-child-modifiers parent child @transformed-parent-bounds))] (cond-> modif-tree (not (ctm/empty? child-modifiers)) (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))] - (let [children (map (d/getf objects) (:shapes transformed-parent))] + (let [children (map (d/getf objects) (:shapes parent))] (reduce process-child modif-tree children)))) + +(defn get-bounds + [objects modif-tree shape] + + (let [modifiers (-> (dm/get-in modif-tree [(:id shape) :modifiers]) + (ctm/select-geometry)) + + children (cph/get-immediate-children objects (:id shape)) + bounds (cond + (cph/group-shape? shape) + (let [children-bounds (->> children (mapv (partial get-bounds objects modif-tree)))] + (gtr/group-bounds shape children-bounds)) + + (cph/mask-shape? shape) + (get-bounds objects modif-tree (-> children first)) + + :else + (:points shape))] + + (gtr/transform-bounds bounds modifiers))) + + (defn- set-layout-modifiers - [modif-tree objects parent] + [modif-tree objects parent transformed-parent-bounds] - (letfn [(apply-modifiers [modif-tree child] - (let [modifiers (-> (dm/get-in modif-tree [(:id child) :modifiers]) - (ctm/select-geometry))] - (cond - (cph/group-like-shape? child) - (gtr/apply-group-modifiers child objects modif-tree) + (letfn [(apply-modifiers [child] + [(-> (get-bounds objects modif-tree child) + (gpo/parent-coords-bounds @transformed-parent-bounds)) + child]) - (some? modifiers) - (gtr/transform-shape child modifiers) - - :else - child))) - - (set-child-modifiers [parent [layout-line modif-tree] child] + (set-child-modifiers [[layout-line modif-tree] [child-bounds child]] (let [[modifiers layout-line] - (gcl/layout-child-modifiers parent child layout-line) + (gcl/layout-child-modifiers parent child child-bounds layout-line) modif-tree (cond-> modif-tree @@ -156,13 +173,12 @@ [layout-line modif-tree]))] - (let [children (map (d/getf objects) (:shapes parent)) - children (->> children (map (partial apply-modifiers modif-tree))) - layout-data (gcl/calc-layout-data parent children) + (let [children (->> (:shapes parent) + (map (comp apply-modifiers (d/getf objects)))) + layout-data (gcl/calc-layout-data parent children @transformed-parent-bounds) children (into [] (cond-> children (not (:reverse? layout-data)) reverse)) max-idx (dec (count children)) layout-lines (:layout-lines layout-data)] - (loop [modif-tree modif-tree layout-line (first layout-lines) pending (rest layout-lines) @@ -172,7 +188,7 @@ children (subvec children from-idx to-idx) [_ modif-tree] - (reduce (partial set-child-modifiers parent) [layout-line modif-tree] children)] + (reduce set-child-modifiers [layout-line modif-tree] children)] (recur modif-tree (first pending) (rest pending) to-idx)) modif-tree))))) @@ -195,11 +211,9 @@ (ctm/resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))] (let [children (->> parent :shapes (map (d/getf objects))) - {auto-width :width auto-height :height} (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) (gcl/layout-content-bounds parent children))] - (cond-> (ctm/empty) (and (some? auto-width) (ctl/auto-width? parent)) (set-parent-auto-width auto-width) @@ -214,11 +228,12 @@ root? (= uuid/zero parent-id) modifiers (-> (dm/get-in modif-tree [parent-id :modifiers]) (ctm/select-geometry)) - transformed-parent (gtr/transform-shape parent modifiers) + + transformed-parent-bounds (delay (gtr/transform-bounds (:points parent) modifiers)) has-modifiers? (ctm/child-modifiers? modifiers) layout? (ctl/layout? parent) - auto? (or (ctl/auto-height? transformed-parent) (ctl/auto-width? transformed-parent)) + auto? (or (ctl/auto-height? parent) (ctl/auto-width? parent)) parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) ;; If the current child is inside the layout we ignore the constraints @@ -226,11 +241,11 @@ [(cond-> modif-tree (and (not layout?) has-modifiers? parent? (not root?)) - (set-children-modifiers objects parent transformed-parent (or ignore-constraints inside-layout?)) + (set-children-modifiers objects parent transformed-parent-bounds (or ignore-constraints inside-layout?)) layout? - (-> (process-layout-children objects parent transformed-parent) - (set-layout-modifiers objects transformed-parent))) + (-> (process-layout-children objects parent transformed-parent-bounds) + (set-layout-modifiers objects parent transformed-parent-bounds))) ;; Auto-width/height can change the positions in the parent so we need to recalculate (cond-> autolayouts auto? (conj (:id parent)))])) @@ -247,21 +262,21 @@ [objects tree-seq modif-tree] (letfn [(apply-shape [objects {:keys [id] :as shape}] - (if (cph/group-shape? shape) - (let [children (cph/get-children objects id)] - (assoc objects id - (cond - (cph/mask-shape? shape) - (gtr/update-mask-selrect shape children) + (let [modifiers (get-in modif-tree [id :modifiers]) + object + (cond + (cph/mask-shape? shape) + (gtr/update-mask-selrect shape (cph/get-children objects id)) - :else - (gtr/update-group-selrect shape children)))) + (cph/group-shape? shape) + (gtr/update-group-selrect shape (cph/get-children objects id)) - (let [modifiers (get-in modif-tree [id :modifiers]) - object (cond-> shape - (some? modifiers) - (gtr/transform-shape modifiers))] - (assoc objects id object))))] + (some? modifiers) + (gtr/transform-shape shape modifiers) + + :else + shape)] + (assoc objects id object)))] (reduce apply-shape objects (reverse tree-seq)))) diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc index 93dd26dcd8..8b5c8b0538 100644 --- a/common/src/app/common/geom/shapes/pixel_precision.cljc +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -9,15 +9,15 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.point :as gpt] + [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.rect :as gpr] - [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm])) (defn size-pixel-precision - [modifiers {:keys [points transform transform-inverse] :as shape}] + [modifiers {:keys [transform transform-inverse] :as shape} points] (let [origin (gpo/origin points) curr-width (gpo/width-points points) curr-height (gpo/height-points points) @@ -36,7 +36,7 @@ (ctm/resize scalev origin transform transform-inverse)))) (defn position-pixel-precision - [modifiers {:keys [points]}] + [modifiers _ points] (let [bounds (gpr/points->rect points) corner (gpt/point bounds) target-corner (gpt/round corner) @@ -47,24 +47,28 @@ (defn set-pixel-precision "Adjust modifiers so they adjust to the pixel grid" [modifiers shape] - (let [move? (ctm/only-move? modifiers)] - (cond-> modifiers - (not move?) - (size-pixel-precision shape) + (let [points (-> shape :points (gco/transform-points (ctm/modifiers->transform modifiers))) + has-resize? (not (ctm/only-move? modifiers)) - :always - (position-pixel-precision shape)))) + [modifiers points] + (let [modifiers + (cond-> modifiers + has-resize? (size-pixel-precision shape points)) + + points + (cond-> (:points shape) + has-resize? (gco/transform-points (ctm/modifiers->transform modifiers)))] + [modifiers points])] + (position-pixel-precision modifiers shape points))) (defn adjust-pixel-precision [modif-tree objects] (let [update-modifiers (fn [modif-tree shape] (let [modifiers (dm/get-in modif-tree [(:id shape) :modifiers])] - (if-not (ctm/has-geometry? modifiers) - modif-tree - (let [shape (gtr/transform-shape shape modifiers)] - (-> modif-tree - (update-in [(:id shape) :modifiers] set-pixel-precision shape))))))] + (cond-> modif-tree + (ctm/has-geometry? modifiers) + (update-in [(:id shape) :modifiers] set-pixel-precision shape))))] (->> (keys modif-tree) (map (d/getf objects)) diff --git a/common/src/app/common/geom/shapes/points.cljc b/common/src/app/common/geom/shapes/points.cljc index adae25aced..3bbfdeaa78 100644 --- a/common/src/app/common/geom/shapes/points.cljc +++ b/common/src/app/common/geom/shapes/points.cljc @@ -6,12 +6,21 @@ (ns app.common.geom.shapes.points (:require - [app.common.geom.point :as gpt])) + [app.common.geom.point :as gpt] + [app.common.geom.shapes.intersect :as gsi])) (defn origin [points] (nth points 0)) +(defn hv + [[p0 p1 _ _]] + (gpt/to-vec p0 p1)) + +(defn vv + [[p0 _ _ p3]] + (gpt/to-vec p0 p3)) + (defn start-hv "Horizontal vector from the origin with a magnitude `val`" [[p0 p1 _ _] val] @@ -60,3 +69,46 @@ (-> p1 (gpt/add right-v) (gpt/add top-v)) (-> p2 (gpt/add right-v) (gpt/add bottom-v)) (-> p3 (gpt/add left-v) (gpt/add bottom-v))]))) + + + +#_(defn parent-coords-rect + [child-bounds parent-bounds] + #_(-> child-bounds + (gco/transform-points (:transform-inverse parent)) + (gpr/points->rect))) + +(defn closest-first + "Reorders the points so the closest to the line start-end is the first" + [[a b c d] start end] + + (let [da (gpt/point-line-distance a start end) + db (gpt/point-line-distance b start end) + dc (gpt/point-line-distance c start end) + dd (gpt/point-line-distance d start end)] + + (cond + (and (<= da db) (<= da dc) (<= da dd)) + [a b c d] + + (and (<= db da) (<= db dc) (<= db dd)) + [b c d a] + + (and (<= dc da) (<= dc db) (<= dc dd)) + [c d a b] + + :else + [d a b c]))) + +(defn parent-coords-bounds + [bounds [p1 p2 _ p4]] + + (let [[b1 b2 b3 b4] (closest-first bounds p1 p2) + hv (gpt/to-vec p1 p2) + vv (gpt/to-vec p1 p4) + + i1 (gsi/line-line-intersect b1 (gpt/add hv b1) b4 (gpt/add b4 vv)) + i2 (gsi/line-line-intersect b1 (gpt/add hv b1) b2 (gpt/add b2 vv)) + i3 (gsi/line-line-intersect b3 (gpt/add hv b3) b2 (gpt/add b2 vv)) + i4 (gsi/line-line-intersect b3 (gpt/add hv b3) b4 (gpt/add b4 vv))] + [i1 i2 i3 i4])) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 43b4dad21d..43d8902e2f 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -369,6 +369,16 @@ (update :width + (:width deltas)) (update :height + (:height deltas))))))) +(defn group-bounds + [group children-bounds] + (let [shape-center (gco/center-shape group) + points (flatten children-bounds) + points (if (empty? points) (:points group) points)] + (-> points + (gco/transform-points shape-center (:transform-inverse group (gmt/matrix))) + (gpr/squared-points) + (gco/transform-points shape-center (:transform group (gmt/matrix)))))) + (defn update-group-selrect [group children] (let [shape-center (gco/center-shape group) @@ -541,19 +551,3 @@ :else group)))) - -(defn parent-coords-rect - [child parent] - (-> child - :points - (gco/transform-points (:transform-inverse parent)) - (gpr/points->rect))) - -(defn parent-coords-points - [child parent] - (-> child - :points - (gco/transform-points (:transform-inverse parent)) - (gpr/points->rect) - (gpr/rect->points) - (gco/transform-points (:transform parent)))) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index d95d0d31fe..cd4b445b58 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -8,6 +8,7 @@ (:refer-clojure :exclude [empty empty?]) (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco] @@ -50,7 +51,6 @@ (or (not (mth/almost-zero? (- (:x vector) 1))) (not (mth/almost-zero? (- (:y vector) 1))))) - (defn- mergeable-move? [op1 op2] (and (= :move (:type op1)) @@ -106,6 +106,13 @@ (conj item))) (conj operations op))))) +(defn valid-vector? + [{:keys [x y]}] + (and (some? x) + (some? y) + (not (mth/nan? x)) + (not (mth/nan? y)))) + ;; Public builder API (defn empty [] @@ -116,12 +123,14 @@ (move-parent modifiers (gpt/point x y))) ([modifiers vector] + (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) (cond-> modifiers (move-vec? vector) - (update :geometry-parent conjv {:type :move :vector vector})))) + (update :geometry-parent maybe-add-move {:type :move :vector vector})))) (defn resize-parent ([modifiers vector origin] + (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) (cond-> modifiers (resize-vec? vector) (update :geometry-parent maybe-add-resize {:type :resize @@ -129,6 +138,7 @@ :origin origin}))) ([modifiers vector origin transform transform-inverse] + (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) (cond-> modifiers (resize-vec? vector) (update :geometry-parent maybe-add-resize {:type :resize @@ -141,12 +151,14 @@ (move modifiers (gpt/point x y))) ([modifiers vector] + (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) (cond-> modifiers (move-vec? vector) (update :geometry-child maybe-add-move {:type :move :vector vector})))) (defn resize ([modifiers vector origin] + (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) (cond-> modifiers (resize-vec? vector) (update :geometry-child maybe-add-resize {:type :resize @@ -154,6 +166,7 @@ :origin origin}))) ([modifiers vector origin transform transform-inverse] + (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) (cond-> modifiers (resize-vec? vector) (update :geometry-child maybe-add-resize {:type :resize @@ -355,8 +368,9 @@ (defn only-move? "Returns true if there are only move operations" [{:keys [geometry-child geometry-parent]}] - (and (every? #(= :move (:type %)) geometry-child) - (every? #(= :move (:type %)) geometry-parent))) + (let [move-op? #(= :move (:type %))] + (and (every? move-op? geometry-child) + (every? move-op? geometry-parent)))) (defn has-geometry? [{:keys [geometry-parent geometry-child]}] @@ -418,16 +432,19 @@ (gmt/multiply (gmt/translate-matrix vector) matrix) :resize - (gmt/multiply - (-> (gmt/matrix) - (gmt/translate origin) - (cond-> (some? transform) - (gmt/multiply transform)) - (gmt/scale vector) - (cond-> (some? transform-inverse) - (gmt/multiply transform-inverse)) - (gmt/translate (gpt/negate origin))) - matrix) + (let [origin (cond-> origin + (or (some? transform-inverse)(some? transform)) + (gpt/transform transform-inverse))] + (gmt/multiply + (-> (gmt/matrix) + (cond-> (some? transform) + (gmt/multiply transform)) + (gmt/translate origin) + (gmt/scale vector) + (gmt/translate (gpt/negate origin)) + (cond-> (some? transform-inverse) + (gmt/multiply transform-inverse))) + matrix)) :rotation (gmt/multiply diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 24e9413777..0d078ecbd5 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -12,9 +12,8 @@ [app.common.files.features :as ffeat] [app.common.geom.align :as gal] [app.common.geom.point :as gpt] - [app.common.geom.proportions :as gpr] + [app.common.geom.proportions :as gpp] [app.common.geom.shapes :as gsh] - [app.common.geom.shapes.rect :as gpsr] [app.common.logging :as log] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] @@ -937,7 +936,7 @@ (if-not lock (assoc shape :proportion-lock false) (-> (assoc shape :proportion-lock true) - (gpr/assign-proportions))))] + (gpp/assign-proportions))))] (rx/of (dch/update-shapes [id] assign-proportions)))))) (defn toggle-proportion-lock @@ -1776,10 +1775,10 @@ media (vals (:media file-data')) media-points - (map #(assoc % :points (gpsr/rect->points {:x 0 - :y 0 - :width (:width %) - :height (:height %)})) + (map #(assoc % :points (gsh/rect->points {:x 0 + :y 0 + :width (:width %) + :height (:height %)})) media) shape-grid diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 4143b31520..01a2bdb292 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -8,7 +8,7 @@ (:require [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.geom.proportions :as gpr] + [app.common.geom.proportions :as gpp] [app.common.geom.shapes :as gsh] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] @@ -69,7 +69,7 @@ (get-shape-layer-position objects selected-non-frames attrs)] (-> (merge default-attrs attrs) - (gpr/setup-proportions) + (gpp/setup-proportions) (assoc :frame-id frame-id :parent-id parent-id :index index)))) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 02c0724340..6798a4a5e1 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -146,8 +146,8 @@ (fn [id] (let [new-shape (get text-shapes id) old-shape (get prev-text-shapes id) - old-modifiers (get prev-modifiers id) - new-modifiers (get modifiers id) + old-modifiers (ctm/select-geometry (get prev-modifiers id)) + new-modifiers (ctm/select-geometry (get modifiers id)) remote? (some? (-> new-shape meta :session-id)) ] @@ -155,10 +155,9 @@ (not (identical? old-shape new-shape)) (not= (dissoc old-shape :migrate) (dissoc new-shape :migrate))) + (and (not= new-modifiers old-modifiers) - (or (nil? new-modifiers) - (nil? old-modifiers) - (not (ctm/only-move? new-modifiers)) + (or (not (ctm/only-move? new-modifiers)) (not (ctm/only-move? old-modifiers)))) ;; When the position data is nil we force to recalculate diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 80217083d2..e50758d55e 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -429,6 +429,12 @@ :hover-top-frame-id @hover-top-frame-id :zoom zoom}]) + (when (debug? :parent-bounds) + [:& wvd/debug-parent-bounds {:selected-shapes selected-shapes + :objects objects-modified + :hover-top-frame-id @hover-top-frame-id + :zoom zoom}]) + (when show-selection-handlers? [:g.selection-handlers {:clipPath "url(#clip-handlers)"} [:defs diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index f1fcecf0ed..14da00e68a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -14,6 +14,7 @@ [app.common.geom.shapes.points :as gpo] [app.common.pages.helpers :as cph] [app.common.types.shape.layout :as ctl] + [app.common.uuid :as uuid] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -76,7 +77,7 @@ col? (ctl/col? shape) children (cph/get-immediate-children objects (:id shape)) - layout-data (gsl/calc-layout-data shape children) + layout-data (gsl/calc-layout-data shape children (:points shape)) layout-bounds (:layout-bounds layout-data) xv #(gpo/start-hv layout-bounds %) @@ -112,8 +113,9 @@ shape (or selected-frame (get objects hover-top-frame-id))] (when (and shape (:layout shape)) - (let [children (cph/get-immediate-children objects (:id shape)) - layout-data (gsl/calc-layout-data shape children) + (let [children (->> (cph/get-immediate-children objects (:id shape)) + (map #(vector (gpo/parent-coords-bounds (:points %) (:points shape)) %))) + layout-data (gsl/calc-layout-data shape children (:points shape)) drop-areas (gsl/layout-drop-areas shape layout-data children)] [:g.debug-layout {:pointer-events "none" :transform (gsh/transform-str shape)} @@ -135,3 +137,51 @@ :alignment-baseline "hanging" :fill "black"} (:index drop-area)]])])))) + +(mf/defc shape-parent-bound + {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "parent"]))] + ::mf/wrap-props false} + [props] + + (let [shape (unchecked-get props "shape") + parent (unchecked-get props "parent") + zoom (unchecked-get props "zoom") + [i1 i2 i3 i4] (gpo/parent-coords-bounds (:points shape) (:points parent))] + [:* + [:polygon {:points (->> [i1 i2 i3 i4] (map #(dm/fmt "%,%" (:x %) (:y %))) (str/join ",")) + :style {:fill "none" :stroke "red" :stroke-width (/ 1 zoom)}}] + + [:line {:x1 (:x i1) + :y1 (:y i1) + :x2 (:x i2) + :y2 (:y i2) + :style {:stroke "green" :stroke-width (/ 1 zoom)}}] + [:line {:x1 (:x i1) + :y1 (:y i1) + :x2 (:x i4) + :y2 (:y i4) + :style {:stroke "blue" :stroke-width (/ 1 zoom)}}]])) + +(mf/defc debug-parent-bounds + {::mf/wrap-props false} + [props] + + (let [objects (unchecked-get props "objects") + zoom (unchecked-get props "objects") + selected-shapes (unchecked-get props "selected-shapes") + hover-top-frame-id (unchecked-get props "hover-top-frame-id") + + selected-frame + (when (and (= (count selected-shapes) 1) (= :frame (-> selected-shapes first :type))) + (first selected-shapes)) + + parent (or selected-frame (get objects hover-top-frame-id))] + + (when (and (some? parent) (not= uuid/zero (:id parent))) + (let [children (cph/get-immediate-children objects (:id parent))] + [:g.debug-parent-bounds {:pointer-events "none"} + (for [[idx child] (d/enumerate children)] + [:> shape-parent-bound {:key (dm/str "bound-" idx) + :zoom zoom + :shape child + :parent parent}])])))) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index f64e98f4b0..18900f901b 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -76,6 +76,9 @@ ;; Makes the pixel grid red so its more visibile :pixel-grid + + ;; Show the bounds relative to the parent + :parent-bounds }) ;; These events are excluded when we activate the :events flag From 6f1c2f474bf47d8964382bb877cd71173f054c0f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 24 Nov 2022 12:21:58 +0100 Subject: [PATCH 258/682] :paperclip: Add missing change on devenv compose --- docker/devenv/docker-compose.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/devenv/docker-compose.yaml b/docker/devenv/docker-compose.yaml index f16c80f220..93fc93a0c3 100644 --- a/docker/devenv/docker-compose.yaml +++ b/docker/devenv/docker-compose.yaml @@ -8,7 +8,7 @@ networks: - subnet: 172.177.9.0/24 volumes: - postgres_data: + postgres_data_pg15: user_data: minio_data: @@ -109,7 +109,7 @@ services: volumes: - ./files/postgresql.conf:/etc/postgresql.conf:z - ./files/postgresql_init.sql:/docker-entrypoint-initdb.d/init.sql:z - - postgres_data:/var/lib/postgresql/data + - postgres_data_pg15:/var/lib/postgresql/data redis: image: redis:7 From 99adbbe91d69e1a871e5ce18449859ba834cb3e0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 24 Nov 2022 12:47:41 +0100 Subject: [PATCH 259/682] :paperclip: Add postgres-upgrade.sh script --- docker/postgres-upgrade.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100755 docker/postgres-upgrade.sh diff --git a/docker/postgres-upgrade.sh b/docker/postgres-upgrade.sh new file mode 100755 index 0000000000..07f627b1ca --- /dev/null +++ b/docker/postgres-upgrade.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +set -x + +export OLDVER=${1:-13} +export NEWVER=$(pg_ctl --version | sed -nE 's/^.+ .+ ([0-9]+).*$/\1/p'); + +export PGBINOLD=/usr/lib/postgresql/${OLDVER}/bin +export PGBINNEW=/usr/lib/postgresql/${NEWVER}/bin +export PGDATAOLD=/var/lib/postgresql/${OLDVER}/data +export PGDATANEW=/var/lib/postgresql/${NEWVER}/data + +sed -i "s/$/ ${OLDVER}/" /etc/apt/sources.list.d/pgdg.list + +apt-get update \ + && apt-get install -y --no-install-recommends postgresql-${OLDVER} \ + && rm -rf /var/lib/apt/lists/* + +mkdir -p "$PGDATAOLD" "$PGDATANEW" \ + && chown -R postgres:postgres /var/lib/postgresql + +pushd /var/lib/postgresql + +PGDATA=$PGDATANEW gosu postgres initdb -U penpot --data-checksums +gosu postgres pg_upgrade -U penpot + +popd From 9593ded808057e71a5ae54abf7f6037d6e057a68 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 24 Nov 2022 13:38:53 +0100 Subject: [PATCH 260/682] :paperclip: Add missing copy on postgres upgrade script --- docker/postgres-upgrade.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docker/postgres-upgrade.sh b/docker/postgres-upgrade.sh index 07f627b1ca..3cae26b265 100755 --- a/docker/postgres-upgrade.sh +++ b/docker/postgres-upgrade.sh @@ -24,4 +24,6 @@ pushd /var/lib/postgresql PGDATA=$PGDATANEW gosu postgres initdb -U penpot --data-checksums gosu postgres pg_upgrade -U penpot +cp $PGDATAOLD/pg_hba.conf $PGDATANEW/pg_hba.conf + popd From f2525f8159158ef17691c53e873a0a1fcabdb695 Mon Sep 17 00:00:00 2001 From: Prithvi Tharun Date: Tue, 22 Nov 2022 21:33:02 +0530 Subject: [PATCH 261/682] :sparkles: Add several improvements to invitations page --- frontend/src/app/main/ui/dashboard/team.cljs | 2 +- frontend/translations/en.po | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 2ae90655c8..3967627d93 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -61,7 +61,7 @@ [:a {:on-click go-settings} (tr "labels.settings")]]]] [:div.dashboard-buttons (if (and (or invitations-section? members-section?) (:is-admin permissions)) - [:a.btn-secondary.btn-small {:on-click invite-member :data-test "invite-member"} + [:a.btn-primary.btn-small {:on-click invite-member :data-test "invite-member"} (tr "dashboard.invite-profile")] [:div.blank-space])]])) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 6c77b236f3..2a15e57a3d 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -460,7 +460,7 @@ msgstr "Uploading file: %s" #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" -msgstr "Invite to team" +msgstr "Invite people" #: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.leave-team" @@ -1285,11 +1285,11 @@ msgstr "You have no pending comment notifications" #: src/app/main/ui/dashboard/team.cljs msgid "labels.no-invitations" -msgstr "There are no invitations." +msgstr "No pending invitations." #: src/app/main/ui/dashboard/team.cljs msgid "labels.no-invitations-hint" -msgstr "Press the button \"Invite to team\" to invite more members to this team." +msgstr "Click the **Invite people** button to invite people to this team." #: src/app/main/ui/static.cljs msgid "labels.not-found.desc-message" From 706714d5579a4a334e723708d93aedbbeb52ea4b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 25 Nov 2022 10:42:17 +0100 Subject: [PATCH 262/682] :tada: Restore removed by mistake search rpc method --- backend/src/app/rpc.clj | 1 + backend/src/app/rpc/commands/search.clj | 68 +++++++++++++++++++++++ backend/src/app/rpc/queries/files.clj | 13 +++++ frontend/src/app/main/data/dashboard.cljs | 2 +- frontend/src/app/main/repo.cljs | 1 + 5 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 backend/src/app/rpc/commands/search.clj diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index b8a7417d23..52cf69b914 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -239,6 +239,7 @@ 'app.rpc.commands.comments 'app.rpc.commands.management 'app.rpc.commands.verify-token + 'app.rpc.commands.search 'app.rpc.commands.auth 'app.rpc.commands.ldap 'app.rpc.commands.demo diff --git a/backend/src/app/rpc/commands/search.clj b/backend/src/app/rpc/commands/search.clj new file mode 100644 index 0000000000..23fd19bef7 --- /dev/null +++ b/backend/src/app/rpc/commands/search.clj @@ -0,0 +1,68 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.commands.search + (:require + [app.common.spec :as us] + [app.db :as db] + [app.rpc.doc :as-alias doc] + [app.util.services :as sv] + [clojure.spec.alpha :as s])) + +(def ^:private sql:search-files + "with projects as ( + select p.* + from project as p + inner join team_profile_rel as tpr on (tpr.team_id = p.team_id) + where tpr.profile_id = ? + and p.team_id = ? + and (p.deleted_at is null or p.deleted_at > now()) + and (tpr.is_admin = true or + tpr.is_owner = true or + tpr.can_edit = true) + union + select p.* + from project as p + inner join project_profile_rel as ppr on (ppr.project_id = p.id) + where ppr.profile_id = ? + and p.team_id = ? + and (p.deleted_at is null or p.deleted_at > now()) + and (ppr.is_admin = true or + ppr.is_owner = true or + ppr.can_edit = true) + ) + select distinct + f.id, + f.project_id, + f.created_at, + f.modified_at, + f.name, + f.is_shared + from file as f + inner join projects as pr on (f.project_id = pr.id) + where f.name ilike ('%' || ? || '%') + order by f.created_at asc") + +(defn search-files + [conn {:keys [profile-id team-id search-term] :as params}] + (db/exec! conn [sql:search-files + profile-id team-id + profile-id team-id + search-term])) + +(s/def ::profile-id ::us/uuid) +(s/def ::team-id ::us/uuid) +(s/def ::search-files ::us/string) + +(s/def ::search-files + (s/keys :req-un [::profile-id ::team-id] + :opt-un [::search-term])) + +(sv/defmethod ::search-files + {::doc/added "1.17"} + [{:keys [pool]} {:keys [search-term] :as params}] + (when search-term + (search-files pool params))) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 58025328f9..0672e5f89a 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -9,6 +9,7 @@ [app.common.spec :as us] [app.db :as db] [app.rpc.commands.files :as cmd.files] + [app.rpc.commands.search :as cmd.search] [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] [app.rpc.queries.projects :as projects] @@ -169,3 +170,15 @@ (cmd.files/check-read-permissions! conn profile-id file-id) (-> (cmd.files/get-file-thumbnail conn file-id revn) (rph/with-http-cache cmd.files/long-cache-duration)))) + + +;; --- QUERY: search files + +(s/def ::search-files ::cmd.search/search-files) + +(sv/defmethod ::search-files + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [pool]} {:keys [search-term] :as params}] + (when search-term + (cmd.search/search-files pool params))) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 595a1029ad..0b4d26e8da 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -193,7 +193,7 @@ (watch [_ state _] (let [team-id (:current-team-id state) params (assoc params :team-id team-id)] - (->> (rp/query! :search-files params) + (->> (rp/cmd! :search-files params) (rx/map search-result-fetched)))))) ;; --- EVENT: files diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index d0876afbad..67993a4192 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -16,6 +16,7 @@ (derive :get-file-object-thumbnails ::query) (derive :get-file-libraries ::query) (derive :get-file-fragment ::query) +(derive :search-files ::query) (defn handle-response [{:keys [status body] :as response}] From f579bb0c8db3a95ed23840928e25007458a48d05 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 24 Nov 2022 12:16:59 +0100 Subject: [PATCH 263/682] :sparkles: Viewport and hug performance enhances --- common/src/app/common/data.cljc | 9 + common/src/app/common/geom/point.cljc | 4 + .../app/common/geom/shapes/constraints.cljc | 15 +- .../geom/shapes/flex_layout/bounds.cljc | 43 ++-- .../geom/shapes/flex_layout/modifiers.cljc | 9 +- .../src/app/common/geom/shapes/modifiers.cljc | 199 +++++++++--------- .../common/geom/shapes/pixel_precision.cljc | 2 +- common/src/app/common/geom/shapes/points.cljc | 90 +++++--- common/src/app/common/geom/shapes/rect.cljc | 9 + .../app/common/geom/shapes/transforms.cljc | 21 +- common/src/app/common/types/shape/layout.cljc | 6 + .../src/app/main/data/workspace/groups.cljs | 12 +- .../app/main/data/workspace/transforms.cljs | 3 +- .../src/app/main/ui/workspace/viewport.cljs | 17 +- .../app/main/ui/workspace/viewport/debug.cljs | 27 +-- frontend/src/debug.cljs | 3 + 16 files changed, 270 insertions(+), 199 deletions(-) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index c89794e9d8..8e44246553 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -688,3 +688,12 @@ (if (contains? set value) (disj set value) (conj set value))))) + +(defn lazy-map + "Creates a map with lazy values given the generator function that receives as argument + the key for the value to be generated" + [keys generator-fn] + (into {} + (map (fn [key] + [key (delay (generator-fn key))])) + keys)) diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 36b0c9fadb..5bc1d4e5e5 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -252,6 +252,10 @@ (let [v-length (length v)] (divide v (point v-length v-length)))) +(defn perpendicular + [{:keys [x y]}] + (Point. (- y) x)) + (defn project "V1 perpendicular projection on vector V2" [v1 v2] diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index b5e0602146..35ddd66a83 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -251,9 +251,11 @@ (defn normalize-modifiers "Before aplying constraints we need to remove the deformation caused by the resizing of the parent" - [constraints-h constraints-v modifiers child {:keys [transform transform-inverse] :as parent} transformed-child-bounds transformed-parent-bounds] + [constraints-h constraints-v modifiers + {:keys [transform transform-inverse] :as parent} + child-bounds transformed-child-bounds parent-bounds transformed-parent-bounds] - (let [child-bb-before (gpo/parent-coords-bounds (:points child) (:points parent)) + (let [child-bb-before (gpo/parent-coords-bounds child-bounds parent-bounds) child-bb-after (gpo/parent-coords-bounds transformed-child-bounds transformed-parent-bounds) scale-x (if (= :scale constraints-h) @@ -278,7 +280,7 @@ resize-transform-inverse)))) (defn calc-child-modifiers - [parent child modifiers ignore-constraints parent-bounds transformed-parent-bounds] + [parent child modifiers ignore-constraints child-bounds parent-bounds transformed-parent-bounds] (let [modifiers (ctm/select-child-modifiers modifiers) @@ -295,10 +297,13 @@ (if (and (= :scale constraints-h) (= :scale constraints-v)) modifiers - (let [child-bounds (:points child) + (let [transformed-parent-bounds @transformed-parent-bounds + modifiers (ctm/select-child-modifiers modifiers) transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) - modifiers (normalize-modifiers constraints-h constraints-v modifiers child parent transformed-child-bounds transformed-parent-bounds) + modifiers (normalize-modifiers constraints-h constraints-v + modifiers parent + child-bounds transformed-child-bounds parent-bounds transformed-parent-bounds) transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) child-points-before (gpo/parent-coords-bounds child-bounds parent-bounds) diff --git a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc index 07552e540c..a24e81214e 100644 --- a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc @@ -7,21 +7,19 @@ (ns app.common.geom.shapes.flex-layout.bounds (:require [app.common.geom.point :as gpt] - [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.points :as gpo] - [app.common.geom.shapes.rect :as gre] [app.common.math :as mth] [app.common.types.shape.layout :as ctl])) (defn- child-layout-bound-points "Returns the bounds of the children as points" - [parent child] + [parent child parent-bounds child-bounds] (let [row? (ctl/row? parent) col? (ctl/col? parent) - hv (partial gpo/start-hv (:points parent)) - vv (partial gpo/start-vv (:points parent)) + hv (partial gpo/start-hv parent-bounds) + vv (partial gpo/start-vv parent-bounds) v-start? (ctl/v-start? parent) v-center? (ctl/v-center? parent) @@ -30,10 +28,10 @@ h-center? (ctl/h-center? parent) h-end? (ctl/h-end? parent) - base-p (first (:points child)) + base-p (gpo/origin child-bounds) - width (-> child :selrect :width) - height (-> child :selrect :height) + width (gpo/width-points child-bounds) + height (gpo/height-points child-bounds) min-width (if (ctl/fill-width? child) (ctl/child-min-width child) @@ -91,22 +89,29 @@ (gpt/subtract (vv min-height))))))) (defn layout-content-bounds - [{:keys [layout-padding] :as parent} children] + [bounds {:keys [layout-padding] :as parent} children] - (let [{pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding + (let [parent-id (:id parent) + parent-bounds @(get bounds parent-id) + + {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding pad-top (or pad-top 0) pad-right (or pad-right 0) pad-bottom (or pad-bottom 0) pad-left (or pad-left 0) child-bounds - (fn [{:keys [points] :as child}] - (if (or (ctl/fill-height? child) (ctl/fill-height? child)) - (child-layout-bound-points parent child) - points))] + (fn [child] + (let [child-id (:id child) + child-bounds @(get bounds child-id) + child-bounds + (if (or (ctl/fill-height? child) (ctl/fill-height? child)) + (child-layout-bound-points parent child parent-bounds parent-bounds) + child-bounds)] + (gpo/parent-coords-bounds child-bounds parent-bounds)))] + + (as-> children $ + (map child-bounds $) + (gpo/merge-parent-coords-bounds $ parent-bounds) + (gpo/pad-points $ (- pad-top) (- pad-right) (- pad-bottom) (- pad-left))))) - (-> (mapcat child-bounds children) - (gco/transform-points (gco/center-shape parent) (:transform-inverse parent)) - (gre/squared-points) - (gpo/pad-points (- pad-top) (- pad-right) (- pad-bottom) (- pad-left)) - (gre/points->rect)))) diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 406a31927e..8ad9364783 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -16,12 +16,9 @@ (defn normalize-child-modifiers "Apply the modifiers and then normalized them against the parent coordinates" - [modifiers {:keys [transform transform-inverse] :as parent} child transformed-parent-bounds] + [modifiers {:keys [transform transform-inverse] :as parent} child-bounds parent-bounds transformed-parent-bounds] - (let [child-bounds (:points child) - parent-bounds (:points parent) - - transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) + (let [transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) child-bb-before (gpo/parent-coords-bounds child-bounds parent-bounds) child-bb-after (gpo/parent-coords-bounds transformed-child-bounds transformed-parent-bounds) @@ -30,7 +27,7 @@ scale-y (/ (gpo/height-points child-bb-before) (gpo/height-points child-bb-after)) resize-vector (gpt/point scale-x scale-y) - modif-transform (ctm/modifiers->transform modifiers) + modif-transform (or (ctm/modifiers->transform modifiers) (gmt/matrix)) modif-transform-inverse (gmt/inverse modif-transform) resize-transform (gmt/multiply modif-transform transform) resize-transform-inverse (gmt/multiply transform-inverse modif-transform-inverse) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 5b022868e7..d0588cfa96 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -92,7 +92,7 @@ (defn- set-children-modifiers "Propagates the modifiers from a parent too its children applying constraints if necesary" - [modif-tree objects parent transformed-parent-bounds ignore-constraints] + [modif-tree objects bounds parent transformed-parent-bounds ignore-constraints] (let [children (:shapes parent) modifiers (dm/get-in modif-tree [(:id parent) :modifiers])] @@ -106,59 +106,67 @@ modif-tree)) ;; Check the constraints, then resize - (let [parent-bounds (gtr/transform-bounds (:points parent) (ctm/select-parent-modifiers modifiers))] + (let [parent-id (:id parent) + parent-bounds (gtr/transform-bounds @(get bounds parent-id) (ctm/select-parent-modifiers modifiers))] (loop [modif-tree modif-tree children (seq children)] (if (empty? children) modif-tree - (let [child-id (first children) - child (get objects child-id) - child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints parent-bounds @transformed-parent-bounds)] + (let [child-id (first children) + child (get objects child-id) + child-bounds @(get bounds child-id) + child-modifiers (gct/calc-child-modifiers parent child modifiers ignore-constraints child-bounds parent-bounds transformed-parent-bounds)] (recur (cond-> modif-tree (not (ctm/empty? child-modifiers)) (update-in [child-id :modifiers] ctm/add-modifiers child-modifiers)) (rest children))))))))) (defn- process-layout-children - [modif-tree objects parent transformed-parent-bounds] + [modif-tree objects bounds parent transformed-parent-bounds] (letfn [(process-child [modif-tree child] - (let [modifiers (dm/get-in modif-tree [(:id parent) :modifiers]) + (let [child-id (:id child) + parent-id (:id parent) + modifiers (dm/get-in modif-tree [parent-id :modifiers]) + child-bounds @(get bounds child-id) + parent-bounds @(get bounds parent-id) child-modifiers (-> modifiers (ctm/select-child-geometry-modifiers) - (gcl/normalize-child-modifiers parent child @transformed-parent-bounds))] + (gcl/normalize-child-modifiers parent child-bounds parent-bounds @transformed-parent-bounds))] (cond-> modif-tree (not (ctm/empty? child-modifiers)) - (update-in [(:id child) :modifiers] ctm/add-modifiers child-modifiers))))] + (update-in [child-id :modifiers] ctm/add-modifiers child-modifiers))))] (let [children (map (d/getf objects) (:shapes parent))] (reduce process-child modif-tree children)))) -(defn get-bounds - [objects modif-tree shape] - - (let [modifiers (-> (dm/get-in modif-tree [(:id shape) :modifiers]) +(defn get-group-bounds + [objects bounds modif-tree shape] + (let [shape-id (:id shape) + modifiers (-> (dm/get-in modif-tree [shape-id :modifiers]) (ctm/select-geometry)) - children (cph/get-immediate-children objects (:id shape)) - bounds (cond - (cph/group-shape? shape) - (let [children-bounds (->> children (mapv (partial get-bounds objects modif-tree)))] - (gtr/group-bounds shape children-bounds)) + children (cph/get-immediate-children objects shape-id) + group-bounds + (cond + (cph/group-shape? shape) + (let [children-bounds (->> children (mapv #(get-group-bounds objects bounds modif-tree %)))] + (gtr/group-bounds shape children-bounds)) - (cph/mask-shape? shape) - (get-bounds objects modif-tree (-> children first)) + (cph/mask-shape? shape) + (get-group-bounds objects bounds modif-tree (-> children first)) - :else - (:points shape))] - - (gtr/transform-bounds bounds modifiers))) + :else + @(get bounds shape-id))] + (cond-> group-bounds + (not (ctm/empty? modifiers)) + (gtr/transform-bounds modifiers)))) (defn- set-layout-modifiers - [modif-tree objects parent transformed-parent-bounds] + [modif-tree objects bounds parent transformed-parent-bounds] (letfn [(apply-modifiers [child] - [(-> (get-bounds objects modif-tree child) + [(-> (get-group-bounds objects bounds modif-tree child) (gpo/parent-coords-bounds @transformed-parent-bounds)) child]) @@ -195,57 +203,63 @@ (defn- calc-auto-modifiers "Calculates the modifiers to adjust the bounds for auto-width/auto-height shapes" - [objects parent] - (letfn [(set-parent-auto-width - [modifiers auto-width] - (let [origin (-> parent :points first) - scale-width (/ auto-width (-> parent :selrect :width) )] - (-> modifiers - (ctm/resize-parent (gpt/point scale-width 1) origin (:transform parent) (:transform-inverse parent))))) + [objects bounds parent] + (let [parent-id (:id parent) + parent-bounds (get bounds parent-id) - (set-parent-auto-height - [modifiers auto-height] - (let [origin (-> parent :points first) - scale-height (/ auto-height (-> parent :selrect :height) )] - (-> modifiers - (ctm/resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent)))))] + set-parent-auto-width + (fn [modifiers auto-width] + (let [origin (gpo/origin @parent-bounds) + scale-width (/ auto-width (gpo/width-points @parent-bounds))] + (-> modifiers + (ctm/resize-parent (gpt/point scale-width 1) origin (:transform parent) (:transform-inverse parent))))) - (let [children (->> parent :shapes (map (d/getf objects))) - {auto-width :width auto-height :height} - (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) - (gcl/layout-content-bounds parent children))] - (cond-> (ctm/empty) - (and (some? auto-width) (ctl/auto-width? parent)) - (set-parent-auto-width auto-width) + set-parent-auto-height + (fn [modifiers auto-height] + (let [origin (gpo/origin @parent-bounds) + scale-height (/ auto-height (gpo/height-points @parent-bounds))] + (-> modifiers + (ctm/resize-parent (gpt/point 1 scale-height) origin (:transform parent) (:transform-inverse parent))))) - (and (some? auto-height) (ctl/auto-height? parent)) - (set-parent-auto-height auto-height))))) + children (->> parent :shapes (map (d/getf objects))) + content-bounds + (when (and (d/not-empty? children) (or (ctl/auto-height? parent) (ctl/auto-width? parent))) + (gcl/layout-content-bounds bounds parent children)) + + auto-width (when content-bounds (gpo/width-points content-bounds)) + auto-height (when content-bounds (gpo/height-points content-bounds))] + + (cond-> (ctm/empty) + (and (some? auto-width) (ctl/auto-width? parent)) + (set-parent-auto-width auto-width) + + (and (some? auto-height) (ctl/auto-height? parent)) + (set-parent-auto-height auto-height)))) (defn- propagate-modifiers "Propagate modifiers to its children" - [objects ignore-constraints [modif-tree autolayouts] parent] - (let [parent-id (:id parent) - root? (= uuid/zero parent-id) - modifiers (-> (dm/get-in modif-tree [parent-id :modifiers]) - (ctm/select-geometry)) - - transformed-parent-bounds (delay (gtr/transform-bounds (:points parent) modifiers)) - + [objects bounds ignore-constraints [modif-tree autolayouts] parent] + (let [parent-id (:id parent) + root? (= uuid/zero parent-id) + modifiers (-> (dm/get-in modif-tree [parent-id :modifiers]) + (ctm/select-geometry)) has-modifiers? (ctm/child-modifiers? modifiers) - layout? (ctl/layout? parent) - auto? (or (ctl/auto-height? parent) (ctl/auto-width? parent)) - parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) + layout? (ctl/layout? parent) + auto? (or (ctl/auto-height? parent) (ctl/auto-width? parent)) + parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) ;; If the current child is inside the layout we ignore the constraints - inside-layout? (ctl/inside-layout? objects parent)] + inside-layout? (ctl/inside-layout? objects parent) + + transformed-parent-bounds (delay (gtr/transform-bounds @(get bounds parent-id) modifiers))] [(cond-> modif-tree (and (not layout?) has-modifiers? parent? (not root?)) - (set-children-modifiers objects parent transformed-parent-bounds (or ignore-constraints inside-layout?)) + (set-children-modifiers objects bounds parent transformed-parent-bounds (or ignore-constraints inside-layout?)) layout? - (-> (process-layout-children objects parent transformed-parent-bounds) - (set-layout-modifiers objects parent transformed-parent-bounds))) + (-> (process-layout-children objects bounds parent transformed-parent-bounds) + (set-layout-modifiers objects bounds parent transformed-parent-bounds))) ;; Auto-width/height can change the positions in the parent so we need to recalculate (cond-> autolayouts auto? (conj (:id parent)))])) @@ -258,28 +272,6 @@ (update id ctm/apply-structure-modifiers modifiers)))] (reduce apply-shape objects modif-tree))) -(defn- apply-partial-objects-modifiers - [objects tree-seq modif-tree] - - (letfn [(apply-shape [objects {:keys [id] :as shape}] - (let [modifiers (get-in modif-tree [id :modifiers]) - object - (cond - (cph/mask-shape? shape) - (gtr/update-mask-selrect shape (cph/get-children objects id)) - - (cph/group-shape? shape) - (gtr/update-group-selrect shape (cph/get-children objects id)) - - (some? modifiers) - (gtr/transform-shape shape modifiers) - - :else - shape)] - (assoc objects id object)))] - - (reduce apply-shape objects (reverse tree-seq)))) - (defn merge-modif-tree [modif-tree other-tree] (reduce (fn [modif-tree [id {:keys [modifiers]}]] @@ -287,41 +279,58 @@ modif-tree other-tree)) +(defn transform-bounds + [bounds objects modif-tree] + + (loop [result bounds + ids (keys modif-tree)] + (if (empty? ids) + result + + (let [id (first ids) + shape (get objects id) + new-bounds (delay (get-group-bounds objects bounds modif-tree shape)) + result (assoc result id new-bounds)] + (recur result (rest ids)))))) + (defn sizing-auto-modifiers "Recalculates the layouts to adjust the sizing: auto new sizes" - [modif-tree sizing-auto-layouts objects ignore-constraints] + [modif-tree sizing-auto-layouts objects bounds ignore-constraints] (loop [modif-tree modif-tree + bounds (transform-bounds bounds objects modif-tree) sizing-auto-layouts (reverse sizing-auto-layouts)] (if-let [current (first sizing-auto-layouts)] (let [parent-base (get objects current) - tree-seq (resolve-tree-sequence #{current} objects) - - ;; Apply the current stack of transformations so we can calculate the auto-layouts - objects (apply-partial-objects-modifiers objects tree-seq modif-tree) resize-modif-tree - {current {:modifiers (calc-auto-modifiers objects parent-base)}} + {current {:modifiers (calc-auto-modifiers objects bounds parent-base)}} tree-seq (resolve-tree-sequence #{current} objects) [resize-modif-tree _] - (reduce (partial propagate-modifiers objects ignore-constraints) [resize-modif-tree #{}] tree-seq) + (reduce #(propagate-modifiers objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq) + + bounds (transform-bounds bounds objects resize-modif-tree) modif-tree (merge-modif-tree modif-tree resize-modif-tree)] - (recur modif-tree (rest sizing-auto-layouts))) + (recur modif-tree bounds (rest sizing-auto-layouts))) modif-tree))) (defn set-objects-modifiers [modif-tree objects ignore-constraints snap-pixel?] (let [objects (apply-structure-modifiers objects modif-tree) + bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points])) + shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects) [modif-tree sizing-auto-layouts] - (reduce (partial propagate-modifiers objects ignore-constraints) [modif-tree #{}] shapes-tree) + (reduce #(propagate-modifiers objects bounds ignore-constraints %1 %2) [modif-tree #{}] shapes-tree) ;; Calculate hug layouts positions - modif-tree (sizing-auto-modifiers modif-tree sizing-auto-layouts objects ignore-constraints) + modif-tree + (-> modif-tree + (sizing-auto-modifiers sizing-auto-layouts objects bounds ignore-constraints)) modif-tree (cond-> modif-tree diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc index 8b5c8b0538..7b44280641 100644 --- a/common/src/app/common/geom/shapes/pixel_precision.cljc +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -37,7 +37,7 @@ (defn position-pixel-precision [modifiers _ points] - (let [bounds (gpr/points->rect points) + (let [bounds (gpr/bounds->rect points) corner (gpt/point bounds) target-corner (gpt/round corner) deltav (gpt/to-vec corner target-corner)] diff --git a/common/src/app/common/geom/shapes/points.cljc b/common/src/app/common/geom/shapes/points.cljc index 3bbfdeaa78..f8ea543690 100644 --- a/common/src/app/common/geom/shapes/points.cljc +++ b/common/src/app/common/geom/shapes/points.cljc @@ -7,7 +7,8 @@ (ns app.common.geom.shapes.points (:require [app.common.geom.point :as gpt] - [app.common.geom.shapes.intersect :as gsi])) + [app.common.geom.shapes.intersect :as gsi] + [app.common.math :as mth])) (defn origin [points] @@ -70,45 +71,66 @@ (-> p2 (gpt/add right-v) (gpt/add bottom-v)) (-> p3 (gpt/add left-v) (gpt/add bottom-v))]))) +(defn- project-t + "Given a point and a line returns the parametric t the cross point with the line going through the other axis projected" + [point [start end] other-axis-vec] - -#_(defn parent-coords-rect - [child-bounds parent-bounds] - #_(-> child-bounds - (gco/transform-points (:transform-inverse parent)) - (gpr/points->rect))) - -(defn closest-first - "Reorders the points so the closest to the line start-end is the first" - [[a b c d] start end] - - (let [da (gpt/point-line-distance a start end) - db (gpt/point-line-distance b start end) - dc (gpt/point-line-distance c start end) - dd (gpt/point-line-distance d start end)] - + (let [line-vec (gpt/to-vec start end) + pr-point (gsi/line-line-intersect point (gpt/add point other-axis-vec) start end)] (cond - (and (<= da db) (<= da dc) (<= da dd)) - [a b c d] + (not (mth/almost-zero? (:x line-vec))) + (/ (- (:x pr-point) (:x start)) (:x line-vec)) - (and (<= db da) (<= db dc) (<= db dd)) - [b c d a] - - (and (<= dc da) (<= dc db) (<= dc dd)) - [c d a b] + (not (mth/almost-zero? (:y line-vec))) + (/ (- (:y pr-point) (:y start)) (:y line-vec)) + ;; Vector is almost zero :else - [d a b c]))) + 0))) (defn parent-coords-bounds - [bounds [p1 p2 _ p4]] + [child-bounds [p1 p2 _ p4 :as parent-bounds]] - (let [[b1 b2 b3 b4] (closest-first bounds p1 p2) - hv (gpt/to-vec p1 p2) - vv (gpt/to-vec p1 p4) + (if (empty? child-bounds) + parent-bounds - i1 (gsi/line-line-intersect b1 (gpt/add hv b1) b4 (gpt/add b4 vv)) - i2 (gsi/line-line-intersect b1 (gpt/add hv b1) b2 (gpt/add b2 vv)) - i3 (gsi/line-line-intersect b3 (gpt/add hv b3) b2 (gpt/add b2 vv)) - i4 (gsi/line-line-intersect b3 (gpt/add hv b3) b4 (gpt/add b4 vv))] - [i1 i2 i3 i4])) + (let [rh [p1 p2] + rv [p1 p4] + + hv (gpt/to-vec p1 p2) + vv (gpt/to-vec p1 p4) + + ph #(gpt/add p1 (gpt/scale hv %)) + pv #(gpt/add p1 (gpt/scale vv %)) + + find-boundary-ts + (fn [[th-min th-max tv-min tv-max] current-point] + (let [cth (project-t current-point rh vv) + ctv (project-t current-point rv hv)] + [(min th-min cth) + (max th-max cth) + (min tv-min ctv) + (max tv-max ctv)])) + + [th-min th-max tv-min tv-max] + (->> child-bounds (reduce find-boundary-ts [##Inf ##-Inf ##Inf ##-Inf])) + + minv-start (pv tv-min) + minv-end (gpt/add minv-start hv) + minh-start (ph th-min) + minh-end (gpt/add minh-start vv) + + maxv-start (pv tv-max) + maxv-end (gpt/add maxv-start hv) + maxh-start (ph th-max) + maxh-end (gpt/add maxh-start vv) + + i1 (gsi/line-line-intersect minv-start minv-end minh-start minh-end) + i2 (gsi/line-line-intersect minv-start minv-end maxh-start maxh-end) + i3 (gsi/line-line-intersect maxv-start maxv-end maxh-start maxh-end) + i4 (gsi/line-line-intersect maxv-start maxv-end minh-start minh-end)] + [i1 i2 i3 i4]))) + +(defn merge-parent-coords-bounds + [bounds parent-bounds] + (parent-coords-bounds (flatten bounds) parent-bounds)) diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index be8d5a5b5e..672928a61c 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -91,6 +91,15 @@ (when (d/num? minx miny maxx maxy) (make-rect minx miny (- maxx minx) (- maxy miny)))))) +(defn bounds->rect + [[{ax :x ay :y} {bx :x by :y} {cx :x cy :y} {dx :x dy :y}]] + (let [minx (min ax bx cx dx) + miny (min ay by cy dy) + maxx (max ax bx cx dx) + maxy (max ay by cy dy)] + (when (d/num? minx miny maxx maxy) + (make-rect minx miny (- maxx minx) (- maxy miny))))) + (defn squared-points [points] (when (d/not-empty? points) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 43d8902e2f..38a4ee7e6d 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -463,18 +463,19 @@ (apply-modifiers modifiers))))) (defn apply-objects-modifiers - [objects modifiers] + ([objects modifiers] + (apply-objects-modifiers objects modifiers (keys modifiers))) - (loop [objects objects - entry (first modifiers) - modifiers (rest modifiers)] + ([objects modifiers ids] + (loop [objects objects + ids (seq ids)] + (if (empty? ids) + objects - (if (nil? entry) - objects - (let [[id modifier] entry] - (recur (d/update-when objects id transform-shape (:modifiers modifier)) - (first modifiers) - (rest modifiers)))))) + (let [id (first ids) + modifier (dm/get-in modifiers [id :modifiers])] + (recur (d/update-when objects id transform-shape modifier) + (rest ids))))))) (defn transform-bounds ([points modifiers] diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index c8b0e0c36d..1fb0f40509 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -106,6 +106,12 @@ parent (get objects parent-id)] (layout? parent))) +(defn layout-child-id? [objects id] + (let [shape (get objects id) + parent-id (:parent-id shape) + parent (get objects parent-id)] + (layout? parent))) + (defn inside-layout? "Check if the shape is inside a layout" [objects shape] diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index bc7c5bc270..4a5fad453b 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -14,6 +14,7 @@ [app.common.types.shape-tree :as ctt] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] + [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [beicon.core :as rx] [potok.core :as ptk])) @@ -174,15 +175,22 @@ (cph/frame-shape? shape) (remove-frame-changes it page-id shape objects)))) + selected (wsh/lookup-selected state) changes-list (sequence (keep prepare) - (wsh/lookup-selected state)) + selected) + + parents (into #{} + (comp (map #(cph/get-parent objects %)) + (keep :id)) + selected) changes {:redo-changes (vec (mapcat :redo-changes changes-list)) :undo-changes (vec (mapcat :undo-changes changes-list)) :origin it}] - (rx/of (dch/commit-changes changes)))))) + (rx/of (dch/commit-changes changes) + (dwul/update-layout-positions parents)))))) (def mask-group (ptk/reify ::mask-group diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 96088ec36e..14cac6d30b 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -443,7 +443,8 @@ exclude-frames-siblings (into exclude-frames - (mapcat (partial cph/get-siblings-ids objects)) + (comp (mapcat (partial cph/get-siblings-ids objects)) + (filter (partial ctl/layout-child-id? objects))) selected) fix-axis diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index e50758d55e..30514cfe3e 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -80,7 +80,8 @@ modifiers (mf/deref refs/workspace-modifiers) objects-modified (mf/with-memo [base-objects modifiers] - (gsh/apply-objects-modifiers base-objects modifiers)) + (gsh/apply-objects-modifiers base-objects modifiers selected)) + background (get options :background clr/canvas) ;; STATE @@ -423,11 +424,17 @@ :hover-top-frame-id @hover-top-frame-id :zoom zoom}]) + (when (debug? :layout-content-bounds) + [:& wvd/debug-content-bounds {:selected-shapes selected-shapes + :objects objects-modified + :hover-top-frame-id @hover-top-frame-id + :zoom zoom}]) + (when (debug? :layout-lines) - [:& wvd/debug-layout {:selected-shapes selected-shapes - :objects objects-modified - :hover-top-frame-id @hover-top-frame-id - :zoom zoom}]) + [:& wvd/debug-layout-lines {:selected-shapes selected-shapes + :objects objects-modified + :hover-top-frame-id @hover-top-frame-id + :zoom zoom}]) (when (debug? :parent-bounds) [:& wvd/debug-parent-bounds {:selected-shapes selected-shapes diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index 14da00e68a..5da0460f8e 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -19,7 +19,7 @@ [rumext.v2 :as mf])) ;; Helper to debug the bounds when set the "hug" content property -#_(mf/defc debug-layout +(mf/defc debug-content-bounds "Debug component to show the auto-layout drop areas" {::mf/wrap-props false} [props] @@ -36,27 +36,12 @@ (when (and shape (:layout shape)) (let [children (cph/get-immediate-children objects (:id shape)) - layout-data (gsl/calc-layout-data shape children) + layout-bounds (gsl/layout-content-bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points])) shape children)] + [:g.debug-layout {:pointer-events "none"} + [:polygon {:points (->> layout-bounds (map #(dm/fmt "%, %" (:x %) (:y %))) (str/join " ")) + :style {:stroke "red" :fill "none"}}]])))) - {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} (:layout-padding shape) - pad-top (or pad-top 0) - pad-right (or pad-right 0) - pad-bottom (or pad-bottom 0) - pad-left (or pad-left 0) - - layout-bounds (gsl/layout-content-bounds shape children)] - [:g.debug-layout {:pointer-events "none" - :transform (gsh/transform-str shape)} - - - [:rect {:x (:x layout-bounds) - :y (:y layout-bounds) - :width (:width layout-bounds) - :height (:height layout-bounds) - :style {:stroke "red" - :fill "none"}}]])))) - -(mf/defc debug-layout +(mf/defc debug-layout-lines "Debug component to show the auto-layout drop areas" {::mf/wrap-props false} [props] diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 18900f901b..81ea45c608 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -74,6 +74,9 @@ ;; Display the layout lines :layout-lines + ;; Display the bounds for the hug content adjust + :layout-content-bounds + ;; Makes the pixel grid red so its more visibile :pixel-grid From 427e43585c034cd44efa1bf31a90cabb60d8087a Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 28 Nov 2022 09:56:52 +0100 Subject: [PATCH 264/682] :bug: Fix strokes class --- frontend/src/app/main/ui/shapes/custom_stroke.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index dfbda44d06..373be81003 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -431,6 +431,7 @@ stroke-id (dm/fmt "strokes-%" (:id shape)) stroke-props (-> (obj/create) (obj/set! "id" stroke-id) + (obj/set! "class" "strokes") (cond-> ;; There is a blur (and (:blur shape) (not (cph/frame-shape? shape)) (-> shape :blur :hidden not)) @@ -442,7 +443,7 @@ [:* (when (d/not-empty? (:strokes shape)) - [:> :g.strokes stroke-props + [:> :g stroke-props (for [[index value] (-> (d/enumerate (:strokes shape)) reverse)] (let [props (build-stroke-props index child value render-id) shape (assoc value :points (:points shape))] From 10bf6c5e56c6594b82b3088c7dcdcd27c8226c19 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 22 Nov 2022 13:04:33 +0100 Subject: [PATCH 265/682] :recycle: Normalize redis api and its usage in msgbus module --- backend/src/app/msgbus.clj | 55 +++++++++--------- backend/src/app/redis.clj | 113 +++++++++++++++++++++---------------- common/deps.edn | 2 +- 3 files changed, 94 insertions(+), 76 deletions(-) diff --git a/backend/src/app/msgbus.clj b/backend/src/app/msgbus.clj index 7d3959931e..371af7de0b 100644 --- a/backend/src/app/msgbus.clj +++ b/backend/src/app/msgbus.clj @@ -20,7 +20,8 @@ [clojure.core.async :as a] [clojure.spec.alpha :as s] [integrant.core :as ig] - [promesa.core :as p])) + [promesa.core :as p] + [promesa.exec :as px])) (set! *warn-on-reflection* true) @@ -52,8 +53,8 @@ (s/def ::rcv-ch ::aa/channel) (s/def ::pub-ch ::aa/channel) (s/def ::state ::us/agent) -(s/def ::pconn ::redis/connection) -(s/def ::sconn ::redis/connection) +(s/def ::pconn ::redis/connection-holder) +(s/def ::sconn ::redis/connection-holder) (s/def ::msgbus (s/keys :req [::cmd-ch ::rcv-ch ::pub-ch ::state ::pconn ::sconn ::wrk/executor])) @@ -205,31 +206,33 @@ (when-let [closed (a/> (vals state) + (mapcat identity) + (filter some?) + (run! a/close!)) + nil))) - (a/go-loop [] - (let [[val port] (a/alts! [pub-ch rcv-ch])] - (cond - (nil? val) - (do - (l/trace :hint "stopping io-loop, nil received") - (send-via executor state (fn [state] - (->> (vals state) - (mapcat identity) - (filter some?) - (run! a/close!)) - nil))) + (= port rcv-ch) + (do + (a/ state ::connection deref) - cmd (.async ^StatefulRedisConnection rconn) + (let [cmd (.async ^StatefulRedisConnection @connection) keys (into-array String (map str (::rscript/keys script))) vals (into-array String (map str (::rscript/vals script))) sname (::rscript/name script)] @@ -276,20 +291,20 @@ (if (instance? io.lettuce.core.RedisNoScriptException cause) (do (l/error :hint "no script found" :name sname :cause cause) - (-> (load-script) - (p/then eval-script))) + (->> (load-script) + (p/mapcat eval-script))) (if-let [on-error (::rscript/on-error script)] (on-error cause) (p/rejected cause)))) (eval-script [sha] (let [tpoint (dt/tpoint)] - (-> (.evalsha ^RedisScriptingAsyncCommands cmd - ^String sha - ^ScriptOutputType ScriptOutputType/MULTI - ^"[Ljava.lang.String;" keys - ^"[Ljava.lang.String;" vals) - (p/then (fn [result] + (->> (.evalsha ^RedisScriptingAsyncCommands cmd + ^String sha + ^ScriptOutputType ScriptOutputType/MULTI + ^"[Ljava.lang.String;" keys + ^"[Ljava.lang.String;" vals) + (p/map (fn [result] (let [elapsed (tpoint)] (mtx/run! metrics {:id :redis-eval-timing :labels [(name sname)] @@ -300,20 +315,20 @@ :params (str/join "," (::rscript/vals script)) :elapsed (dt/format-duration elapsed)) result))) - (p/catch on-error)))) + (p/error on-error)))) (read-script [] (-> script ::rscript/path io/resource slurp)) (load-script [] (l/trace :hint "load script" :name sname) - (-> (.scriptLoad ^RedisScriptingAsyncCommands cmd + (->> (.scriptLoad ^RedisScriptingAsyncCommands cmd ^String (read-script)) - (p/then (fn [sha] + (p/map (fn [sha] (swap! scripts-cache assoc sname sha) sha))))] (if-let [sha (get @scripts-cache sname)] (eval-script sha) - (-> (load-script) - (p/then eval-script)))))) + (->> (load-script) + (p/mapcat eval-script)))))) diff --git a/common/deps.edn b/common/deps.edn index 2ffa930007..1e3652f86e 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -23,7 +23,7 @@ com.cognitect/transit-cljs {:mvn/version "0.8.280"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} - funcool/promesa {:mvn/version "9.0.507"} + funcool/promesa {:mvn/version "9.1.539"} funcool/cuerdas {:mvn/version "2022.06.16-403"} lambdaisland/uri {:mvn/version "1.13.95" From 13a092b1926ef1cf4df2962948ddbc10058e18f2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 22 Nov 2022 14:51:26 +0100 Subject: [PATCH 266/682] :recycle: Normalize internal naming on the worker module --- backend/src/app/main.clj | 17 +++-- backend/src/app/rpc/rlimit.clj | 6 +- backend/src/app/storage/tmp.clj | 7 +- backend/src/app/worker.clj | 111 +++++++++++++------------------- common/deps.edn | 2 +- 5 files changed, 59 insertions(+), 84 deletions(-) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index ae32a063c3..a5cb516734 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -139,9 +139,8 @@ [::worker :app.worker/executor] {:parallelism (cf/get :worker-executor-parallelism 20)} - :app.worker/scheduler - {:parallelism 1 - :prefix :scheduler} + :app.worker/scheduled-executor + {:parallelism 1} :app.worker/executors {:default (ig/ref [::default :app.worker/executor]) @@ -171,7 +170,7 @@ :app.storage.tmp/cleaner {:executor (ig/ref [::worker :app.worker/executor]) - :scheduler (ig/ref :app.worker/scheduler)} + :scheduled-executor (ig/ref :app.worker/scheduled-executor)} :app.storage/gc-deleted-task {:pool (ig/ref :app.db/pool) @@ -315,7 +314,7 @@ :app.rpc/rlimit {:executor (ig/ref [::worker :app.worker/executor]) - :scheduler (ig/ref :app.worker/scheduler)} + :scheduled-executor (ig/ref :app.worker/scheduled-executor)} :app.rpc/methods {:pool (ig/ref :app.db/pool) @@ -464,10 +463,10 @@ (def worker-config {:app.worker/cron - {:executor (ig/ref [::worker :app.worker/executor]) - :scheduler (ig/ref :app.worker/scheduler) - :tasks (ig/ref :app.worker/registry) - :pool (ig/ref :app.db/pool) + {:executor (ig/ref [::worker :app.worker/executor]) + :scheduled-executor (ig/ref :app.worker/scheduled-executor) + :tasks (ig/ref :app.worker/registry) + :pool (ig/ref :app.db/pool) :entries [{:cron #app/cron "0 0 * * * ?" ;; hourly :task :file-xlog-gc} diff --git a/backend/src/app/rpc/rlimit.clj b/backend/src/app/rpc/rlimit.clj index 390892e33c..7c07d67589 100644 --- a/backend/src/app/rpc/rlimit.clj +++ b/backend/src/app/rpc/rlimit.clj @@ -332,7 +332,7 @@ ::limits limits})))) (defn- refresh-config - [{:keys [state path executor scheduler] :as params}] + [{:keys [state path executor scheduled-executor] :as params}] (letfn [(update-config [{:keys [::updated-at] :as state}] (let [updated-at' (fs/last-modified-time path)] (merge state @@ -347,7 +347,7 @@ state))))) (schedule-next [state] - (px/schedule! scheduler + (px/schedule! scheduled-executor (inst-ms (::refresh state)) (partial refresh-config params)) state)] @@ -371,7 +371,7 @@ (and (fs/exists? path) (fs/regular-file? path) path))) (defmethod ig/pre-init-spec :app.rpc/rlimit [_] - (s/keys :req-un [::wrk/executor ::wrk/scheduler])) + (s/keys :req-un [::wrk/executor ::wrk/scheduled-executor])) (defmethod ig/init-key ::rpc/rlimit [_ {:keys [executor] :as params}] diff --git a/backend/src/app/storage/tmp.clj b/backend/src/app/storage/tmp.clj index 89382a121c..1fd069ef33 100644 --- a/backend/src/app/storage/tmp.clj +++ b/backend/src/app/storage/tmp.clj @@ -24,9 +24,8 @@ (defonce queue (a/chan 128)) (s/def ::min-age ::dt/duration) - (defmethod ig/pre-init-spec ::cleaner [_] - (s/keys :req-un [::min-age ::wrk/scheduler ::wrk/executor])) + (s/keys :req-un [::min-age ::wrk/scheduled-executor ::wrk/executor])) (defmethod ig/prep-key ::cleaner [_ cfg] @@ -34,7 +33,7 @@ (d/without-nils cfg))) (defmethod ig/init-key ::cleaner - [_ {:keys [scheduler executor min-age] :as cfg}] + [_ {:keys [scheduled-executor executor min-age] :as cfg}] (l/info :hint "starting tempfile cleaner service") (let [cch (a/chan)] (a/go-loop [] @@ -42,7 +41,7 @@ (when (not= port cch) (l/trace :hint "schedule tempfile deletion" :path path :expires-at (dt/plus (dt/now) min-age)) - (px/schedule! scheduler + (px/schedule! scheduled-executor (inst-ms min-age) (partial remove-temp-file executor path)) (recur)))) diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index ea9df91afc..b6f0ed859d 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -26,73 +26,49 @@ java.util.concurrent.Executors java.util.concurrent.ForkJoinPool java.util.concurrent.Future - java.util.concurrent.ForkJoinPool$ForkJoinWorkerThreadFactory - java.util.concurrent.ForkJoinWorkerThread - java.util.concurrent.ScheduledExecutorService - java.util.concurrent.ThreadFactory - java.util.concurrent.atomic.AtomicLong)) + java.util.concurrent.ScheduledExecutorService)) (set! *warn-on-reflection* true) (s/def ::executor #(instance? ExecutorService %)) -(s/def ::scheduler #(instance? ScheduledExecutorService %)) +(s/def ::scheduled-executor #(instance? ScheduledExecutorService %)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Executor ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(declare ^:private get-fj-thread-factory) -(declare ^:private get-thread-factory) - (s/def ::parallelism ::us/integer) (defmethod ig/pre-init-spec ::executor [_] - (s/keys :opt-un [::parallelism])) + (s/keys :req-un [::parallelism])) (defmethod ig/init-key ::executor [skey {:keys [parallelism]}] - (let [prefix (if (vector? skey) (-> skey first name keyword) :default)] - (if parallelism - (ForkJoinPool. (int parallelism) (get-fj-thread-factory prefix) nil false) - (Executors/newCachedThreadPool (get-thread-factory prefix))))) + (let [prefix (if (vector? skey) (-> skey first name keyword) :default) + tname (str "penpot/" prefix "/%s") + factory (px/forkjoin-thread-factory :name tname)] + (px/forkjoin-executor + :factory factory + :parallelism parallelism + :async? true))) (defmethod ig/halt-key! ::executor [_ instance] (.shutdown ^ExecutorService instance)) -(defmethod ig/pre-init-spec ::scheduler [_] - (s/keys :req-un [::prefix] - :opt-un [::parallelism])) +(defmethod ig/pre-init-spec ::scheduled-executor [_] + (s/keys :opt-un [::parallelism])) -(defmethod ig/init-key ::scheduler - [_ {:keys [parallelism prefix] :or {parallelism 1}}] - (px/scheduled-pool parallelism (get-thread-factory prefix))) +(defmethod ig/init-key ::scheduled-executor + [_ {:keys [parallelism] :or {parallelism 1}}] + (px/scheduled-executor + :parallelism parallelism + :factory (px/thread-factory :name "penpot/scheduled-executor/%s"))) (defmethod ig/halt-key! ::scheduler [_ instance] (.shutdown ^ExecutorService instance)) -(defn- get-fj-thread-factory - ^ForkJoinPool$ForkJoinWorkerThreadFactory - [prefix] - (let [^AtomicLong counter (AtomicLong. 0)] - (reify ForkJoinPool$ForkJoinWorkerThreadFactory - (newThread [_ pool] - (let [thread (.newThread ForkJoinPool/defaultForkJoinWorkerThreadFactory pool) - tname (str "penpot/" (name prefix) "-" (.getAndIncrement counter))] - (.setName ^ForkJoinWorkerThread thread ^String tname) - thread))))) - -(defn- get-thread-factory - ^ThreadFactory - [prefix] - (let [^AtomicLong counter (AtomicLong. 0)] - (reify ThreadFactory - (newThread [_ runnable] - (doto (Thread. runnable) - (.setDaemon true) - (.setName (str "penpot/" (name prefix) "-" (.getAndIncrement counter)))))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Executor Monitor ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -190,6 +166,30 @@ :queue :default} (d/without-nils cfg))) +(defmethod ig/init-key ::worker + [_ {:keys [pool name queue] :as cfg}] + (let [close-ch (a/chan 1) + cfg (assoc cfg :close-ch close-ch)] + + (if (db/read-only? pool) + (l/warn :hint "worker not started, db is read-only" + :name (d/name name) + :queue (d/name queue)) + (do + (l/info :hint "worker initialized" + :name (d/name name) + :queue (d/name queue)) + (event-loop cfg))) + + (reify + java.lang.AutoCloseable + (close [_] + (a/close! close-ch))))) + +(defmethod ig/halt-key! ::worker + [_ instance] + (.close ^java.lang.AutoCloseable instance)) + (defn- event-loop "Main, worker eventloop" [{:keys [pool poll-interval close-ch] :as cfg}] @@ -235,29 +235,6 @@ (a/ Date: Tue, 22 Nov 2022 18:06:24 +0100 Subject: [PATCH 267/682] :recycle: Make the worker abstraction more scalable Start using redis for dispatcher to worker communication and add the ability to start multiple threads to worker for increase the concurrency. --- .clj-kondo/config.edn | 1 + backend/src/app/config.clj | 6 +- backend/src/app/db.clj | 15 + backend/src/app/main.clj | 202 ++-- backend/src/app/msgbus.clj | 4 +- backend/src/app/redis.clj | 176 ++-- backend/src/app/rpc.clj | 2 + backend/src/app/srepl/main.clj | 11 + backend/src/app/util/closeable.clj | 31 - backend/src/app/worker.clj | 951 ++++++++++-------- backend/test/backend_tests/helpers.clj | 2 +- .../backend_tests/tasks_telemetry_test.clj | 8 +- common/deps.edn | 2 +- common/src/app/common/data.cljc | 20 +- common/src/app/common/data/macros.cljc | 17 +- common/src/app/common/exceptions.cljc | 4 + 16 files changed, 827 insertions(+), 625 deletions(-) delete mode 100644 backend/src/app/util/closeable.clj diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 7b0b571f28..e655c6e4c9 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -7,6 +7,7 @@ app.common.data/export clojure.core/def app.db/with-atomic clojure.core/with-open app.common.data.macros/get-in clojure.core/get-in + app.common.data.macros/with-open clojure.core/with-open app.common.data.macros/select-keys clojure.core/select-keys app.common.logging/with-context clojure.core/do} diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 7d8556e0cf..e2d30373b4 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -106,7 +106,8 @@ (s/def ::file-change-snapshot-timeout ::dt/duration) (s/def ::default-executor-parallelism ::us/integer) -(s/def ::worker-executor-parallelism ::us/integer) +(s/def ::scheduled-executor-parallelism ::us/integer) +(s/def ::worker-parallelism ::us/integer) (s/def ::authenticated-cookie-domain ::us/string) (s/def ::authenticated-cookie-name ::us/string) @@ -218,7 +219,8 @@ ::default-rpc-rlimit ::error-report-webhook ::default-executor-parallelism - ::worker-executor-parallelism + ::scheduled-executor-parallelism + ::worker-parallelism ::file-change-snapshot-every ::file-change-snapshot-timeout ::user-feedback-destination diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index e62b8290b8..90b960c4aa 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -493,3 +493,18 @@ (let [n (xact-check-param n) row (exec-one! conn ["select pg_try_advisory_xact_lock(?::bigint) as lock" n])] (:lock row))) + +(defn sql-exception? + [cause] + (instance? java.sql.SQLException cause)) + +(defn connection-error? + [cause] + (and (sql-exception? cause) + (contains? #{"08003" "08006" "08001" "08004"} + (.getSQLState ^java.sql.SQLException cause)))) + +(defn serialization-error? + [cause] + (and (sql-exception? cause) + (= "40001" (.getSQLState ^java.sql.SQLException cause)))) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index a5cb516734..4a8119d3ea 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -9,8 +9,13 @@ [app.auth.oidc] [app.common.logging :as l] [app.config :as cf] + [app.db :as-alias db] + [app.metrics :as-alias mtx] [app.metrics.definition :as-alias mdef] + [app.redis :as-alias rds] + [app.storage :as-alias sto] [app.util.time :as dt] + [app.worker :as-alias wrk] [cuerdas.core :as str] [integrant.core :as ig]) (:gen-class)) @@ -120,90 +125,84 @@ ::mdef/type :gauge}}) (def system-config - {:app.db/pool + {::db/pool {:uri (cf/get :database-uri) :username (cf/get :database-username) :password (cf/get :database-password) :read-only (cf/get :database-readonly false) - :metrics (ig/ref :app.metrics/metrics) + :metrics (ig/ref ::mtx/metrics) :migrations (ig/ref :app.migrations/all) :name :main :min-size (cf/get :database-min-pool-size 0) :max-size (cf/get :database-max-pool-size 60)} ;; Default thread pool for IO operations - [::default :app.worker/executor] - {:parallelism (cf/get :default-executor-parallelism 70)} + ::wrk/executor + {::wrk/parallelism (cf/get :default-executor-parallelism 100)} - ;; Dedicated thread pool for background tasks execution. - [::worker :app.worker/executor] - {:parallelism (cf/get :worker-executor-parallelism 20)} + ::wrk/scheduled-executor + {::wrk/parallelism (cf/get :scheduled-executor-parallelism 20)} - :app.worker/scheduled-executor - {:parallelism 1} - - :app.worker/executors - {:default (ig/ref [::default :app.worker/executor]) - :worker (ig/ref [::worker :app.worker/executor])} - - :app.worker/executor-monitor - {:metrics (ig/ref :app.metrics/metrics) - :executors (ig/ref :app.worker/executors)} + ::wrk/monitor + {::mtx/metrics (ig/ref ::mtx/metrics) + ::wrk/name "default" + ::wrk/executor (ig/ref ::wrk/executor)} :app.migrations/migrations {} - :app.metrics/metrics + ::mtx/metrics {:default default-metrics} :app.migrations/all {:main (ig/ref :app.migrations/migrations)} - :app.redis/redis - {:uri (cf/get :redis-uri) - :metrics (ig/ref :app.metrics/metrics)} + ::rds/redis + {::rds/uri (cf/get :redis-uri) + ::mtx/metrics (ig/ref ::mtx/metrics)} :app.msgbus/msgbus {:backend (cf/get :msgbus-backend :redis) - :executor (ig/ref [::default :app.worker/executor]) - :redis (ig/ref :app.redis/redis)} + :executor (ig/ref ::wrk/executor) + :redis (ig/ref ::rds/redis)} + ;; TODO: refactor execution model :app.storage.tmp/cleaner - {:executor (ig/ref [::worker :app.worker/executor]) - :scheduled-executor (ig/ref :app.worker/scheduled-executor)} + {:executor (ig/ref ::wrk/executor) + :scheduled-executor (ig/ref ::wrk/scheduled-executor)} - :app.storage/gc-deleted-task - {:pool (ig/ref :app.db/pool) - :storage (ig/ref :app.storage/storage) - :executor (ig/ref [::worker :app.worker/executor])} + ::sto/gc-deleted-task + {:pool (ig/ref ::db/pool) + :storage (ig/ref ::sto/storage) + :executor (ig/ref ::wrk/executor)} - :app.storage/gc-touched-task - {:pool (ig/ref :app.db/pool)} + ::sto/gc-touched-task + {:pool (ig/ref ::db/pool)} :app.http/client - {:executor (ig/ref [::default :app.worker/executor])} + {:executor (ig/ref ::wrk/executor)} :app.http.session/manager - {:pool (ig/ref :app.db/pool) + {:pool (ig/ref ::db/pool) :sprops (ig/ref :app.setup/props) - :executor (ig/ref [::default :app.worker/executor])} + :executor (ig/ref ::wrk/executor)} :app.http.session/gc-task - {:pool (ig/ref :app.db/pool) + {:pool (ig/ref ::db/pool) :max-age (cf/get :auth-token-cookie-max-age)} :app.http.awsns/handler {:sprops (ig/ref :app.setup/props) - :pool (ig/ref :app.db/pool) + :pool (ig/ref ::db/pool) :http-client (ig/ref :app.http/client) - :executor (ig/ref [::worker :app.worker/executor])} + :executor (ig/ref ::wrk/executor)} :app.http/server {:port (cf/get :http-server-port) :host (cf/get :http-server-host) :router (ig/ref :app.http/router) - :metrics (ig/ref :app.metrics/metrics) - :executor (ig/ref [::default :app.worker/executor]) + :metrics (ig/ref ::mtx/metrics) + :executor (ig/ref ::wrk/executor) :io-threads (cf/get :http-server-io-threads) :max-body-size (cf/get :http-server-max-body-size) :max-multipart-body-size (cf/get :http-server-max-multipart-body-size)} @@ -263,10 +262,10 @@ :oidc (ig/ref :app.auth.oidc/generic-provider)} :sprops (ig/ref :app.setup/props) :http-client (ig/ref :app.http/client) - :pool (ig/ref :app.db/pool) + :pool (ig/ref ::db/pool) :session (ig/ref :app.http.session/manager) :public-uri (cf/get :public-uri) - :executor (ig/ref [::default :app.worker/executor])} + :executor (ig/ref ::wrk/executor)} ;; TODO: revisit the dependencies of this service, looks they are too much unused of them :app.http/router @@ -277,61 +276,60 @@ :debug-routes (ig/ref :app.http.debug/routes) :oidc-routes (ig/ref :app.auth.oidc/routes) :ws (ig/ref :app.http.websocket/handler) - :metrics (ig/ref :app.metrics/metrics) + :metrics (ig/ref ::mtx/metrics) :public-uri (cf/get :public-uri) - :storage (ig/ref :app.storage/storage) + :storage (ig/ref ::sto/storage) :audit-handler (ig/ref :app.loggers.audit/http-handler) :rpc-routes (ig/ref :app.rpc/routes) :doc-routes (ig/ref :app.rpc.doc/routes) - :executor (ig/ref [::default :app.worker/executor])} + :executor (ig/ref ::wrk/executor)} :app.http.debug/routes - {:pool (ig/ref :app.db/pool) - :executor (ig/ref [::worker :app.worker/executor]) - :storage (ig/ref :app.storage/storage) + {:pool (ig/ref ::db/pool) + :executor (ig/ref ::wrk/executor) + :storage (ig/ref ::sto/storage) :session (ig/ref :app.http.session/manager)} :app.http.websocket/handler - {:pool (ig/ref :app.db/pool) - :metrics (ig/ref :app.metrics/metrics) + {:pool (ig/ref ::db/pool) + :metrics (ig/ref ::mtx/metrics) :msgbus (ig/ref :app.msgbus/msgbus)} :app.http.assets/handlers - {:metrics (ig/ref :app.metrics/metrics) + {:metrics (ig/ref ::mtx/metrics) :assets-path (cf/get :assets-path) - :storage (ig/ref :app.storage/storage) - :executor (ig/ref [::default :app.worker/executor]) + :storage (ig/ref ::sto/storage) + :executor (ig/ref ::wrk/executor) :cache-max-age (dt/duration {:hours 24}) :signature-max-age (dt/duration {:hours 24 :minutes 5})} :app.http.feedback/handler - {:pool (ig/ref :app.db/pool) - :executor (ig/ref [::default :app.worker/executor])} + {:pool (ig/ref ::db/pool) + :executor (ig/ref ::wrk/executor)} :app.rpc/climit - {:metrics (ig/ref :app.metrics/metrics) - :executor (ig/ref [::default :app.worker/executor])} + {:metrics (ig/ref ::mtx/metrics) + :executor (ig/ref ::wrk/executor)} :app.rpc/rlimit - {:executor (ig/ref [::worker :app.worker/executor]) - :scheduled-executor (ig/ref :app.worker/scheduled-executor)} + {:executor (ig/ref ::wrk/executor) + :scheduled-executor (ig/ref ::wrk/scheduled-executor)} :app.rpc/methods - {:pool (ig/ref :app.db/pool) + {:pool (ig/ref ::db/pool) :session (ig/ref :app.http.session/manager) :sprops (ig/ref :app.setup/props) - :metrics (ig/ref :app.metrics/metrics) - :storage (ig/ref :app.storage/storage) + :metrics (ig/ref ::mtx/metrics) + :storage (ig/ref ::sto/storage) :msgbus (ig/ref :app.msgbus/msgbus) :public-uri (cf/get :public-uri) - :redis (ig/ref :app.redis/redis) + :redis (ig/ref ::rds/redis) :audit (ig/ref :app.loggers.audit/collector) :ldap (ig/ref :app.auth.ldap/provider) :http-client (ig/ref :app.http/client) :climit (ig/ref :app.rpc/climit) :rlimit (ig/ref :app.rpc/rlimit) - :executors (ig/ref :app.worker/executors) - :executor (ig/ref [::default :app.worker/executor]) + :executor (ig/ref ::wrk/executor) :templates (ig/ref :app.setup/builtin-templates) } @@ -341,15 +339,15 @@ :app.rpc/routes {:methods (ig/ref :app.rpc/methods)} - :app.worker/registry - {:metrics (ig/ref :app.metrics/metrics) + ::wrk/registry + {:metrics (ig/ref ::mtx/metrics) :tasks {:sendmail (ig/ref :app.emails/handler) :objects-gc (ig/ref :app.tasks.objects-gc/handler) :file-gc (ig/ref :app.tasks.file-gc/handler) :file-xlog-gc (ig/ref :app.tasks.file-xlog-gc/handler) - :storage-gc-deleted (ig/ref :app.storage/gc-deleted-task) - :storage-gc-touched (ig/ref :app.storage/gc-touched-task) + :storage-gc-deleted (ig/ref ::sto/gc-deleted-task) + :storage-gc-touched (ig/ref ::sto/gc-touched-task) :tasks-gc (ig/ref :app.tasks.tasks-gc/handler) :telemetry (ig/ref :app.tasks.telemetry/handler) :session-gc (ig/ref :app.http.session/gc-task) @@ -369,24 +367,24 @@ :app.emails/handler {:sendmail (ig/ref :app.emails/sendmail) - :metrics (ig/ref :app.metrics/metrics)} + :metrics (ig/ref ::mtx/metrics)} :app.tasks.tasks-gc/handler - {:pool (ig/ref :app.db/pool) + {:pool (ig/ref ::db/pool) :max-age cf/deletion-delay} :app.tasks.objects-gc/handler - {:pool (ig/ref :app.db/pool) - :storage (ig/ref :app.storage/storage)} + {:pool (ig/ref ::db/pool) + :storage (ig/ref ::sto/storage)} :app.tasks.file-gc/handler - {:pool (ig/ref :app.db/pool)} + {:pool (ig/ref ::db/pool)} :app.tasks.file-xlog-gc/handler - {:pool (ig/ref :app.db/pool)} + {:pool (ig/ref ::db/pool)} :app.tasks.telemetry/handler - {:pool (ig/ref :app.db/pool) + {:pool (ig/ref ::db/pool) :version (:full cf/version) :uri (cf/get :telemetry-uri) :sprops (ig/ref :app.setup/props) @@ -400,28 +398,28 @@ {:http-client (ig/ref :app.http/client)} :app.setup/props - {:pool (ig/ref :app.db/pool) + {:pool (ig/ref ::db/pool) :key (cf/get :secret-key)} :app.loggers.zmq/receiver {:endpoint (cf/get :loggers-zmq-uri)} :app.loggers.audit/http-handler - {:pool (ig/ref :app.db/pool) - :executor (ig/ref [::default :app.worker/executor])} + {:pool (ig/ref ::db/pool) + :executor (ig/ref ::wrk/executor)} :app.loggers.audit/collector - {:pool (ig/ref :app.db/pool) - :executor (ig/ref [::worker :app.worker/executor])} + {:pool (ig/ref ::db/pool) + :executor (ig/ref ::wrk/executor)} :app.loggers.audit/archive-task {:uri (cf/get :audit-log-archive-uri) :sprops (ig/ref :app.setup/props) - :pool (ig/ref :app.db/pool) + :pool (ig/ref ::db/pool) :http-client (ig/ref :app.http/client)} :app.loggers.audit/gc-task - {:pool (ig/ref :app.db/pool)} + {:pool (ig/ref ::db/pool)} :app.loggers.loki/reporter {:uri (cf/get :loggers-loki-uri) @@ -435,12 +433,12 @@ :app.loggers.database/reporter {:receiver (ig/ref :app.loggers.zmq/receiver) - :pool (ig/ref :app.db/pool) - :executor (ig/ref [::worker :app.worker/executor])} + :pool (ig/ref ::db/pool) + :executor (ig/ref ::wrk/executor)} - :app.storage/storage - {:pool (ig/ref :app.db/pool) - :executor (ig/ref [::default :app.worker/executor]) + ::sto/storage + {:pool (ig/ref ::db/pool) + :executor (ig/ref ::wrk/executor) :backends {:assets-s3 (ig/ref [::assets :app.storage.s3/backend]) @@ -454,7 +452,7 @@ {:region (cf/get :storage-assets-s3-region) :endpoint (cf/get :storage-assets-s3-endpoint) :bucket (cf/get :storage-assets-s3-bucket) - :executor (ig/ref [::default :app.worker/executor])} + :executor (ig/ref ::wrk/executor)} [::assets :app.storage.fs/backend] {:directory (cf/get :storage-assets-fs-directory)} @@ -462,12 +460,11 @@ (def worker-config - {:app.worker/cron - {:executor (ig/ref [::worker :app.worker/executor]) - :scheduled-executor (ig/ref :app.worker/scheduled-executor) - :tasks (ig/ref :app.worker/registry) - :pool (ig/ref :app.db/pool) - :entries + {::wrk/cron + {::wrk/scheduled-executor (ig/ref ::wrk/scheduled-executor) + ::wrk/registry (ig/ref ::wrk/registry) + ::db/pool (ig/ref ::db/pool) + ::wrk/entries [{:cron #app/cron "0 0 * * * ?" ;; hourly :task :file-xlog-gc} @@ -500,11 +497,18 @@ {:cron #app/cron "30 */5 * * * ?" ;; every 5m :task :audit-log-gc})]} - :app.worker/worker - {:executor (ig/ref [::worker :app.worker/executor]) - :tasks (ig/ref :app.worker/registry) - :metrics (ig/ref :app.metrics/metrics) - :pool (ig/ref :app.db/pool)}}) + ::wrk/scheduler + {::rds/redis (ig/ref ::rds/redis) + ::mtx/metrics (ig/ref ::mtx/metrics) + ::db/pool (ig/ref ::db/pool)} + + ::wrk/worker + {::wrk/parallelism (cf/get ::worker-parallelism 3) + ::wrk/queue "default" + ::rds/redis (ig/ref ::rds/redis) + ::wrk/registry (ig/ref ::wrk/registry) + ::mtx/metrics (ig/ref ::mtx/metrics) + ::db/pool (ig/ref ::db/pool)}}) (def system nil) diff --git a/backend/src/app/msgbus.clj b/backend/src/app/msgbus.clj index 371af7de0b..8dd5b3345a 100644 --- a/backend/src/app/msgbus.clj +++ b/backend/src/app/msgbus.clj @@ -123,8 +123,8 @@ (defn- redis-disconnect [{:keys [::pconn ::sconn] :as cfg}] - (redis/close! pconn) - (redis/close! sconn)) + (d/close! pconn) + (d/close! sconn)) (defn- conj-subscription "A low level function that is responsible to create on-demand diff --git a/backend/src/app/redis.clj b/backend/src/app/redis.clj index 3c8c1d3997..dcf9b7ea29 100644 --- a/backend/src/app/redis.clj +++ b/backend/src/app/redis.clj @@ -21,13 +21,19 @@ [promesa.core :as p]) (:import clojure.lang.IDeref + clojure.lang.MapEntry + io.lettuce.core.KeyValue io.lettuce.core.RedisClient + io.lettuce.core.RedisCommandInterruptedException + io.lettuce.core.RedisCommandTimeoutException + io.lettuce.core.RedisException io.lettuce.core.RedisURI io.lettuce.core.ScriptOutputType io.lettuce.core.api.StatefulConnection io.lettuce.core.api.StatefulRedisConnection io.lettuce.core.api.async.RedisAsyncCommands io.lettuce.core.api.async.RedisScriptingAsyncCommands + io.lettuce.core.api.sync.RedisCommands io.lettuce.core.codec.ByteArrayCodec io.lettuce.core.codec.RedisCodec io.lettuce.core.codec.StringCodec @@ -45,8 +51,7 @@ (declare initialize-resources) (declare shutdown-resources) -(declare connect) -(declare close!) +(declare connect*) (s/def ::timer #(instance? Timer %)) @@ -82,32 +87,37 @@ (s/def ::connect? ::us/boolean) (s/def ::io-threads ::us/integer) (s/def ::worker-threads ::us/integer) +(s/def ::cache #(instance? clojure.lang.Atom %)) (s/def ::redis - (s/keys :req [::resources ::redis-uri ::timer ::mtx/metrics] - :opt [::connection])) - -(defmethod ig/pre-init-spec ::redis [_] - (s/keys :req-un [::uri ::mtx/metrics] - :opt-un [::timeout - ::connect? - ::io-threads - ::worker-threads])) + (s/keys :req [::resources + ::redis-uri + ::timer + ::mtx/metrics] + :opt [::connection + ::cache])) (defmethod ig/prep-key ::redis [_ cfg] (let [runtime (Runtime/getRuntime) cpus (.availableProcessors ^Runtime runtime)] - (merge {:timeout (dt/duration 5000) - :io-threads (max 3 cpus) - :worker-threads (max 3 cpus)} + (merge {::timeout (dt/duration "10s") + ::io-threads (max 3 cpus) + ::worker-threads (max 3 cpus)} (d/without-nils cfg)))) +(defmethod ig/pre-init-spec ::redis [_] + (s/keys :req [::uri ::mtx/metrics] + :opt [::timeout + ::connect? + ::io-threads + ::worker-threads])) + (defmethod ig/init-key ::redis - [_ {:keys [connect?] :as cfg}] - (let [cfg (initialize-resources cfg)] - (cond-> cfg - connect? (assoc ::connection (connect cfg))))) + [_ {:keys [::connect?] :as cfg}] + (let [state (initialize-resources cfg)] + (cond-> state + connect? (assoc ::connection (connect* cfg {}))))) (defmethod ig/halt-key! ::redis [_ state] @@ -121,7 +131,7 @@ (defn- initialize-resources "Initialize redis connection resources" - [{:keys [uri io-threads worker-threads connect? metrics] :as cfg}] + [{:keys [::uri ::io-threads ::worker-threads ::connect?] :as cfg}] (l/info :hint "initialize redis resources" :uri uri :io-threads io-threads @@ -138,53 +148,60 @@ redis-uri (RedisURI/create ^String uri)] (-> cfg - (assoc ::mtx/metrics metrics) - (assoc ::cache (atom {})) + (assoc ::resources resources) (assoc ::timer timer) - (assoc ::redis-uri redis-uri) - (assoc ::resources resources)))) + (assoc ::cache (atom {})) + (assoc ::redis-uri redis-uri)))) (defn- shutdown-resources [{:keys [::resources ::cache ::timer]}] - (run! close! (vals @cache)) + (run! d/close! (vals @cache)) (when resources (.shutdown ^ClientResources resources)) (when timer (.stop ^Timer timer))) -(defn connect +(defn connect* [{:keys [::resources ::redis-uri] :as state} - & {:keys [timeout codec type] - :or {codec default-codec type :default}}] + {:keys [timeout codec type] + :or {codec default-codec type :default}}] (us/assert! ::resources resources) - (let [client (RedisClient/create ^ClientResources resources ^RedisURI redis-uri) - timeout (or timeout (:timeout state)) + timeout (or timeout (::timeout state)) conn (case type :default (.connect ^RedisClient client ^RedisCodec codec) :pubsub (.connectPubSub ^RedisClient client ^RedisCodec codec))] (.setTimeout ^StatefulConnection conn ^Duration timeout) - (assoc state ::connection - (reify - IDeref - (deref [_] conn) + (reify + IDeref + (deref [_] conn) - AutoCloseable - (close [_] - (.close ^StatefulConnection conn) - (.shutdown ^RedisClient client)))))) + AutoCloseable + (close [_] + (.close ^StatefulConnection conn) + (.shutdown ^RedisClient client))))) + +(defn connect + [state & {:as opts}] + (let [connection (connect* state opts)] + (-> state + (assoc ::connection connection) + (dissoc ::cache) + (vary-meta assoc `d/close! (fn [_] (d/close! connection)))))) (defn get-or-connect [{:keys [::cache] :as state} key options] - (assoc state ::connection - (or (get @cache key) - (-> (swap! cache (fn [cache] - (when-let [prev (get cache key)] - (close! prev)) - (assoc cache key (connect state options)))) - (get key))))) + (-> state + (assoc ::connection + (or (get @cache key) + (-> (swap! cache (fn [cache] + (when-let [prev (get cache key)] + (d/close! prev)) + (assoc cache key (connect* state options)))) + (get key)))) + (dissoc ::cache))) (defn add-listener! [{:keys [::connection] :as conn} listener] @@ -210,18 +227,63 @@ [{:keys [::connection] :as conn} & topics] (us/assert! ::connection-holder conn) (us/assert! ::pubsub-connection connection) - (let [topics (into-array String (map str topics)) - cmd (.sync ^StatefulRedisPubSubConnection @connection)] - (.subscribe ^RedisPubSubCommands cmd topics))) + (try + (let [topics (into-array String (map str topics)) + cmd (.sync ^StatefulRedisPubSubConnection @connection)] + (.subscribe ^RedisPubSubCommands cmd topics)) + (catch RedisCommandInterruptedException cause + (throw (InterruptedException. (ex-message cause)))))) (defn unsubscribe! "Blocking operation, intended to be used on a thread/agent thread." [{:keys [::connection] :as conn} & topics] (us/assert! ::connection-holder conn) (us/assert! ::pubsub-connection connection) - (let [topics (into-array String (map str topics)) - cmd (.sync ^StatefulRedisPubSubConnection @connection)] - (.unsubscribe ^RedisPubSubCommands cmd topics))) + (try + (let [topics (into-array String (map str topics)) + cmd (.sync ^StatefulRedisPubSubConnection @connection)] + (.unsubscribe ^RedisPubSubCommands cmd topics)) + (catch RedisCommandInterruptedException cause + (throw (InterruptedException. (ex-message cause)))))) + +(defn rpush! + [{:keys [::connection] :as conn} key payload] + (us/assert! ::connection-holder conn) + (us/assert! (or (and (vector? payload) + (every? bytes? payload)) + (bytes? payload))) + (try + (let [cmd (.sync ^StatefulRedisConnection @connection) + data (if (vector? payload) payload [payload]) + vals (make-array (. Class (forName "[B")) (count data))] + + (loop [i 0 xs (seq data)] + (when xs + (aset ^"[[B" vals i ^bytes (first xs)) + (recur (inc i) (next xs)))) + + (.rpush ^RedisCommands cmd + ^String key + ^"[[B" vals)) + + (catch RedisCommandInterruptedException cause + (throw (InterruptedException. (ex-message cause)))))) + +(defn blpop! + [{:keys [::connection] :as conn} timeout & keys] + (us/assert! ::connection-holder conn) + (try + (let [keys (into-array Object (map str keys)) + cmd (.sync ^StatefulRedisConnection @connection) + timeout (/ (double (inst-ms timeout)) 1000.0)] + (when-let [res (.blpop ^RedisCommands cmd + ^double timeout + ^"[Ljava.lang.String;" keys)] + (MapEntry/create + (.getKey ^KeyValue res) + (.getValue ^KeyValue res)))) + (catch RedisCommandInterruptedException cause + (throw (InterruptedException. (ex-message cause)))))) (defn open? [{:keys [::connection] :as conn}] @@ -256,12 +318,6 @@ (when on-unsubscribe (on-unsubscribe nil topic count))))) -(defn close! - [{:keys [::connection] :as conn}] - (us/assert! ::connection-holder conn) - (us/assert! ::connection connection) - (.close ^AutoCloseable connection)) - (def ^:private scripts-cache (atom {})) (def noop-fn (constantly nil)) @@ -332,3 +388,11 @@ (eval-script sha) (->> (load-script) (p/mapcat eval-script)))))) + +(defn timeout-exception? + [cause] + (instance? RedisCommandTimeoutException cause)) + +(defn exception? + [cause] + (instance? RedisException cause)) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 52cf69b914..adeeceb9e4 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -23,6 +23,7 @@ [app.storage :as-alias sto] [app.util.services :as sv] [app.util.time :as ts] + [app.worker :as-alias wrk] [clojure.spec.alpha :as s] [integrant.core :as ig] [promesa.core :as p] @@ -270,6 +271,7 @@ ::http-client ::rlimit ::climit + ::wrk/executor ::mtx/metrics ::db/pool ::ldap])) diff --git a/backend/src/app/srepl/main.clj b/backend/src/app/srepl/main.clj index 5a85ab684a..80d5a7035b 100644 --- a/backend/src/app/srepl/main.clj +++ b/backend/src/app/srepl/main.clj @@ -20,6 +20,7 @@ [app.util.objects-map :as omap] [app.util.pointer-map :as pmap] [app.util.time :as dt] + [app.worker :as wrk] [clojure.pprint :refer [pprint]] [cuerdas.core :as str])) @@ -37,6 +38,16 @@ (task-fn params) (println (format "no task '%s' found" name)))))) +(defn schedule-task! + ([system name] + (schedule-task! system name {})) + ([system name props] + (let [pool (:app.db/pool system)] + (wrk/submit! + ::wrk/conn pool + ::wrk/task name + ::wrk/props props)))) + (defn send-test-email! [system destination] (us/verify! diff --git a/backend/src/app/util/closeable.clj b/backend/src/app/util/closeable.clj deleted file mode 100644 index 6d20f765f9..0000000000 --- a/backend/src/app/util/closeable.clj +++ /dev/null @@ -1,31 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.util.closeable - "A closeable abstraction. A drop in replacement for - clojure builtin `with-open` syntax abstraction." - (:refer-clojure :exclude [with-open])) - -(defprotocol ICloseable - (-close [_] "Close the resource.")) - -(defmacro with-open - [bindings & body] - {:pre [(vector? bindings) - (even? (count bindings)) - (pos? (count bindings))]} - (reduce (fn [acc bindings] - `(let ~(vec bindings) - (try - ~acc - (finally - (-close ~(first bindings)))))) - `(do ~@body) - (reverse (partition 2 bindings)))) - -(extend-protocol ICloseable - java.lang.AutoCloseable - (-close [this] (.close this))) diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index b6f0ed859d..f54a3add1d 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -8,22 +8,22 @@ "Async tasks abstraction (impl)." (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.spec :as us] + [app.common.transit :as t] [app.common.uuid :as uuid] [app.db :as db] [app.metrics :as mtx] - [app.util.async :as aa] + [app.redis :as rds] [app.util.time :as dt] - [clojure.core.async :as a] [clojure.spec.alpha :as s] [cuerdas.core :as str] [integrant.core :as ig] [promesa.exec :as px]) (:import java.util.concurrent.ExecutorService - java.util.concurrent.Executors java.util.concurrent.ForkJoinPool java.util.concurrent.Future java.util.concurrent.ScheduledExecutorService)) @@ -40,10 +40,10 @@ (s/def ::parallelism ::us/integer) (defmethod ig/pre-init-spec ::executor [_] - (s/keys :req-un [::parallelism])) + (s/keys :req [::parallelism])) (defmethod ig/init-key ::executor - [skey {:keys [parallelism]}] + [skey {:keys [::parallelism]}] (let [prefix (if (vector? skey) (-> skey first name keyword) :default) tname (str "penpot/" prefix "/%s") factory (px/forkjoin-thread-factory :name tname)] @@ -54,43 +54,78 @@ (defmethod ig/halt-key! ::executor [_ instance] - (.shutdown ^ExecutorService instance)) + (px/shutdown! instance)) (defmethod ig/pre-init-spec ::scheduled-executor [_] - (s/keys :opt-un [::parallelism])) + (s/keys :req [::parallelism])) (defmethod ig/init-key ::scheduled-executor - [_ {:keys [parallelism] :or {parallelism 1}}] + [_ {:keys [::parallelism]}] (px/scheduled-executor :parallelism parallelism :factory (px/thread-factory :name "penpot/scheduled-executor/%s"))) -(defmethod ig/halt-key! ::scheduler +(defmethod ig/halt-key! ::scheduled-executor [_ instance] - (.shutdown ^ExecutorService instance)) + (px/shutdown! instance)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Executor Monitor +;; TASKS REGISTRY ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(s/def ::executors - (s/map-of keyword? ::executor)) +(defn- wrap-task-handler + [metrics tname f] + (let [labels (into-array String [tname])] + (fn [params] + (let [tp (dt/tpoint)] + (try + (f params) + (finally + (mtx/run! metrics + {:id :tasks-timing + :val (inst-ms (tp)) + :labels labels}))))))) -(defmethod ig/pre-init-spec ::executor-monitor [_] - (s/keys :req-un [::executors ::mtx/metrics])) +(s/def ::registry (s/map-of ::us/string fn?)) -(defmethod ig/init-key ::executor-monitor - [_ {:keys [executors metrics interval] :or {interval 3000}}] - (letfn [(monitor! [state skey ^ForkJoinPool executor] - (let [prev-steals (get state skey 0) - running (.getRunningThreadCount executor) - queued (.getQueuedSubmissionCount executor) - active (.getPoolSize executor) - steals (.getStealCount executor) - labels (into-array String [(name skey)]) +(defmethod ig/pre-init-spec ::registry [_] + (s/keys :req-un [::mtx/metrics ::tasks])) - steals-increment (- steals prev-steals) - steals-increment (if (neg? steals-increment) 0 steals-increment)] +(defmethod ig/init-key ::registry + [_ {:keys [metrics tasks]}] + (l/info :hint "registry initialized" :tasks (count tasks)) + (reduce-kv (fn [registry k v] + (let [tname (name k)] + (l/debug :hint "register task" :name tname) + (assoc registry tname (wrap-task-handler metrics tname v)))) + {} + tasks)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; EXECUTOR MONITOR +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(s/def ::name ::us/keyword) + +(defmethod ig/pre-init-spec ::monitor [_] + (s/keys :req [::name ::executor ::mtx/metrics])) + +(defmethod ig/prep-key ::monitor + [_ cfg] + (merge {::interval (dt/duration "2s")} + (d/without-nils cfg))) + +(defmethod ig/init-key ::monitor + [_ {:keys [::executor ::mtx/metrics ::interval ::name]}] + (letfn [(monitor! [^ForkJoinPool executor prev-steals] + (let [running (.getRunningThreadCount executor) + queued (.getQueuedSubmissionCount executor) + active (.getPoolSize executor) + steals (.getStealCount executor) + labels (into-array String [(d/name name)]) + + steals-inc (- steals prev-steals) + steals-inc (if (neg? steals-inc) 0 steals-inc)] (mtx/run! metrics :id :executor-active-threads @@ -104,138 +139,495 @@ :labels labels :val queued) (mtx/run! metrics - :id :executors-completed-tasks - :labels labels - :inc steals-increment) + :id :executors-completed-tasks + :labels labels + :inc steals-inc) - (aa/thread-sleep interval) - (if (.isShutdown executor) - (l/debug :hint "stopping monitor; cause: executor is shutdown") - (assoc state skey steals)))) + steals))] - (monitor-fn [] - (try - (loop [items (into (d/queue) executors) - state {}] - (when-let [[skey executor :as item] (peek items)] - (if-let [state (monitor! state skey executor)] - (recur (conj items item) state) - (recur items state)))) - (catch InterruptedException _cause - (l/debug :hint "stopping monitor; interrupted"))))] + (px/thread + {:name "penpot/executors-monitor"} + (l/info :hint "monitor: started" :name name) + (try + (loop [steals 0] + (when-not (px/shutdown? executor) + (px/sleep interval) + (recur (long (monitor! executor steals))))) + (catch InterruptedException _cause + (l/debug :hint "monitor: interrupted" :name name)) + (catch Throwable cause + (l/error :hint "monitor: unexpected error" :name name :cause cause)) + (finally + (l/info :hint "monitor: terminated" :name name)))))) - (let [thread (Thread. monitor-fn)] - (.setDaemon thread true) - (.setName thread "penpot/executor-monitor") - (.start thread) - - thread))) - -(defmethod ig/halt-key! ::executor-monitor +(defmethod ig/halt-key! ::monitor [_ thread] - (.interrupt ^Thread thread)) + (px/interrupt! thread)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Worker +;; SCHEDULER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(declare event-loop-fn) -(declare event-loop) +(defn- decode-task-row + [{:keys [props] :as row}] + (cond-> row + (db/pgobject? props) + (assoc :props (db/decode-transit-pgobject props)))) -(s/def ::queue keyword?) -(s/def ::parallelism ::us/integer) -(s/def ::batch-size ::us/integer) -(s/def ::tasks (s/map-of keyword? fn?)) -(s/def ::poll-interval ::dt/duration) +(s/def ::queue ::us/string) +(s/def ::wait-duration ::dt/duration) + +(defmethod ig/pre-init-spec ::scheduler [_] + (s/keys :req [::mtx/metrics + ::db/pool + ::rds/redis] + :opt [::wait-duration + ::batch-size])) + +(defmethod ig/prep-key ::scheduler + [_ cfg] + (merge {::batch-size 1 + ::wait-duration (dt/duration "2s")} + (d/without-nils cfg))) + +(def ^:private sql:select-next-tasks + "select * from task as t + where t.scheduled_at <= now() + and (t.status = 'new' or t.status = 'retry') + order by t.priority desc, t.scheduled_at + limit ? + for update skip locked") + +(defn- format-queue + [queue] + (str/ffmt "penpot-tasks-queue:%" queue)) + +(defmethod ig/init-key ::scheduler + [_ {:keys [::db/pool ::rds/redis ::batch-size] :as cfg}] + (letfn [(get-tasks-batch [conn] + (->> (db/exec! conn [sql:select-next-tasks batch-size]) + (map decode-task-row) + (seq))) + + (queue-task [conn rconn {:keys [id queue] :as task}] + (db/update! conn :task {:status "ready"} {:id id}) + (let [queue (format-queue queue) + payload (t/encode id) + result (rds/rpush! rconn queue payload)] + (l/debug :hist "scheduler: task pushed to redis" + :task-id id + :key queue + :queued result))) + + (run-batch [rconn] + (db/with-atomic [conn pool] + (when-let [tasks (get-tasks-batch conn)] + (run! (partial queue-task conn rconn) tasks) + true))) + ] + + (if (db/read-only? pool) + (l/warn :hint "scheduler: not started (db is read-only)") + (px/thread + {:name "penpot/scheduler"} + (l/info :hint "scheduler: started") + (try + (dm/with-open [rconn (rds/connect redis)] + (loop [] + (when (px/interrupted?) + (throw (InterruptedException. "interrumpted"))) + + (try + (when-not (run-batch rconn) + (px/sleep (::wait-duration cfg))) + (catch InterruptedException cause + (throw cause)) + (catch Exception cause + (cond + (rds/exception? cause) + (do + (l/warn :hint "scheduler: redis exception (will retry in an instant)" :cause cause) + (px/sleep (::rds/timeout rconn))) + + (db/sql-exception? cause) + (do + (l/warn :hint "scheduler: database exception (will retry in an instant)" :cause cause) + (px/sleep (::rds/timeout rconn))) + + :else + (do + (l/error :hint "scheduler: unhandled exception (will retry in an instant)" :cause cause) + (px/sleep (::rds/timeout rconn)))))) + + (recur))) + + (catch InterruptedException _ + (l/debug :hint "scheduler: interrupted")) + (catch Throwable cause + (l/error :hint "scheduler: unexpected exception" :cause cause)) + (finally + (l/info :hint "scheduler: terminated"))))))) + +(defmethod ig/halt-key! ::scheduler + [_ thread] + (some-> thread px/interrupt!)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; WORKER +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare ^:private run-worker-loop!) +(declare ^:private start-worker!) +(declare ^:private get-error-context) (defmethod ig/pre-init-spec ::worker [_] - (s/keys :req-un [::executor - ::mtx/metrics - ::db/pool - ::batch-size - ::name - ::poll-interval - ::queue - ::tasks])) + (s/keys :req [::parallelism + ::mtx/metrics + ::db/pool + ::rds/redis + ::queue + ::registry])) (defmethod ig/prep-key ::worker [_ cfg] - (d/merge {:batch-size 2 - :name :worker - :poll-interval (dt/duration {:seconds 5}) - :queue :default} - (d/without-nils cfg))) + (merge {::queue "default" + ::parallelism 1} + (d/without-nils cfg))) (defmethod ig/init-key ::worker - [_ {:keys [pool name queue] :as cfg}] - (let [close-ch (a/chan 1) - cfg (assoc cfg :close-ch close-ch)] - - (if (db/read-only? pool) - (l/warn :hint "worker not started, db is read-only" - :name (d/name name) - :queue (d/name queue)) - (do - (l/info :hint "worker initialized" - :name (d/name name) - :queue (d/name queue)) - (event-loop cfg))) - - (reify - java.lang.AutoCloseable - (close [_] - (a/close! close-ch))))) + [_ {:keys [::db/pool ::queue ::parallelism] :as cfg}] + (if (db/read-only? pool) + (l/warn :hint "workers: not started (db is read-only)" :queue queue) + (doall + (->> (range parallelism) + (map #(assoc cfg ::worker-id %)) + (map start-worker!))))) (defmethod ig/halt-key! ::worker + [_ threads] + (run! px/interrupt! threads)) + +(defn- start-worker! + [{:keys [::rds/redis ::worker-id] :as cfg}] + (px/thread + {:name (format "penpot/worker/%s" worker-id)} + (l/info :hint "worker: started" :worker-id worker-id) + (try + (dm/with-open [rconn (rds/connect redis)] + (let [cfg (-> cfg + (update ::queue format-queue) + (assoc ::rds/rconn rconn) + (assoc ::timeout (dt/duration "5s")))] + (loop [] + (when (px/interrupted?) + (throw (InterruptedException. "interrupted"))) + + (run-worker-loop! cfg) + (recur)))) + + (catch InterruptedException _ + (l/debug :hint "worker: interrupted" + :worker-id worker-id)) + (catch Throwable cause + (l/error :hint "worker: unexpected exception" + :worker-id worker-id + :cause cause)) + (finally + (l/info :hint "worker: terminated" :worker-id worker-id))))) + +(defn- run-worker-loop! + [{:keys [::db/pool ::rds/rconn ::timeout ::queue ::registry ::worker-id]}] + (letfn [(handle-task-retry [{:keys [task error inc-by delay] :or {inc-by 1 delay 1000}}] + (let [explain (ex-message error) + nretry (+ (:retry-num task) inc-by) + now (dt/now) + delay (->> (iterate #(* % 2) delay) (take nretry) (last))] + (db/update! pool :task + {:error explain + :status "retry" + :modified-at now + :scheduled-at (dt/plus now delay) + :retry-num nretry} + {:id (:id task)}) + nil)) + + (handle-task-failure [{:keys [task error]}] + (let [explain (ex-message error)] + (db/update! pool :task + {:error explain + :modified-at (dt/now) + :status "failed"} + {:id (:id task)}) + nil)) + + (handle-task-completion [{:keys [task]}] + (let [now (dt/now)] + (db/update! pool :task + {:completed-at now + :modified-at now + :status "completed"} + {:id (:id task)}) + nil)) + + (decode-payload [^bytes payload] + (try + (let [task-id (t/decode payload)] + (if (uuid? task-id) + task-id + (l/error :hint "worker: received unexpected payload (uuid expected)" + :payload task-id))) + (catch Throwable cause + (l/error :hint "worker: unable to decode payload" + :payload payload + :length (alength payload) + :cause cause)))) + + (handle-task [{:keys [name] :as task}] + (let [task-fn (get registry name)] + (if task-fn + (task-fn task) + (l/warn :hint "no task handler found" :name name)) + {:status :completed :task task})) + + (handle-task-exception [cause task] + (let [edata (ex-data cause)] + (if (and (< (:retry-num task) + (:max-retries task)) + (= ::retry (:type edata))) + (cond-> {:status :retry :task task :error cause} + (dt/duration? (:delay edata)) + (assoc :delay (:delay edata)) + + (= ::noop (:strategy edata)) + (assoc :inc-by 0)) + (do + (l/error :hint "worker: unhandled exception on task" + ::l/context (get-error-context cause task) + :cause cause) + (if (>= (:retry-num task) (:max-retries task)) + {:status :failed :task task :error cause} + {:status :retry :task task :error cause}))))) + + (get-task [task-id] + (ex/try (db/get* pool :task {:id task-id}))) + + (run-task [task-id] + (loop [task (get-task task-id)] + (cond + (ex/exception? task) + (if (or (db/connection-error? task) + (db/serialization-error? task)) + (do + (l/warn :hint "worker: connection error on retrieving task from database (retrying in some instants)" + :worker-id worker-id + :cause task) + (px/sleep (::rds/timeout rconn)) + (recur (get-task task-id))) + (do + (l/error :hint "worker: unhandled exception on retrieving task from database (retrying in some instants)" + :worker-id worker-id + :cause task) + (px/sleep (::rds/timeout rconn)) + (recur (get-task task-id)))) + + (nil? task) + (l/warn :hint "worker: no task found on the database" + :worker-id worker-id + :task-id task-id) + + :else + (try + (l/trace :hint "worker: executing task" + :worker-id worker-id + :task-id (:id task) + :task-name (:name task) + :task-retry (:retry-num task)) + (handle-task task) + (catch InterruptedException cause + (throw cause)) + (catch Throwable cause + (handle-task-exception cause task)))))) + + (process-result [{:keys [status] :as result}] + (ex/try! + (case status + :retry (handle-task-retry result) + :failed (handle-task-failure result) + :completed (handle-task-completion result)))) + + (run-task-loop [task-id] + (loop [result (run-task task-id)] + (when-let [cause (process-result result)] + (if (or (db/connection-error? cause) + (db/serialization-error? cause)) + (do + (l/warn :hint "worker: database exeption on processing task result (retrying in some instants)" + :cause cause) + (px/sleep (::rds/timeout rconn)) + (recur result)) + (do + (l/error :hint "worker: unhandled exception on processing task result (retrying in some instants)" + :cause cause) + (px/sleep (::rds/timeout rconn)) + (recur result))))))] + + (try + (let [[_ payload] (rds/blpop! rconn timeout queue)] + (some-> payload + decode-payload + run-task-loop)) + + (catch InterruptedException cause + (throw cause)) + + (catch Exception cause + (if (rds/timeout-exception? cause) + (do + (l/error :hint "worker: redis pop operation timeout, consider increasing redis timeout (will retry in some instants)" + :timeout timeout + :cause cause) + (px/sleep timeout)) + + (l/error :hint "worker: unhandled exception" :cause cause)))))) + +(defn- get-error-context + [error item] + (let [data (ex-data error)] + (merge + {:hint (ex-message error) + :spec-problems (some->> data ::s/problems (take 10) seq vec) + :spec-value (some->> data ::s/value) + :data (some-> data (dissoc ::s/problems ::s/value ::s/spec)) + :params item} + (when (and data (::s/problems data)) + {:spec-explain (us/pretty-explain data)})))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; CRON +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(declare schedule-cron-task) +(declare synchronize-cron-entries!) + +(s/def ::fn (s/or :var var? :fn fn?)) +(s/def ::id keyword?) +(s/def ::cron dt/cron?) +(s/def ::props (s/nilable map?)) +(s/def ::task keyword?) + +(s/def ::cron-task + (s/keys :req-un [::cron ::task] + :opt-un [::props ::id])) + +(s/def ::entries (s/coll-of (s/nilable ::cron-task))) + +(defmethod ig/pre-init-spec ::cron [_] + (s/keys :req [::scheduled-executor ::db/pool ::entries ::registry])) + +(defmethod ig/init-key ::cron + [_ {:keys [::entries ::registry ::db/pool] :as cfg}] + (if (db/read-only? pool) + (l/warn :hint "cron: not started (db is read-only)") + (let [running (atom #{}) + entries (->> entries + (filter some?) + ;; If id is not defined, use the task as id. + (map (fn [{:keys [id task] :as item}] + (if (some? id) + (assoc item :id (d/name id)) + (assoc item :id (d/name task))))) + (map (fn [item] + (update item :task d/name))) + (map (fn [{:keys [task] :as item}] + (let [f (get registry task)] + (when-not f + (ex/raise :type :internal + :code :task-not-found + :hint (str/fmt "task %s not configured" task))) + (-> item + (dissoc :task) + (assoc :fn f)))))) + + cfg (assoc cfg ::entries entries ::running running)] + + (l/info :hint "cron: started" :tasks (count entries)) + (synchronize-cron-entries! cfg) + + (->> (filter some? entries) + (run! (partial schedule-cron-task cfg))) + + (reify + clojure.lang.IDeref + (deref [_] @running) + + java.lang.AutoCloseable + (close [_] + (l/info :hint "cron: terminated") + (doseq [item @running] + (when-not (.isDone ^Future item) + (.cancel ^Future item true)))))))) + +(defmethod ig/halt-key! ::cron [_ instance] - (.close ^java.lang.AutoCloseable instance)) + (some-> instance d/close!)) -(defn- event-loop - "Main, worker eventloop" - [{:keys [pool poll-interval close-ch] :as cfg}] - (let [poll-ms (inst-ms poll-interval)] - (a/go-loop [] - (let [[val port] (a/alts! [close-ch (event-loop-fn cfg)] :priority true)] - (cond - ;; Terminate the loop if close channel is closed or - ;; event-loop-fn returns nil. - (or (= port close-ch) (nil? val)) - (l/debug :hint "stop condition found") +(def sql:upsert-cron-task + "insert into scheduled_task (id, cron_expr) + values (?, ?) + on conflict (id) + do update set cron_expr=?") - (db/closed? pool) - (do - (l/debug :hint "eventloop aborted because pool is closed") - (a/close! close-ch)) +(defn- synchronize-cron-entries! + [{:keys [::db/pool ::entries]}] + (db/with-atomic [conn pool] + (doseq [{:keys [id cron]} entries] + (l/trace :hint "register cron task" :id id :cron (str cron)) + (db/exec-one! conn [sql:upsert-cron-task id (str cron) (str cron)])))) - (and (instance? java.sql.SQLException val) - (contains? #{"08003" "08006" "08001" "08004"} (.getSQLState ^java.sql.SQLException val))) - (do - (l/warn :hint "connection error, trying resume in some instants") - (a/> (iterate #(* % 2) delay) (take nretry) (last)) - sqlv [sql:mark-as-retry (db/interval delay) explain nretry (:id task)]] - (db/exec-one! conn sqlv) - nil)) - -(defn- mark-as-failed - [conn {:keys [task error]}] - (let [explain (ex-message error)] - (db/update! conn :task - {:error explain - :modified-at (dt/now) - :status "failed"} - {:id (:id task)}) - nil)) - -(defn- mark-as-completed - [conn {:keys [task] :as cfg}] - (let [now (dt/now)] - (db/update! conn :task - {:completed-at now - :modified-at now - :status "completed"} - {:id (:id task)}) - nil)) - -(defn- decode-task-row - [{:keys [props name] :as row}] - (when row - (cond-> row - (db/pgobject? props) (assoc :props (db/decode-transit-pgobject props)) - (string? name) (assoc :name (keyword name))))) - -(defn- handle-task - [tasks {:keys [name] :as item}] - (let [task-fn (get tasks name)] - (if task-fn - (task-fn item) - (l/warn :hint "no task handler found" - :name (d/name name))) - {:status :completed :task item})) - -(defn get-error-context - [error item] - (let [data (ex-data error)] - (merge - {:hint (ex-message error) - :spec-problems (some->> data ::s/problems (take 10) seq vec) - :spec-value (some->> data ::s/value) - :data (some-> data (dissoc ::s/problems ::s/value ::s/spec)) - :params item} - (when (and data (::s/problems data)) - {:spec-explain (us/pretty-explain data)})))) - -(defn- handle-exception - [error item] - (let [edata (ex-data error)] - (if (and (< (:retry-num item) - (:max-retries item)) - (= ::retry (:type edata))) - (cond-> {:status :retry :task item :error error} - (dt/duration? (:delay edata)) - (assoc :delay (:delay edata)) - - (= ::noop (:strategy edata)) - (assoc :inc-by 0)) - (do - (l/error :hint "unhandled exception on task" - ::l/context (get-error-context error item) - :cause error) - (if (>= (:retry-num item) (:max-retries item)) - {:status :failed :task item :error error} - {:status :retry :task item :error error}))))) - -(defn- run-task - [{:keys [tasks]} item] - (let [name (d/name (:name item))] - (try - (l/trace :action "execute task" - :id (:id item) - :name name - :retry (:retry-num item)) - (handle-task tasks item) - (catch Exception e - (handle-exception e item))))) - -(def sql:select-next-tasks - "select * from task as t - where t.scheduled_at <= now() - and t.queue = ? - and (t.status = 'new' or t.status = 'retry') - order by t.priority desc, t.scheduled_at - limit ? - for update skip locked") - -(defn- event-loop-fn* - [{:keys [pool executor batch-size] :as cfg}] - (db/with-atomic [conn pool] - (let [queue (name (:queue cfg)) - items (->> (db/exec! conn [sql:select-next-tasks queue batch-size]) - (map decode-task-row) - (seq)) - cfg (assoc cfg :conn conn)] - - (if (nil? items) - ::empty - (let [proc-xf (comp (map #(partial run-task cfg %)) - (map #(px/submit! executor %)))] - (->> (into [] proc-xf items) - (map deref) - (run! (fn [res] - (case (:status res) - :retry (mark-as-retry conn res) - :failed (mark-as-failed conn res) - :completed (mark-as-completed conn res))))) - ::handled))))) - -(defn- event-loop-fn - [{:keys [executor] :as cfg}] - (aa/thread-call executor #(event-loop-fn* cfg))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Scheduler -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(declare schedule-cron-task) -(declare synchronize-cron-entries!) - -(s/def ::fn (s/or :var var? :fn fn?)) -(s/def ::id keyword?) -(s/def ::cron dt/cron?) -(s/def ::props (s/nilable map?)) -(s/def ::task keyword?) - -(s/def ::cron-task - (s/keys :req-un [::cron ::task] - :opt-un [::props ::id])) - -(s/def ::entries (s/coll-of (s/nilable ::cron-task))) - -(defmethod ig/pre-init-spec ::cron [_] - (s/keys :req-un [::executor ::scheduled-executor ::db/pool ::entries ::tasks])) - -(defmethod ig/init-key ::cron - [_ {:keys [entries tasks pool] :as cfg}] - (if (db/read-only? pool) - (l/warn :hint "scheduler not started, db is read-only") - (let [running (atom #{}) - entries (->> entries - (filter some?) - ;; If id is not defined, use the task as id. - (map (fn [{:keys [id task] :as item}] - (if (some? id) - (assoc item :id (d/name id)) - (assoc item :id (d/name task))))) - (map (fn [{:keys [task] :as item}] - (let [f (get tasks task)] - (when-not f - (ex/raise :type :internal - :code :task-not-found - :hint (str/fmt "task %s not configured" task))) - (-> item - (dissoc :task) - (assoc :fn f)))))) - - cfg (assoc cfg :entries entries :running running)] - - (l/info :hint "cron initialized" :tasks (count entries)) - (synchronize-cron-entries! cfg) - - (->> (filter some? entries) - (run! (partial schedule-cron-task cfg))) - - (reify - clojure.lang.IDeref - (deref [_] @running) - - java.lang.AutoCloseable - (close [_] - (doseq [item @running] - (when-not (.isDone ^Future item) - (.cancel ^Future item true)))))))) - - -(defmethod ig/halt-key! ::cron - [_ instance] - (when instance - (.close ^java.lang.AutoCloseable instance))) - -(def sql:upsert-cron-task - "insert into scheduled_task (id, cron_expr) - values (?, ?) - on conflict (id) - do update set cron_expr=?") - -(defn- synchronize-cron-entries! - [{:keys [pool entries]}] - (db/with-atomic [conn pool] - (doseq [{:keys [id cron]} entries] - (l/trace :hint "register cron task" :id id :cron (str cron)) - (db/exec-one! conn [sql:upsert-cron-task id (str cron) (str cron)])))) - -(def sql:lock-cron-task - "select id from scheduled_task where id=? for update skip locked") - -(defn- execute-cron-task - [{:keys [executor pool] :as cfg} {:keys [id] :as task}] - (letfn [(run-task [conn] - (when (db/exec-one! conn [sql:lock-cron-task (d/name id)]) - (l/trace :hint "execute cron task" :id id) - ((:fn task) task))) - - (handle-task [] - (try - (db/with-atomic [conn pool] - (run-task conn)) - (catch Throwable cause - (l/error :hint "unhandled exception on scheduled task" - ::l/context (get-error-context cause task) - :task-id id - :cause cause))))] - - (px/run! executor handle-task) - (px/run! executor #(schedule-cron-task cfg task)) - nil)) - -(defn- ms-until-valid - [cron] - (s/assert dt/cron? cron) - (let [now (dt/now) - next (dt/next-valid-instant-from cron now)] - (inst-ms (dt/diff now next)))) - -(def ^:private - xf-without-done - (remove #(.isDone ^Future %))) - -(defn- schedule-cron-task - [{:keys [scheduled-executor running] :as cfg} {:keys [cron] :as task}] - (let [ft (px/schedule! scheduled-executor - (ms-until-valid cron) - (partial execute-cron-task cfg task))] - (swap! running #(into #{ft} xf-without-done %)))) - -;; --- INSTRUMENTATION - -(defn- wrap-task-handler - [metrics tname f] - (let [labels (into-array String [tname])] - (fn [params] - (let [start (System/nanoTime)] - (try - (f params) - (finally - (mtx/run! metrics - {:id :tasks-timing - :val (/ (- (System/nanoTime) start) 1000000) - :labels labels}))))))) - -(defmethod ig/pre-init-spec ::registry [_] - (s/keys :req-un [::mtx/metrics ::tasks])) - -(defmethod ig/init-key ::registry - [_ {:keys [metrics tasks]}] - (l/info :hint "registry initialized" :tasks (count tasks)) - (reduce-kv (fn [res k v] - (let [tname (name k)] - (l/debug :hint "register task" :name tname) - (assoc res k (wrap-task-handler metrics tname v)))) - {} - tasks)) diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj index 3e12312edd..b25fc6bcb7 100644 --- a/backend/test/backend_tests/helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -324,7 +324,7 @@ (run-task! name {})) ([name params] (let [tasks (:app.worker/registry *system*)] - (let [task-fn (get tasks name)] + (let [task-fn (get tasks (d/name name))] (task-fn params))))) ;; --- UTILS diff --git a/backend/test/backend_tests/tasks_telemetry_test.clj b/backend/test/backend_tests/tasks_telemetry_test.clj index 7ff727b8bc..43e8a59ebc 100644 --- a/backend/test/backend_tests/tasks_telemetry_test.clj +++ b/backend/test/backend_tests/tasks_telemetry_test.clj @@ -20,12 +20,10 @@ (t/deftest test-base-report-data-structure (with-mocks [mock {:target 'app.tasks.telemetry/send! :return nil}] - (let [task-fn (-> th/*system* :app.worker/registry :telemetry) - prof (th/create-profile* 1 {:is-active true - :props {:newsletter-news true}})] + (let [prof (th/create-profile* 1 {:is-active true + :props {:newsletter-news true}})] - ;; run the task - (task-fn {:send? true :enabled? true}) + (th/run-task! :telemetry {:send? true :enabled? true}) (t/is (:called? @mock)) (let [[_ data] (-> @mock :call-args)] diff --git a/common/deps.edn b/common/deps.edn index f9f253fb3b..efe44a5945 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -23,7 +23,7 @@ com.cognitect/transit-cljs {:mvn/version "0.8.280"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} - funcool/promesa {:mvn/version "9.1.540"} + funcool/promesa {:mvn/version "9.2.541"} funcool/cuerdas {:mvn/version "2022.06.16-403"} lambdaisland/uri {:mvn/version "1.13.95" diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 8e44246553..d2461675ef 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -5,7 +5,8 @@ ;; Copyright (c) KALEIDOS INC (ns app.common.data - "Data manipulation and query helper functions." + "A collection if helpers for working with data structures and other + data resources." (:refer-clojure :exclude [read-string hash-map merge name update-vals parse-double group-by iteration concat mapcat]) #?(:cljs @@ -22,7 +23,9 @@ [linked.set :as lks]) #?(:clj - (:import linked.set.LinkedSet))) + (:import + linked.set.LinkedSet + java.lang.AutoCloseable))) (def boolean-or-nil? (some-fn nil? boolean?)) @@ -697,3 +700,16 @@ (map (fn [key] [key (delay (generator-fn key))])) keys)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Util protocols +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defprotocol ICloseable + :extend-via-metadata true + (close! [_] "Close the resource.")) + +#?(:clj + (extend-protocol ICloseable + AutoCloseable + (close! [this] (.close this)))) diff --git a/common/src/app/common/data/macros.cljc b/common/src/app/common/data/macros.cljc index 6c93dab858..0d204e7efa 100644 --- a/common/src/app/common/data/macros.cljc +++ b/common/src/app/common/data/macros.cljc @@ -7,7 +7,7 @@ #_:clj-kondo/ignore (ns app.common.data.macros "Data retrieval & manipulation specific macros." - (:refer-clojure :exclude [get-in select-keys str]) + (:refer-clojure :exclude [get-in select-keys str with-open]) #?(:cljs (:require-macros [app.common.data.macros])) (:require #?(:clj [clojure.core :as c] @@ -94,5 +94,16 @@ [s & params] `(str/ffmt ~s ~@params)) - - +(defmacro with-open + [bindings & body] + {:pre [(vector? bindings) + (even? (count bindings)) + (pos? (count bindings))]} + (reduce (fn [acc bindings] + `(let ~(vec bindings) + (try + ~acc + (finally + (d/close! ~(first bindings)))))) + `(do ~@body) + (reverse (partition 2 bindings)))) diff --git a/common/src/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc index e136285158..11a1d432ff 100644 --- a/common/src/app/common/exceptions.cljc +++ b/common/src/app/common/exceptions.cljc @@ -50,6 +50,10 @@ [& exprs] `(try* (^:once fn* [] ~@exprs) identity)) +(defmacro try! + [& exprs] + `(try* (^:once fn* [] ~@exprs) identity)) + (defn with-always "A helper that evaluates an exptession independently if the body raises exception or not." From 69011007ac28bb89fcd81315f713aae89a3fe0ce Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 23 Nov 2022 23:45:10 +0100 Subject: [PATCH 268/682] :sparkles: Change execution model of storage.tmp cleaner --- backend/src/app/main.clj | 5 ++-- backend/src/app/storage/tmp.clj | 42 +++++++++++++++++---------------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 4a8119d3ea..1e909f8222 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -166,10 +166,9 @@ :executor (ig/ref ::wrk/executor) :redis (ig/ref ::rds/redis)} - ;; TODO: refactor execution model :app.storage.tmp/cleaner - {:executor (ig/ref ::wrk/executor) - :scheduled-executor (ig/ref ::wrk/scheduled-executor)} + {::wrk/executor (ig/ref ::wrk/executor) + ::wrk/scheduled-executor (ig/ref ::wrk/scheduled-executor)} ::sto/gc-deleted-task {:pool (ig/ref ::db/pool) diff --git a/backend/src/app/storage/tmp.clj b/backend/src/app/storage/tmp.clj index 1fd069ef33..3e64e6bfcb 100644 --- a/backend/src/app/storage/tmp.clj +++ b/backend/src/app/storage/tmp.clj @@ -12,6 +12,7 @@ (:require [app.common.data :as d] [app.common.logging :as l] + [app.storage :as-alias sto] [app.util.time :as dt] [app.worker :as wrk] [clojure.core.async :as a] @@ -23,42 +24,43 @@ (declare remove-temp-file) (defonce queue (a/chan 128)) -(s/def ::min-age ::dt/duration) (defmethod ig/pre-init-spec ::cleaner [_] - (s/keys :req-un [::min-age ::wrk/scheduled-executor ::wrk/executor])) + (s/keys :req [::sto/min-age ::wrk/scheduled-executor])) (defmethod ig/prep-key ::cleaner [_ cfg] - (merge {:min-age (dt/duration {:minutes 30})} + (merge {::sto/min-age (dt/duration "30m")} (d/without-nils cfg))) (defmethod ig/init-key ::cleaner - [_ {:keys [scheduled-executor executor min-age] :as cfg}] - (l/info :hint "starting tempfile cleaner service") - (let [cch (a/chan)] - (a/go-loop [] - (let [[path port] (a/alts! [queue cch])] - (when (not= port cch) + [_ {:keys [::sto/min-age ::wrk/scheduled-executor] :as cfg}] + (px/thread + {:name "penpot/storage-tmp-cleaner"} + (try + (l/info :hint "started tmp file cleaner") + (loop [] + (when-let [path (a/ close-ch a/close!)) + [_ thread] + (px/interrupt! thread)) (defn- remove-temp-file "Permanently delete tempfile" - [executor path] - (px/with-dispatch executor - (l/trace :hint "permanently delete tempfile" :path path) - (when (fs/exists? path) - (fs/delete path)))) + [path] + (l/trace :hint "permanently delete tempfile" :path path) + (when (fs/exists? path) + (fs/delete path))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; API From bcfb4e0f81bf74e42a8630786adfb7892668dfc3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 28 Nov 2022 12:25:30 +0100 Subject: [PATCH 269/682] :bug: Fix metrics code on rpc.climit --- backend/src/app/rpc/climit.clj | 14 +++++++------- common/deps.edn | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/backend/src/app/rpc/climit.clj b/backend/src/app/rpc/climit.clj index 76c6f44e79..8b97c4cce2 100644 --- a/backend/src/app/rpc/climit.clj +++ b/backend/src/app/rpc/climit.clj @@ -63,16 +63,16 @@ (l/trace :hint "enqueued" :key (name bkey) :skey (str skey) - :queue-size (get instance :current-queue-size) - :concurrency (get instance :current-concurrency) + :queue-size (get instance ::pxb/current-queue-size) + :concurrency (get instance ::pxb/current-concurrency)) (mtx/run! metrics :id :rpc-climit-queue-size - :val (get instance :current-queue-size) + :val (get instance ::pxb/current-queue-size) :labels labels) (mtx/run! metrics :id :rpc-climit-concurrency - :val (get instance :current-concurrency) - :labels labels))) + :val (get instance ::pxb/current-concurrency) + :labels labels)) on-run (fn [instance task] (let [elapsed (- (inst-ms (dt/now)) @@ -87,11 +87,11 @@ :labels labels) (mtx/run! metrics :id :rpc-climit-queue-size - :val (get instance :current-queue-size) + :val (get instance ::pxb/current-queue-size) :labels labels) (mtx/run! metrics :id :rpc-climit-concurrency - :val (get instance :current-concurrency) + :val (get instance ::pxb/current-concurrency) :labels labels))) options {:executor executor diff --git a/common/deps.edn b/common/deps.edn index efe44a5945..435fffa59f 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -23,7 +23,7 @@ com.cognitect/transit-cljs {:mvn/version "0.8.280"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} - funcool/promesa {:mvn/version "9.2.541"} + funcool/promesa {:mvn/version "9.2.542"} funcool/cuerdas {:mvn/version "2022.06.16-403"} lambdaisland/uri {:mvn/version "1.13.95" From 329b1eb6f317ae2db9da5db9316df759029afb68 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 28 Nov 2022 12:37:48 +0100 Subject: [PATCH 270/682] :paperclip: Fix on test initialization on CI --- backend/test/backend_tests/helpers.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj index b25fc6bcb7..e5ff498466 100644 --- a/backend/test/backend_tests/helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -68,7 +68,7 @@ :thumbnail-uri "test" :path (-> "backend_tests/test_files/template.penpot" io/resource fs/path)}] system (-> (merge main/system-config main/worker-config) - (assoc-in [:app.redis/redis :uri] (:redis-uri config)) + (assoc-in [:app.redis/redis :app.redis/uri] (:redis-uri config)) (assoc-in [:app.db/pool :uri] (:database-uri config)) (assoc-in [:app.db/pool :username] (:database-username config)) (assoc-in [:app.db/pool :password] (:database-password config)) From cd47c0356a2e42f865172c7d1bb2c96654c028fb Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 22 Nov 2022 16:52:31 +0100 Subject: [PATCH 271/682] :tada: Add workspace read-only setting --- frontend/src/app/main/data/workspace.cljs | 23 ++ .../app/main/data/workspace/shortcuts.cljs | 128 ++++++----- .../main/data/workspace/state_helpers.cljs | 1 + frontend/src/app/main/refs.cljs | 3 + .../app/main/ui/components/tab_container.cljs | 4 +- frontend/src/app/main/ui/context.cljs | 28 +-- frontend/src/app/main/ui/hooks.cljs | 8 +- frontend/src/app/main/ui/workspace.cljs | 36 +-- .../src/app/main/ui/workspace/header.cljs | 70 +++--- .../app/main/ui/workspace/left_toolbar.cljs | 158 ++++++------- .../app/main/ui/workspace/sidebar/assets.cljs | 213 +++++++++--------- .../app/main/ui/workspace/sidebar/layers.cljs | 66 +++--- .../main/ui/workspace/sidebar/options.cljs | 6 +- .../workspace/sidebar/options/menus/text.cljs | 2 +- .../sidebar/options/menus/typography.cljs | 30 +-- .../main/ui/workspace/sidebar/sitemap.cljs | 108 ++++----- .../src/app/main/ui/workspace/viewport.cljs | 15 +- .../main/ui/workspace/viewport/actions.cljs | 68 +++--- .../app/main/ui/workspace/viewport/hooks.cljs | 6 +- .../main/ui/workspace/viewport/selection.cljs | 19 +- .../main/ui/workspace/viewport/widgets.cljs | 30 +-- frontend/src/debug.cljs | 5 + 22 files changed, 567 insertions(+), 460 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index c658a3c9f3..fa87849ea2 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -39,6 +39,7 @@ [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwco] [app.main.data.workspace.drawing :as dwd] + [app.main.data.workspace.drawing.common :as dwdc] [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.fix-bool-contents :as fbc] [app.main.data.workspace.groups :as dwg] @@ -1769,6 +1770,28 @@ (rx/of (modal/hide) (complete-remove-graphics))))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Read only +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(defn set-workspace-read-only + [read-only?] + (ptk/reify ::set-workspace-read-only + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-global :read-only?] read-only?)) + + ptk/WatchEvent + (watch [_ _ _] + (if read-only? + (rx/of :interrupt + (dwdc/clear-drawing) + (remove-layout-flag :colorpalette) + (remove-layout-flag :textpalette)) + (rx/empty))))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Exports ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 31fc056ee5..26936068d3 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -20,6 +20,7 @@ [app.main.data.workspace.texts :as dwtxt] [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] + [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.hooks.resize :as r] [app.util.dom :as dom])) @@ -33,6 +34,11 @@ (-> (dw/toggle-layout-flag flag) (vary-meta assoc ::ev/origin "workspace-shortcuts"))) +(defn emit-when-no-readonly + [& events] + (when-not (deref refs/workspace-read-only?) + (run! st/emit! events))) + ;; Shortcuts format https://github.com/ccampbell/mousetrap (def base-shortcuts @@ -40,17 +46,17 @@ :undo {:tooltip (ds/meta "Z") :command (ds/c-mod "z") :subsections [:edit] - :fn #(st/emit! dwc/undo)} + :fn #(emit-when-no-readonly dwc/undo)} :redo {:tooltip (ds/meta "Y") :command [(ds/c-mod "shift+z") (ds/c-mod "y")] :subsections [:edit] - :fn #(st/emit! dwc/redo)} + :fn #(emit-when-no-readonly dwc/redo)} :clear-undo {:tooltip (ds/alt "Z") :command "alt+z" :subsections [:edit] - :fn #(st/emit! dwu/reinitialize-undo)} + :fn #(emit-when-no-readonly dwu/reinitialize-undo)} :copy {:tooltip (ds/meta "C") :command (ds/c-mod "c") @@ -60,8 +66,8 @@ :cut {:tooltip (ds/meta "X") :command (ds/c-mod "x") :subsections [:edit] - :fn #(st/emit! (dw/copy-selected) - (dw/delete-selected))} + :fn #(emit-when-no-readonly (dw/copy-selected) + (dw/delete-selected))} :paste {:tooltip (ds/meta "V") :disabled true @@ -72,29 +78,29 @@ :delete {:tooltip (ds/supr) :command ["del" "backspace"] :subsections [:edit] - :fn #(st/emit! (dw/delete-selected))} + :fn #(emit-when-no-readonly (dw/delete-selected))} :duplicate {:tooltip (ds/meta "D") :command (ds/c-mod "d") :subsections [:edit] - :fn #(st/emit! (dw/duplicate-selected true))} + :fn #(emit-when-no-readonly (dw/duplicate-selected true))} :start-editing {:tooltip (ds/enter) :command "enter" :subsections [:edit] - :fn #(st/emit! (dw/start-editing-selected))} + :fn #(emit-when-no-readonly (dw/start-editing-selected))} :start-measure {:tooltip (ds/alt "") :command ["alt" "."] :type "keydown" :subsections [:edit] - :fn #(st/emit! (dw/toggle-distances-display true))} + :fn #(emit-when-no-readonly (dw/toggle-distances-display true))} :stop-measure {:tooltip (ds/alt "") :command ["alt" "."] :type "keyup" :subsections [:edit] - :fn #(st/emit! (dw/toggle-distances-display false))} + :fn #(emit-when-no-readonly (dw/toggle-distances-display false))} :escape {:tooltip (ds/esc) :command "escape" @@ -107,149 +113,149 @@ :group {:tooltip (ds/meta "G") :command (ds/c-mod "g") :subsections [:modify-layers] - :fn #(st/emit! dw/group-selected)} + :fn #(emit-when-no-readonly dw/group-selected)} :ungroup {:tooltip (ds/shift "G") :command "shift+g" :subsections [:modify-layers] - :fn #(st/emit! dw/ungroup-selected)} + :fn #(emit-when-no-readonly dw/ungroup-selected)} :mask {:tooltip (ds/meta "M") :command (ds/c-mod "m") :subsections [:modify-layers] - :fn #(st/emit! dw/mask-group)} + :fn #(emit-when-no-readonly dw/mask-group)} :unmask {:tooltip (ds/meta-shift "M") :command (ds/c-mod "shift+m") :subsections [:modify-layers] - :fn #(st/emit! dw/unmask-group)} + :fn #(emit-when-no-readonly dw/unmask-group)} :create-component {:tooltip (ds/meta "K") :command (ds/c-mod "k") :subsections [:modify-layers] - :fn #(st/emit! (dwl/add-component))} + :fn #(emit-when-no-readonly (dwl/add-component))} :detach-component {:tooltip (ds/meta-shift "K") :command (ds/c-mod "shift+k") :subsections [:modify-layers] - :fn #(st/emit! dwl/detach-selected-components)} + :fn #(emit-when-no-readonly dwl/detach-selected-components)} :flip-vertical {:tooltip (ds/shift "V") :command "shift+v" :subsections [:modify-layers] - :fn #(st/emit! (dw/flip-vertical-selected))} + :fn #(emit-when-no-readonly (dw/flip-vertical-selected))} :flip-horizontal {:tooltip (ds/shift "H") :command "shift+h" :subsections [:modify-layers] - :fn #(st/emit! (dw/flip-horizontal-selected))} + :fn #(emit-when-no-readonly (dw/flip-horizontal-selected))} :bring-forward {:tooltip (ds/meta ds/up-arrow) :command (ds/c-mod "up") :subsections [:modify-layers] - :fn #(st/emit! (dw/vertical-order-selected :up))} + :fn #(emit-when-no-readonly (dw/vertical-order-selected :up))} :bring-backward {:tooltip (ds/meta ds/down-arrow) :command (ds/c-mod "down") :subsections [:modify-layers] - :fn #(st/emit! (dw/vertical-order-selected :down))} + :fn #(emit-when-no-readonly (dw/vertical-order-selected :down))} :bring-front {:tooltip (ds/meta-shift ds/up-arrow) :command (ds/c-mod "shift+up") :subsections [:modify-layers] - :fn #(st/emit! (dw/vertical-order-selected :top))} + :fn #(emit-when-no-readonly (dw/vertical-order-selected :top))} :bring-back {:tooltip (ds/meta-shift ds/down-arrow) :command (ds/c-mod "shift+down") :subsections [:modify-layers] - :fn #(st/emit! (dw/vertical-order-selected :bottom))} + :fn #(emit-when-no-readonly (dw/vertical-order-selected :bottom))} :move-fast-up {:tooltip (ds/shift ds/up-arrow) :command "shift+up" :subsections [:modify-layers] - :fn #(st/emit! (dwt/move-selected :up true))} + :fn #(emit-when-no-readonly (dwt/move-selected :up true))} :move-fast-down {:tooltip (ds/shift ds/down-arrow) :command "shift+down" :subsections [:modify-layers] - :fn #(st/emit! (dwt/move-selected :down true))} + :fn #(emit-when-no-readonly (dwt/move-selected :down true))} :move-fast-right {:tooltip (ds/shift ds/right-arrow) :command "shift+right" :subsections [:modify-layers] - :fn #(st/emit! (dwt/move-selected :right true))} + :fn #(emit-when-no-readonly (dwt/move-selected :right true))} :move-fast-left {:tooltip (ds/shift ds/left-arrow) :command "shift+left" :subsections [:modify-layers] - :fn #(st/emit! (dwt/move-selected :left true))} + :fn #(emit-when-no-readonly (dwt/move-selected :left true))} :move-unit-up {:tooltip ds/up-arrow :command "up" :subsections [:modify-layers] - :fn #(st/emit! (dwt/move-selected :up false))} + :fn #(emit-when-no-readonly (dwt/move-selected :up false))} :move-unit-down {:tooltip ds/down-arrow :command "down" :subsections [:modify-layers] - :fn #(st/emit! (dwt/move-selected :down false))} + :fn #(emit-when-no-readonly (dwt/move-selected :down false))} :move-unit-left {:tooltip ds/right-arrow :command "right" :subsections [:modify-layers] - :fn #(st/emit! (dwt/move-selected :right false))} + :fn #(emit-when-no-readonly (dwt/move-selected :right false))} :move-unit-right {:tooltip ds/left-arrow :command "left" :subsections [:modify-layers] - :fn #(st/emit! (dwt/move-selected :left false))} + :fn #(emit-when-no-readonly (dwt/move-selected :left false))} :artboard-selection {:tooltip (ds/meta (ds/alt "G")) :command (ds/c-mod "alt+g") :subsections [:modify-layers] - :fn #(st/emit! (dws/create-artboard-from-selection))} + :fn #(emit-when-no-readonly (dws/create-artboard-from-selection))} :toogle-layout-flex {:tooltip (ds/shift "F") :command "shift+f" :subsections [:modify-layers] - :fn #(st/emit! (dwsl/toogle-layout-flex))} + :fn #(emit-when-no-readonly (dwsl/toogle-layout-flex))} ;; TOOLS :draw-frame {:tooltip "B" :command ["b" "a"] :subsections [:tools :basics] - :fn #(st/emit! (dwd/select-for-drawing :frame))} + :fn #(emit-when-no-readonly (dwd/select-for-drawing :frame))} :move {:tooltip "V" :command "v" :subsections [:tools] - :fn #(st/emit! :interrupt)} + :fn #(emit-when-no-readonly :interrupt)} :draw-rect {:tooltip "R" :command "r" :subsections [:tools] - :fn #(st/emit! (dwd/select-for-drawing :rect))} + :fn #(emit-when-no-readonly (dwd/select-for-drawing :rect))} :draw-ellipse {:tooltip "E" :command "e" :subsections [:tools] - :fn #(st/emit! (dwd/select-for-drawing :circle))} + :fn #(emit-when-no-readonly (dwd/select-for-drawing :circle))} :draw-text {:tooltip "T" :command "t" :subsections [:tools] - :fn #(st/emit! dwtxt/start-edit-if-selected + :fn #(emit-when-no-readonly dwtxt/start-edit-if-selected (dwd/select-for-drawing :text))} :draw-path {:tooltip "P" :command "p" :subsections [:tools] - :fn #(st/emit! (dwd/select-for-drawing :path))} + :fn #(emit-when-no-readonly (dwd/select-for-drawing :path))} :draw-curve {:tooltip (ds/shift "C") :command "shift+c" :subsections [:tools] - :fn #(st/emit! (dwd/select-for-drawing :curve))} + :fn #(emit-when-no-readonly (dwd/select-for-drawing :curve))} :add-comment {:tooltip "C" :command "c" @@ -264,74 +270,74 @@ :toggle-visibility {:tooltip (ds/meta-shift "H") :command (ds/c-mod "shift+h") :subsections [:tools] - :fn #(st/emit! (dw/toggle-visibility-selected))} + :fn #(emit-when-no-readonly (dw/toggle-visibility-selected))} :toggle-lock {:tooltip (ds/meta-shift "L") :command (ds/c-mod "shift+l") :subsections [:tools] - :fn #(st/emit! (dw/toggle-lock-selected))} + :fn #(emit-when-no-readonly (dw/toggle-lock-selected))} :toggle-lock-size {:tooltip (ds/meta (ds/alt "L")) :command (ds/c-mod "alt+l") :subsections [:tools] - :fn #(st/emit! (dw/toggle-proportion-lock))} + :fn #(emit-when-no-readonly (dw/toggle-proportion-lock))} :toggle-scale-text {:tooltip "K" :command "k" :subsections [:tools] - :fn #(st/emit! (toggle-layout-flag :scale-text))} + :fn #(emit-when-no-readonly (toggle-layout-flag :scale-text))} :open-color-picker {:tooltip "I" :command "i" :subsections [:tools] - :fn #(st/emit! (mdc/picker-for-selected-shape))} + :fn #(emit-when-no-readonly (mdc/picker-for-selected-shape))} :toggle-focus-mode {:command "f" :tooltip "F" :subsections [:basics :tools] - :fn #(st/emit! (dw/toggle-focus-mode))} + :fn #(emit-when-no-readonly (dw/toggle-focus-mode))} ;; ITEM ALIGNMENT :align-left {:tooltip (ds/alt "A") :command "alt+a" :subsections [:alignment] - :fn #(st/emit! (dw/align-objects :hleft))} + :fn #(emit-when-no-readonly (dw/align-objects :hleft))} :align-right {:tooltip (ds/alt "D") :command "alt+d" :subsections [:alignment] - :fn #(st/emit! (dw/align-objects :hright))} + :fn #(emit-when-no-readonly (dw/align-objects :hright))} :align-top {:tooltip (ds/alt "W") :command "alt+w" :subsections [:alignment] - :fn #(st/emit! (dw/align-objects :vtop))} + :fn #(emit-when-no-readonly (dw/align-objects :vtop))} :align-hcenter {:tooltip (ds/alt "H") :command "alt+h" :subsections [:alignment] - :fn #(st/emit! (dw/align-objects :hcenter))} + :fn #(emit-when-no-readonly (dw/align-objects :hcenter))} :align-vcenter {:tooltip (ds/alt "V") :command "alt+v" :subsections [:alignment] - :fn #(st/emit! (dw/align-objects :vcenter))} + :fn #(emit-when-no-readonly (dw/align-objects :vcenter))} :align-bottom {:tooltip (ds/alt "S") :command "alt+s" :subsections [:alignment] - :fn #(st/emit! (dw/align-objects :vbottom))} + :fn #(emit-when-no-readonly (dw/align-objects :vbottom))} :h-distribute {:tooltip (ds/meta-shift (ds/alt "H")) :command (ds/c-mod "shift+alt+h") :subsections [:alignment] - :fn #(st/emit! (dw/distribute-objects :horizontal))} + :fn #(emit-when-no-readonly (dw/distribute-objects :horizontal))} :v-distribute {:tooltip (ds/meta-shift (ds/alt "V")) :command (ds/c-mod "shift+alt+v") :subsections [:alignment] - :fn #(st/emit! (dw/distribute-objects :vertical))} + :fn #(emit-when-no-readonly (dw/distribute-objects :vertical))} ;; MAIN MENU @@ -406,20 +412,20 @@ :toggle-history {:tooltip (ds/alt "H") :command (ds/a-mod "h") :subsections [:panels] - :fn #(st/emit! (dw/go-to-layout :document-history))} + :fn #(emit-when-no-readonly (dw/go-to-layout :document-history))} :toggle-colorpalette {:tooltip (ds/alt "P") :command (ds/a-mod "p") :subsections [:panels] :fn #(do (r/set-resize-type! :bottom) - (st/emit! (dw/remove-layout-flag :textpalette) + (emit-when-no-readonly (dw/remove-layout-flag :textpalette) (toggle-layout-flag :colorpalette)))} :toggle-textpalette {:tooltip (ds/alt "T") :command (ds/a-mod "t") :subsections [:panels] :fn #(do (r/set-resize-type! :bottom) - (st/emit! (dw/remove-layout-flag :colorpalette) + (emit-when-no-readonly (dw/remove-layout-flag :colorpalette) (toggle-layout-flag :textpalette)))} :hide-ui {:tooltip "\\" @@ -482,22 +488,22 @@ :bool-union {:tooltip (ds/meta (ds/alt "U")) :command (ds/c-mod "alt+u") :subsections [:shape] - :fn #(st/emit! (dw/create-bool :union))} + :fn #(emit-when-no-readonly (dw/create-bool :union))} :bool-difference {:tooltip (ds/meta (ds/alt "D")) :command (ds/c-mod "alt+d") :subsections [:shape] - :fn #(st/emit! (dw/create-bool :difference))} + :fn #(emit-when-no-readonly (dw/create-bool :difference))} :bool-intersection {:tooltip (ds/meta (ds/alt "I")) :command (ds/c-mod "alt+i") :subsections [:shape] - :fn #(st/emit! (dw/create-bool :intersection))} + :fn #(emit-when-no-readonly (dw/create-bool :intersection))} :bool-exclude {:tooltip (ds/meta (ds/alt "E")) :command (ds/c-mod "alt+e") :subsections [:shape] - :fn #(st/emit! (dw/create-bool :exclude))}} + :fn #(emit-when-no-readonly (dw/create-bool :exclude))}} ) (def opacity-shortcuts @@ -507,7 +513,7 @@ {:tooltip (str n) :command (str n) :subsections [:modify-layers] - :fn #(st/emit! (dwly/pressed-opacity n))}]))))) + :fn #(emit-when-no-readonly (dwly/pressed-opacity n))}]))))) (def shortcuts (merge base-shortcuts opacity-shortcuts)) diff --git a/frontend/src/app/main/data/workspace/state_helpers.cljs b/frontend/src/app/main/data/workspace/state_helpers.cljs index e8bee24ecd..b6596511b2 100644 --- a/frontend/src/app/main/data/workspace/state_helpers.cljs +++ b/frontend/src/app/main/data/workspace/state_helpers.cljs @@ -146,3 +146,4 @@ (let [{:keys [x y width height]} (get-in state [:workspace-local :vbox])] (gpt/point (+ x (/ width 2)) (+ y (/ height 2))))) + diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 9711aeefb3..147efe5dca 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -261,6 +261,9 @@ (def workspace-page-objects (l/derived wsh/lookup-page-objects st/state =)) +(def workspace-read-only? + (l/derived :read-only? workspace-global)) + (defn object-by-id [id] (l/derived #(get % id) workspace-page-objects)) diff --git a/frontend/src/app/main/ui/components/tab_container.cljs b/frontend/src/app/main/ui/components/tab_container.cljs index 579014fbb9..1052cbe121 100644 --- a/frontend/src/app/main/ui/components/tab_container.cljs +++ b/frontend/src/app/main/ui/components/tab_container.cljs @@ -20,7 +20,9 @@ (mf/defc tab-container {::mf/wrap-props false} [props] - (let [children (unchecked-get props "children") + (let [children (->> + (unchecked-get props "children") + (filter some?)) selected (unchecked-get props "selected") on-change (unchecked-get props "on-change-tab") diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index 51f0bb1823..3b0f84bf15 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -8,20 +8,22 @@ (:require [rumext.v2 :as mf])) -(def render-id (mf/create-context nil)) +(def render-id (mf/create-context nil)) -(def current-route (mf/create-context nil)) -(def current-profile (mf/create-context nil)) -(def current-team-id (mf/create-context nil)) -(def current-project-id (mf/create-context nil)) -(def current-page-id (mf/create-context nil)) -(def current-file-id (mf/create-context nil)) +(def current-route (mf/create-context nil)) +(def current-profile (mf/create-context nil)) +(def current-team-id (mf/create-context nil)) +(def current-project-id (mf/create-context nil)) +(def current-page-id (mf/create-context nil)) +(def current-file-id (mf/create-context nil)) -(def active-frames (mf/create-context nil)) -(def render-thumbnails (mf/create-context nil)) +(def active-frames (mf/create-context nil)) +(def render-thumbnails (mf/create-context nil)) -(def libraries (mf/create-context nil)) -(def components-v2 (mf/create-context nil)) +(def libraries (mf/create-context nil)) +(def components-v2 (mf/create-context nil)) -(def current-scroll (mf/create-context nil)) -(def current-zoom (mf/create-context nil)) +(def current-scroll (mf/create-context nil)) +(def current-zoom (mf/create-context nil)) + +(def workspace-read-only? (mf/create-context nil)) diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index 0d1238e409..9b6bdb6049 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -84,7 +84,9 @@ ;; things go weird. (defn use-sortable - [& {:keys [data-type data on-drop on-drag on-hold disabled detect-center?] :as opts}] + [& {:keys [data-type data on-drop on-drag on-hold disabled detect-center? draggable?] + :or {draggable? true} + :as opts}] (let [ref (mf/use-ref) state (mf/use-state {:over nil :timer nil @@ -169,7 +171,7 @@ on-mount (fn [] (let [dom (mf/ref-val ref)] - (.setAttribute dom "draggable" true) + (.setAttribute dom "draggable" draggable?) ;; Register all events in the (default) bubble mode, so that they ;; are captured by the most leaf item. The handler will stop @@ -189,7 +191,7 @@ (.removeEventListener dom "dragend" on-drag-end))))] (mf/use-effect - (mf/deps data on-drop) + (mf/deps data on-drop draggable?) on-mount) [(deref state) ref])) diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 05e5e57831..84de5c4a47 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -117,11 +117,12 @@ (mf/defc workspace {::mf/wrap [mf/memo]} [{:keys [project-id file-id page-id layout-name] :as props}] - (let [file (mf/deref refs/workspace-file) - project (mf/deref refs/workspace-project) - layout (mf/deref refs/workspace-layout) - wglobal (mf/deref refs/workspace-global) + (let [file (mf/deref refs/workspace-file) + project (mf/deref refs/workspace-project) + layout (mf/deref refs/workspace-layout) + wglobal (mf/deref refs/workspace-global) ready? (mf/deref refs/workspace-ready?) + workspace-read-only? (mf/deref refs/workspace-read-only?) components-v2 (features/use-feature :components-v2) @@ -152,22 +153,23 @@ [:& (mf/provider ctx/current-project-id) {:value (:id project)} [:& (mf/provider ctx/current-page-id) {:value page-id} [:& (mf/provider ctx/components-v2) {:value components-v2} - [:section#workspace {:style {:background-color background-color}} - (when (not (:hide-ui layout)) - [:& header {:file file - :page-id page-id - :project project - :layout layout}]) + [:& (mf/provider ctx/workspace-read-only?) {:value workspace-read-only?} + [:section#workspace {:style {:background-color background-color}} + (when (not (:hide-ui layout)) + [:& header {:file file + :page-id page-id + :project project + :layout layout}]) - [:& context-menu] + [:& context-menu] (if ready? - [:& workspace-page {:key (dm/str "page-" page-id) - :page-id page-id - :file file - :wglobal wglobal - :layout layout}] - [:& workspace-loader])]]]]]])) + [:& workspace-page {:key (dm/str "page-" page-id) + :page-id page-id + :file file + :wglobal wglobal + :layout layout}] + [:& workspace-loader])]]]]]]])) (mf/defc remove-graphics-dialog {::mf/register modal/components diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 9b742fec8f..f8e5729ec3 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -21,6 +21,7 @@ [app.main.repo :as rp] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.context :as ctx] [app.main.ui.export :refer [export-progress-widget]] [app.main.ui.formats :as fmt] [app.main.ui.hooks.resize :as r] @@ -107,13 +108,14 @@ (mf/defc menu [{:keys [layout project file team-id] :as props}] - (let [show-menu? (mf/use-state false) - show-sub-menu? (mf/use-state false) - editing? (mf/use-state false) - edit-input-ref (mf/use-ref nil) - objects (mf/deref refs/workspace-page-objects) - frames (->> (cph/get-immediate-children objects uuid/zero) - (filterv cph/frame-shape?)) + (let [show-menu? (mf/use-state false) + show-sub-menu? (mf/use-state false) + editing? (mf/use-state false) + edit-input-ref (mf/use-ref nil) + objects (mf/deref refs/workspace-page-objects) + frames (->> (cph/get-immediate-children objects uuid/zero) + (filterv cph/frame-shape?)) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) add-shared-fn #(st/emit! (dwl/set-file-shared (:id file) true)) @@ -328,25 +330,27 @@ (tr "workspace.header.menu.show-grid"))] [:span.shortcut (sc/get-tooltip :toggle-grid)]] - [:li {:on-click (fn [] - (r/set-resize-type! :bottom) - (st/emit! (dw/remove-layout-flag :textpalette) - (toggle-flag :colorpalette)))} - [:span - (if (contains? layout :colorpalette) - (tr "workspace.header.menu.hide-palette") - (tr "workspace.header.menu.show-palette"))] - [:span.shortcut (sc/get-tooltip :toggle-colorpalette)]] + (when-not workspace-read-only? + [:* + [:li {:on-click (fn [] + (r/set-resize-type! :bottom) + (st/emit! (dw/remove-layout-flag :textpalette) + (toggle-flag :colorpalette)))} + [:span + (if (contains? layout :colorpalette) + (tr "workspace.header.menu.hide-palette") + (tr "workspace.header.menu.show-palette"))] + [:span.shortcut (sc/get-tooltip :toggle-colorpalette)]] - [:li {:on-click (fn [] - (r/set-resize-type! :bottom) - (st/emit! (dw/remove-layout-flag :colorpalette) - (toggle-flag :textpalette)))} - [:span - (if (contains? layout :textpalette) - (tr "workspace.header.menu.hide-textpalette") - (tr "workspace.header.menu.show-textpalette"))] - [:span.shortcut (sc/get-tooltip :toggle-textpalette)]] + [:li {:on-click (fn [] + (r/set-resize-type! :bottom) + (st/emit! (dw/remove-layout-flag :colorpalette) + (toggle-flag :textpalette)))} + [:span + (if (contains? layout :textpalette) + (tr "workspace.header.menu.hide-textpalette") + (tr "workspace.header.menu.show-textpalette"))] + [:span.shortcut (sc/get-tooltip :toggle-textpalette)]]]) [:li {:on-click #(st/emit! (toggle-flag :display-artboard-names))} [:span @@ -436,6 +440,7 @@ (let [team-id (:team-id project) zoom (mf/deref refs/selected-zoom) params {:page-id page-id :file-id (:id file) :section "interactions"} + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) close-modals (mf/use-callback @@ -474,13 +479,14 @@ [:div.options-section [:& persistence-state-widget] [:& export-progress-widget] - [:button.document-history - {:alt (tr "workspace.sidebar.history" (sc/get-tooltip :toggle-history)) - :aria-label (tr "workspace.sidebar.history" (sc/get-tooltip :toggle-history)) - :class (when (contains? layout :document-history) "selected") - :on-click #(st/emit! (-> (dw/toggle-layout-flag :document-history) - (vary-meta assoc ::ev/origin "workspace-header")))} - i/recent]] + (when-not workspace-read-only? + [:button.document-history + {:alt (tr "workspace.sidebar.history" (sc/get-tooltip :toggle-history)) + :aria-label (tr "workspace.sidebar.history" (sc/get-tooltip :toggle-history)) + :class (when (contains? layout :document-history) "selected") + :on-click #(st/emit! (-> (dw/toggle-layout-flag :document-history) + (vary-meta assoc ::ev/origin "workspace-header")))} + i/recent])] [:div.options-section [:& zoom-widget-workspace diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index 458632e6b3..bf01aa001b 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -15,6 +15,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.file-uploader :refer [file-uploader]] + [app.main.ui.context :as ctx] [app.main.ui.hooks.resize :as r] [app.main.ui.icons :as i] [app.util.dom :as dom] @@ -64,10 +65,11 @@ {::mf/wrap [mf/memo] ::mf/wrap-props false} [props] - (let [layout (obj/get props "layout") - selected-drawtool (mf/deref refs/selected-drawing-tool) - select-drawtool #(st/emit! :interrupt (dw/select-for-drawing %)) - edition (mf/deref refs/selected-edition)] + (let [layout (obj/get props "layout") + selected-drawtool (mf/deref refs/selected-drawing-tool) + select-drawtool #(st/emit! :interrupt (dw/select-for-drawing %)) + edition (mf/deref refs/selected-edition) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)] [:aside.left-toolbar [:ul.left-toolbar-options [:li @@ -78,56 +80,58 @@ (not edition)) "selected") :on-click #(st/emit! :interrupt)} i/pointer-inner]] - [:li - [:button.tooltip.tooltip-right - {:alt (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame)) - :aria-label (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame)) - :class (when (= selected-drawtool :frame) "selected") - :on-click (partial select-drawtool :frame) - :data-test "artboard-btn"} - i/artboard]] - [:li - [:button.tooltip.tooltip-right - {:alt (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect)) - :aria-label (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect)) - :class (when (= selected-drawtool :rect) "selected") - :on-click (partial select-drawtool :rect) - :data-test "rect-btn"} - i/box]] - [:li - [:button.tooltip.tooltip-right - {:alt (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse)) - :aria-label (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse)) - :class (when (= selected-drawtool :circle) "selected") - :on-click (partial select-drawtool :circle) - :data-test "ellipse-btn"} - i/circle]] - [:li - [:button.tooltip.tooltip-right - {:alt (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text)) - :aria-label (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text)) - :class (when (= selected-drawtool :text) "selected") - :on-click (partial select-drawtool :text)} - i/text]] + (when-not workspace-read-only? + [:* + [:li + [:button.tooltip.tooltip-right + {:alt (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame)) + :aria-label (tr "workspace.toolbar.frame" (sc/get-tooltip :draw-frame)) + :class (when (= selected-drawtool :frame) "selected") + :on-click (partial select-drawtool :frame) + :data-test "artboard-btn"} + i/artboard]] + [:li + [:button.tooltip.tooltip-right + {:alt (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect)) + :aria-label (tr "workspace.toolbar.rect" (sc/get-tooltip :draw-rect)) + :class (when (= selected-drawtool :rect) "selected") + :on-click (partial select-drawtool :rect) + :data-test "rect-btn"} + i/box]] + [:li + [:button.tooltip.tooltip-right + {:alt (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse)) + :aria-label (tr "workspace.toolbar.ellipse" (sc/get-tooltip :draw-ellipse)) + :class (when (= selected-drawtool :circle) "selected") + :on-click (partial select-drawtool :circle) + :data-test "ellipse-btn"} + i/circle]] + [:li + [:button.tooltip.tooltip-right + {:alt (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text)) + :aria-label (tr "workspace.toolbar.text" (sc/get-tooltip :draw-text)) + :class (when (= selected-drawtool :text) "selected") + :on-click (partial select-drawtool :text)} + i/text]] - [:& image-upload] + [:& image-upload] - [:li - [:button.tooltip.tooltip-right - {:alt (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve)) - :aria-label (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve)) - :class (when (= selected-drawtool :curve) "selected") - :on-click (partial select-drawtool :curve) - :data-test "curve-btn"} - i/pencil]] - [:li - [:button.tooltip.tooltip-right - {:alt (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path)) - :aria-label (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path)) - :class (when (= selected-drawtool :path) "selected") - :on-click (partial select-drawtool :path) - :data-test "path-btn"} - i/pen]] + [:li + [:button.tooltip.tooltip-right + {:alt (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve)) + :aria-label (tr "workspace.toolbar.curve" (sc/get-tooltip :draw-curve)) + :class (when (= selected-drawtool :curve) "selected") + :on-click (partial select-drawtool :curve) + :data-test "curve-btn"} + i/pencil]] + [:li + [:button.tooltip.tooltip-right + {:alt (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path)) + :aria-label (tr "workspace.toolbar.path" (sc/get-tooltip :draw-path)) + :class (when (= selected-drawtool :path) "selected") + :on-click (partial select-drawtool :path) + :data-test "path-btn"} + i/pen]]]) [:li [:button.tooltip.tooltip-right @@ -138,31 +142,33 @@ i/chat]]] [:ul.left-toolbar-options.panels - [:li - [:button.tooltip.tooltip-right - {:alt (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette)) - :aria-label (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette)) - :class (when (contains? layout :textpalette) "selected") - :on-click (fn [] - (r/set-resize-type! :bottom) - (dom/add-class! (dom/get-element-by-class "color-palette") "fade-out-down") - (ts/schedule 300 #(st/emit! (dw/remove-layout-flag :colorpalette) - (-> (dw/toggle-layout-flag :textpalette) - (vary-meta assoc ::ev/origin "workspace-left-toolbar")))))} - "Ag"]] + (when-not workspace-read-only? + [:* + [:li + [:button.tooltip.tooltip-right + {:alt (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette)) + :aria-label (tr "workspace.toolbar.text-palette" (sc/get-tooltip :toggle-textpalette)) + :class (when (contains? layout :textpalette) "selected") + :on-click (fn [] + (r/set-resize-type! :bottom) + (dom/add-class! (dom/get-element-by-class "color-palette") "fade-out-down") + (ts/schedule 300 #(st/emit! (dw/remove-layout-flag :colorpalette) + (-> (dw/toggle-layout-flag :textpalette) + (vary-meta assoc ::ev/origin "workspace-left-toolbar")))))} + "Ag"]] - [:li - [:button.tooltip.tooltip-right - {:alt (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette)) - :aria-label (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette)) - :class (when (contains? layout :colorpalette) "selected") - :on-click (fn [] - (r/set-resize-type! :bottom) - (dom/add-class! (dom/get-element-by-class "color-palette") "fade-out-down") - (ts/schedule 300 #(st/emit! (dw/remove-layout-flag :textpalette) - (-> (dw/toggle-layout-flag :colorpalette) - (vary-meta assoc ::ev/origin "workspace-left-toolbar")))))} - i/palette]] + [:li + [:button.tooltip.tooltip-right + {:alt (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette)) + :aria-label (tr "workspace.toolbar.color-palette" (sc/get-tooltip :toggle-colorpalette)) + :class (when (contains? layout :colorpalette) "selected") + :on-click (fn [] + (r/set-resize-type! :bottom) + (dom/add-class! (dom/get-element-by-class "color-palette") "fade-out-down") + (ts/schedule 300 #(st/emit! (dw/remove-layout-flag :textpalette) + (-> (dw/toggle-layout-flag :colorpalette) + (vary-meta assoc ::ev/origin "workspace-left-toolbar")))))} + i/palette]]]) [:li [:button.tooltip.tooltip-right.separator {:alt (tr "workspace.toolbar.shortcuts" (sc/get-tooltip :show-shortcuts)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index d77fb28e15..99171cd79d 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -364,11 +364,11 @@ (mf/defc components-item [{:keys [component renaming listing-thumbs? selected-components - on-asset-click on-context-menu on-drag-start do-rename - cancel-rename selected-components-full selected-components-paths]}] - (let [item-ref (mf/use-ref) - - dragging? (mf/use-state false) + on-asset-click on-context-menu on-drag-start do-rename cancel-rename + selected-components-full selected-components-paths]}] + (let [item-ref (mf/use-ref) + dragging? (mf/use-state false) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) unselect-all (mf/use-fn @@ -422,7 +422,7 @@ :grid-cell @listing-thumbs? :enum-item (not @listing-thumbs?)) :id (str "component-shape-id-" (:id component)) - :draggable true + :draggable (not workspace-read-only?) :on-click on-component-click :on-context-menu (on-context-menu (:id component)) :on-drag-start on-component-drag-start @@ -552,11 +552,12 @@ (mf/defc components-box [{:keys [file-id local? components listing-thumbs? open? reverse-sort? open-groups selected-assets on-asset-click on-assets-delete on-clear-selection] :as props}] - (let [input-ref (mf/use-ref nil) - state (mf/use-state {:renaming nil - :component-id nil}) + (let [input-ref (mf/use-ref nil) + state (mf/use-state {:renaming nil + :component-id nil}) - menu-state (mf/use-state auto-pos-menu-state) + menu-state (mf/use-state auto-pos-menu-state) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) selected-components (:components selected-assets) selected-components-full (filter #(contains? selected-components (:id %)) components) @@ -627,10 +628,10 @@ on-context-menu (mf/use-fn - (mf/deps selected-components on-clear-selection) + (mf/deps selected-components on-clear-selection workspace-read-only?) (fn [component-id] (fn [event] - (when local? + (when (and local? (not workspace-read-only?)) (when-not (contains? selected-components component-id) (on-clear-selection)) (swap! state assoc :component-id component-id) @@ -715,7 +716,7 @@ :open? open?} (when local? [:& asset-section-block {:role :title-button} - (when components-v2 + (when (and components-v2 (not workspace-read-only?)) [:div.assets-button {:on-click add-component} i/plus [:& file-uploader {:accept cm/str-image-types @@ -759,9 +760,10 @@ [{:keys [object renaming listing-thumbs? selected-objects on-asset-click on-context-menu on-drag-start do-rename cancel-rename selected-graphics-full selected-graphics-paths]}] - (let [item-ref (mf/use-ref) - visible? (h/use-visible item-ref :once? true) - dragging? (mf/use-state false) + (let [item-ref (mf/use-ref) + visible? (h/use-visible item-ref :once? true) + dragging? (mf/use-state false) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) on-drop (mf/use-fn @@ -795,7 +797,7 @@ :selected (contains? selected-objects (:id object)) :grid-cell @listing-thumbs? :enum-item (not @listing-thumbs?)) - :draggable true + :draggable (not workspace-read-only?) :on-click #(on-asset-click % (:id object) nil) :on-context-menu (on-context-menu (:id object)) :on-drag-start on-grahic-drag-start @@ -928,18 +930,19 @@ (mf/defc graphics-box [{:keys [file-id project-id local? objects listing-thumbs? open? open-groups selected-assets reverse-sort? on-asset-click on-assets-delete on-clear-selection] :as props}] - (let [input-ref (mf/use-ref nil) - state (mf/use-state {:renaming nil - :object-id nil}) + (let [input-ref (mf/use-ref nil) + state (mf/use-state {:renaming nil + :object-id nil}) - menu-state (mf/use-state auto-pos-menu-state) + menu-state (mf/use-state auto-pos-menu-state) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) - selected-objects (:graphics selected-assets) + selected-objects (:graphics selected-assets) selected-graphics-full (filter #(contains? selected-objects (:id %)) objects) - multi-objects? (> (count selected-objects) 1) - multi-assets? (or (seq (:components selected-assets)) - (seq (:colors selected-assets)) - (seq (:typographies selected-assets))) + multi-objects? (> (count selected-objects) 1) + multi-assets? (or (seq (:components selected-assets)) + (seq (:colors selected-assets)) + (seq (:typographies selected-assets))) objects (->> objects (map dwl/extract-path-if-missing)) @@ -996,10 +999,10 @@ on-context-menu (mf/use-fn - (mf/deps selected-objects on-clear-selection) + (mf/deps selected-objects on-clear-selection workspace-read-only?) (fn [object-id] (fn [event] - (when local? + (when (and local? (not workspace-read-only?)) (when-not (contains? selected-objects object-id) (on-clear-selection)) (swap! state assoc :object-id object-id) @@ -1084,7 +1087,7 @@ :open? open?} (when local? [:& asset-section-block {:role :title-button} - (when-not components-v2 + (when (and (not components-v2) (not workspace-read-only?)) [:div.assets-button {:on-click add-graphic} i/plus [:& file-uploader {:accept cm/str-image-types @@ -1125,13 +1128,14 @@ [{:keys [color local? file-id selected-colors multi-colors? multi-assets? on-asset-click on-assets-delete on-clear-selection on-group selected-colors-full selected-colors-paths move-color] :as props}] - (let [item-ref (mf/use-ref) - dragging? (mf/use-state false) - rename? (= (:color-for-rename @refs/workspace-local) (:id color)) - input-ref (mf/use-ref) - state (mf/use-state {:editing rename?}) + (let [item-ref (mf/use-ref) + dragging? (mf/use-state false) + rename? (= (:color-for-rename @refs/workspace-local) (:id color)) + input-ref (mf/use-ref) + state (mf/use-state {:editing rename?}) - menu-state (mf/use-state auto-pos-menu-state) + menu-state (mf/use-state auto-pos-menu-state) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) default-name (cond (:gradient color) (bc/gradient-type->string (get-in color [:gradient :type])) @@ -1182,7 +1186,7 @@ rename-color-clicked (fn [event] - (when local? + (when (and local? (not workspace-read-only?)) (dom/prevent-default event) (swap! state assoc :editing true))) @@ -1213,9 +1217,9 @@ on-context-menu (mf/use-fn - (mf/deps color selected-colors on-clear-selection) + (mf/deps color selected-colors on-clear-selection workspace-read-only?) (fn [event] - (when local? + (when (and local? (not workspace-read-only?)) (when-not (contains? selected-colors (:id color)) (on-clear-selection)) (swap! menu-state #(open-auto-pos-menu % event))))) @@ -1265,7 +1269,7 @@ #(on-asset-click % (:id color) (partial apply-color (:id color)))) :ref item-ref - :draggable true + :draggable (not workspace-read-only?) :on-drag-start on-color-drag-start :on-drag-enter on-drag-enter :on-drag-leave on-drag-leave @@ -1400,14 +1404,15 @@ (mf/defc colors-box [{:keys [file-id local? colors open? open-groups selected-assets reverse-sort? on-asset-click on-assets-delete on-clear-selection] :as props}] - (let [selected-colors (:colors selected-assets) + (let [selected-colors (:colors selected-assets) selected-colors-full (filter #(contains? selected-colors (:id %)) colors) - multi-colors? (> (count selected-colors) 1) - multi-assets? (or (seq (:components selected-assets)) - (seq (:graphics selected-assets)) - (seq (:typographies selected-assets))) + multi-colors? (> (count selected-colors) 1) + multi-assets? (or (seq (:components selected-assets)) + (seq (:graphics selected-assets)) + (seq (:typographies selected-assets))) - groups (group-assets colors reverse-sort?) + groups (group-assets colors reverse-sort?) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) add-color (mf/use-fn @@ -1501,8 +1506,9 @@ :open? open?} (when local? [:& asset-section-block {:role :title-button} - [:div.assets-button {:on-click add-color-clicked} - i/plus]]) + (when-not workspace-read-only? + [:div.assets-button {:on-click add-color-clicked} + i/plus])]) [:& asset-section-block {:role :content} [:& colors-group {:file-id file-id @@ -1526,10 +1532,11 @@ (mf/defc typography-item [{:keys [typography file local? handle-change selected-typographies apply-typography - editing-id local-data on-asset-click on-context-menu - selected-typographies-full selected-typographies-paths move-typography] :as props}] - (let [item-ref (mf/use-ref) - dragging? (mf/use-state false) + editing-id local-data on-asset-click on-context-menu selected-typographies-full + selected-typographies-paths move-typography] :as props}] + (let [item-ref (mf/use-ref) + dragging? (mf/use-state false) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) on-drop (mf/use-fn (mf/deps typography dragging? selected-typographies selected-typographies-full selected-typographies-paths move-typography) @@ -1558,7 +1565,7 @@ (on-asset-drag-start event typography selected-typographies item-ref :typographies identity)))] [:div.typography-container {:ref item-ref - :draggable true + :draggable (not workspace-read-only?) :on-drag-start on-typography-drag-start :on-drag-enter on-drag-enter :on-drag-leave on-drag-leave @@ -1568,7 +1575,7 @@ {:key (:id typography) :typography typography :file file - :read-only? (not local?) + :local? local? :on-context-menu #(on-context-menu (:id typography) %) :on-change #(handle-change typography %) :selected? (contains? selected-typographies (:id typography)) @@ -1581,8 +1588,8 @@ (mf/defc typographies-group [{:keys [file-id prefix groups open-groups file local? selected-typographies local-data - editing-id on-asset-click handle-change apply-typography - on-rename-group on-ungroup on-context-menu selected-typographies-full]}] + editing-id on-asset-click handle-change apply-typography on-rename-group + on-ungroup on-context-menu selected-typographies-full]}] (let [group-open? (get open-groups prefix true) dragging? (mf/use-state false) @@ -1690,6 +1697,7 @@ multi-assets? (or (seq (:components selected-assets)) (seq (:graphics selected-assets)) (seq (:colors selected-assets))) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) add-typography (mf/use-fn @@ -1783,9 +1791,9 @@ on-context-menu (mf/use-fn - (mf/deps selected-typographies on-clear-selection) + (mf/deps selected-typographies on-clear-selection workspace-read-only?) (fn [id event] - (when local? + (when (and local? (not workspace-read-only?)) (when-not (contains? selected-typographies id) (on-clear-selection)) (swap! state assoc :id id) @@ -1833,8 +1841,9 @@ :open? open?} (when local? [:& asset-section-block {:role :title-button} - [:div.assets-button {:on-click add-typography} - i/plus]]) + (when-not workspace-read-only? + [:div.assets-button {:on-click add-typography} + i/plus])]) [:& asset-section-block {:role :content} [:& typographies-group {:file-id file-id @@ -1929,52 +1938,52 @@ (mf/defc file-library [{:keys [file local? default-open? filters] :as props}] - (let [open-file (mf/deref (make-open-file-ref (:id file))) - open? (-> open-file - :library - (d/nilv default-open?)) - open-box? (fn [box] - (-> open-file - box - (d/nilv true))) - open-groups (fn [box] - (-> open-file - :groups - box - (d/nilv {}))) - shared? (:is-shared file) - router (mf/deref refs/router) + (let [open-file (mf/deref (make-open-file-ref (:id file))) + open? (-> open-file + :library + (d/nilv default-open?)) + open-box? (fn [box] + (-> open-file + box + (d/nilv true))) + open-groups (fn [box] + (-> open-file + :groups + box + (d/nilv {}))) + shared? (:is-shared file) + router (mf/deref refs/router) - reverse-sort? (mf/use-state false) - listing-thumbs? (mf/use-state true) + reverse-sort? (mf/use-state false) + listing-thumbs? (mf/use-state true) - selected-assets (mf/deref refs/selected-assets) + selected-assets (mf/deref refs/selected-assets) - selected-count (+ (count (:components selected-assets)) - (count (:graphics selected-assets)) - (count (:colors selected-assets)) - (count (:typographies selected-assets))) + selected-count (+ (count (:components selected-assets)) + (count (:graphics selected-assets)) + (count (:colors selected-assets)) + (count (:typographies selected-assets))) - components-v2 (mf/use-ctx ctx/components-v2) + components-v2 (mf/use-ctx ctx/components-v2) - toggle-open #(st/emit! (dwl/set-assets-box-open (:id file) :library (not open?))) + toggle-open #(st/emit! (dwl/set-assets-box-open (:id file) :library (not open?))) - url (rt/resolve router :workspace + url (rt/resolve router :workspace {:project-id (:project-id file) :file-id (:id file)} {:page-id (get-in file [:data :pages 0])}) - colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) - colors (apply-filters (mf/deref colors-ref) filters @reverse-sort?) + colors-ref (mf/use-memo (mf/deps (:id file)) #(file-colors-ref (:id file))) + colors (apply-filters (mf/deref colors-ref) filters @reverse-sort?) - typography-ref (mf/use-memo (mf/deps (:id file)) #(file-typography-ref (:id file))) - typographies (apply-filters (mf/deref typography-ref) filters @reverse-sort?) + typography-ref (mf/use-memo (mf/deps (:id file)) #(file-typography-ref (:id file))) + typographies (apply-filters (mf/deref typography-ref) filters @reverse-sort?) - media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) - media (apply-filters (mf/deref media-ref) filters @reverse-sort?) + media-ref (mf/use-memo (mf/deps (:id file)) #(file-media-ref (:id file))) + media (apply-filters (mf/deref media-ref) filters @reverse-sort?) - components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file))) - components (apply-filters (mf/deref components-ref) filters @reverse-sort?) + components-ref (mf/use-memo (mf/deps (:id file)) #(file-components-ref (:id file))) + components (apply-filters (mf/deref components-ref) filters @reverse-sort?) toggle-sort (mf/use-fn @@ -2171,12 +2180,13 @@ (mf/defc assets-toolbox [] - (let [libraries (->> (mf/deref refs/workspace-libraries) - (vals) - (remove :is-indirect)) - file (mf/deref refs/workspace-file) - team-id (mf/use-ctx ctx/current-team-id) - filters (mf/use-state {:term "" :box :all}) + (let [libraries (->> (mf/deref refs/workspace-libraries) + (vals) + (remove :is-indirect)) + file (mf/deref refs/workspace-file) + team-id (mf/use-ctx ctx/current-team-id) + filters (mf/use-state {:term "" :box :all}) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) on-search-term-change (mf/use-fn @@ -2205,9 +2215,10 @@ [:div.tool-window-content [:div.assets-bar-title (tr "workspace.assets.assets") - [:div.libraries-button {:on-click #(modal/show! :libraries-dialog {})} - i/text-align-justify - (tr "workspace.assets.libraries")]] + (when-not workspace-read-only? + [:div.libraries-button {:on-click #(modal/show! :libraries-dialog {})} + i/text-align-justify + (tr "workspace.assets.libraries")])] [:div.search-block [:input.search-input diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 52bce124f4..caff9ca995 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -87,25 +87,26 @@ (mf/defc layer-item [{:keys [index item selected objects sortable? filtered?] :as props}] - (let [id (:id item) - blocked? (:blocked item) - hidden? (:hidden item) + (let [id (:id item) + blocked? (:blocked item) + hidden? (:hidden item) - disable-drag (mf/use-state false) - scroll-to-middle? (mf/use-var true) - expanded-iref (mf/with-memo [id] - (-> (l/in [:expanded id]) - (l/derived refs/workspace-local))) + disable-drag (mf/use-state false) + scroll-to-middle? (mf/use-var true) + expanded-iref (mf/with-memo [id] + (-> (l/in [:expanded id]) + (l/derived refs/workspace-local))) - expanded? (mf/deref expanded-iref) - selected? (contains? selected id) - container? (or (cph/frame-shape? item) - (cph/group-shape? item)) + expanded? (mf/deref expanded-iref) + selected? (contains? selected id) + container? (or (cph/frame-shape? item) + (cph/group-shape? item)) - components-v2 (mf/use-ctx ctx/components-v2) - main-instance? (if components-v2 - (:main-instance? item) - true) + components-v2 (mf/use-ctx ctx/components-v2) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) + main-instance? (if components-v2 + (:main-instance? item) + true) toggle-collapse (mf/use-fn @@ -170,12 +171,13 @@ on-context-menu (mf/use-fn - (mf/deps item) + (mf/deps item workspace-read-only?) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (let [pos (dom/get-client-position event)] - (st/emit! (dw/show-shape-context-menu {:position pos :shape item}))))) + (when-not workspace-read-only? + (let [pos (dom/get-client-position event)] + (st/emit! (dw/show-shape-context-menu {:position pos :shape item})))))) on-drag (mf/use-fn @@ -192,7 +194,7 @@ (st/emit! (dw/relocate-selected-shapes id 0)) (let [to-index (if (= side :top) (inc index) index) parent-id (cph/get-parent-id objects id)] - (st/emit! (dw/relocate-selected-shapes parent-id to-index)))))) + (st/emit! (dw/relocate-selected-shapes parent-id to-index)))))) on-hold (mf/use-fn @@ -201,17 +203,18 @@ (when-not expanded? (st/emit! (dwc/toggle-collapse id))))) - [dprops dref] (when sortable? - (hooks/use-sortable - :data-type "penpot/layer" - :on-drop on-drop - :on-drag on-drag - :on-hold on-hold - :disabled @disable-drag - :detect-center? container? - :data {:id (:id item) - :index index - :name (:name item)})) + [dprops dref] + (hooks/use-sortable + :data-type "penpot/layer" + :on-drop on-drop + :on-drag on-drag + :on-hold on-hold + :disabled @disable-drag + :detect-center? container? + :data {:id (:id item) + :index index + :name (:name item)} + :draggable? (and sortable? (not workspace-read-only?))) ref (mf/use-ref)] @@ -257,6 +260,7 @@ :main-instance? main-instance?}]] [:& layer-name {:shape item :name-ref ref + :disabled-double-click workspace-read-only? :on-start-edit #(reset! disable-drag true) :on-stop-edit #(reset! disable-drag false)}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 360151a9c7..413a776c84 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -68,7 +68,11 @@ selected-shapes (into [] (keep (d/getf objects)) selected)] [:div.tool-window [:div.tool-window-content - [:& tab-container {:on-change-tab #(st/emit! (udw/set-options-mode %)) + [:& tab-container {:on-change-tab (fn [options-mode] + (st/emit! (udw/set-options-mode options-mode)) + (if (= options-mode :prototype) ;;TODO remove, only for test palba + (st/emit! :interrupt (udw/deselect-all true) (udw/set-workspace-read-only true)) + (st/emit! :interrupt (udw/set-workspace-read-only false)))) :selected section} [:& tab-element {:id :design :title (tr "workspace.options.design")} diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs index 24672804a9..8f3d81ed66 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/text.cljs @@ -320,7 +320,7 @@ (cond typography [:& typography-entry {:typography typography - :read-only? (not= (:typography-ref-file values) file-id) + :local? (= (:typography-ref-file values) file-id) :file (get shared-libs (:typography-ref-file values)) :on-detach handle-detach-typography :on-change handle-change-typography}] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index 4fd7e6aed5..62c77cb0df 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -18,6 +18,7 @@ [app.main.store :as st] [app.main.ui.components.editable-select :refer [editable-select]] [app.main.ui.components.numeric-input :refer [numeric-input]] + [app.main.ui.context :as ctx] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] [app.util.dom :as dom] @@ -455,11 +456,13 @@ ;; In summary, this need to a good UX/UI/IMPL rework. (mf/defc typography-entry - [{:keys [typography read-only? selected? on-click on-change on-detach on-context-menu editing? focus-name? file]}] - (let [open? (mf/use-state editing?) - hover-detach (mf/use-state false) - name-input-ref (mf/use-ref) - on-change-ref (mf/use-ref nil) + [{:keys [typography local? selected? on-click on-change on-detach on-context-menu editing? focus-name? file]}] + (let [open? (mf/use-state editing?) + hover-detach (mf/use-state false) + name-input-ref (mf/use-ref) + on-change-ref (mf/use-ref nil) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) + editable? (and local? (not workspace-read-only?)) on-name-blur (mf/use-callback @@ -511,7 +514,7 @@ [:& advanced-options {:visible? @open? :on-close #(reset! open? false)} - (if read-only? + (if (not editable?) [:div.element-set-content.typography-read-only-data [:div.row-flex.typography-name [:span (:name typography)]] @@ -544,13 +547,14 @@ [:span.label (tr "workspace.assets.typography.text-transform")] [:span (:text-transform typography)]] - [:div.row-flex - [:a.go-to-lib-button - {:on-click #(st/emit! (rt/nav-new-window* {:rname :workspace - :path-params {:project-id (:project-id file) - :file-id (:id file)} - :query-params {:page-id (get-in file [:data :pages 0])}}))} - (tr "workspace.assets.typography.go-to-edit")]]] + (when-not local? + [:div.row-flex + [:a.go-to-lib-button + {:on-click #(st/emit! (rt/nav-new-window* {:rname :workspace + :path-params {:project-id (:project-id file) + :file-id (:id file)} + :query-params {:page-id (get-in file [:data :pages 0])}}))} + (tr "workspace.assets.typography.go-to-edit")]])] [:* [:div.element-set-content diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs index 0821a50ab1..e4235954b4 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs @@ -27,42 +27,46 @@ (mf/defc page-item [{:keys [page index deletable? selected?] :as props}] - (let [local (mf/use-state {}) - input-ref (mf/use-ref) - id (:id page) - state (mf/use-state {:menu-open false}) + (let [local (mf/use-state {}) + input-ref (mf/use-ref) + id (:id page) + state (mf/use-state {:menu-open false}) - delete-fn (mf/use-callback (mf/deps id) #(st/emit! (dw/delete-page id))) - navigate-fn (mf/use-callback (mf/deps id) #(st/emit! :interrupt (dw/go-to-page id))) + delete-fn (mf/use-callback (mf/deps id) #(st/emit! (dw/delete-page id))) + navigate-fn (mf/use-callback (mf/deps id) #(st/emit! :interrupt (dw/go-to-page id))) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) on-context-menu (mf/use-callback - (mf/deps id) + (mf/deps id workspace-read-only?) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (let [pos (dom/get-client-position event)] - (swap! state assoc - :menu-open true - :top (:y pos) - :left (:x pos))))) + (when-not workspace-read-only? + (let [pos (dom/get-client-position event)] + (swap! state assoc + :menu-open true + :top (:y pos) + :left (:x pos)))))) on-delete (mf/use-callback (mf/deps id) #(st/emit! (modal/show - {:type :confirm - :title (tr "modals.delete-page.title") - :message (tr "modals.delete-page.body") - :on-accept delete-fn}))) + {:type :confirm + :title (tr "modals.delete-page.title") + :message (tr "modals.delete-page.body") + :on-accept delete-fn}))) on-double-click (mf/use-callback + (mf/deps workspace-read-only?) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (swap! local assoc :edition true) - (swap! state assoc :menu-open false))) + (when-not workspace-read-only? + (swap! local assoc :edition true) + (swap! state assoc :menu-open false)))) on-blur (mf/use-callback @@ -75,13 +79,13 @@ on-key-down (mf/use-callback - (fn [event] - (cond - (kbd/enter? event) - (on-blur event) + (fn [event] + (cond + (kbd/enter? event) + (on-blur event) - (kbd/esc? event) - (swap! local assoc :edition false)))) + (kbd/esc? event) + (swap! local assoc :edition false)))) on-drop (mf/use-callback @@ -100,7 +104,8 @@ :on-drop on-drop :data {:id id :index index - :name (:name page)})] + :name (:name page)} + :draggable? (not workspace-read-only?))] (mf/use-effect (mf/deps selected?) @@ -141,22 +146,23 @@ [:* [:span (:name page)] [:div.page-actions - (when deletable? + (when (and deletable? (not workspace-read-only?)) [:a {:on-click on-delete} i/trash])]])]] - [:& context-menu - {:selectable false - :show (:menu-open @state) - :on-close #(swap! state assoc :menu-open false) - :top (:top @state) - :left (:left @state) - :options (cond-> [] - deletable? - (conj [(tr "workspace.assets.delete") on-delete]) + (when-not workspace-read-only? + [:& context-menu + {:selectable false + :show (:menu-open @state) + :on-close #(swap! state assoc :menu-open false) + :top (:top @state) + :left (:left @state) + :options (cond-> [] + deletable? + (conj [(tr "workspace.assets.delete") on-delete]) - :always - (-> (conj [(tr "workspace.assets.rename") on-double-click]) - (conj [(tr "workspace.assets.duplicate") on-duplicate])))}]])) + :always + (-> (conj [(tr "workspace.assets.rename") on-double-click]) + (conj [(tr "workspace.assets.duplicate") on-duplicate])))}])])) ;; --- Page Item Wrapper @@ -198,26 +204,26 @@ (mf/defc sitemap [] - (let [file (mf/deref refs/workspace-file) - create (mf/use-callback - (mf/deps file) - (fn [] - (st/emit! (dw/create-page {:file-id (:id file) - :project-id (:project-id file)})))) - show-pages? (mf/use-state true) - - {:keys [on-pointer-down on-lost-pointer-capture on-mouse-move parent-ref size]} + (let [{:keys [on-pointer-down on-lost-pointer-capture on-mouse-move parent-ref size]} (use-resize-hook :sitemap 200 38 400 :y false nil) - size (if @show-pages? size 38) - toggle-pages - (mf/use-callback #(reset! show-pages? not))] + file (mf/deref refs/workspace-file) + create (mf/use-callback + (mf/deps file) + (fn [] + (st/emit! (dw/create-page {:file-id (:id file) + :project-id (:project-id file)})))) + show-pages? (mf/use-state true) + size (if @show-pages? size 38) + toggle-pages (mf/use-callback #(reset! show-pages? not)) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?)] [:div#sitemap.tool-window {:ref parent-ref :style #js {"--height" (str size "px")}} [:div.tool-window-bar [:span (tr "workspace.sidebar.sitemap")] - [:div.add-page {:on-click create} i/close] + (when-not workspace-read-only? + [:div.add-page {:on-click create} i/close]) [:div.collapse-pages {:on-click toggle-pages :style {:transform (when (not @show-pages?) "rotate(-90deg)")}} i/arrow-slide]] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 80217083d2..7e6e6c19ba 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -130,22 +130,25 @@ node-editing? (and edition (not= :text (get-in base-objects [edition :type]))) text-editing? (and edition (= :text (get-in base-objects [edition :type]))) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) + on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect) - on-context-menu (actions/on-context-menu hover hover-ids) - on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition) + on-context-menu (actions/on-context-menu hover hover-ids workspace-read-only?) + on-double-click (actions/on-double-click hover hover-ids drawing-path? base-objects edition workspace-read-only?) on-drag-enter (actions/on-drag-enter) on-drag-over (actions/on-drag-over) on-drop (actions/on-drop file viewport-ref zoom) on-mouse-down (actions/on-mouse-down @hover selected edition drawing-tool text-editing? node-editing? - drawing-path? create-comment? space? viewport-ref zoom panning) + drawing-path? create-comment? space? viewport-ref zoom panning + workspace-read-only?) on-mouse-up (actions/on-mouse-up disable-paste) on-pointer-down (actions/on-pointer-down) on-pointer-enter (actions/on-pointer-enter in-viewport?) on-pointer-leave (actions/on-pointer-leave in-viewport?) on-pointer-move (actions/on-pointer-move viewport-ref zoom move-stream) on-pointer-up (actions/on-pointer-up) - on-move-selected (actions/on-move-selected hover hover-ids selected space?) - on-menu-selected (actions/on-menu-selected hover hover-ids selected) + on-move-selected (actions/on-move-selected hover hover-ids selected space? workspace-read-only?) + on-menu-selected (actions/on-menu-selected hover hover-ids selected workspace-read-only?) on-frame-enter (actions/on-frame-enter frame-hover) on-frame-leave (actions/on-frame-leave frame-hover) @@ -184,7 +187,7 @@ disabled-guides? (or drawing-tool transform)] - (hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?) + (hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport? workspace-read-only?) (hooks/setup-viewport-size viewport-ref) (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing?) (hooks/setup-keyboard alt? mod? space?) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index bbf0c740b3..4316ee8559 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -34,11 +34,12 @@ (defn on-mouse-down [{:keys [id blocked hidden type]} selected edition drawing-tool text-editing? - node-editing? drawing-path? create-comment? space? viewport-ref zoom panning] + node-editing? drawing-path? create-comment? space? viewport-ref zoom panning + workspace-read-only?] (mf/use-callback (mf/deps id blocked hidden type selected edition drawing-tool text-editing? node-editing? drawing-path? create-comment? @space? viewport-ref zoom - panning) + panning workspace-read-only?) (fn [bevent] (when (or (dom/class? (dom/get-target bevent) "viewport-controls") (dom/class? (dom/get-target bevent) "viewport-selrect")) @@ -81,7 +82,8 @@ (cond node-editing? ;; Handle path node area selection - (st/emit! (dwdp/handle-area-selection shift?)) + (when-not workspace-read-only? + (st/emit! (dwdp/handle-area-selection shift?))) (and @space? mod?) (let [raw-pt (dom/get-client-position event) @@ -93,18 +95,20 @@ (st/emit! (dw/start-panning)) drawing-tool - (st/emit! (dd/start-drawing drawing-tool)) + (when-not workspace-read-only? + (st/emit! (dd/start-drawing drawing-tool))) (or (not id) mod?) (st/emit! (dw/handle-area-selection shift? mod?)) (not drawing-tool) - (st/emit! (dw/start-move-selected id shift?))))))))))) + (when-not workspace-read-only? + (st/emit! (dw/start-move-selected id shift?)))))))))))) (defn on-move-selected - [hover hover-ids selected space?] + [hover hover-ids selected space? workspace-read-only?] (mf/use-callback - (mf/deps @hover @hover-ids selected @space?) + (mf/deps @hover @hover-ids selected @space? workspace-read-only?) (fn [bevent] (let [event (.-nativeEvent bevent) shift? (kbd/shift? event) @@ -117,7 +121,8 @@ (not @space?)) (dom/prevent-default bevent) (dom/stop-propagation bevent) - (st/emit! (dw/start-move-selected))))))) + (when-not workspace-read-only? + (st/emit! (dw/start-move-selected)))))))) (defn on-frame-select [selected] @@ -167,10 +172,10 @@ (st/emit! (dw/select-shape (:id @hover) shift?)))))))) (defn on-double-click - [hover hover-ids drawing-path? objects edition] + [hover hover-ids drawing-path? objects edition workspace-read-only?] (mf/use-callback - (mf/deps @hover @hover-ids drawing-path? edition) + (mf/deps @hover @hover-ids drawing-path? edition workspace-read-only?) (fn [event] (dom/stop-propagation event) (let [ctrl? (kbd/ctrl? event) @@ -189,7 +194,7 @@ (fn [] (when (and (not drawing-path?) shape) (cond - (and editable? (not= id edition)) + (and editable? (not= id edition) (not workspace-read-only?)) (st/emit! (dw/select-shape id) (dw/start-editing-selected)) @@ -202,33 +207,37 @@ (st/emit! (dw/select-shape (:id selected))))))))))))) (defn on-context-menu - [hover hover-ids] + [hover hover-ids workspace-read-only?] (mf/use-callback - (mf/deps @hover @hover-ids) + (mf/deps @hover @hover-ids workspace-read-only?) (fn [event] - (when (or (dom/class? (dom/get-target event) "viewport-controls") - (dom/class? (dom/get-target event) "viewport-selrect")) + (if workspace-read-only? (dom/prevent-default event) + (when (or (dom/class? (dom/get-target event) "viewport-controls") + (dom/class? (dom/get-target event) "viewport-selrect") + (workspace-read-only?)) + (dom/prevent-default event) - (let [position (dom/get-client-position event)] + (let [position (dom/get-client-position event)] ;; Delayed callback because we need to wait to the previous context menu to be closed - (timers/schedule - #(st/emit! - (if (some? @hover) - (dw/show-shape-context-menu {:position position - :shape @hover - :hover-ids @hover-ids}) - (dw/show-context-menu {:position position}))))))))) + (timers/schedule + #(st/emit! + (if (some? @hover) + (dw/show-shape-context-menu {:position position + :shape @hover + :hover-ids @hover-ids}) + (dw/show-context-menu {:position position})))))))))) (defn on-menu-selected - [hover hover-ids selected] + [hover hover-ids selected workspace-read-only?] (mf/use-callback - (mf/deps @hover @hover-ids selected) + (mf/deps @hover @hover-ids selected workspace-read-only?) (fn [event] (dom/prevent-default event) (dom/stop-propagation event) - (let [position (dom/get-client-position event)] - (st/emit! (dw/show-shape-context-menu {:position position :hover-ids @hover-ids})))))) + (when-not workspace-read-only? + (let [position (dom/get-client-position event)] + (st/emit! (dw/show-shape-context-menu {:position position :hover-ids @hover-ids}))))))) (defn on-mouse-up [disable-paste] @@ -484,12 +493,13 @@ :blobs (seq files)}] (st/emit! (dwm/upload-media-workspace params)))))))) -(defn on-paste [disable-paste in-viewport?] +(defn on-paste [disable-paste in-viewport? workspace-read-only?] (mf/use-callback + (mf/deps workspace-read-only?) (fn [event] ;; We disable the paste just after mouse-up of a middle button so when panning won't ;; paste the content into the workspace (let [tag-name (-> event dom/get-target dom/get-tag-name)] - (when (and (not (#{"INPUT" "TEXTAREA"} tag-name)) (not @disable-paste)) + (when (and (not (#{"INPUT" "TEXTAREA"} tag-name)) (not @disable-paste) (not workspace-read-only?)) (st/emit! (dw/paste-from-event event @in-viewport?))))))) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 2ea2310dab..ffd21250e8 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -32,14 +32,14 @@ [rumext.v2 :as mf]) (:import goog.events.EventType)) -(defn setup-dom-events [viewport-ref zoom disable-paste in-viewport?] +(defn setup-dom-events [viewport-ref zoom disable-paste in-viewport? workspace-read-only?] (let [on-key-down (actions/on-key-down) on-key-up (actions/on-key-up) on-mouse-move (actions/on-mouse-move viewport-ref zoom) on-mouse-wheel (actions/on-mouse-wheel viewport-ref zoom) - on-paste (actions/on-paste disable-paste in-viewport?)] + on-paste (actions/on-paste disable-paste in-viewport? workspace-read-only?)] (mf/use-layout-effect - (mf/deps on-key-down on-key-up on-mouse-move on-mouse-wheel on-paste) + (mf/deps on-key-down on-key-up on-mouse-move on-mouse-wheel on-paste workspace-read-only?) (fn [] (let [node (mf/ref-val viewport-ref) keys [(events/listen js/document EventType.KEYDOWN on-key-down) diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 0a1cb7821b..f4f829fc7c 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -15,6 +15,7 @@ [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.context :as ctx] [app.main.ui.cursors :as cur] [app.main.ui.workspace.shapes.path.editor :refer [path-editor]] [app.util.dom :as dom] @@ -286,13 +287,14 @@ (mf/defc controls-handlers {::mf/wrap-props false} [props] - (let [shape (obj/get props "shape") - zoom (obj/get props "zoom") - color (obj/get props "color") - on-resize (obj/get props "on-resize") - on-rotate (obj/get props "on-rotate") - disable-handlers (obj/get props "disable-handlers") - current-transform (mf/deref refs/current-transform) + (let [shape (obj/get props "shape") + zoom (obj/get props "zoom") + color (obj/get props "color") + on-resize (obj/get props "on-resize") + on-rotate (obj/get props "on-rotate") + disable-handlers (obj/get props "disable-handlers") + current-transform (mf/deref refs/current-transform) + workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) selrect (:selrect shape) transform (gsh/transform-matrix shape {:no-flip true}) @@ -302,7 +304,8 @@ (gpt/angle) (mod 360))] - (when (not (#{:move :rotate} current-transform)) + (when (and (not (#{:move :rotate} current-transform)) + (not workspace-read-only?)) [:g.controls {:pointer-events (if disable-handlers "none" "visible")} ;; Handlers (for [{:keys [type position props]} (handlers-for-selection selrect shape zoom)] diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index d78b534633..8e6fae8f13 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -16,6 +16,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.streams :as ms] + [app.main.ui.context :as ctx] [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] [app.main.ui.workspace.viewport.path-actions :refer [path-actions]] @@ -87,15 +88,17 @@ (mf/defc frame-title {::mf/wrap [mf/memo]} [{:keys [frame selected? zoom show-artboard-names? on-frame-enter on-frame-leave on-frame-select]}] - (let [on-mouse-down + (let [workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) + on-mouse-down (mf/use-callback - (mf/deps (:id frame) on-frame-select) + (mf/deps (:id frame) on-frame-select workspace-read-only?) (fn [bevent] (let [event (.-nativeEvent bevent)] (when (= 1 (.-which event)) (dom/prevent-default event) (dom/stop-propagation event) - (on-frame-select event (:id frame)))))) + (when-not workspace-read-only? + (on-frame-select event (:id frame))))))) on-double-click (mf/use-callback @@ -105,13 +108,14 @@ on-context-menu (mf/use-callback - (mf/deps frame) + (mf/deps frame workspace-read-only?) (fn [bevent] (let [event (.-nativeEvent bevent) position (dom/get-client-position event)] (dom/prevent-default event) (dom/stop-propagation event) - (st/emit! (dw/show-shape-context-menu {:position position :shape frame}))))) + (when-not workspace-read-only? + (st/emit! (dw/show-shape-context-menu {:position position :shape frame})))))) on-pointer-enter (mf/use-callback @@ -156,15 +160,15 @@ {::mf/wrap-props false ::mf/wrap [mf/memo]} [props] - (let [objects (unchecked-get props "objects") - zoom (unchecked-get props "zoom") - selected (or (unchecked-get props "selected") #{}) + (let [objects (unchecked-get props "objects") + zoom (unchecked-get props "zoom") + selected (or (unchecked-get props "selected") #{}) show-artboard-names? (unchecked-get props "show-artboard-names?") - on-frame-enter (unchecked-get props "on-frame-enter") - on-frame-leave (unchecked-get props "on-frame-leave") - on-frame-select (unchecked-get props "on-frame-select") - frames (ctt/get-frames objects) - focus (unchecked-get props "focus")] + on-frame-enter (unchecked-get props "on-frame-enter") + on-frame-leave (unchecked-get props "on-frame-leave") + on-frame-select (unchecked-get props "on-frame-select") + frames (ctt/get-frames objects) + focus (unchecked-get props "focus")] [:g.frame-titles (for [frame frames] diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index f64e98f4b0..0edef0ee28 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -316,3 +316,8 @@ objects (get-in @st/state [:workspace-data :pages-index page-id :objects])] (.log js/console (modif->js (:workspace-modifiers @st/state) objects))) nil) + +(defn ^:export set-workspace-read-only + [read-only?] + (st/emit! (dw/set-workspace-read-only read-only?))) + From 197eff93e893b5fcaac026bec2cde3c38f5b0196 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 09:59:26 +0100 Subject: [PATCH 272/682] :paperclip: Fix nodejs compatibility issue on uuid_impl --- common/src/app/common/uuid_impl.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/common/src/app/common/uuid_impl.js b/common/src/app/common/uuid_impl.js index 5c184f808c..fd257f6ab9 100644 --- a/common/src/app/common/uuid_impl.js +++ b/common/src/app/common/uuid_impl.js @@ -24,8 +24,10 @@ goog.scope(function() { }; } else if (typeof require === "function") { const crypto = require("crypto"); + const randomBytes = crypto["randomBytes"]; + return (buf) => { - const bytes = crypto.randomBytes(buf.length); + const bytes = randomBytes(buf.length); buf.set(bytes) return buf; }; From f6305db2a8f70103f05e9ab5b31b3ac189bfc938 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 10:02:23 +0100 Subject: [PATCH 273/682] :sparkles: Reorganize a bit the common.data ns --- common/src/app/common/data.cljc | 186 ++++++++++++++++---------------- 1 file changed, 93 insertions(+), 93 deletions(-) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index d2461675ef..9fa9df4f5d 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -57,9 +57,13 @@ ([a & more] (into (queue) (cons a more)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Data Structures Manipulation +;; Data Structures Access & Manipulation ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn not-empty? + [coll] + (boolean (seq coll))) + (defn editable-collection? [m] #?(:clj (instance? clojure.lang.IEditableCollection m) @@ -145,6 +149,16 @@ (rest items) (conj! res [idx (first items)])))))) +(defn group-by + ([kf coll] (group-by kf identity [] coll)) + ([kf vf coll] (group-by kf vf [] coll)) + ([kf vf iv coll] + (let [conj (fnil conj iv)] + (reduce (fn [result item] + (update result (kf item) conj (vf item))) + {} + coll)))) + (defn seek ([pred coll] (seek pred coll nil)) @@ -243,12 +257,12 @@ (defn filterm "Filter values of a map that satisfy a predicate" [pred coll] - (into {} (filter pred coll))) + (into {} (filter pred) coll)) (defn removem "Remove values of a map that satisfy a predicate" [pred coll] - (into {} (remove pred coll))) + (into {} (remove pred) coll)) (defn map-perm "Maps a function to each pair of values that can be combined inside the @@ -373,6 +387,80 @@ (do (vswap! seen conj input*) (rf result input))))))))) +(defn with-next + "Given a collection will return a new collection where each element + is paired with the next item in the collection + (with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]]" + [coll] + (map vector + coll + (c/concat (rest coll) [nil]))) + +(defn with-prev + "Given a collection will return a new collection where each element + is paired with the previous item in the collection + (with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]]" + [coll] + (map vector + coll + (c/cons nil coll))) + +(defn with-prev-next + "Given a collection will return a new collection where every item is paired + with the previous and the next item of a collection + (with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]]" + [coll] + (map vector + coll + (c/cons nil coll) + (c/concat (rest coll) [nil]))) + +(defn deep-mapm + "Applies a map function to an associative map and recurses over its children + when it's a vector or a map" + [mfn m] + (let [do-map + (fn [entry] + (let [[k v] (mfn entry)] + (cond + (or (vector? v) (map? v)) + [k (deep-mapm mfn v)] + + :else + (mfn [k v]))))] + (cond + (map? m) + (into {} (map do-map) m) + + (vector? m) + (into [] (map (partial deep-mapm mfn)) m) + + :else + m))) + +(defn iteration + "Creates a totally lazy seqable via repeated calls to step, a + function of some (continuation token) 'k'. The first call to step + will be passed initk, returning 'ret'. If (somef ret) is true, (vf + ret) will be included in the iteration, else iteration will + terminate and vf/kf will not be called. If (kf ret) is non-nil it + will be passed to the next step call, else iteration will terminate. + + This can be used e.g. to consume APIs that return paginated or batched data. + + step - (possibly impure) fn of 'k' -> 'ret' + :somef - fn of 'ret' -> logical true/false, default 'some?' + :vf - fn of 'ret' -> 'v', a value produced by the iteration, default 'identity' + :kf - fn of 'ret' -> 'next-k' or nil (signaling 'do not continue'), default 'identity' + :initk - the first value passed to step, default 'nil' + + It is presumed that step with non-initk is + unreproducible/non-idempotent. If step with initk is unreproducible + it is on the consumer to not consume twice." + [& args] + (->> (apply c/iteration args) + (concat-all))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Parsing / Conversion ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -439,8 +527,9 @@ (or val default)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Data Parsing / Conversion +;; Utilities ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn nilf "Returns a new function that if you pass nil as any argument will return nil" @@ -494,34 +583,6 @@ (or default-value (str maybe-keyword))))) -(defn with-next - "Given a collection will return a new collection where each element - is paired with the next item in the collection - (with-next (range 5)) => [[0 1] [1 2] [2 3] [3 4] [4 nil]]" - [coll] - (map vector - coll - (c/concat (rest coll) [nil]))) - -(defn with-prev - "Given a collection will return a new collection where each element - is paired with the previous item in the collection - (with-prev (range 5)) => [[0 nil] [1 0] [2 1] [3 2] [4 3]]" - [coll] - (map vector - coll - (c/concat [nil] coll))) - -(defn with-prev-next - "Given a collection will return a new collection where every item is paired - with the previous and the next item of a collection - (with-prev-next (range 5)) => [[0 nil 1] [1 0 2] [2 1 3] [3 2 4] [4 3 nil]]" - [coll] - (map vector - coll - (c/concat [nil] coll) - (c/concat (rest coll) [nil]))) - (defn prefix-keyword "Given a keyword and a prefix will return a new keyword with the prefix attached (prefix-keyword \"prefix\" :test) => :prefix-test" @@ -612,33 +673,6 @@ (recur (inc counter)) candidate)))))))) -(defn deep-mapm - "Applies a map function to an associative map and recurses over its children - when it's a vector or a map" - [mfn m] - (let [do-map - (fn [entry] - (let [[k v] (mfn entry)] - (cond - (or (vector? v) (map? v)) - [k (deep-mapm mfn v)] - - :else - (mfn [k v]))))] - (cond - (map? m) - (into {} (map do-map) m) - - (vector? m) - (into [] (map (partial deep-mapm mfn)) m) - - :else - m))) - -(defn not-empty? - [coll] - (boolean (seq coll))) - (defn kebab-keys [m] (->> m (deep-mapm @@ -647,40 +681,6 @@ [(keyword (str/kebab (name k))) v] [k v]))))) - -(defn group-by - ([kf coll] (group-by kf identity [] coll)) - ([kf vf coll] (group-by kf vf [] coll)) - ([kf vf iv coll] - (let [conj (fnil conj iv)] - (reduce (fn [result item] - (update result (kf item) conj (vf item))) - {} - coll)))) - -(defn iteration - "Creates a totally lazy seqable via repeated calls to step, a - function of some (continuation token) 'k'. The first call to step - will be passed initk, returning 'ret'. If (somef ret) is true, (vf - ret) will be included in the iteration, else iteration will - terminate and vf/kf will not be called. If (kf ret) is non-nil it - will be passed to the next step call, else iteration will terminate. - - This can be used e.g. to consume APIs that return paginated or batched data. - - step - (possibly impure) fn of 'k' -> 'ret' - :somef - fn of 'ret' -> logical true/false, default 'some?' - :vf - fn of 'ret' -> 'v', a value produced by the iteration, default 'identity' - :kf - fn of 'ret' -> 'next-k' or nil (signaling 'do not continue'), default 'identity' - :initk - the first value passed to step, default 'nil' - - It is presumed that step with non-initk is - unreproducible/non-idempotent. If step with initk is unreproducible - it is on the consumer to not consume twice." - [& args] - (->> (apply c/iteration args) - (concat-all))) - (defn toggle-selection ([set value] (toggle-selection set value false)) From 89a19dec5b97047bd1e4b907179d524cdd76c107 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 10:24:31 +0100 Subject: [PATCH 274/682] :tada: Add cljs optimized get-prop helper macro --- common/src/app/common/data.cljc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 9fa9df4f5d..ff7d98f281 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -461,6 +461,15 @@ (->> (apply c/iteration args) (concat-all))) +(defmacro get-prop + "A macro based, optimized variant of `get` that access the property + directly on CLJS, on CLJ works as get." + [obj prop] + (if (:ns &env) + (list (symbol ".") (with-meta obj {:tag 'js}) (symbol (str "-" (c/name prop)))) + `(c/get ~obj ~prop))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Parsing / Conversion ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; From 380cba3a72025e37edd57c0d61e983c4a615c6b8 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 10:25:36 +0100 Subject: [PATCH 275/682] :paperclip: Add bench namespace to fronend/dev --- frontend/dev/bench.cljs | 48 ++++++++++++++++++++++++++++++++++++++++ frontend/shadow-cljs.edn | 4 ++-- 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 frontend/dev/bench.cljs diff --git a/frontend/dev/bench.cljs b/frontend/dev/bench.cljs new file mode 100644 index 0000000000..cf2a406328 --- /dev/null +++ b/frontend/dev/bench.cljs @@ -0,0 +1,48 @@ +(ns bench + (:require + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes.rect :as gsr] + [app.common.perf :as perf] + [clojure.spec.alpha :as s] + [clojure.test.check.generators :as gen])) + +(def points + (gen/sample (s/gen ::gpt/point) 20)) + +(defn points->rect + [points] + (when-let [points (seq points)] + (loop [minx ##Inf + miny ##Inf + maxx ##-Inf + maxy ##-Inf + pts points] + (if-let [pt ^boolean (first pts)] + (let [x (d/get-prop pt :x) + y (d/get-prop pt :y)] + (recur (min minx x) + (min miny y) + (max maxx x) + (max maxy y) + (rest pts))) + (when (d/num? minx miny maxx maxy) + (gsr/make-rect minx miny (- maxx minx) (- maxy miny))))))) + +(defn bench-points + [] + (perf/benchmark + :f #(gsr/points->rect points) + :name "base") + (perf/benchmark + :f #(points->rect points) + :name "optimized")) + + +(defn main + [& [name]] + (case name + "points" (bench-points) + (println "available: points"))) + + diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index dd5285eac1..bb641ad0ce 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -69,10 +69,10 @@ {:target :node-script :output-to "target/bench.js" :output-dir "target/bench/" - :main cljs.user/main + :main bench/main :compiler-options - {:output-feature-set :es8 + {:output-feature-set :es2020 :output-wrapper false :warnings {:fn-deprecated false}} From c28534555baa023647d460ca01e662ca575e117f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Nov 2022 11:08:15 +0100 Subject: [PATCH 276/682] :paperclip: Add minor microptimizations and tests to points->rect --- common/src/app/common/data.cljc | 34 ++++++++++++------- common/src/app/common/geom/shapes/rect.cljc | 23 +++++++++---- common/src/app/common/math.cljc | 5 ++- common/test/common_tests/data_test.cljc | 14 ++++++++ .../test/common_tests/geom_shapes_test.cljc | 23 +++++++++++-- 5 files changed, 76 insertions(+), 23 deletions(-) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index ff7d98f281..03fe97ca38 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -555,22 +555,32 @@ (defn num? "Checks if a value `val` is a number but not an Infinite or NaN" - ([val] - (and (number? val) - (mth/finite? val) - (not (mth/nan? val)))) - - ([val & vals] - (and (num? val) - (->> vals (every? num?))))) + ([a] + (mth/finite? a)) + ([a b] + (and (mth/finite? a) + (mth/finite? b))) + ([a b c] + (and (mth/finite? a) + (mth/finite? b) + (mth/finite? c))) + ([a b c d] + (and (mth/finite? a) + (mth/finite? b) + (mth/finite? c) + (mth/finite? d))) + ([a b c d & others] + (and (mth/finite? a) + (mth/finite? b) + (mth/finite? c) + (mth/finite? d) + (every? mth/finite? others)))) (defn check-num "Function that checks if a number is nil or nan. Will return 0 when not valid and the number otherwise." - ([v] - (check-num v 0)) - ([v default] - (if (num? v) v default))) + ([v] (mth/finite v 0)) + ([v default] (mth/finite v default))) (defn any-key? [element & rest] (some #(contains? element %) rest)) diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 672928a61c..057687e1e1 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -83,13 +83,22 @@ (defn points->rect [points] - (when (d/not-empty? points) - (let [minx (transduce (keep :x) min ##Inf points) - miny (transduce (keep :y) min ##Inf points) - maxx (transduce (keep :x) max ##-Inf points) - maxy (transduce (keep :y) max ##-Inf points)] - (when (d/num? minx miny maxx maxy) - (make-rect minx miny (- maxx minx) (- maxy miny)))))) + (when-let [points (seq points)] + (loop [minx ##Inf + miny ##Inf + maxx ##-Inf + maxy ##-Inf + pts points] + (if-let [pt (first pts)] + (let [x (d/get-prop pt :x) + y (d/get-prop pt :y)] + (recur (min minx x) + (min miny y) + (max maxx x) + (max maxy y) + (rest pts))) + (when (d/num? minx miny maxx maxy) + (make-rect minx miny (- maxx minx) (- maxy miny))))))) (defn bounds->rect [[{ax :x ay :y} {bx :x by :y} {cx :x cy :y} {dx :x dy :y}]] diff --git a/common/src/app/common/math.cljc b/common/src/app/common/math.cljc index e5483b1189..eee928bd95 100644 --- a/common/src/app/common/math.cljc +++ b/common/src/app/common/math.cljc @@ -19,10 +19,13 @@ #?(:cljs (js/isNaN v) :clj (Double/isNaN v))) +;; NOTE: on cljs we don't need to check for `number?` so we explicitly +;; ommit it for performance reasons. + (defn finite? [v] #?(:cljs (and (not (nil? v)) (js/isFinite v)) - :clj (and (not (nil? v)) (Double/isFinite v)))) + :clj (and (not (nil? v)) (number? v) (Double/isFinite v)))) (defn finite [v default] diff --git a/common/test/common_tests/data_test.cljc b/common/test/common_tests/data_test.cljc index 5fd35b9200..0c0521003c 100644 --- a/common/test/common_tests/data_test.cljc +++ b/common/test/common_tests/data_test.cljc @@ -51,3 +51,17 @@ (t/is (= [1 10 100 2 20 200 3 30 300] (d/join [1 2 3] [1 10 100] *)))) +(t/deftest num-predicate + (t/is (not (d/num? ##NaN))) + (t/is (not (d/num? nil))) + (t/is (d/num? 1)) + (t/is (d/num? -0.3)) + (t/is (not (d/num? {})))) + +(t/deftest check-num-helper + (t/is (= 1 (d/check-num 1 0))) + (t/is (= 0 (d/check-num ##NaN 0))) + (t/is (= 0 (d/check-num {} 0))) + (t/is (= 0 (d/check-num [] 0))) + (t/is (= 0 (d/check-num :foo 0))) + (t/is (= 0 (d/check-num nil 0)))) diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc index 864bf6b8c8..a9d1e2df74 100644 --- a/common/test/common_tests/geom_shapes_test.cljc +++ b/common/test/common_tests/geom_shapes_test.cljc @@ -21,7 +21,8 @@ {:command :curve-to :params {:x 40 :y 40 :c1x 35 :c1y 35 :c2x 45 :c2y 45}} {:command :close-path}]) -(defn add-path-data [shape] +(defn add-path-data + [shape] (let [content (:content shape default-path) selrect (gsh/content->selrect content) points (gsh/rect->points selrect)] @@ -30,7 +31,8 @@ :selrect selrect :points points))) -(defn add-rect-data [shape] +(defn add-rect-data + [shape] (let [shape (-> shape (assoc :width 20 :height 20)) selrect (gsh/rect->selrect shape) @@ -49,7 +51,7 @@ (not= type :path) (add-rect-data))))) -(t/deftest transform-shape-tests +(t/deftest transform-shapes (t/testing "Shape without modifiers should stay the same" (t/are [type] (let [shape-before (create-test-shape type) @@ -181,3 +183,18 @@ :path {:x 0.0 :y 0.0 :x1 0.0 :y1 0.0 :x2 ##Inf :y2 ##Inf :width ##Inf :height ##Inf} :rect nil :path nil))) + +(t/deftest points-to-selrect + (let [points [(gpt/point 0.5 0.5) + (gpt/point -1 -2) + (gpt/point 20 65.2) + (gpt/point 12 -10)] + result (gsh/points->rect points) + expect {:x -1, :y -10, :width 21, :height 75.2}] + + (t/is (= (:x expect) (:x result))) + (t/is (= (:y expect) (:y result))) + (t/is (= (:width expect) (:width result))) + (t/is (= (:height expect) (:height result))) + )) + From fc4e755f2b19b136efd9a5c873c6438600d0774b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 20 Nov 2022 20:05:15 +0100 Subject: [PATCH 277/682] :zap: Optimize point functions --- common/src/app/common/data.cljc | 8 - common/src/app/common/data/macros.cljc | 12 + common/src/app/common/geom/point.cljc | 363 +++++++++++------- .../common/geom/shapes/pixel_precision.cljc | 4 +- common/src/app/common/geom/shapes/rect.cljc | 5 +- common/test/common_tests/geom_point_test.cljc | 295 ++++++++++++++ frontend/src/app/main/snap.cljs | 4 +- .../frontend_tests/test_helpers_shapes.cljs | 53 +++ 8 files changed, 589 insertions(+), 155 deletions(-) create mode 100644 common/test/common_tests/geom_point_test.cljc create mode 100644 frontend/test/frontend_tests/test_helpers_shapes.cljs diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 03fe97ca38..ae52003c18 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -461,14 +461,6 @@ (->> (apply c/iteration args) (concat-all))) -(defmacro get-prop - "A macro based, optimized variant of `get` that access the property - directly on CLJS, on CLJ works as get." - [obj prop] - (if (:ns &env) - (list (symbol ".") (with-meta obj {:tag 'js}) (symbol (str "-" (c/name prop)))) - `(c/get ~obj ~prop))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Data Parsing / Conversion diff --git a/common/src/app/common/data/macros.cljc b/common/src/app/common/data/macros.cljc index 0d204e7efa..76a168459d 100644 --- a/common/src/app/common/data/macros.cljc +++ b/common/src/app/common/data/macros.cljc @@ -107,3 +107,15 @@ (d/close! ~(first bindings)))))) `(do ~@body) (reverse (partition 2 bindings)))) + +(defmacro get-prop + "A macro based, optimized variant of `get` that access the property + directly on CLJS, on CLJ works as get." + [obj prop] + ;; `(do + ;; (when-not (record? ~obj) + ;; (js/console.trace (pr-str ~obj))) + ;; (c/get ~obj ~prop))) + (if (:ns &env) + (list (symbol ".") (with-meta obj {:tag 'js}) (symbol (str "-" (c/name prop)))) + `(c/get ~obj ~prop))) diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 5bc1d4e5e5..5421af3cef 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -11,6 +11,8 @@ :clj [clojure.pprint :as pp]) #?(:cljs [cljs.core :as c] :clj [clojure.core :as c]) + [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.math :as mth] [app.common.spec :as us] [clojure.spec.alpha :as s] @@ -20,18 +22,20 @@ (defrecord Point [x y]) -(defn s [{:keys [x y]}] (str "(" x "," y ")")) +(defn s + [pt] + (dm/str "(" (dm/get-prop pt :x) "," (dm/get-prop pt :y) ")")) (defn point? "Return true if `v` is Point instance." [v] - (or (instance? Point v) - (and (map? v) (contains? v :x) (contains? v :y)))) + (instance? Point v)) (s/def ::x ::us/safe-number) (s/def ::y ::us/safe-number) -(s/def ::point-attrs (s/keys :req-un [::x ::y])) +(s/def ::point-attrs + (s/keys :req-un [::x ::y])) (s/def ::point (s/with-gen (s/and ::point-attrs point?) @@ -40,10 +44,8 @@ (defn point-like? [{:keys [x y] :as v}] (and (map? v) - (not (nil? x)) - (not (nil? y)) - (number? x) - (number? y))) + (d/num? x) + (d/num? y))) (defn point "Create a Point instance." @@ -51,13 +53,13 @@ ([v] (cond (point? v) - (Point. (:x v) (:y v)) + v (number? v) (point v v) (point-like? v) - (point (:x v) (:y v)) + (map->Point v) :else (throw (ex-info "Invalid arguments" {:v v})))) @@ -66,128 +68,178 @@ (defn close? [p1 p2] - (and (mth/close? (:x p1) (:x p2)) - (mth/close? (:y p1) (:y p2)))) + (and (mth/close? (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (mth/close? (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) -(defn angle->point [{:keys [x y]} angle distance] +(defn angle->point + [pt angle distance] (point - (+ x (* distance (mth/cos angle))) - (- y (* distance (mth/sin angle))))) + (+ (dm/get-prop pt :x) (* distance (mth/cos angle))) + (- (dm/get-prop pt :y) (* distance (mth/sin angle))))) (defn add "Returns the addition of the supplied value to both coordinates of the point as a new point." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (Point. (+ x ox) (+ y oy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be pointer instance") + (Point. (+ (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (+ (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) (defn subtract "Returns the subtraction of the supplied value to both coordinates of the point as a new point." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (Point. (- x ox) (- y oy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be pointer instance") + (Point. (- (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (- (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) (defn multiply "Returns the subtraction of the supplied value to both coordinates of the point as a new point." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (Point. (* x ox) (* y oy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be pointer instance") + (Point. (* (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (* (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) (defn divide - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (Point. (/ x ox) (/ y oy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be pointer instance") + (Point. (/ (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (/ (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) (defn min - ([] (min nil nil)) - ([p1] (min p1 nil)) - ([{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}] + ([] nil) + ([p1] p1) + ([p1 p2] (cond (nil? p1) p2 (nil? p2) p1 - :else (Point. (c/min x1 x2) (c/min y1 y2))))) - + :else (Point. (c/min (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (c/min (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))))) (defn max - ([] (max nil nil)) - ([p1] (max p1 nil)) - ([{x1 :x y1 :y :as p1} {x2 :x y2 :y :as p2}] + ([] nil) + ([p1] p1) + ([p1 p2] (cond (nil? p1) p2 (nil? p2) p1 - :else (Point. (c/max x1 x2) (c/max y1 y2))))) - + :else (Point. (c/max (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (c/max (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))))) (defn inverse - [{:keys [x y] :as p}] - (assert (point? p)) - (Point. (/ 1 x) (/ 1 y))) + [pt] + (assert (point? pt) "point instance expected") + (Point. (/ 1.0 (dm/get-prop pt :x)) + (/ 1.0 (dm/get-prop pt :y)))) (defn negate - [{x :x y :y :as p}] - (assert (point? p)) - (Point. (- x) (- y))) + [pt] + (assert (point? pt) "point instance expected") + (Point. (- (dm/get-prop pt :x)) + (- (dm/get-prop pt :y)))) (defn distance "Calculate the distance between two points." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (let [dx (- x ox) - dy (- y oy)] + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be point instances") + (let [dx (- (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + dy (- (dm/get-prop p1 :y) + (dm/get-prop p2 :y))] (mth/sqrt (+ (mth/pow dx 2) (mth/pow dy 2))))) (defn distance-vector "Calculate the distance, separated x and y." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - (let [dx (mth/abs (- x ox)) - dy (mth/abs (- y oy))] - (Point. dx dy))) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be point instances") + (let [dx (- (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + dy (- (dm/get-prop p1 :y) + (dm/get-prop p2 :y))] + (Point. (mth/abs dx) + (mth/abs dy)))) (defn length - [{x :x y :y :as p}] - (assert (point? p)) - (mth/sqrt (+ (mth/pow x 2) - (mth/pow y 2)))) + [pt] + (assert (point? pt) "point instance expected") + (let [x (dm/get-prop pt :x) + y (dm/get-prop pt :y)] + (mth/sqrt (+ (mth/pow x 2) + (mth/pow y 2))))) (defn angle "Returns the smaller angle between two vectors. If the second vector is not provided, the angle will be measured from x-axis." - ([{x :x y :y :as p}] - (-> (mth/atan2 y x) - (mth/degrees))) - ([p center] - (angle (subtract p center)))) + ([pt] + (assert (point? pt) "point instance expected") + (let [x (dm/get-prop pt :x) + y (dm/get-prop pt :y)] + (-> (mth/atan2 y x) + (mth/degrees)))) + ([pt center] + (assert (point? pt) "point instance expected") + (assert (point? center) "point instance expected") + (let [x (- (dm/get-prop pt :x) + (dm/get-prop center :x)) + y (- (dm/get-prop pt :y) + (dm/get-prop center :y))] + (-> (mth/atan2 y x) + (mth/degrees))))) (defn angle-with-other "Consider point as vector and calculate the angle between two vectors." - [{x :x y :y :as p} {ox :x oy :y :as other}] - (assert (point? p)) - (assert (point? other)) - - (let [length-p (length p) - length-other (length other)] - (if (or (mth/almost-zero? length-p) - (mth/almost-zero? length-other)) + [p1 p2] + (assert (and (point? p1) + (point? p2)) + "arguments should be point instances") + (let [length-p1 (length p1) + length-p2 (length p2)] + (if (or (mth/almost-zero? length-p1) + (mth/almost-zero? length-p2)) 0 - (let [a (/ (+ (* x ox) - (* y oy)) - (* length-p length-other)) + (let [a (/ (+ (* (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (* (dm/get-prop p1 :y) + (dm/get-prop p2 :y))) + (* length-p1 length-p2)) a (mth/acos (if (< a -1) -1 (if (> a 1) 1 a))) d (mth/degrees a)] (if (mth/nan? d) 0 d))))) -(defn angle-sign [v1 v2] - (if (> (* (:y v1) (:x v2)) (* (:x v1) (:y v2))) -1 1)) +(defn angle-sign + [p1 p2] + (if (> (* (dm/get-prop p1 :y) (dm/get-prop p2 :x)) + (* (dm/get-prop p1 :x) (dm/get-prop p2 :y))) + -1 + 1)) (defn signed-angle-with-other [v1 v2] @@ -196,61 +248,79 @@ (defn update-angle "Update the angle of the point." [p angle] - (assert (point? p)) - (assert (number? angle)) - (let [len (length p) + (assert (number? angle) "expected number") + (let [len (length p) angle (mth/radians angle)] (Point. (* (mth/cos angle) len) (* (mth/sin angle) len)))) (defn quadrant "Return the quadrant of the angle of the point." - [{:keys [x y] :as p}] - (assert (point? p)) - (if (>= x 0) - (if (>= y 0) 1 4) - (if (>= y 0) 2 3))) + [p] + (assert (point? p) "expected point instance") + (let [x (dm/get-prop p :x) + y (dm/get-prop p :y)] + (if (>= x 0) + (if (>= y 0) 1 4) + (if (>= y 0) 2 3)))) (defn round "Round the coordinates of the point to a precision" ([point] (round point 0)) - ([{:keys [x y] :as p} decimals] - (assert (point? p)) - (assert (number? decimals)) - (Point. (mth/precision x decimals) - (mth/precision y decimals)))) + ([pt decimals] + (assert (point? pt) "expected point instance") + (assert (number? decimals) "expected number instance") + (Point. (mth/precision (dm/get-prop pt :x) decimals) + (mth/precision (dm/get-prop pt :y) decimals)))) (defn half-round "Round the coordinates to the closest half-point" - [{:keys [x y] :as p}] - (assert (point? p)) - (Point. (mth/half-round x) - (mth/half-round y))) + [pt] + (assert (point? pt) "expected point instance") + (Point. (mth/half-round (dm/get-prop pt :x)) + (mth/half-round (dm/get-prop pt :y)))) (defn transform "Transform a point applying a matrix transformation." - [{:keys [x y] :as p} {:keys [a b c d e f]}] - (assert (point? p)) - (Point. (+ (* x a) (* y c) e) - (+ (* x b) (* y d) f))) + [p m] + (when (point? p) + (if (nil? m) + p + (let [x (dm/get-prop p :x) + y (dm/get-prop p :y) + a (dm/get-prop m :a) + b (dm/get-prop m :b) + c (dm/get-prop m :c) + d (dm/get-prop m :d) + e (dm/get-prop m :e) + f (dm/get-prop m :f)] + (Point. (+ (* x a) (* y c) e) + (+ (* x b) (* y d) f)))))) + ;; Vector functions (defn to-vec [p1 p2] (subtract p2 p1)) -(defn scale [v scalar] - (-> v - (update :x * scalar) - (update :y * scalar))) +(defn scale + [p scalar] + (Point. (* (dm/get-prop p :x) scalar) + (* (dm/get-prop p :y) scalar))) -(defn dot [{x1 :x y1 :y} {x2 :x y2 :y}] - (+ (* x1 x2) (* y1 y2))) +(defn dot + [p1 p2] + (+ (* (dm/get-prop p1 :x) + (dm/get-prop p2 :x)) + (* (dm/get-prop p1 :y) + (dm/get-prop p2 :y)))) -(defn unit [v] - (let [v-length (length v)] - (divide v (point v-length v-length)))) +(defn unit + [p1] + (let [p-length (length p1)] + (Point. (/ (dm/get-prop p1 :x) p-length) + (/ (dm/get-prop p1 :y) p-length)))) (defn perpendicular [{:keys [x y]}] @@ -259,7 +329,7 @@ (defn project "V1 perpendicular projection on vector V2" [v1 v2] - (let [v2-unit (unit v2) + (let [v2-unit (unit v2) scalar-proj (dot v1 v2-unit)] (scale v2-unit scalar-proj))) @@ -282,43 +352,53 @@ (defn point-line-distance "Returns the distance from a point to a line defined by two points" [point line-point1 line-point2] - (let [{x0 :x y0 :y} point - {x1 :x y1 :y} line-point1 - {x2 :x y2 :y} line-point2 - num (mth/abs - (+ (* x0 (- y2 y1)) - (- (* y0 (- x2 x1))) - (* x2 y1) - (- (* y2 x1)))) - dist (distance line-point2 line-point1)] - (/ num dist))) + (let [x0 (dm/get-prop point :x) + y0 (dm/get-prop point :y) + x1 (dm/get-prop line-point1 :x) + y1 (dm/get-prop line-point1 :y) + x2 (dm/get-prop line-point2 :x) + y2 (dm/get-prop line-point2 :y)] + (/ (mth/abs (+ (* x0 (- y2 y1)) + (- (* y0 (- x2 x1))) + (* x2 y1) + (- (* y2 x1)))) + (distance line-point2 line-point1)))) -(defn almost-zero? [{:keys [x y] :as p}] - (assert (point? p)) - (and (mth/almost-zero? x) - (mth/almost-zero? y))) +(defn almost-zero? + [p] + (assert (point? p) "point instance expected") + (and ^boolean (mth/almost-zero? (dm/get-prop p :x)) + ^boolean (mth/almost-zero? (dm/get-prop p :y)))) (defn lerp "Calculates a linear interpolation between two points given a tvalue" [p1 p2 t] - (let [x (mth/lerp (:x p1) (:x p2) t) - y (mth/lerp (:y p1) (:y p2) t)] - (point x y))) + (let [x (mth/lerp (dm/get-prop p1 :x) (dm/get-prop p2 :x) t) + y (mth/lerp (dm/get-prop p1 :y) (dm/get-prop p2 :y) t)] + (Point. x y))) (defn rotate "Rotates the point around center with an angle" - [{px :x py :y} {cx :x cy :y} angle] + [p c angle] + (prn "ROTATE" p c angle) + (assert (point? p) "point instance expected") + (assert (point? c) "point instance expected") (let [angle (mth/radians angle) + px (dm/get-prop p :x) + py (dm/get-prop p :y) + cx (dm/get-prop c :x) + cy (dm/get-prop c :y) - x (+ (* (mth/cos angle) (- px cx)) - (* (mth/sin angle) (- py cy) -1) - cx) - - y (+ (* (mth/sin angle) (- px cx)) - (* (mth/cos angle) (- py cy)) - cy)] - (point x y))) + sa (mth/sin angle) + ca (mth/cos angle) + x (+ (* ca (- px cx)) + (* sa (- py cy) -1) + cx) + y (+ (* sa (- px cx)) + (* ca (- py cy)) + cy)] + (Point. x y))) (defn scale-from "Moves a point in the vector that creates with center with a scale @@ -331,10 +411,11 @@ (defn no-zeros "Remove zero values from either coordinate" - [point] - (-> point - (update :x #(if (mth/almost-zero? %) 0.001 %)) - (update :y #(if (mth/almost-zero? %) 0.001 %)))) + [p] + (let [x (dm/get-prop p :x) + y (dm/get-prop p :y)] + (Point. (if (mth/almost-zero? x) 0.001 x) + (if (mth/almost-zero? y) 0.001 y)))) (defn abs diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc index 7b44280641..f6451ab022 100644 --- a/common/src/app/common/geom/shapes/pixel_precision.cljc +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -41,8 +41,8 @@ corner (gpt/point bounds) target-corner (gpt/round corner) deltav (gpt/to-vec corner target-corner)] - (-> modifiers - (ctm/move deltav)))) + + (ctm/move modifiers deltav))) (defn set-pixel-precision "Adjust modifiers so they adjust to the pixel grid" diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 057687e1e1..ca400800e2 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -7,6 +7,7 @@ (ns app.common.geom.shapes.rect (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.math :as mth])) @@ -90,8 +91,8 @@ maxy ##-Inf pts points] (if-let [pt (first pts)] - (let [x (d/get-prop pt :x) - y (d/get-prop pt :y)] + (let [x (dm/get-prop pt :x) + y (dm/get-prop pt :y)] (recur (min minx x) (min miny y) (max maxx x) diff --git a/common/test/common_tests/geom_point_test.cljc b/common/test/common_tests/geom_point_test.cljc new file mode 100644 index 0000000000..b83d70e7c7 --- /dev/null +++ b/common/test/common_tests/geom_point_test.cljc @@ -0,0 +1,295 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns common-tests.geom-point-test + (:require + [app.common.geom.point :as gpt] + [clojure.test :as t])) + +(t/deftest add-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/add p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 3 (:x rs))) + (t/is (= 5 (:y rs))))) + +(t/deftest substract-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/subtract p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= -1 (:x rs))) + (t/is (= -1 (:y rs))))) + +(t/deftest multiply-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/multiply p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 2 (:x rs))) + (t/is (= 6 (:y rs))))) + +(t/deftest divide-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 5) + rs (gpt/divide p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 0.5 (:x rs))) + (t/is (= 0.4 (:y rs))))) + +(t/deftest min-point + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 5)] + + (let [rs (gpt/min)] + (t/is (nil? rs))) + + (let [rs (gpt/min p1)] + (t/is (= rs p1))) + + (let [rs (gpt/min nil p1)] + (t/is (= rs p1))) + + (let [rs (gpt/min p1 nil)] + (t/is (= rs p1))) + + (let [rs (gpt/min p1 p2)] + (t/is (= rs p1))) + + (let [rs (gpt/min p2 p1)] + (t/is (= rs p1))) + )) + +(t/deftest max-point + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 5)] + + (let [rs (gpt/max)] + (t/is (nil? rs))) + + (let [rs (gpt/max p1)] + (t/is (= rs p1))) + + (let [rs (gpt/max nil p1)] + (t/is (= rs p1))) + + (let [rs (gpt/max p1 nil)] + (t/is (= rs p1))) + + (let [rs (gpt/max p1 p2)] + (t/is (= rs p2))) + + (let [rs (gpt/max p2 p1)] + (t/is (= rs p2))) + )) + +(t/deftest inverse-point + (let [p1 (gpt/point 1 2) + rs (gpt/inverse p1)] + (t/is (gpt/point? rs)) + (t/is (= 1 (:x rs))) + (t/is (= 0.5 (:y rs))))) + +(t/deftest negate-point + (let [p1 (gpt/point 1 2) + rs (gpt/negate p1)] + (t/is (gpt/point? rs)) + (t/is (= -1 (:x rs))) + (t/is (= -2 (:y rs))))) + +(t/deftest distance-between-two-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 4 6) + rs (gpt/distance p1 p2)] + (t/is (number? rs)) + (t/is (= 5 rs)))) + +(t/deftest distance-vector-between-two-points + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/distance-vector p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 1 (:x rs))) + (t/is (= 1 (:y rs))))) + +(t/deftest point-length + (let [p1 (gpt/point 1 10) + rs (gpt/length p1)] + (t/is (number? rs)) + (t/is (= 10.04987562112089 rs)))) + +(t/deftest point-angle-1 + (let [p1 (gpt/point 1 3) + rs (gpt/angle p1)] + (t/is (number? rs)) + (t/is (= 71.56505117707799 rs)))) + +(t/deftest point-angle-2 + (let [p1 (gpt/point 1 3) + p2 (gpt/point 2 4) + rs (gpt/angle p1 p2)] + (t/is (number? rs)) + (t/is (= -135 rs)))) + +(t/deftest point-angle-with-other + (let [p1 (gpt/point 1 3) + p2 (gpt/point 1 5) + rs (gpt/angle-with-other p1 p2)] + (t/is (number? rs)) + (t/is (= 7.125016348901757 rs)))) + +(t/deftest point-angle-sign + (let [p1 (gpt/point 1 3) + p2 (gpt/point 1 5) + rs (gpt/angle-sign p1 p2)] + (t/is (number? rs)) + (t/is (= 1 rs))) + + (let [p1 (gpt/point -11 -3) + p2 (gpt/point 1 5) + rs (gpt/angle-sign p1 p2)] + (t/is (number? rs)) + (t/is (= -1 rs))) + ) + +(t/deftest update-angle + (let [p1 (gpt/point 1 3) + rs (gpt/update-angle p1 10)] + (t/is (gpt/point? rs)) + (t/is (= 3.1142355569111246 (:x rs))) + (t/is (= 0.5491237529650835 (:y rs))))) + + +(t/deftest point-quadrant + (let [p1 (gpt/point 1 3) + rs (gpt/quadrant p1)] + (t/is (number? rs)) + (t/is (= 1 rs))) + + (let [p1 (gpt/point 1 -3) + rs (gpt/quadrant p1)] + (t/is (number? rs)) + (t/is (= 4 rs))) + + (let [p1 (gpt/point -1 3) + rs (gpt/quadrant p1)] + (t/is (number? rs)) + (t/is (= 2 rs))) + + (let [p1 (gpt/point -1 -3) + rs (gpt/quadrant p1)] + (t/is (number? rs)) + (t/is (= 3 rs))) + ) + +(t/deftest round-point + (let [p1 (gpt/point 1.34567 3.34567) + rs (gpt/round p1)] + (t/is (gpt/point? rs)) + (t/is (= 1 (:x rs))) + (t/is (= 3 (:y rs)))) + + (let [p1 (gpt/point 1.34567 3.34567) + rs (gpt/round p1 2)] + (t/is (gpt/point? rs)) + (t/is (= 1.35 (:x rs))) + (t/is (= 3.35 (:y rs)))) + ) + +(t/deftest halft-round-point + (let [p1 (gpt/point 1.34567 3.34567) + rs (gpt/half-round p1)] + (t/is (gpt/point? rs)) + (t/is (= 1.5 (:x rs))) + (t/is (= 3.5 (:y rs))))) + +(t/deftest transform-point + ;;todo + ) + +(t/deftest scale-point + (let [p1 (gpt/point 1.5 3) + rs (gpt/scale p1 2)] + (t/is (gpt/point? rs)) + (t/is (= 3 (:x rs))) + (t/is (= 6 (:y rs))))) + +(t/deftest dot-point + (let [p1 (gpt/point 1.5 3) + p2 (gpt/point 2 6) + rs (gpt/dot p1 p2)] + (t/is (number? rs)) + (t/is (= 21 rs)))) + +(t/deftest unit-point + (let [p1 (gpt/point 2 3) + rs (gpt/unit p1)] + (t/is (gpt/point? rs)) + (t/is (= 0.5547001962252291 (:x rs))) + (t/is (= 0.8320502943378437 (:y rs))))) + +(t/deftest project-point + (let [p1 (gpt/point 1 3) + p2 (gpt/point 1 6) + rs (gpt/project p1 p2)] + (t/is (gpt/point? rs)) + (t/is (= 0.5135135135135135 (:x rs))) + (t/is (= 3.081081081081081 (:y rs))))) + +(t/deftest center-points + (let [points [(gpt/point 0.5 0.5) + (gpt/point -1 -2) + (gpt/point 20 65.2) + (gpt/point 12 -10)] + rs (gpt/center-points points)] + (t/is (= 7.875 (:x rs))) + (t/is (= 13.425 (:y rs))))) + +(t/deftest normal-left-point + (let [p1 (gpt/point 2 3) + rs (gpt/normal-left p1)] + (t/is (gpt/point? rs)) + (t/is (= -0.8320502943378437 (:x rs))) + (t/is (= 0.5547001962252291 (:y rs))))) + +(t/deftest normal-right-point + (let [p1 (gpt/point 2 3) + rs (gpt/normal-right p1)] + (t/is (gpt/point? rs)) + (t/is (= 0.8320502943378437 (:x rs))) + (t/is (= -0.5547001962252291 (:y rs))))) + +(t/deftest point-line-distance + (let [p1 (gpt/point 2 -3) + p2 (gpt/point -1 4) + p3 (gpt/point 5 6) + rs (gpt/point-line-distance p1 p2 p3)] + (t/is (number? rs)) + (t/is (= 7.58946638440411 rs)))) + +(t/deftest almost-zero-predicate + (let [p1 (gpt/point 0.000001 0.0000002) + p2 (gpt/point 0.001 -0.0003)] + (t/is (gpt/almost-zero? p1)) + (t/is (not (gpt/almost-zero? p2))))) + +(t/deftest lerp-point + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/lerp p1 p2 2)] + (t/is (gpt/point? rs)) + (t/is (= 3 (:x rs))) + (t/is (= 4 (:y rs))))) + +(t/deftest rotate-point + (let [p1 (gpt/point 1 2) + p2 (gpt/point 2 3) + rs (gpt/rotate p1 p2 11)] + (t/is (gpt/point? rs)) + (t/is (= 1.2091818119288809 (:x rs))) + (t/is (= 1.8275638211757912 (:y rs))))) + diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index f9585b5bf0..b4984e6308 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -297,8 +297,8 @@ (mapv (fn [[value points]] [(- value pval) (->> points (mapv #(vector point %)))])))))] - {:x (query-coord point :x) - :y (query-coord point :y)})) + (gpt/point (query-coord point :x) + (query-coord point :y)))) (defn merge-matches ([] {:x nil :y nil}) diff --git a/frontend/test/frontend_tests/test_helpers_shapes.cljs b/frontend/test/frontend_tests/test_helpers_shapes.cljs new file mode 100644 index 0000000000..6b84b8dd42 --- /dev/null +++ b/frontend/test/frontend_tests/test_helpers_shapes.cljs @@ -0,0 +1,53 @@ +(ns frontend-tests.test-helpers-shapes + (:require + [app.common.colors :as clr] + [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.pages.helpers :as cph] + [app.main.data.workspace.libraries :as dwl] + [app.test-helpers.events :as the] + [app.test-helpers.libraries :as thl] + [app.test-helpers.pages :as thp] + [beicon.core :as rx] + [cljs.pprint :refer [pprint]] + [cljs.test :as t :include-macros true] + [clojure.stacktrace :as stk] + [linked.core :as lks] + [potok.core :as ptk])) + +(t/use-fixtures :each + {:before thp/reset-idmap!}) + +(t/deftest test-create-page + (t/testing "create page" + (let [state (-> thp/initial-state + (thp/sample-page)) + page (thp/current-page state)] + (t/is (= (:name page) "page1"))))) + +(t/deftest test-create-shape + (t/testing "create shape" + (let [state (-> thp/initial-state + (thp/sample-page) + (thp/sample-shape :shape1 :rect + {:name "Rect 1"})) + shape (thp/get-shape state :shape1)] + (t/is (= (:name shape) "Rect 1"))))) + +(t/deftest asynctest + (t/testing "asynctest" + (t/async done + (let [state {} + color {:color clr/white} + + store (the/prepare-store state done + (fn [new-state] + (t/is (= (get-in new-state [:workspace-data + :recent-colors]) + [color]))))] + + (ptk/emit! + store + (dwl/add-recent-color color) + :the/end))))) + From 04243be4a54505e0cc4701c1cd6d66d582ad2be8 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Sun, 20 Nov 2022 20:06:34 +0100 Subject: [PATCH 278/682] :paperclip: Update frontend bench namespace --- frontend/dev/bench.cljs | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/frontend/dev/bench.cljs b/frontend/dev/bench.cljs index cf2a406328..41171bafe8 100644 --- a/frontend/dev/bench.cljs +++ b/frontend/dev/bench.cljs @@ -1,6 +1,7 @@ (ns bench (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes.rect :as gsr] [app.common.perf :as perf] @@ -10,39 +11,21 @@ (def points (gen/sample (s/gen ::gpt/point) 20)) -(defn points->rect - [points] - (when-let [points (seq points)] - (loop [minx ##Inf - miny ##Inf - maxx ##-Inf - maxy ##-Inf - pts points] - (if-let [pt ^boolean (first pts)] - (let [x (d/get-prop pt :x) - y (d/get-prop pt :y)] - (recur (min minx x) - (min miny y) - (max maxx x) - (max maxy y) - (rest pts))) - (when (d/num? minx miny maxx maxy) - (gsr/make-rect minx miny (- maxx minx) (- maxy miny))))))) - (defn bench-points [] + #_(perf/benchmark + :f #(gpt/center-points-old points) + :samples 20 + :max-iterations 500000 + :name "base") (perf/benchmark - :f #(gsr/points->rect points) - :name "base") - (perf/benchmark - :f #(points->rect points) + :f #(gpt/center-points points) + :max-iterations 500000 + :samples 20 :name "optimized")) - (defn main [& [name]] (case name "points" (bench-points) (println "available: points"))) - - From 600f9ef07136e0adac7b1aabb922440f5487a8f2 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 28 Nov 2022 13:05:54 +0100 Subject: [PATCH 279/682] :sparkles: Performance improvements --- common/src/app/common/types/modifiers.cljc | 103 ++++++++++++------ .../common_tests/types_modifiers_test.cljc | 26 +++++ 2 files changed, 96 insertions(+), 33 deletions(-) create mode 100644 common/test/common_tests/types_modifiers_test.cljc diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index cd4b445b58..608d78ab10 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -7,6 +7,7 @@ (ns app.common.types.modifiers (:refer-clojure :exclude [empty empty?]) (:require + [app.common.perf :as perf] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] @@ -217,15 +218,44 @@ :property property :value value}))) +(defn- merge-geometry + [geometry other] + + (cond + (c/empty? geometry) + other + + (c/empty? other) + geometry + + :else + (loop [result geometry + modifiers (seq other)] + (if (c/empty? modifiers) + result + (let [current (first modifiers) + result + (cond + (= :move (:type current)) + (maybe-add-move result current) + + (= :resize (:type current)) + (maybe-add-resize result current) + + :else + (conj result current))] + + (recur result (rest modifiers))))))) + (defn add-modifiers [modifiers new-modifiers] (cond-> modifiers (some? (:geometry-child new-modifiers)) - (update :geometry-child #(d/concat-vec [] % (:geometry-child new-modifiers))) + (update :geometry-child merge-geometry (:geometry-child new-modifiers)) (some? (:geometry-parent new-modifiers)) - (update :geometry-parent #(d/concat-vec [] % (:geometry-parent new-modifiers))) + (update :geometry-parent merge-geometry (:geometry-parent new-modifiers)) (some? (:structure-parent new-modifiers)) (update :structure-parent #(d/concat-vec [] % (:structure-parent new-modifiers))) @@ -426,39 +456,46 @@ (defn modifiers->transform "Given a set of modifiers returns its transformation matrix" [modifiers] - (letfn [(apply-modifier [matrix {:keys [type vector rotation center origin transform transform-inverse] :as modifier}] - (case type - :move - (gmt/multiply (gmt/translate-matrix vector) matrix) - :resize - (let [origin (cond-> origin - (or (some? transform-inverse)(some? transform)) - (gpt/transform transform-inverse))] - (gmt/multiply - (-> (gmt/matrix) - (cond-> (some? transform) - (gmt/multiply transform)) - (gmt/translate origin) - (gmt/scale vector) - (gmt/translate (gpt/negate origin)) - (cond-> (some? transform-inverse) - (gmt/multiply transform-inverse))) - matrix)) + (let [modifiers + (if (d/not-empty? (:geometry-parent modifiers)) + (concat (:geometry-parent modifiers) (:geometry-child modifiers)) + (:geometry-child modifiers))] - :rotation - (gmt/multiply - (-> (gmt/matrix) - (gmt/translate center) - (gmt/multiply (gmt/rotate-matrix rotation)) - (gmt/translate (gpt/negate center))) - matrix)))] - (let [modifiers (if (d/not-empty? (:geometry-parent modifiers)) - (d/concat-vec (:geometry-parent modifiers) (:geometry-child modifiers)) - (:geometry-child modifiers))] - (when (d/not-empty? modifiers) - (->> modifiers - (reduce apply-modifier (gmt/matrix))))))) + (when (d/not-empty? modifiers) + (loop [matrix (gmt/matrix) + modifiers (seq modifiers)] + (if (c/empty? modifiers) + matrix + (let [{:keys [type vector rotation center origin transform transform-inverse] :as modifier} (first modifiers) + matrix + (case type + :move + (gmt/multiply (gmt/translate-matrix vector) matrix) + + :resize + (let [origin (cond-> origin + (or (some? transform-inverse)(some? transform)) + (gpt/transform transform-inverse))] + (gmt/multiply + (-> (gmt/matrix) + (cond-> (some? transform) + (gmt/multiply transform)) + (gmt/translate origin) + (gmt/scale vector) + (gmt/translate (gpt/negate origin)) + (cond-> (some? transform-inverse) + (gmt/multiply transform-inverse))) + matrix)) + + :rotation + (gmt/multiply + (-> (gmt/matrix) + (gmt/translate center) + (gmt/multiply (gmt/rotate-matrix rotation)) + (gmt/translate (gpt/negate center))) + matrix))] + (recur matrix (rest modifiers)))))))) (defn apply-structure-modifiers "Apply structure changes to a shape" diff --git a/common/test/common_tests/types_modifiers_test.cljc b/common/test/common_tests/types_modifiers_test.cljc new file mode 100644 index 0000000000..24d6e47a9a --- /dev/null +++ b/common/test/common_tests/types_modifiers_test.cljc @@ -0,0 +1,26 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns common-tests.types-modifiers-test + (:require + [clojure.test :as t] + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] + [app.common.types.modifiers :as ctm])) + +(t/deftest test-modifiers->transform + (let [modifiers + (-> (ctm/empty) + (ctm/move (gpt/point 100 200)) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)) + (ctm/move (gpt/point -100 -200)) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)) + (ctm/rotation (gpt/point 0 0) -100) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5))) + + transform (ctm/modifiers->transform modifiers)] + + (t/is (not (gmt/close? (gmt/matrix) transform))))) From c79d549f539e70c9240fc366fb2e1b9d2880f2d1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 28 Nov 2022 18:20:35 +0100 Subject: [PATCH 280/682] :sparkles: Change modifiers to records --- common/src/app/common/geom/matrix.cljc | 69 ++- common/src/app/common/geom/point.cljc | 5 +- .../app/common/geom/shapes/constraints.cljc | 4 +- .../geom/shapes/flex_layout/modifiers.cljc | 2 +- .../src/app/common/geom/shapes/modifiers.cljc | 2 +- .../app/common/geom/shapes/transforms.cljc | 6 +- common/src/app/common/types/modifiers.cljc | 428 ++++++++++-------- common/test/common_tests/geom_point_test.cljc | 123 ++--- .../test/common_tests/geom_shapes_test.cljc | 2 +- frontend/dev/bench.cljs | 36 +- .../shapes/text/viewport_texts_html.cljs | 27 +- 11 files changed, 436 insertions(+), 268 deletions(-) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index ac7443f2d5..0158fe7df2 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -9,6 +9,7 @@ #?(:cljs [cljs.pprint :as pp] :clj [clojure.pprint :as pp]) [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.math :as mth] [app.common.spec :as us] @@ -123,6 +124,35 @@ ([m1 m2 & others] (reduce multiply (multiply m1 m2) others))) +(defn multiply! + [^Matrix m1 ^Matrix m2] + (let [m1a (.-a m1) + m1b (.-b m1) + m1c (.-c m1) + m1d (.-d m1) + m1e (.-e m1) + m1f (.-f m1) + m2a (.-a m2) + m2b (.-b m2) + m2c (.-c m2) + m2d (.-d m2) + m2e (.-e m2) + m2f (.-f m2)] + #?@(:cljs [(set! (.-a m1) (+ (* m1a m2a) (* m1c m2b))) + (set! (.-b m1) (+ (* m1b m2a) (* m1d m2b))) + (set! (.-c m1) (+ (* m1a m2c) (* m1c m2d))) + (set! (.-d m1) (+ (* m1b m2c) (* m1d m2d))) + (set! (.-e m1) (+ (* m1a m2e) (* m1c m2f) m1e)) + (set! (.-f m1) (+ (* m1b m2e) (* m1d m2f) m1f)) + m1] + :clj [(Matrix. + (+ (* m1a m2a) (* m1c m2b)) + (+ (* m1b m2a) (* m1d m2b)) + (+ (* m1a m2c) (* m1c m2d)) + (+ (* m1b m2c) (* m1d m2d)) + (+ (* m1a m2e) (* m1c m2f) m1e) + (+ (* m1b m2e) (* m1d m2f) m1f))]))) + (defn add-translate "Given two TRANSLATE matrixes (only e and f have significative values), combine them. Quicker than multiplying them, for this @@ -147,26 +177,31 @@ (= v base)) (defn translate-matrix - ([{x :x y :y :as pt}] + ([pt] (assert (gpt/point? pt)) - (Matrix. 1 0 0 1 x y)) + (Matrix. 1 0 0 1 + (dm/get-prop pt :x) + (dm/get-prop pt :y))) ([x y] - (translate-matrix (gpt/point x y)))) + (Matrix. 1 0 0 1 x y))) (defn scale-matrix ([pt center] - (multiply (translate-matrix center) - (scale-matrix pt) - (translate-matrix (gpt/negate center)))) - ([{x :x y :y :as pt}] + (-> (matrix) + (multiply! (translate-matrix center)) + (multiply! (scale-matrix pt)) + (multiply! (translate-matrix (gpt/negate center))))) + ([pt] (assert (gpt/point? pt)) - (Matrix. x 0 0 y 0 0))) + (Matrix. (dm/get-prop pt :x) 0 0 (dm/get-prop pt :y) 0 0))) (defn rotate-matrix - ([angle point] (multiply (translate-matrix point) - (rotate-matrix angle) - (translate-matrix (gpt/negate point)))) + ([angle point] + (-> (matrix) + (multiply! (translate-matrix point)) + (multiply! (rotate-matrix angle)) + (multiply! (translate-matrix (gpt/negate point))))) ([angle] (let [a (mth/radians angle)] (Matrix. (mth/cos a) @@ -200,11 +235,23 @@ ([m scale center] (multiply m (scale-matrix scale center)))) +(defn scale! + "Apply scale transformation to the matrix." + ([m scale] + (multiply! m (scale-matrix scale))) + ([m scale center] + (multiply! m (scale-matrix scale center)))) + (defn translate "Apply translate transformation to the matrix." [m pt] (multiply m (translate-matrix pt))) +(defn translate! + "Apply translate transformation to the matrix." + [m pt] + (multiply! m (translate-matrix pt))) + (defn skew "Apply translate transformation to the matrix." ([m angle-x angle-y] diff --git a/common/src/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc index 5421af3cef..e15f0fff0b 100644 --- a/common/src/app/common/geom/point.cljc +++ b/common/src/app/common/geom/point.cljc @@ -323,8 +323,9 @@ (/ (dm/get-prop p1 :y) p-length)))) (defn perpendicular - [{:keys [x y]}] - (Point. (- y) x)) + [pt] + (Point. (- (dm/get-prop pt :y)) + (dm/get-prop pt :x))) (defn project "V1 perpendicular projection on vector V2" diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 35ddd66a83..6275e673fb 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -282,7 +282,7 @@ (defn calc-child-modifiers [parent child modifiers ignore-constraints child-bounds parent-bounds transformed-parent-bounds] - (let [modifiers (ctm/select-child-modifiers modifiers) + (let [modifiers (ctm/select-child modifiers) constraints-h (if-not ignore-constraints @@ -299,7 +299,7 @@ (let [transformed-parent-bounds @transformed-parent-bounds - modifiers (ctm/select-child-modifiers modifiers) + modifiers (ctm/select-child modifiers) transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) modifiers (normalize-modifiers constraints-h constraints-v modifiers parent diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 8ad9364783..481d27390f 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -34,7 +34,7 @@ resize-origin (gpo/origin transformed-child-bounds)] (-> modifiers - (ctm/select-child-modifiers) + (ctm/select-child) (ctm/resize resize-vector resize-origin diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index d0588cfa96..0677a42931 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -107,7 +107,7 @@ ;; Check the constraints, then resize (let [parent-id (:id parent) - parent-bounds (gtr/transform-bounds @(get bounds parent-id) (ctm/select-parent-modifiers modifiers))] + parent-bounds (gtr/transform-bounds @(get bounds parent-id) (ctm/select-parent modifiers))] (loop [modif-tree modif-tree children (seq children)] (if (empty? children) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 38a4ee7e6d..4383b28e88 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -483,9 +483,9 @@ ([points center modifiers] (let [transform (ctm/modifiers->transform modifiers)] - (cond-> points - (some? transform) - (gco/transform-points center transform))))) + (cond-> points + (some? transform) + (gco/transform-points center transform))))) (defn transform-selrect [selrect modifiers] diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 608d78ab10..b0c8827008 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -7,7 +7,6 @@ (ns app.common.types.modifiers (:refer-clojure :exclude [empty empty?]) (:require - [app.common.perf :as perf] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.matrix :as gmt] @@ -40,44 +39,123 @@ ;; * rotation ;; * change-properties +(defrecord Modifiers + [geometry-parent + geometry-child + structure-parent + structure-child]) + +(defrecord GeometricOperation + [type + vector + origin + transform + transform-inverse + rotation + center]) + +(defrecord StructureOperation + [type + property + value + index]) + +;; Record constructors + +(defn move-op + [vector] + (GeometricOperation. :move vector nil nil nil nil nil)) + +(defn resize-op + ([vector origin] + (GeometricOperation. :resize vector origin nil nil nil nil)) + ([vector origin transform transform-inverse] + (GeometricOperation. :resize vector origin transform transform-inverse nil nil))) + +(defn rotation-geom-op + [center angle] + (GeometricOperation. :rotation nil nil nil nil angle center)) + +(defn rotation-struct-op + [angle] + (StructureOperation. :rotation nil angle nil)) + +(defn remove-children-op + [shapes] + (StructureOperation. :remove-children nil shapes nil)) + +(defn add-children-op + [shapes index] + (StructureOperation. :add-children nil shapes index)) + +(defn reflow-op + [] + (StructureOperation. :reflow nil nil nil)) + +(defn scale-content-op + [value] + (StructureOperation. :scale-content nil value nil)) + +(defn change-property-op + [property value] + (StructureOperation. :change-property property value nil)) + + ;; Private aux functions -(def conjv (fnil conj [])) +(defn- move-vec? + [vector] + (or (not (mth/almost-zero? (dm/get-prop vector :x))) + (not (mth/almost-zero? (dm/get-prop vector :y))))) -(defn- move-vec? [vector] - (or (not (mth/almost-zero? (:x vector))) - (not (mth/almost-zero? (:y vector))))) - -(defn- resize-vec? [vector] - (or (not (mth/almost-zero? (- (:x vector) 1))) - (not (mth/almost-zero? (- (:y vector) 1))))) +(defn- resize-vec? + [vector] + (or (not (mth/almost-zero? (- (dm/get-prop vector :x) 1))) + (not (mth/almost-zero? (- (dm/get-prop vector :y) 1))))) (defn- mergeable-move? [op1 op2] - (and (= :move (:type op1)) - (= :move (:type op2)))) + (let [type-op1 (dm/get-prop op1 :type) + type-op2 (dm/get-prop op2 :type)] + (and (= :move type-op1) (= :move type-op2)))) (defn- mergeable-resize? [op1 op2] - (and (= :resize (:type op1)) - (= :resize (:type op2)) + (let [type-op1 (dm/get-prop op1 :type) + transform-op1 (or (dm/get-prop op1 :transform) (gmt/matrix)) + transform-inv-op1 (or (dm/get-prop op1 :transform-inverse) (gmt/matrix)) + origin-op1 (dm/get-prop op1 :origin) - ;; Same transforms - (gmt/close? (or (:transform op1) (gmt/matrix)) (or (:transform op2) (gmt/matrix))) - (gmt/close? (or (:transform-inverse op1) (gmt/matrix)) (or (:transform-inverse op2) (gmt/matrix))) + type-op2 (dm/get-prop op2 :type) + transform-op2 (or (dm/get-prop op2 :transform) (gmt/matrix)) + transform-inv-op2 (or (dm/get-prop op2 :transform-inverse) (gmt/matrix)) + origin-op2 (dm/get-prop op2 :origin)] + (and (= :resize type-op1) (= :resize type-op2) - ;; Same origin - (gpt/close? (:origin op1) (:origin op2)))) + ;; Same origin + (gpt/close? origin-op1 origin-op2) + + ;; Same transforms + (gmt/close? transform-op1 transform-op2) + (gmt/close? transform-inv-op1 transform-inv-op2)))) (defn- merge-move [op1 op2] - {:type :move - :vector (gpt/add (:vector op1) (:vector op2))}) + (let [vector-op1 (dm/get-prop op1 :vector) + vector-op2 (dm/get-prop op2 :vector)] + (move-op (gpt/add vector-op1 vector-op2)))) (defn- merge-resize [op1 op2] - (let [vector (gpt/point (* (-> op1 :vector :x) (-> op2 :vector :x)) - (* (-> op1 :vector :y) (-> op2 :vector :y)))] + (let [op1-vector (dm/get-prop op1 :vector) + op1-x (dm/get-prop op1-vector :x) + op1-y (dm/get-prop op1-vector :y) + + op2-vector (dm/get-prop op2 :vector) + op2-x (dm/get-prop op2-vector :x) + op2-y (dm/get-prop op2-vector :y) + + vector (gpt/point (* op1-x op2-x) (* op1-y op2-y))] (assoc op1 :vector vector))) (defn- maybe-add-move @@ -89,7 +167,7 @@ (if (mergeable-move? head op) (let [item (merge-move head op)] (cond-> (pop operations) - (move-vec? (:vector item)) + (move-vec? (dm/get-prop item :vector)) (conj item))) (conj operations op))))) @@ -103,21 +181,23 @@ (if (mergeable-resize? head op) (let [item (merge-resize head op)] (cond-> (pop operations) - (resize-vec? (:vector item)) + (resize-vec? (dm/get-prop item :vector)) (conj item))) (conj operations op))))) (defn valid-vector? - [{:keys [x y]}] - (and (some? x) - (some? y) - (not (mth/nan? x)) - (not (mth/nan? y)))) + [vector] + (let [x (dm/get-prop vector :x) + y (dm/get-prop vector :y)] + (and (some? x) + (some? y) + (not (mth/nan? x)) + (not (mth/nan? y))))) ;; Public builder API (defn empty [] - {}) + (Modifiers. [] [] [] [])) (defn move-parent ([modifiers x y] @@ -125,143 +205,118 @@ ([modifiers vector] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (move-vec? vector) - (update :geometry-parent maybe-add-move {:type :move :vector vector})))) + (update :geometry-parent maybe-add-move (move-op vector))))) (defn resize-parent ([modifiers vector origin] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (resize-vec? vector) - (update :geometry-parent maybe-add-resize {:type :resize - :vector vector - :origin origin}))) + (update :geometry-parent maybe-add-resize (resize-op vector origin)))) ([modifiers vector origin transform transform-inverse] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (resize-vec? vector) - (update :geometry-parent maybe-add-resize {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + (update :geometry-parent maybe-add-resize (resize-op vector origin transform transform-inverse))))) + (defn move ([modifiers x y] (move modifiers (gpt/point x y))) ([modifiers vector] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (move-vec? vector) - (update :geometry-child maybe-add-move {:type :move :vector vector})))) + (update :geometry-child maybe-add-move (move-op vector))))) (defn resize ([modifiers vector origin] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (resize-vec? vector) - (update :geometry-child maybe-add-resize {:type :resize - :vector vector - :origin origin}))) + (update :geometry-child maybe-add-resize (resize-op vector origin)))) ([modifiers vector origin transform transform-inverse] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> modifiers + (cond-> (or modifiers (empty)) (resize-vec? vector) - (update :geometry-child maybe-add-resize {:type :resize - :vector vector - :origin origin - :transform transform - :transform-inverse transform-inverse})))) + (update :geometry-child maybe-add-resize (resize-op vector origin transform transform-inverse))))) (defn rotation [modifiers center angle] - (cond-> modifiers + (cond-> (or modifiers (empty)) (not (mth/close? angle 0)) - (-> (update :structure-child conjv {:type :rotation - :rotation angle}) - (update :geometry-child conjv {:type :rotation - :center center - :rotation angle})))) + (-> (update :structure-child conj (rotation-struct-op angle)) + (update :geometry-child conj (rotation-geom-op center angle))))) (defn remove-children [modifiers shapes] - (cond-> modifiers + (cond-> (or modifiers (empty)) (d/not-empty? shapes) - (update :structure-parent conjv {:type :remove-children - :value shapes}))) + (update :structure-parent conj (remove-children-op shapes)))) (defn add-children [modifiers shapes index] - (cond-> modifiers + (cond-> (or modifiers (empty)) (d/not-empty? shapes) - (update :structure-parent conjv {:type :add-children - :value shapes - :index index}))) + (update :structure-parent conj (add-children-op shapes index)))) (defn reflow [modifiers] - (-> modifiers - (update :structure-parent conjv {:type :reflow}))) + (-> (or modifiers (empty)) + (update :structure-parent conj (reflow-op)))) (defn scale-content [modifiers value] - (-> modifiers - (update :structure-child conjv {:type :scale-content :value value}))) + (-> (or modifiers (empty)) + (update :structure-child conj (scale-content-op value)))) (defn change-property [modifiers property value] - (-> modifiers - (update :structure-child conjv {:type :change-property - :property property - :value value}))) + (-> (or modifiers (empty)) + (update :structure-child conj (change-property-op property value)))) (defn- merge-geometry - [geometry other] + [operations other] (cond - (c/empty? geometry) + (c/empty? operations) other (c/empty? other) - geometry + operations :else - (loop [result geometry - modifiers (seq other)] - (if (c/empty? modifiers) + (loop [result operations + operations (seq other)] + (if (c/empty? operations) result - (let [current (first modifiers) + (let [current (first operations) result (cond - (= :move (:type current)) + (= :move (dm/get-prop current :type)) (maybe-add-move result current) - (= :resize (:type current)) + (= :resize (dm/get-prop current :type)) (maybe-add-resize result current) :else (conj result current))] - (recur result (rest modifiers))))))) + (recur result (rest operations))))))) (defn add-modifiers [modifiers new-modifiers] - - (cond-> modifiers - (some? (:geometry-child new-modifiers)) - (update :geometry-child merge-geometry (:geometry-child new-modifiers)) - - (some? (:geometry-parent new-modifiers)) - (update :geometry-parent merge-geometry (:geometry-parent new-modifiers)) - - (some? (:structure-parent new-modifiers)) - (update :structure-parent #(d/concat-vec [] % (:structure-parent new-modifiers))) - - (some? (:structure-child new-modifiers)) - (update :structure-child #(d/concat-vec [] % (:structure-child new-modifiers))))) + (let [modifiers (or modifiers (empty)) + new-modifiers (or new-modifiers (empty))] + (-> modifiers + (update :geometry-child merge-geometry (dm/get-prop new-modifiers :geometry-child)) + (update :geometry-parent merge-geometry (dm/get-prop new-modifiers :geometry-parent)) + (update :structure-parent #(d/concat-vec [] % (dm/get-prop new-modifiers :structure-parent))) + (update :structure-child #(d/concat-vec [] % (dm/get-prop new-modifiers :structure-child)))))) ;; These are convenience methods to create single operation modifiers without the builder @@ -385,27 +440,27 @@ (defn empty? [modifiers] - (and (c/empty? (:geometry-child modifiers)) - (c/empty? (:geometry-parent modifiers)) - (c/empty? (:structure-parent modifiers)) - (c/empty? (:structure-child modifiers)))) + (and (c/empty? (dm/get-prop modifiers :geometry-child)) + (c/empty? (dm/get-prop modifiers :geometry-parent)) + (c/empty? (dm/get-prop modifiers :structure-parent)) + (c/empty? (dm/get-prop modifiers :structure-child)))) (defn child-modifiers? - [{:keys [geometry-child structure-child]}] - (or (d/not-empty? geometry-child) - (d/not-empty? structure-child))) + [modifiers] + (or (d/not-empty? (dm/get-prop modifiers :geometry-child)) + (d/not-empty? (dm/get-prop modifiers :structure-child)))) (defn only-move? "Returns true if there are only move operations" - [{:keys [geometry-child geometry-parent]}] - (let [move-op? #(= :move (:type %))] - (and (every? move-op? geometry-child) - (every? move-op? geometry-parent)))) + [modifiers] + (let [move-op? #(= :move (dm/get-prop % :type))] + (and (every? move-op? (dm/get-prop modifiers :geometry-child)) + (every? move-op? (dm/get-prop modifiers :geometry-parent))))) (defn has-geometry? - [{:keys [geometry-parent geometry-child]}] - (or (d/not-empty? geometry-parent) - (d/not-empty? geometry-child))) + [modifiers] + (or (d/not-empty? (dm/get-prop modifiers :geometry-parent)) + (d/not-empty? (dm/get-prop modifiers :geometry-child)))) (defn has-structure? [{:keys [structure-parent structure-child]}] @@ -414,25 +469,25 @@ ;; Extract subsets of modifiers -(defn select-child-modifiers +(defn select-child [modifiers] - (select-keys modifiers [:geometry-child :structure-child])) + (assoc (or modifiers (empty)) :geometry-parent [] :structure-parent [])) -(defn select-child-geometry-modifiers +(defn select-parent [modifiers] - (select-keys modifiers [:geometry-child])) - -(defn select-parent-modifiers - [modifiers] - (select-keys modifiers [:geometry-parent :structure-parent])) + (assoc (or modifiers (empty)) :geometry-child [] :structure-child [])) (defn select-structure [modifiers] - (select-keys modifiers [:structure-parent :structure-child])) + (assoc (or modifiers (empty)) :geometry-child [] :geometry-parent [])) (defn select-geometry [modifiers] - (select-keys modifiers [:geometry-parent :geometry-child])) + (assoc (or modifiers (empty)) :structure-child [] :structure-parent [])) + +(defn select-child-geometry-modifiers + [modifiers] + (-> modifiers select-child select-geometry)) (defn added-children-frames "Returns the frames that have an 'add-children' operation" @@ -456,46 +511,53 @@ (defn modifiers->transform "Given a set of modifiers returns its transformation matrix" [modifiers] + (let [modifiers (concat (dm/get-prop modifiers :geometry-parent) + (dm/get-prop modifiers :geometry-child))] - (let [modifiers - (if (d/not-empty? (:geometry-parent modifiers)) - (concat (:geometry-parent modifiers) (:geometry-child modifiers)) - (:geometry-child modifiers))] + (loop [matrix (gmt/matrix) + modifiers (seq modifiers)] + (if (c/empty? modifiers) + matrix + (let [modifier (first modifiers) + type (dm/get-prop modifier :type) - (when (d/not-empty? modifiers) - (loop [matrix (gmt/matrix) - modifiers (seq modifiers)] - (if (c/empty? modifiers) - matrix - (let [{:keys [type vector rotation center origin transform transform-inverse] :as modifier} (first modifiers) - matrix - (case type - :move - (gmt/multiply (gmt/translate-matrix vector) matrix) + matrix + (case type + :move + (-> (dm/get-prop modifier :vector) + (gmt/translate-matrix) + (gmt/multiply! matrix)) - :resize - (let [origin (cond-> origin - (or (some? transform-inverse)(some? transform)) - (gpt/transform transform-inverse))] - (gmt/multiply - (-> (gmt/matrix) - (cond-> (some? transform) - (gmt/multiply transform)) - (gmt/translate origin) - (gmt/scale vector) - (gmt/translate (gpt/negate origin)) - (cond-> (some? transform-inverse) - (gmt/multiply transform-inverse))) - matrix)) + :resize + (let [tf (dm/get-prop modifier :transform) + tfi (dm/get-prop modifier :transform-inverse) + vector (dm/get-prop modifier :vector) + origin (dm/get-prop modifier :origin) + origin (if ^boolean (some? tfi) + (gpt/transform origin tfi) + origin)] - :rotation - (gmt/multiply + (gmt/multiply! (-> (gmt/matrix) - (gmt/translate center) - (gmt/multiply (gmt/rotate-matrix rotation)) - (gmt/translate (gpt/negate center))) - matrix))] - (recur matrix (rest modifiers)))))))) + (cond-> ^boolean (some? tf) + (gmt/multiply! tf)) + (gmt/translate! origin) + (gmt/scale! vector) + (gmt/translate! (gpt/negate origin)) + (cond-> ^boolean (some? tfi) + (gmt/multiply! tfi))) + matrix)) + + :rotation + (let [center (dm/get-prop modifier :center) + rotation (dm/get-prop modifier :rotation)] + (gmt/multiply! + (-> (gmt/matrix) + (gmt/translate! center) + (gmt/multiply! (gmt/rotate-matrix rotation)) + (gmt/translate! (gpt/negate center))) + matrix)))] + (recur matrix (next modifiers))))))) (defn apply-structure-modifiers "Apply structure changes to a shape" @@ -519,36 +581,48 @@ (cond-> shape (cph/text-shape? shape) (update :content scale-text-content value)))] + (let [remove-children (fn [shapes children-to-remove] (let [remove? (set children-to-remove)] (d/removev remove? shapes))) apply-modifier - (fn [shape {:keys [type property value index rotation]}] - (cond-> shape - (= type :rotation) - (update :rotation #(mod (+ % rotation) 360)) + (fn [shape operation] + (let [type (dm/get-prop operation :type)] + (case type + :rotation + (let [rotation (dm/get-prop operation :value)] + (update shape :rotation #(mod (+ (or % 0) rotation) 360))) - (and (= type :add-children) (some? index)) - (update :shapes - (fn [shapes] - (if (vector? shapes) - (cph/insert-at-index shapes index value) - (d/concat-vec shapes value)))) + :add-children + (let [value (dm/get-prop operation :value) + index (dm/get-prop operation :index)] + (if (some? index) + (update shape :shapes + (fn [shapes] + (if (vector? shapes) + (cph/insert-at-index shapes index value) + (d/concat-vec shapes value)))) + (update shape :shapes d/concat-vec value))) - (and (= type :add-children) (nil? index)) - (update :shapes d/concat-vec value) + :remove-children + (let [value (dm/get-prop operation :value)] + (update shape :shapes remove-children value)) - (= type :remove-children) - (update :shapes remove-children value) - (= type :scale-content) - (apply-scale-content value) + :scale-content + (let [value (dm/get-prop operation :value)] + (apply-scale-content shape value)) - (= type :change-property) - (assoc property value)))] + :change-property + (let [property (dm/get-prop operation :property) + value (dm/get-prop operation :value)] + (assoc shape property value)) + + ;; :default => no change to shape + shape)))] (as-> shape $ - (reduce apply-modifier $ (:structure-parent modifiers)) - (reduce apply-modifier $ (:structure-child modifiers)))))) + (reduce apply-modifier $ (dm/get-prop modifiers :structure-parent)) + (reduce apply-modifier $ (dm/get-prop modifiers :structure-child)))))) diff --git a/common/test/common_tests/geom_point_test.cljc b/common/test/common_tests/geom_point_test.cljc index b83d70e7c7..c980523151 100644 --- a/common/test/common_tests/geom_point_test.cljc +++ b/common/test/common_tests/geom_point_test.cljc @@ -6,6 +6,7 @@ (ns common-tests.geom-point-test (:require + [app.common.math :as mth] [app.common.geom.point :as gpt] [clojure.test :as t])) @@ -14,32 +15,32 @@ p2 (gpt/point 2 3) rs (gpt/add p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 3 (:x rs))) - (t/is (= 5 (:y rs))))) + (t/is (mth/close? 3 (:x rs))) + (t/is (mth/close? 5 (:y rs))))) (t/deftest substract-points (let [p1 (gpt/point 1 2) p2 (gpt/point 2 3) rs (gpt/subtract p1 p2)] (t/is (gpt/point? rs)) - (t/is (= -1 (:x rs))) - (t/is (= -1 (:y rs))))) + (t/is (mth/close? -1 (:x rs))) + (t/is (mth/close? -1 (:y rs))))) (t/deftest multiply-points (let [p1 (gpt/point 1 2) p2 (gpt/point 2 3) rs (gpt/multiply p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 2 (:x rs))) - (t/is (= 6 (:y rs))))) + (t/is (mth/close? 2 (:x rs))) + (t/is (mth/close? 6 (:y rs))))) (t/deftest divide-points (let [p1 (gpt/point 1 2) p2 (gpt/point 2 5) rs (gpt/divide p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 0.5 (:x rs))) - (t/is (= 0.4 (:y rs))))) + (t/is (mth/close? 0.5 (:x rs))) + (t/is (mth/close? 0.4 (:y rs))))) (t/deftest min-point (let [p1 (gpt/point 1 2) @@ -49,19 +50,19 @@ (t/is (nil? rs))) (let [rs (gpt/min p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/min nil p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/min p1 nil)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/min p1 p2)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/min p2 p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) )) (t/deftest max-point @@ -72,140 +73,140 @@ (t/is (nil? rs))) (let [rs (gpt/max p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/max nil p1)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/max p1 nil)] - (t/is (= rs p1))) + (t/is (gpt/close? rs p1))) (let [rs (gpt/max p1 p2)] - (t/is (= rs p2))) + (t/is (gpt/close? rs p2))) (let [rs (gpt/max p2 p1)] - (t/is (= rs p2))) + (t/is (gpt/close? rs p2))) )) (t/deftest inverse-point (let [p1 (gpt/point 1 2) rs (gpt/inverse p1)] (t/is (gpt/point? rs)) - (t/is (= 1 (:x rs))) - (t/is (= 0.5 (:y rs))))) + (t/is (mth/close? 1 (:x rs))) + (t/is (mth/close? 0.5 (:y rs))))) (t/deftest negate-point (let [p1 (gpt/point 1 2) rs (gpt/negate p1)] (t/is (gpt/point? rs)) - (t/is (= -1 (:x rs))) - (t/is (= -2 (:y rs))))) + (t/is (mth/close? -1 (:x rs))) + (t/is (mth/close? -2 (:y rs))))) (t/deftest distance-between-two-points (let [p1 (gpt/point 1 2) p2 (gpt/point 4 6) rs (gpt/distance p1 p2)] (t/is (number? rs)) - (t/is (= 5 rs)))) + (t/is (mth/close? 5 rs)))) (t/deftest distance-vector-between-two-points (let [p1 (gpt/point 1 2) p2 (gpt/point 2 3) rs (gpt/distance-vector p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 1 (:x rs))) - (t/is (= 1 (:y rs))))) + (t/is (mth/close? 1 (:x rs))) + (t/is (mth/close? 1 (:y rs))))) (t/deftest point-length (let [p1 (gpt/point 1 10) rs (gpt/length p1)] (t/is (number? rs)) - (t/is (= 10.04987562112089 rs)))) + (t/is (mth/close? 10.04987562112089 rs)))) (t/deftest point-angle-1 (let [p1 (gpt/point 1 3) rs (gpt/angle p1)] (t/is (number? rs)) - (t/is (= 71.56505117707799 rs)))) + (t/is (mth/close? 71.56505117707799 rs)))) (t/deftest point-angle-2 (let [p1 (gpt/point 1 3) p2 (gpt/point 2 4) rs (gpt/angle p1 p2)] (t/is (number? rs)) - (t/is (= -135 rs)))) + (t/is (mth/close? -135 rs)))) (t/deftest point-angle-with-other (let [p1 (gpt/point 1 3) p2 (gpt/point 1 5) rs (gpt/angle-with-other p1 p2)] (t/is (number? rs)) - (t/is (= 7.125016348901757 rs)))) + (t/is (mth/close? 7.125016348901757 rs)))) (t/deftest point-angle-sign (let [p1 (gpt/point 1 3) p2 (gpt/point 1 5) rs (gpt/angle-sign p1 p2)] (t/is (number? rs)) - (t/is (= 1 rs))) + (t/is (mth/close? 1 rs))) (let [p1 (gpt/point -11 -3) p2 (gpt/point 1 5) rs (gpt/angle-sign p1 p2)] (t/is (number? rs)) - (t/is (= -1 rs))) + (t/is (mth/close? -1 rs))) ) (t/deftest update-angle (let [p1 (gpt/point 1 3) rs (gpt/update-angle p1 10)] (t/is (gpt/point? rs)) - (t/is (= 3.1142355569111246 (:x rs))) - (t/is (= 0.5491237529650835 (:y rs))))) + (t/is (mth/close? 3.1142355569111246 (:x rs))) + (t/is (mth/close? 0.5491237529650835 (:y rs))))) (t/deftest point-quadrant (let [p1 (gpt/point 1 3) rs (gpt/quadrant p1)] (t/is (number? rs)) - (t/is (= 1 rs))) + (t/is (mth/close? 1 rs))) (let [p1 (gpt/point 1 -3) rs (gpt/quadrant p1)] (t/is (number? rs)) - (t/is (= 4 rs))) + (t/is (mth/close? 4 rs))) (let [p1 (gpt/point -1 3) rs (gpt/quadrant p1)] (t/is (number? rs)) - (t/is (= 2 rs))) + (t/is (mth/close? 2 rs))) (let [p1 (gpt/point -1 -3) rs (gpt/quadrant p1)] (t/is (number? rs)) - (t/is (= 3 rs))) + (t/is (mth/close? 3 rs))) ) (t/deftest round-point (let [p1 (gpt/point 1.34567 3.34567) rs (gpt/round p1)] (t/is (gpt/point? rs)) - (t/is (= 1 (:x rs))) - (t/is (= 3 (:y rs)))) + (t/is (mth/close? 1 (:x rs))) + (t/is (mth/close? 3 (:y rs)))) (let [p1 (gpt/point 1.34567 3.34567) rs (gpt/round p1 2)] (t/is (gpt/point? rs)) - (t/is (= 1.35 (:x rs))) - (t/is (= 3.35 (:y rs)))) + (t/is (mth/close? 1.35 (:x rs))) + (t/is (mth/close? 3.35 (:y rs)))) ) (t/deftest halft-round-point (let [p1 (gpt/point 1.34567 3.34567) rs (gpt/half-round p1)] (t/is (gpt/point? rs)) - (t/is (= 1.5 (:x rs))) - (t/is (= 3.5 (:y rs))))) + (t/is (mth/close? 1.5 (:x rs))) + (t/is (mth/close? 3.5 (:y rs))))) (t/deftest transform-point ;;todo @@ -215,30 +216,30 @@ (let [p1 (gpt/point 1.5 3) rs (gpt/scale p1 2)] (t/is (gpt/point? rs)) - (t/is (= 3 (:x rs))) - (t/is (= 6 (:y rs))))) + (t/is (mth/close? 3 (:x rs))) + (t/is (mth/close? 6 (:y rs))))) (t/deftest dot-point (let [p1 (gpt/point 1.5 3) p2 (gpt/point 2 6) rs (gpt/dot p1 p2)] (t/is (number? rs)) - (t/is (= 21 rs)))) + (t/is (mth/close? 21 rs)))) (t/deftest unit-point (let [p1 (gpt/point 2 3) rs (gpt/unit p1)] (t/is (gpt/point? rs)) - (t/is (= 0.5547001962252291 (:x rs))) - (t/is (= 0.8320502943378437 (:y rs))))) + (t/is (mth/close? 0.5547001962252291 (:x rs))) + (t/is (mth/close? 0.8320502943378437 (:y rs))))) (t/deftest project-point (let [p1 (gpt/point 1 3) p2 (gpt/point 1 6) rs (gpt/project p1 p2)] (t/is (gpt/point? rs)) - (t/is (= 0.5135135135135135 (:x rs))) - (t/is (= 3.081081081081081 (:y rs))))) + (t/is (mth/close? 0.5135135135135135 (:x rs))) + (t/is (mth/close? 3.081081081081081 (:y rs))))) (t/deftest center-points (let [points [(gpt/point 0.5 0.5) @@ -246,22 +247,22 @@ (gpt/point 20 65.2) (gpt/point 12 -10)] rs (gpt/center-points points)] - (t/is (= 7.875 (:x rs))) - (t/is (= 13.425 (:y rs))))) + (t/is (mth/close? 7.875 (:x rs))) + (t/is (mth/close? 13.425 (:y rs))))) (t/deftest normal-left-point (let [p1 (gpt/point 2 3) rs (gpt/normal-left p1)] (t/is (gpt/point? rs)) - (t/is (= -0.8320502943378437 (:x rs))) - (t/is (= 0.5547001962252291 (:y rs))))) + (t/is (mth/close? -0.8320502943378437 (:x rs))) + (t/is (mth/close? 0.5547001962252291 (:y rs))))) (t/deftest normal-right-point (let [p1 (gpt/point 2 3) rs (gpt/normal-right p1)] (t/is (gpt/point? rs)) - (t/is (= 0.8320502943378437 (:x rs))) - (t/is (= -0.5547001962252291 (:y rs))))) + (t/is (mth/close? 0.8320502943378437 (:x rs))) + (t/is (mth/close? -0.5547001962252291 (:y rs))))) (t/deftest point-line-distance (let [p1 (gpt/point 2 -3) @@ -269,7 +270,7 @@ p3 (gpt/point 5 6) rs (gpt/point-line-distance p1 p2 p3)] (t/is (number? rs)) - (t/is (= 7.58946638440411 rs)))) + (t/is (mth/close? 7.58946638440411 rs)))) (t/deftest almost-zero-predicate (let [p1 (gpt/point 0.000001 0.0000002) @@ -282,14 +283,14 @@ p2 (gpt/point 2 3) rs (gpt/lerp p1 p2 2)] (t/is (gpt/point? rs)) - (t/is (= 3 (:x rs))) - (t/is (= 4 (:y rs))))) + (t/is (mth/close? 3 (:x rs))) + (t/is (mth/close? 4 (:y rs))))) (t/deftest rotate-point (let [p1 (gpt/point 1 2) p2 (gpt/point 2 3) rs (gpt/rotate p1 p2 11)] (t/is (gpt/point? rs)) - (t/is (= 1.2091818119288809 (:x rs))) - (t/is (= 1.8275638211757912 (:y rs))))) + (t/is (mth/close? 1.2091818119288809 (:x rs))) + (t/is (mth/close? 1.8275638211757912 (:y rs))))) diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc index a9d1e2df74..07f971e6f4 100644 --- a/common/test/common_tests/geom_shapes_test.cljc +++ b/common/test/common_tests/geom_shapes_test.cljc @@ -140,7 +140,7 @@ (t/testing "Transform shape with rotation modifiers" (t/are [type] (let [shape-before (create-test-shape type) - modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 30 ) + modifiers (ctm/rotation-modifiers shape-before (gsh/center-shape shape-before) 30) shape-before (assoc shape-before :modifiers modifiers) shape-after (gsh/transform-shape shape-before)] diff --git a/frontend/dev/bench.cljs b/frontend/dev/bench.cljs index 41171bafe8..bc1b6cdf81 100644 --- a/frontend/dev/bench.cljs +++ b/frontend/dev/bench.cljs @@ -3,8 +3,10 @@ [app.common.data :as d] [app.common.data.macros :as dm] [app.common.geom.point :as gpt] + [app.common.geom.point :as gpt] [app.common.geom.shapes.rect :as gsr] [app.common.perf :as perf] + [app.common.types.modifiers :as ctm] [clojure.spec.alpha :as s] [clojure.test.check.generators :as gen])) @@ -24,8 +26,40 @@ :samples 20 :name "optimized")) +(def modifiers + (-> (ctm/empty) + (ctm/move (gpt/point 100 200)) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)) + (ctm/move (gpt/point -100 -200)) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)) + (ctm/rotation (gpt/point 0 0) -100) + (ctm/resize (gpt/point 100 200) (gpt/point 2.0 0.5)))) + +(defn bench-modifiers + [] + (perf/benchmark + :f #(ctm/modifiers->transform modifiers) + :max-iterations 50000 + :samples 20 + :name "current") + + #_(perf/benchmark + :f #(ctm/modifiers->transform-2 modifiers) + :max-iterations 50000 + :samples 20 + :name "optimized")) + +;; (ctm/modifiers->transform-2 modifiers) + +(defn ^:dev/after-load after-load + [] + #_(bench-modifiers)) + (defn main [& [name]] (case name "points" (bench-points) - (println "available: points"))) + "modifiers" (bench-modifiers) + (println "available: points")) + #_(.exit js/process 0)) + diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 6798a4a5e1..673f47a35e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -47,10 +47,8 @@ (defn process-shape [modifiers {:keys [id] :as shape}] (let [modifier (dm/get-in modifiers [id :modifiers])] (-> shape - (cond-> (and (some? modifier) - (not (ctm/only-move? modifier))) + (cond-> (and (some? modifier) (not (ctm/only-move? modifier))) (fix-position modifier)) - (cond-> (nil? (:position-data shape)) (assoc :migrate true)) strip-position-data))) @@ -132,6 +130,21 @@ :shape shape :grow-type (:grow-type shape)}])) +(defn text-properties-equal? + [shape other] + (or (identical? shape other) + (and + ;; Check if both shapes are equivalent removing their geometry data + (= (dissoc shape :migrate :points :selrect :height :width :x :y) + (dissoc other :migrate :points :selrect :height :width :x :y)) + + ;; Check if the position and size is close. If any of these changes the shape has changed + ;; and if not there is no geometry relevant change + (mth/close? (:x shape) (:x other)) + (mth/close? (:y shape) (:y other)) + (mth/close? (:width shape) (:width other)) + (mth/close? (:height shape) (:height other))))) + (mf/defc viewport-texts-wrapper {::mf/wrap-props false ::mf/wrap [mf/memo #(mf/deferred % ts/idle-then-raf)]} @@ -149,12 +162,9 @@ old-modifiers (ctm/select-geometry (get prev-modifiers id)) new-modifiers (ctm/select-geometry (get modifiers id)) - remote? (some? (-> new-shape meta :session-id)) ] - + remote? (some? (-> new-shape meta :session-id))] (or (and (not remote?) - (not (identical? old-shape new-shape)) - (not= (dissoc old-shape :migrate) - (dissoc new-shape :migrate))) + (not (text-properties-equal? old-shape new-shape))) (and (not= new-modifiers old-modifiers) (or (not (ctm/only-move? new-modifiers)) @@ -172,6 +182,7 @@ handle-update-modifier (mf/use-callback update-text-modifier) handle-update-shape (mf/use-callback update-text-shape)] + [:* (for [{:keys [id] :as shape} changed-texts] [:& text-container {:shape shape From 0d2b228eb779aaaff84e699e3c162cafe641d905 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 30 Nov 2022 09:55:21 +0100 Subject: [PATCH 281/682] :sparkles: Keep group constraint behaviour inside flex layout --- .../src/app/common/geom/shapes/modifiers.cljc | 59 +++++++++++++------ 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 0677a42931..bedcb1a1de 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -236,7 +236,24 @@ (and (some? auto-height) (ctl/auto-height? parent)) (set-parent-auto-height auto-height)))) -(defn- propagate-modifiers +(defn- propagate-modifiers-constraints + "Propagate modifiers to its children" + [objects bounds ignore-constraints modif-tree parent] + (let [parent-id (:id parent) + root? (= uuid/zero parent-id) + modifiers (-> (dm/get-in modif-tree [parent-id :modifiers]) + (ctm/select-geometry)) + has-modifiers? (ctm/child-modifiers? modifiers) + layout? (ctl/layout? parent) + parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) + + transformed-parent-bounds (delay (gtr/transform-bounds @(get bounds parent-id) modifiers))] + + (cond-> modif-tree + (and (not layout?) has-modifiers? parent? (not root?)) + (set-children-modifiers objects bounds parent transformed-parent-bounds ignore-constraints)))) + +(defn- propagate-modifiers-layout "Propagate modifiers to its children" [objects bounds ignore-constraints [modif-tree autolayouts] parent] (let [parent-id (:id parent) @@ -248,14 +265,11 @@ auto? (or (ctl/auto-height? parent) (ctl/auto-width? parent)) parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) - ;; If the current child is inside the layout we ignore the constraints - inside-layout? (ctl/inside-layout? objects parent) - transformed-parent-bounds (delay (gtr/transform-bounds @(get bounds parent-id) modifiers))] [(cond-> modif-tree (and (not layout?) has-modifiers? parent? (not root?)) - (set-children-modifiers objects bounds parent transformed-parent-bounds (or ignore-constraints inside-layout?)) + (set-children-modifiers objects bounds parent transformed-parent-bounds ignore-constraints) layout? (-> (process-layout-children objects bounds parent transformed-parent-bounds) @@ -280,18 +294,19 @@ other-tree)) (defn transform-bounds - [bounds objects modif-tree] + ([bounds objects modif-tree] + (transform-bounds bounds objects modif-tree (->> (keys modif-tree) (map #(get objects %))))) + ([bounds objects modif-tree tree-seq] - (loop [result bounds - ids (keys modif-tree)] - (if (empty? ids) - result + (loop [result bounds + shapes (reverse tree-seq)] + (if (empty? shapes) + result - (let [id (first ids) - shape (get objects id) - new-bounds (delay (get-group-bounds objects bounds modif-tree shape)) - result (assoc result id new-bounds)] - (recur result (rest ids)))))) + (let [shape (first shapes) + new-bounds (delay (get-group-bounds objects bounds modif-tree shape)) + result (assoc result (:id shape) new-bounds)] + (recur result (rest shapes))))))) (defn sizing-auto-modifiers "Recalculates the layouts to adjust the sizing: auto new sizes" @@ -308,7 +323,7 @@ tree-seq (resolve-tree-sequence #{current} objects) [resize-modif-tree _] - (reduce #(propagate-modifiers objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq) + (reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq) bounds (transform-bounds bounds objects resize-modif-tree) @@ -320,12 +335,18 @@ [modif-tree objects ignore-constraints snap-pixel?] (let [objects (apply-structure-modifiers objects modif-tree) - bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points])) + bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points])) shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects) - [modif-tree sizing-auto-layouts] - (reduce #(propagate-modifiers objects bounds ignore-constraints %1 %2) [modif-tree #{}] shapes-tree) + ;; Calculate the input transformation and constraints + modif-tree' (reduce #(propagate-modifiers-constraints objects bounds ignore-constraints %1 %2) modif-tree shapes-tree) + bounds (transform-bounds bounds objects modif-tree' shapes-tree) + + [modif-tree-layout sizing-auto-layouts] + (reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [{} #{}] shapes-tree) + + modif-tree (merge-modif-tree modif-tree' modif-tree-layout) ;; Calculate hug layouts positions modif-tree From a439fb65ce044b9d933b8d01428e5fee24ffa936 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 30 Nov 2022 11:49:08 +0100 Subject: [PATCH 282/682] :bug: Fix multiple selection in layout container and items --- .../ui/workspace/sidebar/options/menus/layout_item.cljs | 7 +++---- .../main/ui/workspace/sidebar/options/shapes/multiple.cljs | 3 ++- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs index 48474df03e..82d3ae5065 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_item.cljs @@ -38,10 +38,9 @@ margins (if (nil? (:layout-item-margin values)) {:m1 0 :m2 0 :m3 0 :m4 0} (:layout-item-margin values)) - rx (if (and (not (= :multiple (:layout-item-margin-type values))) - (apply = (vals margins))) - (:m1 margins) - "--")] + rx (if (or (= :multiple margins) (not (apply = (vals margins)))) + "--" + (:m1 margins))] [:div.margin-row [:div.margin-icons diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs index fd7281e326..fe9365274f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/multiple.cljs @@ -288,6 +288,7 @@ stroke-ids stroke-values text-ids text-values exports-ids exports-values + layout-container-ids layout-container-values layout-item-ids layout-item-values] (mf/use-memo (mf/deps objects-no-measures) @@ -311,7 +312,7 @@ (when-not (empty? measure-ids) [:& measures-menu {:type type :all-types all-types :ids measure-ids :values measure-values :shape shapes}]) - [:& layout-container-menu {:type type :ids [] :values []}] + [:& layout-container-menu {:type type :ids layout-container-ids :values layout-container-values}] (when is-layout-child? [:& layout-item-menu From be24989eabeb0dae442df84bcd044ac68a40864c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 30 Nov 2022 12:21:00 +0100 Subject: [PATCH 283/682] :bug: Fix problem with hug layout --- .../src/app/common/geom/shapes/modifiers.cljc | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index bedcb1a1de..a80a2f9c6b 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -244,13 +244,11 @@ modifiers (-> (dm/get-in modif-tree [parent-id :modifiers]) (ctm/select-geometry)) has-modifiers? (ctm/child-modifiers? modifiers) - layout? (ctl/layout? parent) parent? (or (cph/group-like-shape? parent) (cph/frame-shape? parent)) - transformed-parent-bounds (delay (gtr/transform-bounds @(get bounds parent-id) modifiers))] (cond-> modif-tree - (and (not layout?) has-modifiers? parent? (not root?)) + (and has-modifiers? parent? (not root?)) (set-children-modifiers objects bounds parent transformed-parent-bounds ignore-constraints)))) (defn- propagate-modifiers-layout @@ -312,7 +310,7 @@ "Recalculates the layouts to adjust the sizing: auto new sizes" [modif-tree sizing-auto-layouts objects bounds ignore-constraints] (loop [modif-tree modif-tree - bounds (transform-bounds bounds objects modif-tree) + bounds bounds sizing-auto-layouts (reverse sizing-auto-layouts)] (if-let [current (first sizing-auto-layouts)] (let [parent-base (get objects current) @@ -325,7 +323,7 @@ [resize-modif-tree _] (reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [resize-modif-tree #{}] tree-seq) - bounds (transform-bounds bounds objects resize-modif-tree) + bounds (transform-bounds bounds objects resize-modif-tree tree-seq) modif-tree (merge-modif-tree modif-tree resize-modif-tree)] (recur modif-tree bounds (rest sizing-auto-layouts))) @@ -335,20 +333,22 @@ [modif-tree objects ignore-constraints snap-pixel?] (let [objects (apply-structure-modifiers objects modif-tree) - + bounds (d/lazy-map (keys objects) #(dm/get-in objects [% :points])) shapes-tree (resolve-tree-sequence (-> modif-tree keys set) objects) ;; Calculate the input transformation and constraints - modif-tree' (reduce #(propagate-modifiers-constraints objects bounds ignore-constraints %1 %2) modif-tree shapes-tree) - bounds (transform-bounds bounds objects modif-tree' shapes-tree) + modif-tree (reduce #(propagate-modifiers-constraints objects bounds ignore-constraints %1 %2) modif-tree shapes-tree) + bounds (transform-bounds bounds objects modif-tree shapes-tree) [modif-tree-layout sizing-auto-layouts] (reduce #(propagate-modifiers-layout objects bounds ignore-constraints %1 %2) [{} #{}] shapes-tree) - modif-tree (merge-modif-tree modif-tree' modif-tree-layout) + modif-tree (merge-modif-tree modif-tree modif-tree-layout) ;; Calculate hug layouts positions + bounds (transform-bounds bounds objects modif-tree-layout shapes-tree) + modif-tree (-> modif-tree (sizing-auto-modifiers sizing-auto-layouts objects bounds ignore-constraints)) From c451c7bb9da0a711fc82ac0cddc56e2a7d82941e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 30 Nov 2022 13:13:11 +0100 Subject: [PATCH 284/682] :bug: Fix regression on worker task handling --- backend/src/app/worker.clj | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index f54a3add1d..6df88a902e 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -409,7 +409,9 @@ {:status :retry :task task :error cause}))))) (get-task [task-id] - (ex/try (db/get* pool :task {:id task-id}))) + (ex/try! + (some-> (db/get* pool :task {:id task-id}) + (decode-task-row)))) (run-task [task-id] (loop [task (get-task task-id)] From 412564b418c67becd28f43c24affee2f99cb900b Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 29 Nov 2022 14:03:08 +0100 Subject: [PATCH 285/682] :tada: Add inspect tab to workspace --- .../partials/sidebar-element-options.scss | 6 ++++ .../styles/main/partials/sidebar.scss | 18 ++++++++++ .../styles/main/partials/workspace.scss | 7 +++- frontend/src/app/main/data/workspace.cljs | 13 +++++++ .../src/app/main/data/workspace/layout.cljs | 2 +- frontend/src/app/main/refs.cljs | 6 ++++ .../src/app/main/ui/viewer/handoff/code.cljs | 12 ++++--- .../main/ui/viewer/handoff/right_sidebar.cljs | 27 +++++++++----- .../src/app/main/ui/workspace/sidebar.cljs | 16 +++++---- .../main/ui/workspace/sidebar/options.cljs | 35 +++++++++++++------ .../src/app/main/ui/workspace/viewport.cljs | 7 ++-- .../app/main/ui/workspace/viewport/hooks.cljs | 11 +++--- frontend/translations/en.po | 3 ++ frontend/translations/es.po | 3 ++ 14 files changed, 130 insertions(+), 36 deletions(-) diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 1f1c28f303..7701d5f23b 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -53,6 +53,12 @@ } } + &.inspect { + & > :first-child { + margin-top: 7px; + } + } + .element-set { border-bottom: 1px solid $color-gray-60; color: $color-gray-20; diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index f14aca961b..15d63f034f 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -197,6 +197,24 @@ height: 100%; width: 100%; overflow-y: auto; + + &.inspect { + .tab-container-tabs { + padding-bottom: 0.5rem; + background-color: $color-gray-50; + border-bottom: 1px solid $color-gray-60; + height: 3rem; + } + + .tab-container-tab-title { + border-radius: 4px; + + &.current { + background-color: $color-primary; + color: black; + } + } + } } .element-list { diff --git a/frontend/resources/styles/main/partials/workspace.scss b/frontend/resources/styles/main/partials/workspace.scss index 53ecfb202f..7cc8587a9a 100644 --- a/frontend/resources/styles/main/partials/workspace.scss +++ b/frontend/resources/styles/main/partials/workspace.scss @@ -47,10 +47,15 @@ $height-palette-max: 80px; } .settings-bar.settings-bar-right { + transition: width 0.2s; min-width: $width-settings-bar; - max-width: 500px; + max-width: $width-settings-bar * 3; width: $width-settings-bar; grid-area: right-sidebar; + + &.expanded { + width: $width-settings-bar * 3; + } } .workspace-loader { diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 4bece07480..1f880406cf 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1818,6 +1818,19 @@ (rx/empty))))) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Inspect +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + + +(defn set-inspect-expanded + [expanded?] + (ptk/reify ::set-inspect-expanded + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :inspect-expanded] expanded?)))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Exports ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/layout.cljs b/frontend/src/app/main/data/workspace/layout.cljs index 03e065882d..ac2fd9cbd4 100644 --- a/frontend/src/app/main/data/workspace/layout.cljs +++ b/frontend/src/app/main/data/workspace/layout.cljs @@ -44,7 +44,7 @@ {:del #{:document-history :assets} :add #{:sitemap :layers}}}) -(s/def ::options-mode #{:design :prototype}) +(s/def ::options-mode #{:design :prototype :inspect}) (def default-layout #{:sitemap diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 147efe5dca..4bc06f8d08 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -165,6 +165,12 @@ (def options-mode (l/derived :options-mode workspace-local)) +(def options-mode-global + (l/derived :options-mode workspace-global)) + +(def inspect-expanded + (l/derived :inspect-expanded workspace-local)) + (def vbox (l/derived :vbox workspace-local)) diff --git a/frontend/src/app/main/ui/viewer/handoff/code.cljs b/frontend/src/app/main/ui/viewer/handoff/code.cljs index 6213719bcf..1f4a9e7f7b 100644 --- a/frontend/src/app/main/ui/viewer/handoff/code.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/code.cljs @@ -7,7 +7,9 @@ (ns app.main.ui.viewer.handoff.code (:require ["js-beautify" :as beautify] + [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] + [app.common.uuid :as uuid] [app.main.data.events :as ev] [app.main.refs :as refs] [app.main.store :as st] @@ -21,8 +23,10 @@ [potok.core :as ptk] [rumext.v2 :as mf])) -(defn generate-markup-code [_type shapes] - (let [frame (dom/query js/document "#svg-frame") +(defn generate-markup-code [_type shapes from] + (let [frame (if (= from :workspace) + (dom/query js/document (dm/str "#shape-" uuid/zero)) + (dom/query js/document "#svg-frame")) markup-shape (fn [shape] (let [selector (str "#shape-" (:id shape) (when (= :text (:type shape)) " .root"))] @@ -50,7 +54,7 @@ (mf/deref get-layout-children-refs))) (mf/defc code - [{:keys [shapes frame on-expand]}] + [{:keys [shapes frame on-expand from]}] (let [style-type (mf/use-state "css") markup-type (mf/use-state "svg") shapes (->> shapes @@ -62,7 +66,7 @@ style-code (-> (cg/generate-style-code @style-type shapes) (format-code "css")) - markup-code (-> (mf/use-memo (mf/deps shapes) #(generate-markup-code @markup-type shapes)) + markup-code (-> (mf/use-memo (mf/deps shapes) #(generate-markup-code @markup-type shapes from)) (format-code "svg")) on-markup-copied diff --git a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs index f2b2288b99..629cc80304 100644 --- a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.viewer.handoff.right-sidebar (:require + [app.main.data.workspace :as dw] [app.main.ui.components.shape-icon :as si] [app.main.ui.components.tab-container :refer [tab-container tab-element]] [app.main.ui.icons :as i] @@ -16,12 +17,16 @@ [rumext.v2 :as mf])) (mf/defc right-sidebar - [{:keys [frame page file selected]}] + [{:keys [frame page file selected shapes page-id file-id from] + :or {from :handoff}}] (let [expanded (mf/use-state false) section (mf/use-state :info #_:code) - shapes (resolve-shapes (:objects page) selected) + shapes (or shapes + (resolve-shapes (:objects page) selected)) - first-shape (first shapes)] + first-shape (first shapes) + page-id (or page-id (:id page)) + file-id (or file-id (:id file))] [:aside.settings-bar.settings-bar-right {:class (when @expanded "expanded")} [:div.settings-bar-inside @@ -48,18 +53,24 @@ ;; handoff.tabs.code.selected.svg-raw ;; handoff.tabs.code.selected.text [:span.tool-window-bar-title (:name first-shape)]])] - [:div.tool-window-content + [:div.tool-window-content.inspect [:& tab-container {:on-change-tab #(do (reset! expanded false) - (reset! section %)) + (reset! section %) + (when (= from :workspace) + (dw/set-inspect-expanded false))) :selected @section} [:& tab-element {:id :info :title (tr "handoff.tabs.info")} - [:& attributes {:page-id (:id page) - :file-id (:id file) + [:& attributes {:page-id page-id + :file-id file-id :frame frame :shapes shapes}]] [:& tab-element {:id :code :title (tr "handoff.tabs.code")} [:& code {:frame frame :shapes shapes - :on-expand #(swap! expanded not)}]]]]])]])) + :on-expand (fn[] + (when (= from :workspace) + (dw/set-inspect-expanded (not expanded))) + (swap! expanded not)) + :from from}]]]]])]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 1d8fbafdea..4add5cba94 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -29,8 +29,10 @@ (mf/defc left-sidebar {:wrap [mf/memo]} [{:keys [layout] :as props}] - (let [section (cond (contains? layout :layers) :layers - (contains? layout :assets) :assets) + (let [options-mode (mf/deref refs/options-mode-global) + mode-inspect? (= options-mode :inspect) + section (cond (or mode-inspect? (contains? layout :layers)) :layers + (contains? layout :assets) :assets) shortcuts? (contains? layout :shortcuts) {:keys [on-pointer-down on-lost-pointer-capture on-mouse-move parent-ref size]} @@ -68,8 +70,9 @@ [:& sitemap {:layout layout}] [:& layers-toolbox]]] - [:& tab-element {:id :assets :title (tr "workspace.toolbar.assets")} - [:& assets-toolbox]]]])]]])) + (when-not mode-inspect? + [:& tab-element {:id :assets :title (tr "workspace.toolbar.assets")} + [:& assets-toolbox]])]])]]])) ;; --- Right Sidebar (Component) @@ -78,9 +81,10 @@ ::mf/wrap [mf/memo]} [props] (let [layout (obj/get props "layout") - drawing-tool (:tool (mf/deref refs/workspace-drawing))] + drawing-tool (:tool (mf/deref refs/workspace-drawing)) + expanded (mf/deref refs/inspect-expanded)] - [:aside.settings-bar.settings-bar-right + [:aside.settings-bar {:class (when expanded "expanded")} [:div.settings-bar-inside (cond (= drawing-tool :comments) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 413a776c84..0be9a249bb 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -8,11 +8,13 @@ (:require [app.common.data :as d] [app.common.geom.shapes :as gsh] + [app.common.pages.helpers :as cph] [app.main.data.workspace :as udw] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.tab-container :refer [tab-container tab-element]] [app.main.ui.context :as ctx] + [app.main.ui.viewer.handoff.right-sidebar :as hrs] [app.main.ui.workspace.sidebar.options.menus.align :refer [align-options]] [app.main.ui.workspace.sidebar.options.menus.bool :refer [bool-options]] [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]] @@ -62,17 +64,21 @@ (mf/defc options-content {::mf/wrap [mf/memo]} [{:keys [selected section shapes shapes-with-children page-id file-id]}] - (let [drawing (mf/deref refs/workspace-drawing) - objects (mf/deref refs/workspace-page-objects) - shared-libs (mf/deref refs/workspace-libraries) - selected-shapes (into [] (keep (d/getf objects)) selected)] + (let [drawing (mf/deref refs/workspace-drawing) + objects (mf/deref refs/workspace-page-objects) + shared-libs (mf/deref refs/workspace-libraries) + selected-shapes (into [] (keep (d/getf objects)) selected) + first-selected-shape (first selected-shapes) + shape-parent-frame (cph/get-frame objects (:frame-id first-selected-shape)) + on-change-tab + (fn [options-mode] + (st/emit! (udw/set-options-mode options-mode)) + (if (= options-mode :inspect) ;;TODO maybe move this logic to set-options-mode + (st/emit! :interrupt (udw/set-workspace-read-only true)) + (st/emit! :interrupt (udw/set-workspace-read-only false))))] [:div.tool-window [:div.tool-window-content - [:& tab-container {:on-change-tab (fn [options-mode] - (st/emit! (udw/set-options-mode options-mode)) - (if (= options-mode :prototype) ;;TODO remove, only for test palba - (st/emit! :interrupt (udw/deselect-all true) (udw/set-workspace-read-only true)) - (st/emit! :interrupt (udw/set-workspace-read-only false)))) + [:& tab-container {:on-change-tab on-change-tab :selected section} [:& tab-element {:id :design :title (tr "workspace.options.design")} @@ -99,7 +105,16 @@ [:& tab-element {:id :prototype :title (tr "workspace.options.prototype")} [:div.element-options - [:& interactions-menu {:shape (first shapes)}]]]]]])) + [:& interactions-menu {:shape (first shapes)}]]] + + [:& tab-element {:id :inspect + :title (tr "workspace.options.inspect")} + [:div.element-options + [:& hrs/right-sidebar {:page-id page-id + :file-id file-id + :frame shape-parent-frame + :shapes selected-shapes + :from :workspace}]]]]]])) ;; TODO: this need optimizations, selected-objects and ;; selected-objects-with-children are derefed always but they only diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index b4cebb8baf..418c22a5a7 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -132,6 +132,7 @@ text-editing? (and edition (= :text (get-in base-objects [edition :type]))) workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) + mode-inspect? (= options-mode :inspect) on-click (actions/on-click hover selected edition drawing-path? drawing-tool space? selrect) on-context-menu (actions/on-context-menu hover hover-ids workspace-read-only?) @@ -181,7 +182,9 @@ (contains? layout :snap-grid)) (or drawing-obj transform)) show-selrect? (and selrect (empty? drawing) (not text-editing?)) - show-measures? (and (not transform) (not node-editing?) show-distances?) + show-measures? (and (not transform) + (not node-editing?) + (or show-distances? mode-inspect?)) show-artboard-names? (contains? layout :display-artboard-names) show-rules? (and (contains? layout :rules) (not (contains? layout :hide-ui))) @@ -190,7 +193,7 @@ (hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport? workspace-read-only?) (hooks/setup-viewport-size viewport-ref) - (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing?) + (hooks/setup-cursor cursor alt? mod? space? panning drawing-tool drawing-path? node-editing? workspace-read-only?) (hooks/setup-keyboard alt? mod? space?) (hooks/setup-hover-shapes page-id move-stream base-objects transform selected mod? hover hover-ids hover-top-frame-id @hover-disabled? focus zoom) (hooks/setup-viewport-modifiers modifiers base-objects) diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index ffd21250e8..508a227dc8 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -63,16 +63,16 @@ ;; We schedule the event so it fires after `initialize-page` event (timers/schedule #(st/emit! (dw/initialize-viewport size))))))) -(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing?] +(defn setup-cursor [cursor alt? mod? space? panning drawing-tool drawing-path? path-editing? workspace-read-only?] (mf/use-effect - (mf/deps @cursor @alt? @mod? @space? panning drawing-tool drawing-path? path-editing?) + (mf/deps @cursor @alt? @mod? @space? panning drawing-tool drawing-path? path-editing? workspace-read-only?) (fn [] (let [show-pen? (or (= drawing-tool :path) (and drawing-path? (not= drawing-tool :curve))) new-cursor (cond - (and @mod? @space?) (utils/get-cursor :zoom) + (and @mod? @space?) (utils/get-cursor :zoom) (or panning @space?) (utils/get-cursor :hand) (= drawing-tool :comments) (utils/get-cursor :comments) (= drawing-tool :frame) (utils/get-cursor :create-artboard) @@ -81,7 +81,10 @@ show-pen? (utils/get-cursor :pen) (= drawing-tool :curve) (utils/get-cursor :pencil) drawing-tool (utils/get-cursor :create-shape) - (and @alt? (not path-editing?)) (utils/get-cursor :duplicate) + (and + @alt? + (not path-editing?) + (not workspace-read-only?)) (utils/get-cursor :duplicate) :else (utils/get-cursor :pointer-inner))] (when (not= @cursor new-cursor) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 2a15e57a3d..a113b8b820 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -3619,6 +3619,9 @@ msgstr "Position" msgid "workspace.options.prototype" msgstr "Prototype" +msgid "workspace.options.inspect" +msgstr "Inspect" + msgid "workspace.options.radius" msgstr "Radius" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 537142d3af..5e9e36dc18 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -4019,6 +4019,9 @@ msgstr "Posición" msgid "workspace.options.prototype" msgstr "Prototipo" +msgid "workspace.options.inspect" +msgstr "Inspeccionar" + msgid "workspace.options.radius" msgstr "Radio" From de1a3de433aaaa556492711519470ab90eed63e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 30 Nov 2022 11:25:54 +0100 Subject: [PATCH 286/682] :lipstick: Make cleaner debug logs --- frontend/src/debug.cljs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 4cdbe42cda..1b1625fc74 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.logging :as l] + [app.common.math :as mth] [app.common.transit :as t] [app.common.types.file :as ctf] [app.common.uuid :as uuid] @@ -120,17 +121,28 @@ (effect-fn input) (rf result input))))) +(defn prettify + "Prepare x fror cleaner output when logged." + [x] + (cond + (map? x) (d/mapm #(prettify %2) x) + (vector? x) (mapv prettify x) + (seq? x) (map prettify x) + (set? x) (into #{} (map prettify x)) + (number? x) (mth/precision x 4) + (uuid? x) (str "#uuid " x) + :else x)) + (defn ^:export logjs ([str] (tap (partial logjs str))) ([str val] - (js/console.log str (clj->js val)) + (js/console.log str (clj->js (prettify val))) val)) (when (exists? js/window) (set! (.-dbg ^js js/window) clj->js) (set! (.-pp ^js js/window) pprint)) - (defonce widget-style " background: black; bottom: 10px; From 4c1f2cfdedbe9d225d878bed7f5387e6991ad268 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Mon, 28 Nov 2022 09:47:47 +0100 Subject: [PATCH 287/682] :tada: Add support color management via library --- common/src/app/common/file_builder.cljc | 19 +++++++++++++++++-- frontend/src/app/libs/file_builder.cljs | 24 ++++++++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index a45d0d027b..d4520fcb51 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -498,15 +498,30 @@ (defn add-library-color [file color] - (let [id (or (:id color) (uuid/next))] (-> file (commit-change {:type :add-color - :id id :color (assoc color :id id)}) (assoc :last-id id)))) +(defn update-library-color + [file color] + (let [id (uuid/uuid (:id color))] + (-> file + (commit-change + {:type :mod-color + :color (assoc color :id id)}) + (assoc :last-id (:id color))))) + +(defn delete-library-color + [file color-id] + (let [id (uuid/uuid color-id)] + (-> file + (commit-change + {:type :del-color + :id id})))) + (defn add-library-typography [file typography] (let [id (or (:id typography) (uuid/next))] diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 1a4af9fb56..25aec3ffee 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -27,7 +27,6 @@ key (-> key d/name str/kebab keyword)] [key value])) $))) - (defn export-file [file] (let [file (assoc file @@ -51,6 +50,14 @@ (rx/observe-on :async) (rx/flat-map e/get-page-data) (rx/share)) + + colors-stream + (->> files-stream + (rx/flat-map vals) + (rx/map #(vector (:id %) (get-in % [:data :colors]))) + (rx/filter #(d/not-empty? (second %))) + (rx/map e/parse-library-color)) + pages-stream (->> render-stream (rx/map e/collect-page))] @@ -64,7 +71,8 @@ (->> (rx/merge manifest-stream - pages-stream) + pages-stream + colors-stream) (rx/reduce conj []) (rx/with-latest-from files-stream) (rx/flat-map (fn [[data _]] @@ -133,6 +141,18 @@ (closeSVG [_] (set! file (fb/close-svg-raw file))) + (addLibraryColor [_ data] + (set! file (fb/add-library-color file (parse-data data))) + (str (:last-id file))) + + (updateLibraryColor [_ data] + (set! file (fb/update-library-color file (parse-data data))) + (str (:last-id file))) + + (deleteLibraryColor [_ data] + (set! file (fb/delete-library-color file (parse-data data))) + (str (:last-id file))) + (asMap [_] (clj->js file)) From a19417417a46656e8fa98d2af072b7eb955ff7a6 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 30 Nov 2022 06:25:21 +0100 Subject: [PATCH 288/682] :tada: Add support components managemente via library --- common/src/app/common/file_builder.cljc | 37 +++++++++++++++++++++- frontend/src/app/libs/file_builder.cljs | 41 +++++++++++++++++++++---- 2 files changed, 71 insertions(+), 7 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index d4520fcb51..2bd2a5dc00 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -661,6 +661,40 @@ shapes) (dissoc $ :current-component-id)))) +(defn create-component-instance + [file data] + (let [component-id (uuid/uuid (:component-id data)) + x (:x data) + y (:y data) + file (assoc file :current-component-id component-id) + page-id (:current-page-id file) + page (ctpl/get-page (:data file) page-id) + component (ctkl/get-component (:data file) component-id) + ;; main-instance-id (:main-instance-id component) + + [shape shapes] + (ctn/make-component-instance page + component + (:id file) + (gpt/point x + y) + #_{:main-instance? true + :force-id main-instance-id})] + + (as-> file $ + (reduce #(commit-change %1 + {:type :add-obj + :id (:id %2) + :page-id (:id page) + :parent-id (:parent-id %2) + :frame-id (:frame-id %2) + :obj %2}) + $ + shapes) + + (assoc $ :last-id (:id shape)) + (dissoc $ :current-component-id)))) + (defn delete-object [file id] (let [page-id (:current-page-id file)] @@ -687,7 +721,8 @@ {:type :mod-obj :operations (reduce generate-operation [] attrs) :page-id page-id - :id (:id old-obj)})))) + :id (:id old-obj)}) + (assoc :last-id (:id old-obj))))) (defn get-current-page [file] diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 25aec3ffee..4070ed1f85 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -58,6 +58,12 @@ (rx/filter #(d/not-empty? (second %))) (rx/map e/parse-library-color)) + components-stream + (->> files-stream + (rx/flat-map vals) + (rx/filter #(d/not-empty? (get-in % [:data :components]))) + (rx/flat-map e/parse-library-components)) + pages-stream (->> render-stream (rx/map e/collect-page))] @@ -72,6 +78,7 @@ (->> (rx/merge manifest-stream pages-stream + components-stream colors-stream) (rx/reduce conj []) (rx/with-latest-from files-stream) @@ -153,16 +160,38 @@ (set! file (fb/delete-library-color file (parse-data data))) (str (:last-id file))) + (startComponent [_ data] + (set! file (fb/start-component file (parse-data data))) + (str (:current-component-id file))) + + (finishComponent [_] + (set! file (fb/finish-component file))) + + (createComponentInstance [_ data] + (set! file (fb/create-component-instance file (parse-data data))) + (str (:last-id file))) + + (lookupShape [_ shape-id] + (clj->js (fb/lookup-shape file (uuid/uuid shape-id)))) + + (updateObject [_ id new-obj] + (let [old-obj (fb/lookup-shape file (uuid/uuid id)) + new-obj (d/deep-merge old-obj (parse-data new-obj))] + (set! file (fb/update-object file old-obj new-obj)))) + + (deleteObject [_ id] + (set! file (fb/delete-object file (uuid/uuid id)))) + (asMap [_] (clj->js file)) (export [_] - (->> (export-file file) - (rx/subs - (fn [value] - (when (not (contains? value :type)) - (let [[file export-blob] value] - (dom/trigger-download (:name file) export-blob)))))))) + (->> (export-file file) + (rx/subs + (fn [value] + (when (not (contains? value :type)) + (let [[file export-blob] value] + (dom/trigger-download (:name file) export-blob)))))))) (defn create-file-export [^string name] (File. (fb/create-file name))) From fffacf35528369773e7e341c5bfbe5a1c846bf6a Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 30 Nov 2022 13:29:02 +0100 Subject: [PATCH 289/682] :tada: Add support media management via library --- common/src/app/common/file_builder.cljc | 8 ++++ frontend/src/app/libs/file_builder.cljs | 49 +++++++++++++++++++++++++ frontend/src/app/worker/export.cljs | 17 ++++----- 3 files changed, 65 insertions(+), 9 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 2bd2a5dc00..0f04df9806 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -541,6 +541,14 @@ :object (assoc media :id id)}) (assoc :last-id id)))) +(defn delete-library-media + [file media-id] + (let [id (uuid/uuid media-id)] + (-> file + (commit-change + {:type :del-media + :id id})))) + (defn start-component [file data] diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 4070ed1f85..0d4e723511 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -8,8 +8,11 @@ (:require [app.common.data :as d] [app.common.file-builder :as fb] + [app.common.media :as cm] [app.common.uuid :as uuid] [app.util.dom :as dom] + [app.util.json :as json] + [app.util.webapi :as wapi] [app.util.zip :as uz] [app.worker.export :as e] [beicon.core :as rx] @@ -27,6 +30,36 @@ key (-> key d/name str/kebab keyword)] [key value])) $))) +(defn data-uri->blob + [data-uri] + (let [[mtype b64-data] (str/split data-uri ";base64,") + mtype (subs mtype (inc (str/index-of mtype ":"))) + decoded (.atob js/window b64-data) + size (.-length ^js decoded) + content (js/Uint8Array. size)] + + (doseq [i (range 0 size)] + (aset content i (.charCodeAt decoded i))) + + (wapi/create-blob content mtype))) + +(defn parse-library-media + [[file-id media]] + (rx/merge + (let [markup + (->> (vals media) + (reduce e/collect-media {}) + (json/encode))] + (rx/of (vector (str file-id "/media.json") markup))) + + (->> (rx/from (vals media)) + (rx/map #(assoc % :file-id file-id)) + (rx/flat-map + (fn [media] + (let [file-path (str/concat file-id "/media/" (:id media) (cm/mtype->extension (:mtype media))) + blob (data-uri->blob (:uri media))] + (rx/of (vector file-path blob)))))))) + (defn export-file [file] (let [file (assoc file @@ -58,6 +91,13 @@ (rx/filter #(d/not-empty? (second %))) (rx/map e/parse-library-color)) + media-stream + (->> files-stream + (rx/flat-map vals) + (rx/map #(vector (:id %) (get-in % [:data :media]))) + (rx/filter #(d/not-empty? (second %))) + (rx/flat-map parse-library-media)) + components-stream (->> files-stream (rx/flat-map vals) @@ -79,6 +119,7 @@ manifest-stream pages-stream components-stream + media-stream colors-stream) (rx/reduce conj []) (rx/with-latest-from files-stream) @@ -160,6 +201,14 @@ (set! file (fb/delete-library-color file (parse-data data))) (str (:last-id file))) + (addLibraryMedia [_ data] + (set! file (fb/add-library-media file (parse-data data))) + (str (:last-id file))) + + (deleteLibraryMedia [_ data] + (set! file (fb/delete-library-media file (parse-data data))) + (str (:last-id file))) + (startComponent [_ data] (set! file (fb/start-component file (parse-data data))) (str (:current-component-id file))) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index c40a172b90..ac3ecfac4b 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -155,15 +155,14 @@ (->> (r/render-components (:data file) :deleted-components) (rx/map #(vector (str (:id file) "/deleted-components.svg") %)))) -(defn fetch-file-with-libraries - [file-id components-v2] - (let [features (cond-> #{} components-v2 (conj "components/v2"))] - (->> (rx/zip (rp/cmd! :get-file {:id file-id :features features}) - (rp/cmd! :get-file-libraries {:file-id file-id})) - (rx/map - (fn [[file file-libraries]] - (let [libraries-ids (->> file-libraries (map :id) (filterv #(not= (:id file) %)))] - (assoc file :libraries libraries-ids))))))) +(defn fetch-file-with-libraries [file-id components-v2] + (->> (rx/zip (rp/query :file {:id file-id :components-v2 components-v2}) + (rp/query :file-libraries {:file-id file-id})) + (rx/map + (fn [[file file-libraries]] + (let [libraries-ids (->> file-libraries (map :id) (filterv #(not= (:id file) %)))] + (-> file + (assoc :libraries libraries-ids))))))) (defn get-component-ref-file [objects shape] From 32520884948f747fa10bd899404b4fa8eb3edaf5 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Wed, 30 Nov 2022 13:56:44 +0100 Subject: [PATCH 290/682] :tada: Add support typography management via library --- common/src/app/common/file_builder.cljc | 8 ++++++++ frontend/src/app/libs/file_builder.cljs | 18 +++++++++++++++++- frontend/src/app/worker/export.cljs | 17 +++++++++-------- 3 files changed, 34 insertions(+), 9 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 0f04df9806..1706452c2d 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -532,6 +532,14 @@ :typography (assoc typography :id id)}) (assoc :last-id id)))) +(defn delete-library-typography + [file typography-id] + (let [id (uuid/uuid typography-id)] + (-> file + (commit-change + {:type :del-typography + :id id})))) + (defn add-library-media [file media] (let [id (or (:id media) (uuid/next))] diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 0d4e723511..4c1e42adb6 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -91,6 +91,13 @@ (rx/filter #(d/not-empty? (second %))) (rx/map e/parse-library-color)) + typographies-stream + (->> files-stream + (rx/flat-map vals) + (rx/map #(vector (:id %) (get-in % [:data :typographies]))) + (rx/filter #(d/not-empty? (second %))) + (rx/map e/parse-library-typographies)) + media-stream (->> files-stream (rx/flat-map vals) @@ -120,7 +127,8 @@ pages-stream components-stream media-stream - colors-stream) + colors-stream + typographies-stream) (rx/reduce conj []) (rx/with-latest-from files-stream) (rx/flat-map (fn [[data _]] @@ -209,6 +217,14 @@ (set! file (fb/delete-library-media file (parse-data data))) (str (:last-id file))) + (addLibraryTypography [_ data] + (set! file (fb/add-library-typography file (parse-data data))) + (str (:last-id file))) + + (deleteLibraryTypography [_ data] + (set! file (fb/delete-library-typography file (parse-data data))) + (str (:last-id file))) + (startComponent [_ data] (set! file (fb/start-component file (parse-data data))) (str (:current-component-id file))) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index ac3ecfac4b..c40a172b90 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -155,14 +155,15 @@ (->> (r/render-components (:data file) :deleted-components) (rx/map #(vector (str (:id file) "/deleted-components.svg") %)))) -(defn fetch-file-with-libraries [file-id components-v2] - (->> (rx/zip (rp/query :file {:id file-id :components-v2 components-v2}) - (rp/query :file-libraries {:file-id file-id})) - (rx/map - (fn [[file file-libraries]] - (let [libraries-ids (->> file-libraries (map :id) (filterv #(not= (:id file) %)))] - (-> file - (assoc :libraries libraries-ids))))))) +(defn fetch-file-with-libraries + [file-id components-v2] + (let [features (cond-> #{} components-v2 (conj "components/v2"))] + (->> (rx/zip (rp/cmd! :get-file {:id file-id :features features}) + (rp/cmd! :get-file-libraries {:file-id file-id})) + (rx/map + (fn [[file file-libraries]] + (let [libraries-ids (->> file-libraries (map :id) (filterv #(not= (:id file) %)))] + (assoc file :libraries libraries-ids))))))) (defn get-component-ref-file [objects shape] From afe8883e37461e3d76858d4ade9bb60084669191 Mon Sep 17 00:00:00 2001 From: Alejandro Alonso Date: Thu, 1 Dec 2022 11:42:18 +0100 Subject: [PATCH 291/682] :bug: Fix develop branch --- frontend/src/app/main/data/workspace.cljs | 7 ++++--- frontend/src/app/main/data/workspace/media.cljs | 2 +- frontend/src/app/main/data/workspace/svg_upload.cljs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index f7cbdc6c5b..af4f881608 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1609,11 +1609,12 @@ :width width :height height :grow-type (if (> (count text) 100) :auto-height :auto-width) - :content (as-content text)}] + :content (as-content text)} + undo-id (uuid/next)] - (rx/of (dwu/start-undo-transaction) + (rx/of (dwu/start-undo-transaction undo-id) (dwsh/create-and-add-shape :text x y shape) - (dwu/commit-undo-transaction)))))) + (dwu/commit-undo-transaction undo-id)))))) ;; TODO: why not implement it in terms of upload-media-workspace? (defn- paste-svg diff --git a/frontend/src/app/main/data/workspace/media.cljs b/frontend/src/app/main/data/workspace/media.cljs index b94125f8f7..086a4bb951 100644 --- a/frontend/src/app/main/data/workspace/media.cljs +++ b/frontend/src/app/main/data/workspace/media.cljs @@ -249,7 +249,7 @@ process-svg (fn [svg-data] (let [[shape children] - (svg/create-svg-shapes svg-data pos objects uuid/zero #{} false)] + (svg/create-svg-shapes svg-data pos objects uuid/zero nil #{} false)] [shape children]))] (->> (upload-images svg-data) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index d1a6e1bdc5..c96dee88d2 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -559,7 +559,7 @@ page-selected (wsh/lookup-selected state) base (cph/get-base-shape page-objects page-selected) parent-id (:parent-id base) - + [new-shape new-children] (create-svg-shapes svg-data position objects frame-id parent-id selected true) changes (-> (pcb/empty-changes it page-id) From 0e949679d97cd7cbdd4ea50b266ac7db1027c0cd Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 1 Dec 2022 13:04:45 +0100 Subject: [PATCH 292/682] :sparkles: Parse frontend features from flags Simplifies setting features on deployments. --- frontend/src/app/config.cljs | 6 --- frontend/src/app/main/features.cljs | 74 +++++++++++++++++++---------- 2 files changed, 48 insertions(+), 32 deletions(-) diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 52214f9465..3966cfae85 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -65,11 +65,6 @@ flags (sequence (map keyword) (str/words flags))] (flags/parse flags/default default-flags flags))) -(defn- parse-features - [global] - (when-let [features-str (obj/get global "penpotFeatures")] - (map keyword (str/words features-str)))) - (defn- parse-version [global] (-> (obj/get global "penpotVersion") @@ -94,7 +89,6 @@ (def build-date (parse-build-date global)) (def flags (atom (parse-flags global))) -(def features (atom (parse-features global))) (def version (atom (parse-version global))) (def target (atom (parse-target global))) (def browser (atom (parse-browser))) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index 5869826cc3..d7b0f1af66 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -8,38 +8,54 @@ (:require [app.common.data :as d] [app.common.logging :as log] - [app.config :as cfg] + [app.config :as cf] [app.main.store :as st] + [app.util.timers :as tm] + [cuerdas.core :as str] [okulary.core :as l] [potok.core :as ptk] [rumext.v2 :as mf])) -(log/set-level! :debug) +(log/set-level! :trace) -(def available-features #{:auto-layout :components-v2}) +(def available-features + #{:auto-layout :components-v2}) (defn- toggle-feature [feature] (ptk/reify ::toggle-feature ptk/UpdateEvent (update [_ state] - (log/debug :msg "toggle-feature" - :feature (d/name feature) - :result (if (not (contains? (:features state) feature)) - "enabled" - "disabled")) - (-> state - (update :features - (fn [features] - (let [features (or features #{})] - (if (contains? features feature) - (disj features feature) - (conj features feature))))))))) + (let [features (or (:features state) #{})] + (if (contains? features feature) + (do + (log/debug :hint "feature disabled" :feature (d/name feature)) + (assoc state :features (disj features feature))) + (do + (log/debug :hint "feature enabled" :feature (d/name feature)) + (assoc state :features (conj features feature)))))))) + +(defn- enable-feature + [feature] + (ptk/reify ::enable-feature + ptk/UpdateEvent + (update [_ state] + (let [features (or (:features state) #{})] + (if (contains? features feature) + state + (do + (log/debug :hint "feature enabled" :feature (d/name feature)) + (assoc state :features (conj features feature)))))))) (defn toggle-feature! [feature] (assert (contains? available-features feature) "Not supported feature") - (st/emit! (toggle-feature feature))) + (tm/schedule-on-idle #(st/emit! (toggle-feature feature)))) + +(defn enable-feature! + [feature] + (assert (contains? available-features feature) "Not supported feature") + (tm/schedule-on-idle #(st/emit! (enable-feature feature)))) (defn active-feature? ([feature] @@ -62,14 +78,20 @@ active-feature? (mf/deref active-feature-ref)] active-feature?)) -;; Read initial enabled features from config, if set -(if-let [enabled-features @cfg/features] - (doseq [f enabled-features] - (toggle-feature! f)) +;; Enable all features set on the configuration +(->> @cf/flags + (map str) + (keep (fn [flag] + (when (str/starts-with? flag "frontend-feature-") + (subs flag 17)))) + (map keyword) + (run! enable-feature!)) - (when *assert* - ;; By default, all features disabled, except in development - ;; environment, that are enabled except components-v2 - (doseq [f available-features] - (when (not= f :components-v2) - (toggle-feature! f))))) +;; Enable the rest of available configuration if we are on development +;; environemnt (aka devenv). +(when *assert* + ;; By default, all features disabled, except in development + ;; environment, that are enabled except components-v2 + (->> available-features + (remove #(= % :components-v2)) + (run! enable-feature!))) From 296b6c646e4e2481d766eeaef37082dc9361de59 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 1 Dec 2022 15:02:56 +0100 Subject: [PATCH 293/682] :bug: Fix incorrect flag parsing on frontend features --- frontend/src/app/main/features.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/main/features.cljs b/frontend/src/app/main/features.cljs index d7b0f1af66..e6e3acc5b5 100644 --- a/frontend/src/app/main/features.cljs +++ b/frontend/src/app/main/features.cljs @@ -80,7 +80,7 @@ ;; Enable all features set on the configuration (->> @cf/flags - (map str) + (map name) (keep (fn [flag] (when (str/starts-with? flag "frontend-feature-") (subs flag 17)))) From 94cdd4a481abfe54400f65737f51b454fa94e07b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 1 Dec 2022 14:19:27 +0100 Subject: [PATCH 294/682] :sparkles: Include features info in exported zipfiles --- frontend/src/app/libs/file_builder.cljs | 2 +- frontend/src/app/main/ui/dashboard/import.cljs | 4 ---- frontend/src/app/worker/export.cljs | 10 +++++++--- frontend/src/app/worker/import.cljs | 12 ++++++------ 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 4c1e42adb6..f2793f42a8 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -72,7 +72,7 @@ manifest-stream (->> files-stream - (rx/map #(e/create-manifest (uuid/next) (:id file) :all %)) + (rx/map #(e/create-manifest (uuid/next) (:id file) :all % false)) (rx/map (fn [a] (vector "manifest.json" a)))) diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 0500f628f4..ec333749be 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -13,7 +13,6 @@ [app.main.data.events :as ev] [app.main.data.messages :as msg] [app.main.data.modal :as modal] - [app.main.features :as features] [app.main.store :as st] [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.icons :as i] @@ -248,8 +247,6 @@ :files (->> files (mapv #(assoc % :status :analyzing)))}) - components-v2 (features/use-feature :components-v2) - analyze-import (mf/use-callback (fn [files] @@ -271,7 +268,6 @@ :num-files (count files)})) (->> (uw/ask-many! {:cmd :import-files - :components-v2 components-v2 :project-id project-id :files files}) (rx/subs diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index c40a172b90..325d19c14a 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -24,7 +24,7 @@ (defn create-manifest "Creates a manifest entry for the given files" - [team-id file-id export-type files] + [team-id file-id export-type files components-v2] (letfn [(format-page [manifest page] (-> manifest (assoc (str (:id page)) @@ -37,10 +37,14 @@ (mapv str)) index (->> (get-in file [:data :pages-index]) (vals) - (reduce format-page {}))] + (reduce format-page {})) + features (cond-> [] + components-v2 + (conj "components/v2"))] (-> manifest (assoc (str (:id file)) {:name name + :features features :shared is-shared :pages pages :pagesIndex index @@ -395,7 +399,7 @@ manifest-stream (->> files-stream - (rx/map #(create-manifest team-id file-id export-type %)) + (rx/map #(create-manifest team-id file-id export-type % components-v2)) (rx/map #(vector "manifest.json" %))) render-stream diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index cdccc632ee..7cddf6a48d 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -126,10 +126,10 @@ (defn create-file "Create a new file on the back-end" - [context components-v2] + [context] (let [resolve-fn (:resolve context) file-id (resolve-fn (:file-id context)) - features (cond-> #{} components-v2 (conj "components/v2"))] + features (into #{} (:features context))] (rp/cmd! :create-temp-file {:id file-id :name (:name context) @@ -563,14 +563,14 @@ (rx/tap #(rx/end! progress-str)))])) (defn create-files - [context files components-v2] + [context files] (let [data (group-by :file-id files)] (rx/concat (->> (rx/from files) (rx/map #(merge context %)) (rx/flat-map (fn [context] - (->> (create-file context components-v2) + (->> (create-file context) (rx/map #(vector % (first (get data (:file-id context))))))))) (->> (rx/from files) @@ -629,7 +629,7 @@ (rx/of {:uri (:uri file) :error error})))))))))) (defmethod impl/handler :import-files - [{:keys [project-id files components-v2]}] + [{:keys [project-id files]}] (let [context {:project-id project-id :resolve (resolve-factory)} @@ -637,7 +637,7 @@ binary-files (filter #(= "application/octet-stream" (:type %)) files)] (->> (rx/merge - (->> (create-files context zip-files components-v2) + (->> (create-files context zip-files) (rx/flat-map (fn [[file data]] (->> (uz/load-from-url (:uri data)) From 2b7c967920cdc77b987f128aa45d7f1c417a7aed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 1 Dec 2022 16:01:51 +0100 Subject: [PATCH 295/682] :bug: Avoid setting touched flags in imported components --- common/src/app/common/file_builder.cljc | 79 ++++++++++++++----------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 1706452c2d..99f9584e54 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -88,6 +88,7 @@ (let [parent-id (-> file :parent-stack peek) change {:type :add-obj :id (:id obj) + :ignore-touched true :obj obj :parent-id parent-id} @@ -270,6 +271,7 @@ (commit-change file {:type :del-obj + :ignore-touched true :id group-id} {:add-container? true}) @@ -280,14 +282,14 @@ {:type :mod-obj :id group-id :operations - [{:type :set :attr :x :val (-> mask :selrect :x)} - {:type :set :attr :y :val (-> mask :selrect :y)} - {:type :set :attr :width :val (-> mask :selrect :width)} - {:type :set :attr :height :val (-> mask :selrect :height)} - {:type :set :attr :flip-x :val (-> mask :flip-x)} - {:type :set :attr :flip-y :val (-> mask :flip-y)} - {:type :set :attr :selrect :val (-> mask :selrect)} - {:type :set :attr :points :val (-> mask :points)}]} + [{:type :set :attr :x :val (-> mask :selrect :x) :ignore-touched true} + {:type :set :attr :y :val (-> mask :selrect :y) :ignore-touched true} + {:type :set :attr :width :val (-> mask :selrect :width) :ignore-touched true} + {:type :set :attr :height :val (-> mask :selrect :height) :ignore-touched true} + {:type :set :attr :flip-x :val (-> mask :flip-x) :ignore-touched true} + {:type :set :attr :flip-y :val (-> mask :flip-y) :ignore-touched true} + {:type :set :attr :selrect :val (-> mask :selrect) :ignore-touched true} + {:type :set :attr :points :val (-> mask :points) :ignore-touched true}]} {:add-container? true})) :else @@ -297,12 +299,12 @@ {:type :mod-obj :id group-id :operations - [{:type :set :attr :selrect :val (:selrect group')} - {:type :set :attr :points :val (:points group')} - {:type :set :attr :x :val (-> group' :selrect :x)} - {:type :set :attr :y :val (-> group' :selrect :y)} - {:type :set :attr :width :val (-> group' :selrect :width)} - {:type :set :attr :height :val (-> group' :selrect :height)}]} + [{:type :set :attr :selrect :val (:selrect group') :ignore-touched true} + {:type :set :attr :points :val (:points group') :ignore-touched true} + {:type :set :attr :x :val (-> group' :selrect :x) :ignore-touched true} + {:type :set :attr :y :val (-> group' :selrect :y) :ignore-touched true} + {:type :set :attr :width :val (-> group' :selrect :width) :ignore-touched true} + {:type :set :attr :height :val (-> group' :selrect :height) :ignore-touched true}]} {:add-container? true})))] @@ -337,6 +339,7 @@ (commit-change file {:type :del-obj + :ignore-touched true :id bool-id} {:add-container? true}) @@ -348,12 +351,12 @@ {:type :mod-obj :id bool-id :operations - [{:type :set :attr :selrect :val (:selrect bool')} - {:type :set :attr :points :val (:points bool')} - {:type :set :attr :x :val (-> bool' :selrect :x)} - {:type :set :attr :y :val (-> bool' :selrect :y)} - {:type :set :attr :width :val (-> bool' :selrect :width)} - {:type :set :attr :height :val (-> bool' :selrect :height)}]} + [{:type :set :attr :selrect :val (:selrect bool') :ignore-touched true} + {:type :set :attr :points :val (:points bool') :ignore-touched true} + {:type :set :attr :x :val (-> bool' :selrect :x) :ignore-touched true} + {:type :set :attr :y :val (-> bool' :selrect :y) :ignore-touched true} + {:type :set :attr :width :val (-> bool' :selrect :width) :ignore-touched true} + {:type :set :attr :height :val (-> bool' :selrect :height) :ignore-touched true}]} {:add-container? true})))] @@ -490,7 +493,7 @@ :id from-id :operations - [{:type :set :attr :interactions :val interactions}]}))))) + [{:type :set :attr :interactions :val interactions :ignore-touched true}]}))))) (defn generate-changes [file] @@ -610,14 +613,14 @@ {:type :mod-obj :id component-id :operations - [{:type :set :attr :x :val (-> mask :selrect :x)} - {:type :set :attr :y :val (-> mask :selrect :y)} - {:type :set :attr :width :val (-> mask :selrect :width)} - {:type :set :attr :height :val (-> mask :selrect :height)} - {:type :set :attr :flip-x :val (-> mask :flip-x)} - {:type :set :attr :flip-y :val (-> mask :flip-y)} - {:type :set :attr :selrect :val (-> mask :selrect)} - {:type :set :attr :points :val (-> mask :points)}]} + [{:type :set :attr :x :val (-> mask :selrect :x) :ignore-touched true} + {:type :set :attr :y :val (-> mask :selrect :y) :ignore-touched true} + {:type :set :attr :width :val (-> mask :selrect :width) :ignore-touched true} + {:type :set :attr :height :val (-> mask :selrect :height) :ignore-touched true} + {:type :set :attr :flip-x :val (-> mask :flip-x) :ignore-touched true} + {:type :set :attr :flip-y :val (-> mask :flip-y) :ignore-touched true} + {:type :set :attr :selrect :val (-> mask :selrect) :ignore-touched true} + {:type :set :attr :points :val (-> mask :points) :ignore-touched true}]} {:add-container? true})) @@ -628,12 +631,12 @@ {:type :mod-obj :id component-id :operations - [{:type :set :attr :selrect :val (:selrect component')} - {:type :set :attr :points :val (:points component')} - {:type :set :attr :x :val (-> component' :selrect :x)} - {:type :set :attr :y :val (-> component' :selrect :y)} - {:type :set :attr :width :val (-> component' :selrect :width)} - {:type :set :attr :height :val (-> component' :selrect :height)}]} + [{:type :set :attr :selrect :val (:selrect component') :ignore-touched true} + {:type :set :attr :points :val (:points component') :ignore-touched true} + {:type :set :attr :x :val (-> component' :selrect :x) :ignore-touched true} + {:type :set :attr :y :val (-> component' :selrect :y) :ignore-touched true} + {:type :set :attr :width :val (-> component' :selrect :width) :ignore-touched true} + {:type :set :attr :height :val (-> component' :selrect :height) :ignore-touched true}]} {:add-container? true})))] @@ -665,6 +668,7 @@ :page-id (:id page) :parent-id (:parent-id %2) :frame-id (:frame-id %2) + :ignore-touched true :obj %2}) $ shapes) @@ -672,6 +676,7 @@ :id component-id}) (reduce #(commit-change %1 {:type :del-obj :page-id page-id + :ignore-touched true :id (:id %2)}) $ shapes) @@ -704,6 +709,7 @@ :page-id (:id page) :parent-id (:parent-id %2) :frame-id (:frame-id %2) + :ignore-touched true :obj %2}) $ shapes) @@ -718,6 +724,7 @@ file {:type :del-obj :page-id page-id + :ignore-touched true :id id}))) (defn update-object @@ -731,7 +738,7 @@ new-val (get new-obj attr)] (if (= old-val new-val) changes - (conj changes {:type :set :attr attr :val new-val}))))] + (conj changes {:type :set :attr attr :val new-val :ignore-touched true}))))] (-> file (commit-change {:type :mod-obj From c9dbeec68919f98548cd4a00eff30dc521999ab8 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 1 Dec 2022 17:12:08 +0100 Subject: [PATCH 296/682] :bug: Fix right sidebar style --- frontend/src/app/main/ui/workspace/sidebar.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/workspace/sidebar.cljs b/frontend/src/app/main/ui/workspace/sidebar.cljs index 4add5cba94..3c37c0defc 100644 --- a/frontend/src/app/main/ui/workspace/sidebar.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar.cljs @@ -84,7 +84,7 @@ drawing-tool (:tool (mf/deref refs/workspace-drawing)) expanded (mf/deref refs/inspect-expanded)] - [:aside.settings-bar {:class (when expanded "expanded")} + [:aside.settings-bar.settings-bar-right {:class (when expanded "expanded")} [:div.settings-bar-inside (cond (= drawing-tool :comments) From 148f6cb3c240c97236cdca7572ae9889f6bb28b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 1 Dec 2022 11:19:38 +0100 Subject: [PATCH 297/682] :bug: Fix adding an extra blank page on import --- CHANGES.md | 1 + backend/src/app/rpc/commands/files/create.clj | 8 +++++--- backend/src/app/rpc/commands/files/temp.clj | 12 +++++++++++- common/src/app/common/types/file.cljc | 9 ++++++--- frontend/src/app/worker/import.cljs | 1 + 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6c2f5e0388..22a8037b54 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -18,6 +18,7 @@ - Fix some texts and a typo [Taiga #4215](https://tree.taiga.io/project/penpot/issue/4215) - Fix twitter support account link [Taiga #4279](https://tree.taiga.io/project/penpot/issue/4279) - Fix lang autodetect issue [Taiga #4277](https://tree.taiga.io/project/penpot/issue/4277) +- Fix adding an extra page on import [Taiga #4543](https://tree.taiga.io/project/penpot/task/4543) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/backend/src/app/rpc/commands/files/create.clj b/backend/src/app/rpc/commands/files/create.clj index 4a7c5d6005..eb5ebe675a 100644 --- a/backend/src/app/rpc/commands/files/create.clj +++ b/backend/src/app/rpc/commands/files/create.clj @@ -31,9 +31,9 @@ (defn create-file [conn {:keys [id name project-id is-shared data revn - modified-at deleted-at + modified-at deleted-at create-page ignore-sync-until features] - :or {is-shared false revn 0} + :or {is-shared false revn 0 create-page true} :as params}] (let [id (or id (:id data) (uuid/next)) features (-> (into files/default-features features) @@ -43,7 +43,9 @@ (binding [ffeat/*current* features ffeat/*wrap-with-objects-map-fn* (if (features "storate/objects-map") omap/wrap identity) ffeat/*wrap-with-pointer-map-fn* (if (features "storage/pointer-map") pmap/wrap identity)] - (ctf/make-file-data id))) + (if create-page + (ctf/make-file-data id) + (ctf/make-file-data id nil)))) features (db/create-array conn "text" features) file (db/insert! conn :file diff --git a/backend/src/app/rpc/commands/files/temp.clj b/backend/src/app/rpc/commands/files/temp.clj index 350ddb23cd..3dbee423d8 100644 --- a/backend/src/app/rpc/commands/files/temp.clj +++ b/backend/src/app/rpc/commands/files/temp.clj @@ -8,6 +8,7 @@ (:require [app.common.exceptions :as ex] [app.common.pages :as cp] + [app.common.spec :as us] [app.common.uuid :as uuid] [app.db :as db] [app.rpc.commands.files :as files] @@ -22,7 +23,16 @@ ;; --- MUTATION COMMAND: create-temp-file -(s/def ::create-temp-file ::files.create/create-file) +(s/def ::create-page ::us/boolean) + +(s/def ::create-temp-file + (s/keys :req-un [::files/profile-id + ::files/name + ::files/project-id] + :opt-un [::files/id + ::files/is-shared + ::files/features + ::create-page])) (sv/defmethod ::create-temp-file {::doc/added "1.17"} diff --git a/common/src/app/common/types/file.cljc b/common/src/app/common/types/file.cljc index a4b7b26bae..d2143101b5 100644 --- a/common/src/app/common/types/file.cljc +++ b/common/src/app/common/types/file.cljc @@ -72,10 +72,13 @@ (make-file-data file-id (uuid/next))) ([file-id page-id] - (let [page (ctp/make-empty-page page-id "Page-1")] + (let [page (when (some? page-id) + (ctp/make-empty-page page-id "Page-1"))] (cond-> (-> empty-file-data - (assoc :id file-id) - (ctpl/add-page page)) + (assoc :id file-id)) + + (some? page-id) + (ctpl/add-page page) (contains? ffeat/*current* "components/v2") (assoc-in [:options :components-v2] true))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index cdccc632ee..5936bc6cd6 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -135,6 +135,7 @@ :name (:name context) :is-shared (:shared context) :project-id (:project-id context) + :create-page false :features features}))) (defn link-file-libraries From 29b1b4dbc9010ac632073f59a71ccd6b11ba45c5 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 30 Nov 2022 13:27:32 +0100 Subject: [PATCH 298/682] :bug: Fix problem with hug layout and groups --- .../app/common/geom/shapes/flex_layout.cljc | 2 +- .../geom/shapes/flex_layout/bounds.cljc | 2 +- .../src/app/common/geom/shapes/modifiers.cljc | 34 +++++++++++-------- .../app/common/geom/shapes/transforms.cljc | 10 ------ .../app/main/ui/workspace/viewport/debug.cljs | 25 ++++++++++---- 5 files changed, 41 insertions(+), 32 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout.cljc b/common/src/app/common/geom/shapes/flex_layout.cljc index b61eabf097..9379752468 100644 --- a/common/src/app/common/geom/shapes/flex_layout.cljc +++ b/common/src/app/common/geom/shapes/flex_layout.cljc @@ -13,9 +13,9 @@ [app.common.geom.shapes.flex-layout.modifiers :as fmo])) (dm/export fbo/layout-content-bounds) +(dm/export fbo/child-layout-bound-points) (dm/export fdr/get-drop-index) (dm/export fdr/layout-drop-areas) (dm/export fli/calc-layout-data) (dm/export fmo/layout-child-modifiers) (dm/export fmo/normalize-child-modifiers) - diff --git a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc index a24e81214e..307e9ecb26 100644 --- a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc @@ -106,7 +106,7 @@ child-bounds @(get bounds child-id) child-bounds (if (or (ctl/fill-height? child) (ctl/fill-height? child)) - (child-layout-bound-points parent child parent-bounds parent-bounds) + (child-layout-bound-points parent child parent-bounds child-bounds) child-bounds)] (gpo/parent-coords-bounds child-bounds parent-bounds)))] diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index a80a2f9c6b..48b1eda5b9 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -12,6 +12,7 @@ [app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.flex-layout :as gcl] [app.common.geom.shapes.pixel-precision :as gpp] + [app.common.geom.shapes.points :as cpo] [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.transforms :as gtr] [app.common.pages.helpers :as cph] @@ -138,29 +139,34 @@ (let [children (map (d/getf objects) (:shapes parent))] (reduce process-child modif-tree children)))) - (defn get-group-bounds [objects bounds modif-tree shape] (let [shape-id (:id shape) modifiers (-> (dm/get-in modif-tree [shape-id :modifiers]) (ctm/select-geometry)) - children (cph/get-immediate-children objects shape-id) - group-bounds - (cond - (cph/group-shape? shape) - (let [children-bounds (->> children (mapv #(get-group-bounds objects bounds modif-tree %)))] - (gtr/group-bounds shape children-bounds)) + children (cph/get-immediate-children objects shape-id)] - (cph/mask-shape? shape) - (get-group-bounds objects bounds modif-tree (-> children first)) + (cond + (cph/group-shape? shape) + (let [;; Transform here to then calculate the bounds relative to the transform + current-bounds + (cond-> @(get bounds shape-id) + (not (ctm/empty? modifiers)) + (gtr/transform-bounds modifiers)) - :else - @(get bounds shape-id))] + children-bounds + (->> children + (mapv #(get-group-bounds objects bounds modif-tree %)))] + (cpo/merge-parent-coords-bounds children-bounds current-bounds)) - (cond-> group-bounds - (not (ctm/empty? modifiers)) - (gtr/transform-bounds modifiers)))) + (cph/mask-shape? shape) + (get-group-bounds objects bounds modif-tree (-> children first)) + + :else + (cond-> @(get bounds shape-id) + (not (ctm/empty? modifiers)) + (gtr/transform-bounds modifiers))))) (defn- set-layout-modifiers [modif-tree objects bounds parent transformed-parent-bounds] diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 4383b28e88..2e5cc59540 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -369,16 +369,6 @@ (update :width + (:width deltas)) (update :height + (:height deltas))))))) -(defn group-bounds - [group children-bounds] - (let [shape-center (gco/center-shape group) - points (flatten children-bounds) - points (if (empty? points) (:points group) points)] - (-> points - (gco/transform-points shape-center (:transform-inverse group (gmt/matrix))) - (gpr/squared-points) - (gco/transform-points shape-center (:transform group (gmt/matrix)))))) - (defn update-group-selrect [group children] (let [shape-center (gco/center-shape group) diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index 5da0460f8e..b8079c324f 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -152,7 +152,7 @@ [props] (let [objects (unchecked-get props "objects") - zoom (unchecked-get props "objects") + zoom (unchecked-get props "zoom") selected-shapes (unchecked-get props "selected-shapes") hover-top-frame-id (unchecked-get props "hover-top-frame-id") @@ -160,13 +160,26 @@ (when (and (= (count selected-shapes) 1) (= :frame (-> selected-shapes first :type))) (first selected-shapes)) - parent (or selected-frame (get objects hover-top-frame-id))] + parent (or selected-frame (get objects hover-top-frame-id)) + parent-bounds (:points parent)] (when (and (some? parent) (not= uuid/zero (:id parent))) (let [children (cph/get-immediate-children objects (:id parent))] [:g.debug-parent-bounds {:pointer-events "none"} (for [[idx child] (d/enumerate children)] - [:> shape-parent-bound {:key (dm/str "bound-" idx) - :zoom zoom - :shape child - :parent parent}])])))) + [:* + [:> shape-parent-bound {:key (dm/str "bound-" idx) + :zoom zoom + :shape child + :parent parent}] + + (let [child-bounds (:points child) + points + (if (or (ctl/fill-height? child) (ctl/fill-height? child)) + (gsl/child-layout-bound-points parent child parent-bounds child-bounds) + child-bounds)] + (for [point points] + [:circle {:cx (:x point) + :cy (:y point) + :r (/ 2 zoom) + :style {:fill "red"}}]))])])))) From 32350bcf87d6cae0b24fa47ad268b7ac7431103a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 1 Dec 2022 16:13:21 +0100 Subject: [PATCH 299/682] :sparkles: Improved performance for auto-width/auto-height texts --- .../geom/shapes/flex_layout/bounds.cljc | 2 +- .../src/app/common/geom/shapes/modifiers.cljc | 3 +- common/src/app/common/types/modifiers.cljc | 11 +++++ common/src/app/common/types/shape.cljc | 5 +- .../src/app/main/data/workspace/texts.cljs | 49 ++++++++++++------- .../main/ui/workspace/shapes/text/editor.cljs | 9 +++- .../shapes/text/text_edition_outline.cljs | 9 +++- .../shapes/text/viewport_texts_html.cljs | 8 ++- .../src/app/main/ui/workspace/viewport.cljs | 16 +++--- .../app/main/ui/workspace/viewport/hooks.cljs | 15 +++--- .../main/ui/workspace/viewport/outline.cljs | 35 +++++++++---- frontend/src/debug.cljs | 3 ++ 12 files changed, 112 insertions(+), 53 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc index 307e9ecb26..b762090d3e 100644 --- a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc @@ -11,7 +11,7 @@ [app.common.math :as mth] [app.common.types.shape.layout :as ctl])) -(defn- child-layout-bound-points +(defn child-layout-bound-points "Returns the bounds of the children as points" [parent child parent-bounds child-bounds] diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 48b1eda5b9..a2a4e47522 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -12,7 +12,6 @@ [app.common.geom.shapes.constraints :as gct] [app.common.geom.shapes.flex-layout :as gcl] [app.common.geom.shapes.pixel-precision :as gpp] - [app.common.geom.shapes.points :as cpo] [app.common.geom.shapes.points :as gpo] [app.common.geom.shapes.transforms :as gtr] [app.common.pages.helpers :as cph] @@ -158,7 +157,7 @@ children-bounds (->> children (mapv #(get-group-bounds objects bounds modif-tree %)))] - (cpo/merge-parent-coords-bounds children-bounds current-bounds)) + (gpo/merge-parent-coords-bounds children-bounds current-bounds)) (cph/mask-shape? shape) (get-group-bounds objects bounds modif-tree (-> children first)) diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index b0c8827008..22a428b262 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -384,6 +384,17 @@ (-> (empty) (scale-content value))) +(defn change-size + [{:keys [selrect points transform transform-inverse] :as shape} width height] + (let [old-width (-> selrect :width) + old-height (-> selrect :height) + width (or width old-width) + height (or height old-height) + origin (first points) + scalex (/ width old-width) + scaley (/ height old-height)] + (resize-modifiers (gpt/point scalex scaley) origin transform transform-inverse))) + (defn change-dimensions-modifiers [{:keys [transform transform-inverse] :as shape} attr value] (us/assert map? shape) diff --git a/common/src/app/common/types/shape.cljc b/common/src/app/common/types/shape.cljc index 9249ac5608..507860fb4f 100644 --- a/common/src/app/common/types/shape.cljc +++ b/common/src/app/common/types/shape.cljc @@ -375,7 +375,10 @@ "Initializes the selrect and points for a shape." [shape] (let [selrect (gsh/rect->selrect shape) - points (gsh/rect->points shape)] + points (gsh/rect->points shape) + points (cond-> points + (:transform shape) + (gsh/transform-points (gsh/center-points points) (:transform shape)))] (-> shape (assoc :selrect selrect :points points)))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index f0694b253f..77b44c2962 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -14,13 +14,12 @@ [app.common.pages.helpers :as cph] [app.common.text :as txt] [app.common.types.modifiers :as ctm] - [app.common.types.shape :as cts] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] + [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shapes :as dwsh] - [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.util.router :as rt] @@ -74,11 +73,14 @@ (when (and (not= content (:content shape)) (some? (:current-page-id state))) (rx/of - (dch/update-shapes [id] (fn [shape] - (-> shape - (assoc :content content) - (merge modifiers) - (cts/setup-rect-selrect)))) + (dch/update-shapes + [id] + (fn [shape] + (let [{:keys [width height]} modifiers] + (-> shape + (assoc :content content) + (cond-> (or (some? width) (some? height)) + (gsh/transform-shape (ctm/change-size shape width height))))))) (dwu/commit-undo-transaction (:id shape)))))) (when (some? id) @@ -323,20 +325,25 @@ (let [shape (wsh/lookup-shape state id)] (letfn [(update-fn [shape] (let [{:keys [selrect grow-type]} shape - {shape-width :width shape-height :height} selrect] - (cond-> shape - (and (not-changed? shape-width new-width) (= grow-type :auto-width)) - (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width new-width)) + {shape-width :width shape-height :height} selrect - (and (not-changed? shape-height new-height) - (or (= grow-type :auto-height) (= grow-type :auto-width))) - (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height new-height)))))] + shape + (cond-> shape + (and (not-changed? shape-width new-width) (= grow-type :auto-width)) + (gsh/transform-shape (ctm/change-dimensions-modifiers shape :width new-width))) + + shape + (cond-> shape + (and (not-changed? shape-height new-height) + (or (= grow-type :auto-height) (= grow-type :auto-width))) + (gsh/transform-shape (ctm/change-dimensions-modifiers shape :height new-height)))] + + shape))] (when (or (and (not-changed? (:width shape) new-width) (= (:grow-type shape) :auto-width)) (and (not-changed? (:height shape) new-height) (or (= (:grow-type shape) :auto-height) (= (:grow-type shape) :auto-width)))) - (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false}) - (dwul/update-layout-positions [id])))))))) + (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false})))))))) (defn save-font [data] @@ -385,7 +392,9 @@ (not (mth/close? (:width props) (:width shape)))) (and (some? (:height props)) (not (mth/close? (:height props) (:height shape))))) - (rx/of (dwul/update-layout-positions [id]))))))) + + (let [modif-tree (dwm/create-modif-tree [id] (ctm/reflow-modifiers))] + (rx/of (dwm/set-modifiers modif-tree)))))))) (defn clean-text-modifier [id] @@ -401,7 +410,11 @@ (ptk/reify ::remove-text-modifier ptk/UpdateEvent (update [_ state] - (d/dissoc-in state [:workspace-text-modifier id])))) + (d/dissoc-in state [:workspace-text-modifier id])) + + ptk/WatchEvent + (watch [_ _ _] + (rx/of (dwm/apply-modifiers))))) (defn commit-position-data [] diff --git a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs index 419c8f7e55..e323b4672c 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -257,7 +257,9 @@ (mf/defc text-editor-svg {::mf/wrap-props false} [props] - (let [shape (obj/get props "shape") + (let [shape (obj/get props "shape") + modifiers (obj/get props "modifiers") + modifiers (get-in modifiers [(:id shape) :modifiers]) clip-id (dm/str "text-edition-clip" (:id shape)) @@ -270,7 +272,10 @@ shape (cond-> shape (some? text-modifier) - (dwt/apply-text-modifier text-modifier)) + (dwt/apply-text-modifier text-modifier) + + (some? modifiers) + (gsh/transform-shape modifiers)) bounding-box (gsht/position-data-selrect shape) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs b/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs index bacf0eea67..aa4e291643 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/text_edition_outline.cljs @@ -12,14 +12,19 @@ [rumext.v2 :as mf])) (mf/defc text-edition-outline - [{:keys [shape zoom]}] - (let [text-modifier-ref + [{:keys [shape zoom modifiers]}] + (let [modifiers (get-in modifiers [(:id shape) :modifiers]) + + text-modifier-ref (mf/use-memo (mf/deps (:id shape)) #(refs/workspace-text-modifier-by-id (:id shape))) text-modifier (mf/deref text-modifier-ref) shape (cond-> shape + (some? modifiers) + (gsh/transform-shape modifiers) + (some? text-modifier) (dwt/apply-text-modifier text-modifier)) diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 673f47a35e..405b3f1b06 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -36,13 +36,11 @@ (dissoc :position-data))) (defn fix-position [shape modifier] - (let [shape' (-> shape - (assoc :grow-type :fixed) - (gsh/transform-shape modifier)) - + (let [shape' (gsh/transform-shape shape modifier) + ;; We need to remove the movement because the dynamic modifiers will have move it deltav (gpt/to-vec (gpt/point (:selrect shape')) (gpt/point (:selrect shape)))] - (gsh/transform-shape shape' (ctm/move-modifiers deltav)))) + (gsh/transform-shape shape (ctm/move modifier deltav)))) (defn process-shape [modifiers {:keys [id] :as shape}] (let [modifier (dm/get-in modifiers [id :modifiers])] diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 418c22a5a7..5d55889f27 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -204,9 +204,9 @@ [:div.viewport-overlays ;; The behaviour inside a foreign object is a bit different that in plain HTML so we wrap ;; inside a foreign object "dummy" so this awkward behaviour is take into account - [:svg {:style {:top 0 :left 0 :position "fixed" :width "100%" :height "100%" :opacity 0}} + [:svg {:style {:top 0 :left 0 :position "fixed" :width "100%" :height "100%" :opacity (when-not (debug? :html-text) 0)}} [:foreignObject {:x 0 :y 0 :width "100%" :height "100%"} - [:div {:style {:pointer-events "none"}} + [:div {:style {:pointer-events (when-not (debug? :html-text) "none")}} [:& stvh/viewport-texts {:key (dm/str "texts-" page-id) :page-id page-id @@ -289,7 +289,8 @@ [:g {:style {:pointer-events (if disable-events? "none" "auto")}} (when show-text-editor? - [:& editor/text-editor-svg {:shape editing-shape}]) + [:& editor/text-editor-svg {:shape editing-shape + :modifiers modifiers}]) (when show-frame-outline? [:& outline/shape-outlines @@ -298,7 +299,8 @@ (filter #(cph/frame-shape? (get base-objects %))) (remove selected) (first))} - :zoom zoom}]) + :zoom zoom + :modifiers modifiers}]) (when show-outlines? [:& outline/shape-outlines @@ -307,7 +309,8 @@ :hover #{(:id @hover) @frame-hover} :highlighted highlighted :edition edition - :zoom zoom}]) + :zoom zoom + :modifiers modifiers}]) (when show-selection-handlers? [:& selection/selection-area @@ -321,7 +324,8 @@ (when show-text-editor? [:& text-edition-outline {:shape (get base-objects edition) - :zoom zoom}]) + :zoom zoom + :modifiers modifiers}]) (when show-measures? [:& msr/measurement diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index 508a227dc8..c35b2aedbb 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -130,12 +130,15 @@ rect (gsh/center->rect point (/ 5 zoom) (/ 5 zoom))] (if (mf/ref-val hover-disabled-ref) (rx/of nil) - (uw/ask-buffered! - {:cmd :selection/query - :page-id page-id - :rect rect - :include-frames? true - :clip-children? (not mod?)}))))) + (->> (uw/ask-buffered! + {:cmd :selection/query + :page-id page-id + :rect rect + :include-frames? true + :clip-children? (not mod?)}) + ;; When the ask-buffered is canceled returns null. We filter them + ;; to improve the behavior + (rx/filter some?)))))) over-shapes-stream (mf/use-memo diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs index 416f71625f..3cba35078e 100644 --- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.common.geom.shapes :as gsh] + [app.main.ui.hooks :as hooks] [app.util.object :as obj] [app.util.path.format :as upf] [clojure.set :as set] @@ -18,10 +19,12 @@ (mf/defc outline {::mf/wrap-props false} [props] - (let [shape (obj/get props "shape") - zoom (obj/get props "zoom" 1) + (let [shape (obj/get props "shape") + zoom (obj/get props "zoom" 1) + color (obj/get props "color") + modifier (obj/get props "modifier") - color (unchecked-get props "color") + shape (gsh/transform-shape shape (:modifiers modifier)) transform (gsh/transform-str shape) path? (= :path (:type shape)) path-data @@ -64,17 +67,22 @@ (mf/defc shape-outlines-render {::mf/wrap-props false - ::mf/wrap [#(mf/memo' % (mf/check-props ["shapes" "zoom"]))]} + ::mf/wrap [#(mf/memo' % (mf/check-props ["shapes" "zoom" "modifiers"]))]} [props] + (let [shapes (obj/get props "shapes") zoom (obj/get props "zoom") + modifiers (obj/get props "modifiers") color (if (or (> (count shapes) 1) (nil? (:shape-ref (first shapes)))) "var(--color-primary)" "var(--color-component-highlight)")] + (for [shape shapes] - [:& outline {:key (str "outline-" (:id shape)) - :shape shape - :zoom zoom - :color color}]))) + (let [modifier (get modifiers (:id shape))] + [:& outline {:key (str "outline-" (:id shape)) + :shape shape + :modifier modifier + :zoom zoom + :color color}])))) (defn- show-outline? [shape] @@ -91,6 +99,7 @@ objects (obj/get props "objects") edition (obj/get props "edition") zoom (obj/get props "zoom") + modifiers (obj/get props "modifiers") lookup (d/getf objects) edition? (fn [o] (= edition o)) @@ -102,7 +111,13 @@ (set/union selected hover)) (into (comp (remove edition?) (keep lookup)) - highlighted))] + highlighted)) + + modifiers (select-keys modifiers (map :id shapes)) + modifiers (hooks/use-equal-memo modifiers) + shapes (hooks/use-equal-memo shapes)] [:g.outlines - [:& shape-outlines-render {:shapes shapes :zoom zoom}]])) + [:& shape-outlines-render {:shapes shapes + :zoom zoom + :modifiers modifiers}]])) diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index 1b1625fc74..f0b61abd76 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -83,6 +83,9 @@ ;; Show the bounds relative to the parent :parent-bounds + + ;; Show html text + :html-text }) ;; These events are excluded when we activate the :events flag From e53e715861e4069f47034dea93e8a360c7bbbd1d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 1 Dec 2022 16:39:29 +0100 Subject: [PATCH 300/682] :sparkles: Disable feature toggle for layout flex --- .../main/ui/workspace/sidebar/options/shapes/frame.cljs | 8 ++------ .../main/ui/workspace/sidebar/options/shapes/group.cljs | 6 ++---- frontend/src/features.cljs | 3 --- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs index 1fcb34ed6e..2d4e1f6dd7 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.sidebar.options.shapes.frame (:require [app.common.types.shape.layout :as ctl] - [app.main.features :as features] [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] @@ -26,8 +25,6 @@ (let [ids [(:id shape)] type (:type shape) - layout-active? (features/use-feature :auto-layout) - stroke-values (select-keys shape stroke-attrs) layer-values (select-keys shape layer-attrs) measure-values (select-measure-keys shape) @@ -46,10 +43,9 @@ (when (not is-layout-child?) [:& constraints-menu {:ids ids :values constraint-values}]) - (when (or layout-active? is-layout-container?) - [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}]) + [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}] - (when (and layout-active? (or is-layout-child? is-layout-container?)) + (when (or is-layout-child? is-layout-container?) [:& layout-item-menu {:ids ids :type type diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index 1876d74d32..9ed4ad8b90 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.sidebar.options.shapes.group (:require [app.common.data :as d] - [app.main.features :as features] [app.main.refs :as refs] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.color-selection :refer [color-selection-menu]] @@ -34,7 +33,6 @@ shared-libs (unchecked-get props "shared-libs") objects (->> shape-with-children (group-by :id) (d/mapm (fn [_ v] (first v)))) file-id (unchecked-get props "file-id") - layout-active? (features/use-feature :auto-layout) layout-container-values (select-keys shape layout-container-flex-attrs) ids [(:id shape)] is-layout-child-ref (mf/use-memo (mf/deps ids) #(refs/is-layout-child? ids)) @@ -56,8 +54,8 @@ [:div.options [:& measures-menu {:type type :ids measure-ids :values measure-values :shape shape}] [:& component-menu {:ids comp-ids :values comp-values :shape-name (:name shape)}] - (when layout-active? - [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}]) + [:& layout-container-menu {:type type :ids [(:id shape)] :values layout-container-values}] + (when is-layout-child? [:& layout-item-menu {:type type diff --git a/frontend/src/features.cljs b/frontend/src/features.cljs index 4057786036..c042c97767 100644 --- a/frontend/src/features.cljs +++ b/frontend/src/features.cljs @@ -9,9 +9,6 @@ (:require [app.main.features :as features])) -(defn ^:export autolayout [] - (features/toggle-feature! :auto-layout)) - (defn ^:export components-v2 [] (features/toggle-feature! :components-v2)) From e43fc0feb0f46dc27e2a3b89e32241ba2fab9a05 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 2 Dec 2022 09:32:57 +0100 Subject: [PATCH 301/682] :bug: Fix masks in layout flex --- common/src/app/common/geom/shapes/modifiers.cljc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index a2a4e47522..b4fa223bd3 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -147,6 +147,9 @@ children (cph/get-immediate-children objects shape-id)] (cond + (cph/mask-shape? shape) + (get-group-bounds objects bounds modif-tree (-> children first)) + (cph/group-shape? shape) (let [;; Transform here to then calculate the bounds relative to the transform current-bounds @@ -159,9 +162,6 @@ (mapv #(get-group-bounds objects bounds modif-tree %)))] (gpo/merge-parent-coords-bounds children-bounds current-bounds)) - (cph/mask-shape? shape) - (get-group-bounds objects bounds modif-tree (-> children first)) - :else (cond-> @(get bounds shape-id) (not (ctm/empty? modifiers)) From 7f7efc57601711aaf7f30fd7b83b6da2d4fccc0f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 28 Nov 2022 16:47:27 +0100 Subject: [PATCH 302/682] :sparkles: Improve exception formating on backend --- backend/dev/user.clj | 18 +- backend/resources/log4j2-devenv.xml | 6 +- backend/resources/log4j2.xml | 3 +- backend/src/app/auth/oidc.clj | 2 +- backend/src/app/config.clj | 3 +- backend/src/app/http/errors.clj | 5 +- backend/src/app/main.clj | 7 +- backend/src/app/rpc/rlimit.clj | 2 +- backend/src/app/storage/fs.clj | 6 +- backend/src/app/worker.clj | 4 +- common/src/app/common/exceptions.cljc | 175 ++++++++++++++---- common/src/app/common/logging.cljc | 31 +++- common/src/app/common/spec.cljc | 13 +- frontend/src/app/main/data/media.cljs | 2 +- .../app/main/data/workspace/svg_upload.cljs | 27 +-- frontend/src/app/util/color.cljs | 2 +- 16 files changed, 224 insertions(+), 82 deletions(-) diff --git a/backend/dev/user.clj b/backend/dev/user.clj index dce35764b6..80824130a0 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -12,6 +12,7 @@ [app.common.logging :as l] [app.common.perf :as perf] [app.common.pprint :as pp] + [app.common.spec :as us] [app.common.transit :as t] [app.common.uuid :as uuid] [app.config :as cfg] @@ -29,11 +30,13 @@ [clojure.pprint :refer [pprint print-table]] [clojure.repl :refer :all] [clojure.spec.alpha :as s] + [clojure.stacktrace :as trace] [clojure.test :as test] [clojure.test.check.generators :as gen] [clojure.tools.namespace.repl :as repl] [clojure.walk :refer [macroexpand-all]] [criterium.core :as crit] + [cuerdas.core :as str] [datoteka.core] [integrant.core :as ig])) @@ -78,12 +81,15 @@ (defn- start [] - (alter-var-root #'system (fn [sys] - (when sys (ig/halt! sys)) - (-> (merge main/system-config main/worker-config) - (ig/prep) - (ig/init)))) - :started) + (try + (alter-var-root #'system (fn [sys] + (when sys (ig/halt! sys)) + (-> (merge main/system-config main/worker-config) + (ig/prep) + (ig/init)))) + :started + (catch Throwable cause + (ex/print-throwable cause)))) (defn- stop [] diff --git a/backend/resources/log4j2-devenv.xml b/backend/resources/log4j2-devenv.xml index 6e4c305720..4265970086 100644 --- a/backend/resources/log4j2-devenv.xml +++ b/backend/resources/log4j2-devenv.xml @@ -2,11 +2,13 @@ - + - + diff --git a/backend/resources/log4j2.xml b/backend/resources/log4j2.xml index 3a0d04e3f7..fba649ab7c 100644 --- a/backend/resources/log4j2.xml +++ b/backend/resources/log4j2.xml @@ -2,7 +2,8 @@ - + diff --git a/backend/src/app/auth/oidc.clj b/backend/src/app/auth/oidc.clj index d2a285ad79..7a8f5f1a2a 100644 --- a/backend/src/app/auth/oidc.clj +++ b/backend/src/app/auth/oidc.clj @@ -50,7 +50,7 @@ (defn- discover-oidc-config [{:keys [http-client]} {:keys [base-uri] :as opts}] (let [discovery-uri (u/join base-uri ".well-known/openid-configuration") - response (ex/try (http/req! http-client {:method :get :uri (str discovery-uri)} {:sync? true}))] + response (ex/try! (http/req! http-client {:method :get :uri (str discovery-uri)} {:sync? true}))] (cond (ex/exception? response) (do diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index e2d30373b4..e9a4a2c833 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -340,7 +340,8 @@ (when (ex/ex-info? e) (println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;") (println "Error on validating configuration:") - (println (us/pretty-explain (ex-data e))) + (println (some-> e ex-data ex/explain)) + (println (ex/explain (ex-data e))) (println ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;")) (throw e)))) diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index f6764dbabc..791a32b3c0 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -10,7 +10,6 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.common.logging :as l] - [app.common.spec :as us] [app.http :as-alias http] [clojure.spec.alpha :as s] [cuerdas.core :as str] @@ -64,7 +63,7 @@ (let [{:keys [code] :as data} (ex-data err)] (cond (= code :spec-validation) - (let [explain (us/pretty-explain data)] + (let [explain (ex/explain data)] (yrs/response :status 400 :body (-> data (dissoc ::s/problems ::s/value) @@ -79,7 +78,7 @@ (defmethod handle-exception :assertion [error request] (let [edata (ex-data error) - explain (us/pretty-explain edata)] + explain (ex/explain edata)] (l/error ::l/raw (str (ex-message error) "\n" explain) ::l/context (get-context request) :cause error) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 1e909f8222..2c4adb13e7 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -534,4 +534,9 @@ (defn -main [& _args] - (start)) + (try + (start) + (catch Throwable cause + (l/error :hint (ex-message cause) + :cause cause) + (System/exit -1)))) diff --git a/backend/src/app/rpc/rlimit.clj b/backend/src/app/rpc/rlimit.clj index 7c07d67589..5eb0f85eef 100644 --- a/backend/src/app/rpc/rlimit.clj +++ b/backend/src/app/rpc/rlimit.clj @@ -358,7 +358,7 @@ (defn- on-refresh-error [_ cause] (when-not (instance? java.util.concurrent.RejectedExecutionException cause) - (if-let [explain (-> cause ex-data us/pretty-explain)] + (if-let [explain (-> cause ex-data ex/explain)] (l/warn ::l/raw (str "unable to refresh config, invalid format:\n" explain) ::l/async false) (l/warn :hint "unexpected exception on loading config" diff --git a/backend/src/app/storage/fs.clj b/backend/src/app/storage/fs.clj index f6d5a0cea5..c88d4b33e9 100644 --- a/backend/src/app/storage/fs.clj +++ b/backend/src/app/storage/fs.clj @@ -75,8 +75,10 @@ (defmethod impl/get-object-bytes :fs [backend object] (p/let [input (impl/get-object-data backend object)] - (ex/with-always (io/close! input) - (io/read-as-bytes input)))) + (try + (io/read-as-bytes input) + (finally + (io/close! input))))) (defmethod impl/get-object-url :fs [{:keys [uri executor] :as backend} {:keys [id] :as object} _] diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index 6df88a902e..c6efd2df91 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -501,8 +501,8 @@ :spec-value (some->> data ::s/value) :data (some-> data (dissoc ::s/problems ::s/value ::s/spec)) :params item} - (when (and data (::s/problems data)) - {:spec-explain (us/pretty-explain data)})))) + (when-let [explain (ex/explain data)] + {:spec-explain explain})))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; CRON diff --git a/common/src/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc index 11a1d432ff..5041e8b9f7 100644 --- a/common/src/app/common/exceptions.cljc +++ b/common/src/app/common/exceptions.cljc @@ -6,34 +6,25 @@ (ns app.common.exceptions "A helpers for work with exceptions." - #?(:cljs - (:require-macros [app.common.exceptions])) - (:require [clojure.spec.alpha :as s])) + #?(:cljs (:require-macros [app.common.exceptions])) + (:require + #?(:clj [clojure.stacktrace :as strace]) + [app.common.pprint :as pp] + [clojure.spec.alpha :as s] + [cuerdas.core :as str] + [expound.alpha :as expound])) -(s/def ::type keyword?) -(s/def ::code keyword?) -(s/def ::hint string?) -(s/def ::cause #?(:clj #(instance? Throwable %) - :cljs #(instance? js/Error %))) - -(s/def ::error-params - (s/keys :req-un [::type] - :opt-un [::code - ::hint - ::cause])) - -(defn error - [& {:keys [hint cause ::data type] :as params}] - (s/assert ::error-params params) - (let [payload (-> params - (dissoc :cause ::data) - (merge data)) - hint (or hint (pr-str type))] - (ex-info hint payload cause))) +(defmacro error + [& {:keys [type hint] :as params}] + `(ex-info ~(or hint (pr-str type)) + (merge + ~(dissoc params :cause ::data) + ~(::data params)) + ~(:cause params))) (defmacro raise - [& args] - `(throw (error ~@args))) + [& params] + `(throw (error ~@params))) (defn try* [f on-error] @@ -46,20 +37,10 @@ [& exprs] `(try* (^:once fn* [] ~@exprs) (constantly nil))) -(defmacro try - [& exprs] - `(try* (^:once fn* [] ~@exprs) identity)) - (defmacro try! [& exprs] `(try* (^:once fn* [] ~@exprs) identity)) -(defn with-always - "A helper that evaluates an exptession independently if the body - raises exception or not." - [always-expr & body] - `(try ~@body (finally ~always-expr))) - (defn ex-info? [v] (instance? #?(:clj clojure.lang.ExceptionInfo :cljs cljs.core.ExceptionInfo) v)) @@ -68,7 +49,6 @@ [v] (instance? #?(:clj java.lang.Throwable :cljs js/Error) v)) - #?(:cljs (deftype WrappedException [cause meta] cljs.core/IMeta @@ -84,7 +64,6 @@ clojure.lang.IDeref (deref [_] cause))) - #?(:clj (ns-unmap 'app.common.exceptions '->WrappedException)) #?(:clj (ns-unmap 'app.common.exceptions 'map->WrappedException)) @@ -95,3 +74,125 @@ (defn wrap-with-context [cause context] (WrappedException. cause context)) + +(defn explain + ([data] (explain data nil)) + ([data {:keys [max-problems] :or {max-problems 10} :as opts}] + (cond + ;; ;; NOTE: a special case for spec validation errors on integrant + (and (= (:reason data) :integrant.core/build-failed-spec) + (contains? data :explain)) + (explain (:explain data) opts) + + (and (::s/problems data) + (::s/value data) + (::s/spec data)) + (binding [s/*explain-out* expound/printer] + (with-out-str + (s/explain-out (update data ::s/problems #(take max-problems %)))))))) + +#?(:clj +(defn print-throwable + [^Throwable cause + & {:keys [trace? data? chain? data-level data-length trace-length explain-length] + :or {trace? true + data? true + chain? true + explain-length 10 + data-length 10 + data-level 3}}] + (letfn [(print-trace-element [^StackTraceElement e] + (let [class (.getClassName e) + method (.getMethodName e)] + (let [match (re-matches #"^([A-Za-z0-9_.-]+)\$(\w+)__\d+$" (str class))] + (if (and match (= "invoke" method)) + (apply printf "%s/%s" (rest match)) + (printf "%s.%s" class method)))) + (printf "(%s:%d)" (or (.getFileName e) "") (.getLineNumber e))) + + (print-explain [explain] + (print " xp: ") + (let [[line & lines] (str/lines explain)] + (print line) + (newline) + (doseq [line lines] + (println " " line)))) + + (print-data [data] + (when (seq data) + (print " dt: ") + (let [[line & lines] (str/lines (pp/pprint-str data :level data-level :length data-length ))] + (print line) + (newline) + (doseq [line lines] + (println " " line))))) + + (print-trace-title [cause] + (print " → ") + (printf "%s: %s" (.getName (class cause)) (first (str/lines (ex-message cause)))) + + (when-let [e (first (.getStackTrace cause))] + (printf " (%s:%d)" (or (.getFileName e) "") (.getLineNumber e))) + + (newline)) + + (print-summary [cause] + (let [causes (loop [cause (.getCause cause) + result []] + (if cause + (recur (.getCause cause) + (conj result cause)) + result))] + (println "TRACE:") + (print-trace-title cause) + (doseq [cause causes] + (print-trace-title cause)))) + + (print-trace [cause] + (print-trace-title cause) + (let [st (.getStackTrace cause)] + (print " at: ") + (if-let [e (first st)] + (print-trace-element e) + (print "[empty stack trace]")) + (newline) + + (doseq [e (if (nil? trace-length) (rest st) (take (dec trace-length) (rest st)))] + (print " ") + (print-trace-element e) + (newline)))) + + (print-all [cause] + (print-summary cause) + (newline) + (println "DETAIL:") + + (when trace? + (print-trace cause)) + + (when data? + (when-let [data (ex-data cause)] + (if-let [explain (explain data)] + (print-explain explain) + (print-data data)))) + + (when chain? + (loop [cause cause] + (when-let [cause (.getCause cause)] + (newline) + (print-trace cause) + + (when data? + (when-let [data (ex-data cause)] + (if-let [explain (explain data)] + (print-explain explain) + (print-data data)))) + + (recur cause))))) + ] + + (println + (with-out-str + (print-all cause)))))) + + diff --git a/common/src/app/common/logging.cljc b/common/src/app/common/logging.cljc index 33eb6e6342..89ad318e7f 100644 --- a/common/src/app/common/logging.cljc +++ b/common/src/app/common/logging.cljc @@ -32,19 +32,30 @@ (def ^:private reserved-props #{:level :cause ::logger ::async ::raw ::context}) -(def ^:private props-xform - (comp (partition-all 2) - (remove (fn [[k]] (contains? reserved-props k))) - (map vec))) - -(defn build-message +(defn build-message-kv [props] - (loop [pairs (sequence props-xform props) + (loop [pairs (remove (fn [[k]] (contains? reserved-props k)) props) result []] (if-let [[k v] (first pairs)] (recur (rest pairs) (conj result (str/concat (d/name k) "=" (pr-str v)))) - result))) + (str/join ", " result)))) + +(defn build-message-cause + [props] + #?(:clj (when-let [[_ cause] (d/seek (fn [[k]] (= k :cause)) props)] + (with-out-str + (ex/print-throwable cause))) + :cljs nil)) + +(defn build-message + [props] + (let [props (sequence (comp (partition-all 2) (map vec)) props) + message-kv (build-message-kv props) + message-ex (build-message-cause props)] + (cond-> message-kv + (some? message-ex) + (str "\n" message-ex)))) #?(:clj (def logger-context @@ -169,8 +180,8 @@ :spec-problems (some->> data ::s/problems (take 10) seq vec) :spec-value (some->> data ::s/value) :data (some-> data (dissoc ::s/problems ::s/value ::s/spec))} - (when (and data (::s/problems data)) - {:spec-explain (us/pretty-explain data)}))))) + (when-let [explain (ex/explain data)] + {:spec-explain explain}))))) (defmacro log [& props] diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 3b63a6f127..d81e996c22 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -375,7 +375,8 @@ ;; argument is a qualified keyword. (= 2 pcnt) (let [[spec-or-expr value-or-msg] params] - (if (qualified-keyword? spec-or-expr) + (if (or (qualified-keyword? spec-or-expr) + (symbol? spec-or-expr)) `(assert-spec* ~spec-or-expr ~value-or-msg nil) `(assert-expr* ~spec-or-expr ~value-or-msg))) @@ -419,6 +420,8 @@ (spec-assert* ~spec params# ~message mdata#) (apply origf# params#))))))) + +;; FIXME: REMOVE (defn pretty-explain ([data] (pretty-explain data nil)) ([data {:keys [max-problems] :or {max-problems 10}}] @@ -428,3 +431,11 @@ (binding [s/*explain-out* expound/printer] (with-out-str (s/explain-out (update data ::s/problems #(take max-problems %)))))))) + +(defn validation-error? + [cause] + (if (and (map? cause) (= :spec-validation (:type cause))) + cause + (when (ex/ex-info? cause) + (validation-error? (ex-data cause))))) + diff --git a/frontend/src/app/main/data/media.cljs b/frontend/src/app/main/data/media.cljs index 93c3ee0cb4..da1049ecf7 100644 --- a/frontend/src/app/main/data/media.cljs +++ b/frontend/src/app/main/data/media.cljs @@ -41,7 +41,7 @@ (when-not (contains? cm/valid-image-types (.-type file)) (ex/raise :type :validation :code :media-type-not-allowed - :hint (str/fmt "media type %s is not supported" (.-type file)))) + :hint (str/ffmt "media type % is not supported" (.-type file)))) file) (defn notify-start-loading diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index f929e4095d..2363a9a3bf 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -7,6 +7,7 @@ (ns app.main.data.workspace.svg-upload (:require [app.common.data :as d] + [app.common.spec :as us] [app.common.exceptions :as ex] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] @@ -35,22 +36,24 @@ (defonce default-image {:x 0 :y 0 :width 1 :height 1 :rx 0 :ry 0}) (defn- assert-valid-num [attr num] - (when (or (not (d/num? num)) - (>= num max-safe-int ) - (<= num min-safe-int)) - (ex/raise (str (d/name attr) " attribute invalid: " num))) + (us/verify! + :expr (and (d/num? num) + (<= num max-safe-int) + (>= num min-safe-int)) + :hint (str/ffmt "%1 attribute has invalid value: %2" (d/name attr) num)) ;; If the number is between 0-1 we round to 1 (same in negative form (cond - (and (> num 0) (< num 1)) 1 - (and (< num 0) (> num -1)) -1 - :else num)) + (and (> num 0) (< num 1)) 1 + (and (< num 0) (> num -1)) -1 + :else num)) -(defn- assert-valid-pos-num [attr num] - (let [num (assert-valid-num attr num)] - (when (< num 0) - (ex/raise (str (d/name attr) " attribute invalid: " num))) - num)) +(defn- assert-valid-pos-num + [attr num] + (us/verify! + :expr (pos? num) + :hint (str/ffmt "%1 attribute should be positive" (d/name attr))) + num) (defn- svg-dimensions [data] (let [width (get-in data [:attrs :width] 100) diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index ba36c6c326..62da2dbe88 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -162,7 +162,7 @@ [[r g b]] (cond (and (= 255 r) (= 255 g) (= 255 b)) - (ex/raise "Cannot get next color") + (throw (ex-info "cannot get next color" {:r r :g g :b b})) (and (= 255 g) (= 255 b)) [(inc r) 0 0] From 8bad9d8340fca543923eb0b1d920327c661e52d1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 28 Nov 2022 16:48:30 +0100 Subject: [PATCH 303/682] :recycle: Refactor loggers/audit, auth/oidc, and http/clent modules --- backend/src/app/auth/oidc.clj | 238 +++++++------- backend/src/app/config.clj | 2 + backend/src/app/http/awsns.clj | 25 +- backend/src/app/http/client.clj | 49 +-- backend/src/app/http/errors.clj | 32 +- backend/src/app/loggers/audit.clj | 294 ++++++++++-------- backend/src/app/loggers/loki.clj | 85 +++-- backend/src/app/loggers/mattermost.clj | 94 +++--- backend/src/app/loggers/zmq.clj | 108 ++++--- backend/src/app/main.clj | 192 ++++++------ backend/src/app/rpc.clj | 63 ++-- backend/src/app/rpc/commands/auth.clj | 45 ++- backend/src/app/rpc/commands/ldap.clj | 19 +- backend/src/app/rpc/commands/verify_token.clj | 46 ++- backend/src/app/rpc/helpers.clj | 5 + backend/src/app/rpc/mutations/fonts.clj | 13 +- backend/src/app/rpc/mutations/media.clj | 5 +- backend/src/app/rpc/mutations/profile.clj | 96 +----- backend/src/app/rpc/mutations/teams.clj | 16 +- backend/src/app/setup.clj | 5 + backend/src/app/setup/builtin_templates.clj | 6 +- backend/src/app/tasks/telemetry.clj | 41 ++- backend/src/app/worker.clj | 8 +- .../backend_tests/bounce_handling_test.clj | 41 +-- .../test/backend_tests/rpc_profile_test.clj | 23 +- common/src/app/common/exceptions.cljc | 5 + .../types_shape_interactions_test.cljc | 4 +- .../app/main/data/workspace/svg_upload.cljs | 4 +- frontend/src/app/util/color.cljs | 1 - 29 files changed, 796 insertions(+), 769 deletions(-) diff --git a/backend/src/app/auth/oidc.clj b/backend/src/app/auth/oidc.clj index 7a8f5f1a2a..6463c70758 100644 --- a/backend/src/app/auth/oidc.clj +++ b/backend/src/app/auth/oidc.clj @@ -7,6 +7,7 @@ (ns app.auth.oidc "OIDC client implementation." (:require + [app.auth.oidc.providers :as-alias providers] [app.common.data :as d] [app.common.data.macros :as dm] [app.common.exceptions :as ex] @@ -19,6 +20,7 @@ [app.http.middleware :as hmw] [app.http.session :as session] [app.loggers.audit :as audit] + [app.main :as-alias main] [app.rpc.queries.profile :as profile] [app.tokens :as tokens] [app.util.json :as json] @@ -48,9 +50,11 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- discover-oidc-config - [{:keys [http-client]} {:keys [base-uri] :as opts}] + [cfg {:keys [::base-uri] :as opts}] (let [discovery-uri (u/join base-uri ".well-known/openid-configuration") - response (ex/try! (http/req! http-client {:method :get :uri (str discovery-uri)} {:sync? true}))] + response (ex/try! (http/req! cfg + {:method :get :uri (str discovery-uri)} + {:sync? true}))] (cond (ex/exception? response) (do @@ -74,15 +78,15 @@ (defn- prepare-oidc-opts [cfg] - (let [opts {:base-uri (:base-uri cfg) - :client-id (:client-id cfg) - :client-secret (:client-secret cfg) - :token-uri (:token-uri cfg) - :auth-uri (:auth-uri cfg) - :user-uri (:user-uri cfg) - :scopes (:scopes cfg #{"openid" "profile" "email"}) - :roles-attr (:roles-attr cfg) - :roles (:roles cfg) + (let [opts {:base-uri (cf/get :oidc-base-uri) + :client-id (cf/get :oidc-client-id) + :client-secret (cf/get :oidc-client-secret) + :token-uri (cf/get :oidc-token-uri) + :auth-uri (cf/get :oidc-auth-uri) + :user-uri (cf/get :oidc-user-uri) + :scopes (cf/get :oidc-scopes #{"openid" "profile" "email"}) + :roles-attr (cf/get :oidc-roles-attr) + :roles (cf/get :oidc-roles) :name "oidc"} opts (d/without-nils opts)] @@ -97,13 +101,12 @@ (some-> (discover-oidc-config cfg opts) (merge opts {:discover? true})))))) -(defmethod ig/prep-key ::generic-provider - [_ cfg] - (d/without-nils cfg)) +(defmethod ig/pre-init-spec ::providers/generic [_] + (s/keys :req [::http/client])) -(defmethod ig/init-key ::generic-provider +(defmethod ig/init-key ::providers/generic [_ cfg] - (when (:enabled? cfg) + (when (contains? cf/flags :login-with-oidc) (if-let [opts (prepare-oidc-opts cfg)] (do (l/info :hint "provider initialized" @@ -111,10 +114,10 @@ :method (if (:discover? opts) "discover" "manual") :client-id (:client-id opts) :client-secret (obfuscate-string (:client-secret opts)) - :scopes (str/join "," (:scopes opts)) - :auth-uri (:auth-uri opts) - :user-uri (:user-uri opts) - :token-uri (:token-uri opts) + :scopes (str/join "," (:scopes opts)) + :auth-uri (:auth-uri opts) + :user-uri (:user-uri opts) + :token-uri (:token-uri opts) :roles-attr (:roles-attr opts) :roles (:roles opts)) opts) @@ -126,21 +129,17 @@ ;; GOOGLE AUTH PROVIDER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defmethod ig/prep-key ::google-provider - [_ cfg] - (d/without-nils cfg)) - -(defmethod ig/init-key ::google-provider - [_ cfg] - (let [opts {:client-id (:client-id cfg) - :client-secret (:client-secret cfg) +(defmethod ig/init-key ::providers/google + [_ _] + (let [opts {:client-id (cf/get :google-client-id) + :client-secret (cf/get :google-client-secret) :scopes #{"openid" "email" "profile"} :auth-uri "https://accounts.google.com/o/oauth2/v2/auth" :token-uri "https://oauth2.googleapis.com/token" :user-uri "https://openidconnect.googleapis.com/v1/userinfo" :name "google"}] - (when (:enabled? cfg) + (when (contains? cf/flags :login-with-google) (if (and (string? (:client-id opts)) (string? (:client-secret opts))) (do @@ -159,13 +158,14 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- retrieve-github-email - [{:keys [http-client]} tdata info] + [cfg tdata info] (or (some-> info :email p/resolved) - (-> (http/req! http-client {:uri "https://api.github.com/user/emails" - :headers {"Authorization" (dm/str (:type tdata) " " (:token tdata))} - :timeout 6000 - :method :get}) - (p/then (fn [{:keys [status body] :as response}] + (->> (http/req! cfg + {:uri "https://api.github.com/user/emails" + :headers {"Authorization" (dm/str (:type tdata) " " (:token tdata))} + :timeout 6000 + :method :get}) + (p/map (fn [{:keys [status body] :as response}] (when-not (s/int-in-range? 200 300 status) (ex/raise :type :internal :code :unable-to-retrieve-github-emails @@ -174,14 +174,13 @@ :http-body body)) (->> response :body json/read (filter :primary) first :email)))))) -(defmethod ig/prep-key ::github-provider - [_ cfg] - (d/without-nils cfg)) +(defmethod ig/pre-init-spec ::providers/github [_] + (s/keys :req [::http/client])) -(defmethod ig/init-key ::github-provider +(defmethod ig/init-key ::providers/github [_ cfg] - (let [opts {:client-id (:client-id cfg) - :client-secret (:client-secret cfg) + (let [opts {:client-id (cf/get :github-client-id) + :client-secret (cf/get :github-client-secret) :scopes #{"read:user" "user:email"} :auth-uri "https://github.com/login/oauth/authorize" :token-uri "https://github.com/login/oauth/access_token" @@ -192,7 +191,7 @@ ;; retrieve emails. :get-email-fn (partial retrieve-github-email cfg)}] - (when (:enabled? cfg) + (when (contains? cf/flags :login-with-github) (if (and (string? (:client-id opts)) (string? (:client-secret opts))) (do @@ -210,22 +209,18 @@ ;; GITLAB AUTH PROVIDER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defmethod ig/prep-key ::gitlab-provider - [_ cfg] - (d/without-nils cfg)) - -(defmethod ig/init-key ::gitlab-provider - [_ cfg] - (let [base (:base-uri cfg "https://gitlab.com") +(defmethod ig/init-key ::providers/gitlab + [_ _] + (let [base (cf/get :gitlab-base-uri "https://gitlab.com") opts {:base-uri base - :client-id (:client-id cfg) - :client-secret (:client-secret cfg) + :client-id (cf/get :gitlab-client-id) + :client-secret (cf/get :gitlab-client-secret) :scopes #{"openid" "profile" "email"} :auth-uri (str base "/oauth/authorize") :token-uri (str base "/oauth/token") :user-uri (str base "/oauth/userinfo") :name "gitlab"}] - (when (:enabled? cfg) + (when (contains? cf/flags :login-with-gitlab) (if (and (string? (:client-id opts)) (string? (:client-secret opts))) (do @@ -246,7 +241,7 @@ (defn- build-redirect-uri [{:keys [provider] :as cfg}] - (let [public (u/uri (:public-uri cfg))] + (let [public (u/uri (cf/get :public-uri))] (str (assoc public :path (str "/api/auth/oauth/" (:name provider) "/callback"))))) (defn- build-auth-uri @@ -269,7 +264,7 @@ props)) (defn retrieve-access-token - [{:keys [provider http-client] :as cfg} code] + [{:keys [provider] :as cfg} code] (let [params {:client_id (:client-id provider) :client_secret (:client-secret provider) :code code @@ -280,25 +275,25 @@ "accept" "application/json"} :uri (:token-uri provider) :body (u/map->query-string params)}] - (p/then - (http/req! http-client req) - (fn [{:keys [status body] :as res}] - (if (= status 200) - (let [data (json/read body)] - {:token (get data :access_token) - :type (get data :token_type)}) - (ex/raise :type :internal - :code :unable-to-retrieve-token - :http-status status - :http-body body)))))) + (->> (http/req! cfg req) + (p/map (fn [{:keys [status body] :as res}] + (if (= status 200) + (let [data (json/read body)] + {:token (get data :access_token) + :type (get data :token_type)}) + (ex/raise :type :internal + :code :unable-to-retrieve-token + :http-status status + :http-body body))))))) (defn- retrieve-user-info - [{:keys [provider http-client] :as cfg} tdata] + [{:keys [provider] :as cfg} tdata] (letfn [(retrieve [] - (http/req! http-client {:uri (:user-uri provider) - :headers {"Authorization" (str (:type tdata) " " (:token tdata))} - :timeout 6000 - :method :get})) + (http/req! cfg + {:uri (:user-uri provider) + :headers {"Authorization" (str (:type tdata) " " (:token tdata))} + :timeout 6000 + :method :get})) (validate-response [response] (when-not (s/int-in-range? 200 300 (:status response)) (ex/raise :type :internal @@ -355,7 +350,7 @@ ::props])) (defn retrieve-info - [{:keys [sprops provider] :as cfg} {:keys [params] :as request}] + [{:keys [provider] :as cfg} {:keys [params] :as request}] (letfn [(validate-oidc [info] ;; If the provider is OIDC, we can proceed to check ;; roles if they are defined. @@ -394,7 +389,7 @@ (let [state (get params :state) code (get params :code) - state (tokens/verify sprops {:token state :iss :oauth})] + state (tokens/verify (::main/props cfg) {:token state :iss :oauth})] (-> (p/resolved code) (p/then #(retrieve-access-token cfg %)) (p/then #(retrieve-user-info cfg %)) @@ -402,7 +397,7 @@ (p/then' (partial post-process state)))))) (defn- retrieve-profile - [{:keys [pool executor] :as cfg} info] + [{:keys [::db/pool ::wrk/executor] :as cfg} info] (px/with-dispatch executor (with-open [conn (db/open pool)] (some->> (:email info) @@ -415,23 +410,23 @@ (yrs/response :status 302 :headers {"location" (str uri)})) (defn- generate-error-redirect - [cfg error] - (let [uri (-> (u/uri (:public-uri cfg)) + [_ error] + (let [uri (-> (u/uri (cf/get :public-uri)) (assoc :path "/#/auth/login") (assoc :query (u/map->query-string {:error "unable-to-auth" :hint (ex-message error)})))] (redirect-response uri))) (defn- generate-redirect - [{:keys [sprops session audit] :as cfg} request info profile] + [{:keys [::session/session] :as cfg} request info profile] (if profile (let [sxf (session/create-fn session (:id profile)) token (or (:invitation-token info) - (tokens/generate sprops {:iss :auth - :exp (dt/in-future "15m") - :profile-id (:id profile)})) + (tokens/generate (::main/props cfg) + {:iss :auth + :exp (dt/in-future "15m") + :profile-id (:id profile)})) params {:token token} - - uri (-> (u/uri (:public-uri cfg)) + uri (-> (u/uri (cf/get :public-uri)) (assoc :path "/#/auth/verify-token") (assoc :query (u/map->query-string params)))] @@ -439,13 +434,12 @@ (ex/raise :type :restriction :code :profile-blocked)) - (when (fn? audit) - (audit :cmd :submit - :type "command" - :name "login" - :profile-id (:id profile) - :ip-addr (audit/parse-client-ip request) - :props (audit/profile->props profile))) + (when-let [collector (::audit/collector cfg)] + (audit/submit! collector {:type "command" + :name "login" + :profile-id (:id profile) + :ip-addr (audit/parse-client-ip request) + :props (audit/profile->props profile)})) (->> (redirect-response uri) (sxf request))) @@ -454,19 +448,19 @@ :iss :prepared-register :is-active true :exp (dt/in-future {:hours 48})) - token (tokens/generate sprops info) + token (tokens/generate (::main/props cfg) info) params (d/without-nils {:token token :fullname (:fullname info)}) - uri (-> (u/uri (:public-uri cfg)) + uri (-> (u/uri (cf/get :public-uri)) (assoc :path "/#/auth/register/validate") (assoc :query (u/map->query-string params)))] (redirect-response uri)))) (defn- auth-handler - [{:keys [sprops] :as cfg} {:keys [params] :as request}] + [cfg {:keys [params] :as request}] (let [props (audit/extract-utm-params params) - state (tokens/generate sprops + state (tokens/generate (::main/props cfg) {:iss :oauth :invitation-token (:invitation-token params) :props props @@ -492,7 +486,7 @@ {:compile (fn [& _] (fn [handler] - (fn [{:keys [providers] :as cfg} request] + (fn [{:keys [::providers] :as cfg} request] (let [provider (some-> request :path-params :provider keyword)] (if-let [provider (get providers provider)] (handler (assoc cfg :provider provider) request) @@ -501,43 +495,57 @@ :provider provider :hint "provider not configured"))))))}) -(s/def ::public-uri ::us/not-empty-string) -(s/def ::http-client ::http/client) -(s/def ::sprops map?) -(s/def ::providers map?) + +(s/def ::client-id ::cf/oidc-client-id) +(s/def ::client-secret ::cf/oidc-client-secret) +(s/def ::base-uri ::cf/oidc-base-uri) +(s/def ::token-uri ::cf/oidc-token-uri) +(s/def ::auth-uri ::cf/oidc-auth-uri) +(s/def ::user-uri ::cf/oidc-user-uri) +(s/def ::scopes ::cf/oidc-scopes) +(s/def ::roles ::cf/oidc-roles) +(s/def ::roles-attr ::cf/oidc-roles-attr) +(s/def ::email-attr ::cf/oidc-email-attr) +(s/def ::name-attr ::cf/oidc-name-attr) + +;; FIXME: migrate to qualified-keywords +(s/def ::provider + (s/keys :req-un [::client-id + ::client-secret] + :opt-un [::base-uri + ::token-uri + ::auth-uri + ::user-uri + ::scopes + ::roles + ::roles-attr + ::email-attr + ::name-attr])) + +(s/def ::providers (s/map-of ::us/keyword (s/nilable ::provider))) (defmethod ig/pre-init-spec ::routes [_] - (s/keys :req-un [::public-uri - ::session/session - ::sprops - ::http-client - ::providers - ::db/pool - ::wrk/executor])) + (s/keys :req [::http/client + ::wrk/executor + ::main/props + ::db/pool + ::providers + ::session/session])) (defmethod ig/init-key ::routes - [_ {:keys [executor session] :as cfg}] + [_ {:keys [::wrk/executor ::session/session] :as cfg}] (let [cfg (update cfg :provider d/without-nils)] ["" {:middleware [[(:middleware session)] [hmw/with-dispatch executor] [hmw/with-config cfg] [provider-lookup] ]} - ;; We maintain the both URI prefixes for backward compatibility. - ["/auth/oauth" - ["/:provider" - {:handler auth-handler - :allowed-methods #{:post}}] - ["/:provider/callback" - {:handler callback-handler - :allowed-methods #{:get}}]] - - ["/auth/oidc" ["/:provider" {:handler auth-handler :allowed-methods #{:post}}] ["/:provider/callback" {:handler callback-handler :allowed-methods #{:get}}]]])) + diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index e9a4a2c833..32b01ca862 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -100,6 +100,7 @@ (s/def ::telemetry-enabled ::us/boolean) (s/def ::audit-log-archive-uri ::us/string) +(s/def ::audit-log-http-handler-concurrency ::us/integer) (s/def ::admins ::us/set-of-strings) (s/def ::file-change-snapshot-every ::us/integer) @@ -205,6 +206,7 @@ ::admins ::allow-demo-users ::audit-log-archive-uri + ::audit-log-http-handler-concurrency ::auth-token-cookie-name ::auth-token-cookie-max-age ::authenticated-cookie-name diff --git a/backend/src/app/http/awsns.clj b/backend/src/app/http/awsns.clj index 3b65d44b5d..bf5f32aebc 100644 --- a/backend/src/app/http/awsns.clj +++ b/backend/src/app/http/awsns.clj @@ -12,7 +12,9 @@ [app.db :as db] [app.db.sql :as sql] [app.http.client :as http] + [app.main :as-alias main] [app.tokens :as tokens] + [app.worker :as-alias wrk] [clojure.spec.alpha :as s] [cuerdas.core :as str] [integrant.core :as ig] @@ -26,21 +28,21 @@ (declare parse-notification) (declare process-report) -(s/def ::http-client ::http/client) -(s/def ::sprops map?) - (defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::db/pool ::http-client ::sprops])) + (s/keys :req [::http/client + ::main/props + ::db/pool + ::wrk/executor])) (defmethod ig/init-key ::handler - [_ {:keys [executor] :as cfg}] + [_ {:keys [::wrk/executor] :as cfg}] (fn [request respond _] (let [data (-> request yrq/body slurp)] (px/run! executor #(handle-request cfg data))) (respond (yrs/response 200)))) (defn handle-request - [{:keys [http-client] :as cfg} data] + [cfg data] (try (let [body (parse-json data) mtype (get body "Type")] @@ -49,7 +51,7 @@ (let [surl (get body "SubscribeURL") stopic (get body "TopicArn")] (l/info :action "subscription received" :topic stopic :url surl) - (http/req! http-client {:uri surl :method :post :timeout 10000} {:sync? true})) + (http/req! cfg {:uri surl :method :post :timeout 10000} {:sync? true})) (= mtype "Notification") (when-let [message (parse-json (get body "Message"))] @@ -100,10 +102,11 @@ (get mail "headers"))) (defn- extract-identity - [{:keys [sprops]} headers] + [cfg headers] (let [tdata (get headers "x-penpot-data")] (when-not (str/empty? tdata) - (let [result (tokens/verify sprops {:token tdata :iss :profile-identity})] + (let [sprops (::main/props cfg) + result (tokens/verify sprops {:token tdata :iss :profile-identity})] (:profile-id result))))) (defn- parse-notification @@ -136,7 +139,7 @@ (j/read-value v))) (defn- register-bounce-for-profile - [{:keys [pool]} {:keys [type kind profile-id] :as report}] + [{:keys [::db/pool]} {:keys [type kind profile-id] :as report}] (when (= kind "permanent") (db/with-atomic [conn pool] (db/insert! conn :profile-complaint-report @@ -165,7 +168,7 @@ {:id profile-id})))))) (defn- register-complaint-for-profile - [{:keys [pool]} {:keys [type profile-id] :as report}] + [{:keys [::db/pool]} {:keys [type profile-id] :as report}] (db/with-atomic [conn pool] (db/insert! conn :profile-complaint-report {:profile-id profile-id diff --git a/backend/src/app/http/client.clj b/backend/src/app/http/client.clj index 12956f9f81..9e0be572ae 100644 --- a/backend/src/app/http/client.clj +++ b/backend/src/app/http/client.clj @@ -7,34 +7,41 @@ (ns app.http.client "Http client abstraction layer." (:require + [app.common.spec :as us] [app.worker :as wrk] [clojure.spec.alpha :as s] [integrant.core :as ig] - [java-http-clj.core :as http])) + [java-http-clj.core :as http]) + (:import + java.net.http.HttpClient)) -(s/def ::client fn?) +(s/def ::client #(instance? HttpClient %)) +(s/def ::client-holder + (s/keys :req [::client])) -(defmethod ig/pre-init-spec :app.http/client [_] - (s/keys :req-un [::wrk/executor])) +(defmethod ig/pre-init-spec ::client [_] + (s/keys :req [::wrk/executor])) -(defmethod ig/init-key :app.http/client - [_ {:keys [executor] :as cfg}] - (let [client (http/build-client {:executor executor - :connect-timeout 30000 ;; 10s - :follow-redirects :always})] - (with-meta - (fn send - ([req] (send req {})) - ([req {:keys [response-type sync?] :or {response-type :string sync? false}}] - (if sync? - (http/send req {:client client :as response-type}) - (http/send-async req {:client client :as response-type})))) - {::client client}))) +(defmethod ig/init-key ::client + [_ {:keys [::wrk/executor] :as cfg}] + (http/build-client {:executor executor + :connect-timeout 30000 ;; 10s + :follow-redirects :always})) + +(defn send! + ([client req] (send! client req {})) + ([client req {:keys [response-type sync?] :or {response-type :string sync? false}}] + (us/assert! ::client client) + (if sync? + (http/send req {:client client :as response-type}) + (http/send-async req {:client client :as response-type})))) (defn req! "A convencience toplevel function for gradual migration to a new API convention." - ([client request] - (client request)) - ([client request options] - (client request options))) + ([{:keys [::client] :as holder} request] + (us/assert! ::client-holder holder) + (send! client request {})) + ([{:keys [::client] :as holder} request options] + (us/assert! ::client-holder holder) + (send! client request options))) diff --git a/backend/src/app/http/errors.clj b/backend/src/app/http/errors.clj index 791a32b3c0..6e7a2b7482 100644 --- a/backend/src/app/http/errors.clj +++ b/backend/src/app/http/errors.clj @@ -77,11 +77,11 @@ (defmethod handle-exception :assertion [error request] - (let [edata (ex-data error) + (let [edata (ex-data error) explain (ex/explain edata)] - (l/error ::l/raw (str (ex-message error) "\n" explain) - ::l/context (get-context request) - :cause error) + (l/error :hint (ex-message error) + :cause error + ::l/context (get-context request)) (yrs/response :status 500 :body {:type :server-error :code :assertion @@ -102,9 +102,9 @@ :else (do - (l/error ::l/raw (ex-message error) - ::l/context (get-context request) - :cause error) + (l/error :hint (ex-message error) + :cause error + ::l/context (get-context request)) (yrs/response 500 {:type :server-error :code :unhandled :hint (ex-message error) @@ -113,9 +113,9 @@ (defmethod handle-exception org.postgresql.util.PSQLException [error request] (let [state (.getSQLState ^java.sql.SQLException error)] - (l/error ::l/raw (ex-message error) - ::l/context (get-context request) - :cause error) + (l/error :hint (ex-message error) + :cause error + ::l/context (get-context request)) (cond (= state "57014") (yrs/response 504 {:type :server-error @@ -140,9 +140,9 @@ ;; This means that exception is not a controlled exception. (nil? edata) (do - (l/error ::l/raw (ex-message error) - ::l/context (get-context request) - :cause error) + (l/error :hint (ex-message error) + :cause error + ::l/context (get-context request)) (yrs/response 500 {:type :server-error :code :unexpected :hint (ex-message error)})) @@ -158,9 +158,9 @@ :else (do - (l/error ::l/raw (ex-message error) - ::l/context (get-context request) - :cause error) + (l/error :hint (ex-message error) + :cause error + ::l/context (get-context request)) (yrs/response 500 {:type :server-error :code :unhandled :hint (ex-message error) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 7d2263ed8e..629da4824c 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -15,20 +15,27 @@ [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] + [app.http.client :as http] + [app.loggers.audit.tasks :as-alias tasks] + [app.main :as-alias main] + [app.metrics :as mtx] [app.tokens :as tokens] - [app.util.async :as aa] [app.util.time :as dt] [app.worker :as wrk] - [clojure.core.async :as a] [clojure.spec.alpha :as s] [cuerdas.core :as str] [integrant.core :as ig] [lambdaisland.uri :as u] [promesa.core :as p] [promesa.exec :as px] + [promesa.exec.bulkhead :as pxb] [yetti.request :as yrq] [yetti.response :as yrs])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; HELPERS +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + (defn parse-client-ip [request] (or (some-> (yrq/get-header request "x-forwarded-for") (str/split ",") first) @@ -49,10 +56,23 @@ (assoc (->> sk str/kebab (keyword "penpot")) v))))] (reduce-kv process-param {} params))) + +(def ^:private + profile-props + [:id + :is-active + :is-muted + :auth-backend + :email + :default-team-id + :default-project-id + :fullname + :lang]) + (defn profile->props [profile] (-> profile - (select-keys [:id :is-active :is-muted :auth-backend :email :default-team-id :default-project-id :fullname :lang]) + (select-keys profile-props) (merge (:props profile)) (d/without-nils))) @@ -79,11 +99,7 @@ (update event :props #(into {} xform %)))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; HTTP Handler -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(declare persist-http-events) +;; --- SPECS (s/def ::profile-id ::us/uuid) (s/def ::name ::us/string) @@ -98,161 +114,174 @@ (s/def ::frontend-events (s/every ::frontend-event)) +(s/def ::ip-addr ::us/string) +(s/def ::backend-event + (s/keys :req-un [::type ::name ::profile-id] + :opt-un [::ip-addr ::props])) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; HTTP HANDLER +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(s/def ::concurrency ::us/integer) + +(defmethod ig/pre-init-spec ::http-handler [_] + (s/keys :req [::wrk/executor ::db/pool ::mtx/metrics ::concurrency])) + +(defmethod ig/prep-key ::http-handler + [_ cfg] + (merge {::concurrency (cf/get :audit-log-http-handler-concurrency 8)} + (d/without-nils cfg))) + (defmethod ig/init-key ::http-handler - [_ {:keys [executor pool] :as cfg}] - (if (or (db/read-only? pool) (not (contains? cf/flags :audit-log))) + [_ {:keys [::wrk/executor ::db/pool ::mtx/metrics ::concurrency] :as cfg}] + (if (or (db/read-only? pool) + (not (contains? cf/flags :audit-log))) (do - (l/warn :hint "audit log http handler disabled or db is read-only") + (l/warn :hint "audit: http handler disabled or db is read-only") (fn [_ respond _] (respond (yrs/response 204)))) - (letfn [(handler [{:keys [profile-id] :as request}] + (letfn [(event->row [event] + [(uuid/next) + (:name event) + (:source event) + (:type event) + (:timestamp event) + (:profile-id event) + (db/inet (:ip-addr event)) + (db/tjson (:props event)) + (db/tjson (d/without-nils (:context event)))]) + + (handle-request [{:keys [profile-id] :as request}] (let [events (->> (:events (:params request)) (remove #(not= profile-id (:profile-id %))) (us/conform ::frontend-events)) - ip-addr (parse-client-ip request) - cfg (-> cfg - (assoc :source "frontend") - (assoc :events events) - (assoc :ip-addr ip-addr))] - (persist-http-events cfg))) + xform (comp + (map #(assoc % :ip-addr ip-addr)) + (map #(assoc % :source "frontend")) + (map event->row)) - (handle-error [cause] - (let [xdata (ex-data cause)] - (if (= :spec-validation (:code xdata)) - (l/error ::l/raw (str "spec validation on persist-events:\n" (us/pretty-explain xdata))) - (l/error :hint "error on persist-events" :cause cause))))] + columns [:id :name :source :type :tracked-at + :profile-id :ip-addr :props :context]] + (when (seq events) + (->> (into [] xform events) + (db/insert-multi! pool :audit-log columns))))) - (fn [request respond _] - ;; Fire and forget, log error in case of error - (-> (px/submit! executor #(handler request)) - (p/catch handle-error)) + (report-error! [cause] + (if-let [xdata (us/validation-error? cause)] + (l/error ::l/raw (str "audit: validation error frontend events request\n" (ex/explain xdata))) + (l/error :hint "audit: unexpected error on processing frontend events" :cause cause))) - (respond (yrs/response 204)))))) + (on-queue [instance] + (l/trace :hint "http-handler: enqueued" + :queue-size (get instance ::pxb/current-queue-size) + :concurrency (get instance ::pxb/current-concurrency)) + (mtx/run! metrics + :id :audit-http-handler-queue-size + :val (get instance ::pxb/current-queue-size)) + (mtx/run! metrics + :id :audit-http-handler-concurrency + :val (get instance ::pxb/current-concurrency))) -(defn- persist-http-events - [{:keys [pool events ip-addr source] :as cfg}] - (let [columns [:id :name :source :type :tracked-at :profile-id :ip-addr :props :context] - prepare-xf (map (fn [event] - [(uuid/next) - (:name event) - source - (:type event) - (:timestamp event) - (:profile-id event) - (db/inet ip-addr) - (db/tjson (:props event)) - (db/tjson (d/without-nils (:context event)))]))] - (when (seq events) - (->> (into [] prepare-xf events) - (db/insert-multi! pool :audit-log columns))))) + (on-run [instance task] + (let [elapsed (- (inst-ms (dt/now)) + (inst-ms task))] + (l/trace :hint "http-handler: execute" + :elapsed (str elapsed "ms")) + (mtx/run! metrics + :id :audit-http-handler-timing + :val elapsed) + (mtx/run! metrics + :id :audit-http-handler-queue-size + :val (get instance ::pxb/current-queue-size)) + (mtx/run! metrics + :id :audit-http-handler-concurrency + :val (get instance ::pxb/current-concurrency))))] + + (let [limiter (pxb/create :executor executor + :concurrency concurrency + :on-queue on-queue + :on-run on-run)] + (fn [request respond _] + (->> (px/submit! limiter (partial handle-request request)) + (p/fnly (fn [_ cause] + (some-> cause report-error!) + (respond (yrs/response 204)))))))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Collector +;; COLLECTOR ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Defines a service that collects the audit/activity log using ;; internal database. Later this audit log can be transferred to ;; an external storage and data cleared. -(declare persist-events) +(s/def ::collector + (s/nilable + (s/keys :req [::wrk/executor ::db/pool]))) (defmethod ig/pre-init-spec ::collector [_] - (s/keys :req-un [::db/pool ::wrk/executor])) - -(s/def ::ip-addr string?) -(s/def ::backend-event - (s/keys :req-un [::type ::name ::profile-id] - :opt-un [::ip-addr ::props])) - -(def ^:private backend-event-xform - (comp - (filter #(us/valid? ::backend-event %)) - (map clean-props))) + (s/keys :req [::db/pool ::wrk/executor ::mtx/metrics])) (defmethod ig/init-key ::collector - [_ {:keys [pool] :as cfg}] + [_ {:keys [::db/pool] :as cfg}] (cond (not (contains? cf/flags :audit-log)) - (do - (l/info :hint "audit log collection disabled") - (constantly nil)) + (l/info :hint "audit: log collection disabled") (db/read-only? pool) - (do - (l/warn :hint "audit log collection disabled, db is read-only") - (constantly nil)) + (l/warn :hint "audit: log collection disabled (db is read-only)") :else - (let [input (a/chan 512 backend-event-xform) - buffer (aa/batch input {:max-batch-size 100 - :max-batch-age (* 10 1000) ; 10s - :init []})] - (l/info :hint "audit log collector initialized") - (a/go-loop [] - (when-let [[_type events] (a/ (:ip-addr event) db/inet) + :props (db/tjson (:props event)) + :source "backend"})) - :submit - (let [params (-> params - (dissoc :cmd) - (assoc :tracked-at (dt/now)))] - (when-not (a/offer! input params) - (l/warn :hint "activity channel is full")))))))) - -(defn- persist-events - [{:keys [pool executor] :as cfg} events] - (letfn [(event->row [event] - [(uuid/next) - (:name event) - (:type event) - (:profile-id event) - (:tracked-at event) - (some-> (:ip-addr event) db/inet) - (db/tjson (:props event)) - "backend"])] - (aa/with-thread executor - (when (seq events) - (db/with-atomic [conn pool] - (db/insert-multi! conn :audit-log - [:id :name :type :profile-id :tracked-at :ip-addr :props :source] - (sequence (keep event->row) events))))))) +(defn submit! + "Submit audit event to the collector." + [{:keys [::wrk/executor ::db/pool]} params] + (->> (px/submit! executor (partial persist-event! pool (d/without-nils params))) + (p/err (fn [cause] + (l/error :hint "audit: unexpected error processing event" :cause cause) + (p/resolved nil))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Archive Task +;; TASK: ARCHIVE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; This is a task responsible to send the accumulated events to an +;; This is a task responsible to send the accumulated events to ;; external service for archival. (declare archive-events) -(s/def ::http-client fn?) -(s/def ::uri ::us/string) -(s/def ::sprops map?) +(s/def ::tasks/uri ::us/string) -(defmethod ig/pre-init-spec ::archive-task [_] - (s/keys :req-un [::db/pool ::sprops ::http-client] - :opt-un [::uri])) +(defmethod ig/pre-init-spec ::tasks/archive-task [_] + (s/keys :req [::db/pool ::main/props ::http/client])) -(defmethod ig/init-key ::archive-task - [_ {:keys [uri] :as cfg}] - (fn [props] +(defmethod ig/init-key ::tasks/archive + [_ cfg] + (fn [params] ;; NOTE: this let allows overwrite default configured values from ;; the repl, when manually invoking the task. (let [enabled (or (contains? cf/flags :audit-log-archive) - (:enabled props false)) - uri (or uri (:uri props)) - cfg (assoc cfg :uri uri)] + (:enabled params false)) + uri (cf/get :audit-log-archive-uri) + uri (or uri (:uri params)) + cfg (assoc cfg ::uri uri)] (when (and enabled (not uri)) (ex/raise :type :internal @@ -264,20 +293,21 @@ (let [n (archive-events cfg)] (if n (do - (aa/thread-sleep 100) + (px/sleep 100) (recur (+ total n))) (when (pos? total) - (l/trace :hint "events chunk archived" :num total))))))))) + (l/debug :hint "events archived" :total total))))))))) -(def sql:retrieve-batch-of-audit-log - "select * from audit_log +(def ^:private sql:retrieve-batch-of-audit-log + "select * + from audit_log where archived_at is null order by created_at asc limit 256 for update skip locked;") (defn archive-events - [{:keys [pool uri sprops http-client] :as cfg}] + [{:keys [::db/pool ::uri] :as cfg}] (letfn [(decode-row [{:keys [props ip-addr context] :as row}] (cond-> row (db/pgobject? props) @@ -301,9 +331,11 @@ :context])) (send [events] - (let [token (tokens/generate sprops {:iss "authentication" - :iat (dt/now) - :uid uuid/zero}) + (let [token (tokens/generate (::main/props cfg) + {:iss "authentication" + :iat (dt/now) + :uid uuid/zero}) + ;; FIXME tokens/generate body (t/encode {:events events}) headers {"content-type" "application/transit+json" "origin" (cf/get :public-uri) @@ -313,7 +345,7 @@ :method :post :headers headers :body body} - resp (http-client params {:sync? true})] + resp (http/req! cfg params {:sync? true})] (if (= (:status resp) 204) true (do @@ -334,7 +366,7 @@ (map row->event)) events (into [] xform rows)] (when-not (empty? events) - (l/debug :action "archive-events" :uri uri :events (count events)) + (l/trace :hint "archive events chunk" :uri uri :events (count events)) (when (send events) (mark-as-archived conn rows) (count events))))))) @@ -343,7 +375,7 @@ ;; GC Task ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(def sql:clean-archived +(def ^:private sql:clean-archived "delete from audit_log where archived_at is not null") @@ -354,10 +386,10 @@ (l/debug :hint "delete archived audit log entries" :deleted result) result)) -(defmethod ig/pre-init-spec ::gc-task [_] - (s/keys :req-un [::db/pool])) +(defmethod ig/pre-init-spec ::tasks/gc [_] + (s/keys :req [::db/pool])) -(defmethod ig/init-key ::gc-task +(defmethod ig/init-key ::tasks/gc [_ cfg] (fn [_] (clean-archived cfg))) diff --git a/backend/src/app/loggers/loki.clj b/backend/src/app/loggers/loki.clj index 12507ef8bc..3f95d3ad68 100644 --- a/backend/src/app/loggers/loki.clj +++ b/backend/src/app/loggers/loki.clj @@ -8,58 +8,55 @@ "A Loki integration." (:require [app.common.logging :as l] - [app.common.spec :as us] - [app.config :as cfg] + [app.config :as cf] + [app.http.client :as http] + [app.loggers.zmq :as lzmq] [app.util.json :as json] [clojure.core.async :as a] [clojure.spec.alpha :as s] - [integrant.core :as ig])) + [integrant.core :as ig] + [promesa.exec :as px])) (declare ^:private handle-event) -(declare ^:private start-rcv-loop) - -(s/def ::uri ::us/string) -(s/def ::receiver fn?) -(s/def ::http-client fn?) (defmethod ig/pre-init-spec ::reporter [_] - (s/keys :req-un [ ::receiver ::http-client] - :opt-un [::uri])) + (s/keys :req [::http/client + ::lzmq/receiver])) (defmethod ig/init-key ::reporter - [_ {:keys [receiver uri] :as cfg}] - (when uri - (l/info :msg "initializing loki reporter" :uri uri) - (let [input (a/chan (a/dropping-buffer 2048))] - (receiver :sub input) + [_ cfg] + (when-let [uri (cf/get :loggers-loki-uri)] + (px/thread + {:name "penpot/loki-reporter"} + (l/info :hint "reporter started" :uri uri) + (let [input (a/chan (a/dropping-buffer 2048)) + cfg (assoc cfg ::uri uri)] - (doto (Thread. #(start-rcv-loop cfg input)) - (.setDaemon true) - (.setName "penpot/loki-sender") - (.start)) + (try + (lzmq/sub! (::lzmq/receiver cfg) input) + (loop [] + (when-let [msg (a/ thread px/interrupt!)) (defn- prepare-payload [event] - (let [labels {:host (cfg/get :host) - :tenant (cfg/get :tenant) - :version (:full cfg/version) + (let [labels {:host (cf/get :host) + :tenant (cf/get :tenant) + :version (:full cf/version) :logger (:logger/name event) :level (:logger/level event)}] {:streams @@ -69,15 +66,15 @@ (when-let [error (:trace event)] (str "\n" error)))]]}]})) - (defn- make-request - [{:keys [http-client uri] :as cfg} payload] - (http-client {:uri uri - :timeout 3000 - :method :post - :headers {"content-type" "application/json"} - :body (json/write payload)} - {:sync? true})) + [{:keys [::uri] :as cfg} payload] + (http/req! cfg + {:uri uri + :timeout 3000 + :method :post + :headers {"content-type" "application/json"} + :body (json/write payload)} + {:sync? true})) (defn- handle-event [cfg event] diff --git a/backend/src/app/loggers/mattermost.clj b/backend/src/app/loggers/mattermost.clj index dce9a35bae..15c51d044b 100644 --- a/backend/src/app/loggers/mattermost.clj +++ b/backend/src/app/loggers/mattermost.clj @@ -9,67 +9,69 @@ (:require [app.common.logging :as l] [app.config :as cf] + [app.http.client :as http] [app.loggers.database :as ldb] + [app.loggers.zmq :as lzmq] [app.util.json :as json] [clojure.core.async :as a] [clojure.spec.alpha :as s] [integrant.core :as ig] - [promesa.core :as p])) + [promesa.exec :as px])) (defonce enabled (atom true)) (defn- send-mattermost-notification! - [{:keys [http-client] :as cfg} {:keys [host id public-uri] :as event}] - (let [uri (:uri cfg) - text (str "Exception on (host: " host ", url: " public-uri "/dbg/error/" id ")\n" - (when-let [pid (:profile-id event)] - (str "- profile-id: #uuid-" pid "\n")))] - (p/then - (http-client {:uri uri - :method :post - :headers {"content-type" "application/json"} - :body (json/write-str {:text text})}) - (fn [{:keys [status] :as rsp}] - (when (not= status 200) - (l/warn :hint "error on sending data to mattermost" - :response (pr-str rsp))))))) + [cfg {:keys [host id public-uri] :as event}] + (let [text (str "Exception on (host: " host ", url: " public-uri "/dbg/error/" id ")\n" + (when-let [pid (:profile-id event)] + (str "- profile-id: #uuid-" pid "\n"))) + resp (http/req! cfg + {:uri (cf/get :error-report-webhook) + :method :post + :headers {"content-type" "application/json"} + :body (json/write-str {:text text})} + {:sync? true})] + + (when (not= 200 (:status resp)) + (l/warn :hint "error on sending data" + :response (pr-str resp))))) (defn handle-event [cfg event] - (let [ch (a/chan)] - (-> (p/let [event (ldb/parse-event event)] - (send-mattermost-notification! cfg event)) - (p/finally (fn [_ cause] - (when cause - (l/warn :hint "unexpected exception on error reporter" :cause cause)) - (a/close! ch)))) - ch)) - -(s/def ::http-client fn?) -(s/def ::uri ::cf/error-report-webhook) + (try + (let [event (ldb/parse-event event)] + (when @enabled + (send-mattermost-notification! cfg event))) + (catch Throwable cause + (l/warn :hint "unhandled error" + :cause cause)))) (defmethod ig/pre-init-spec ::reporter [_] - (s/keys :req-un [::http-client ::receiver] - :opt-un [::uri])) + (s/keys :req [::http/client + ::lzmq/receiver])) (defmethod ig/init-key ::reporter - [_ {:keys [receiver uri] :as cfg}] - (when uri - (l/info :msg "initializing mattermost error reporter" :uri uri) - (let [output (a/chan (a/sliding-buffer 128) - (filter (fn [event] - (= (:logger/level event) "error"))))] - (receiver :sub output) - (a/go-loop [] - (let [msg (a/ thread px/interrupt!)) diff --git a/backend/src/app/loggers/zmq.clj b/backend/src/app/loggers/zmq.clj index c7ea94c474..19a7e98008 100644 --- a/backend/src/app/loggers/zmq.clj +++ b/backend/src/app/loggers/zmq.clj @@ -9,13 +9,15 @@ (:require [app.common.exceptions :as ex] [app.common.logging :as l] - [app.common.spec :as us] + [app.config :as cf] + [app.loggers.zmq.receiver :as-alias receiver] [app.util.json :as json] [app.util.time :as dt] [clojure.core.async :as a] [clojure.spec.alpha :as s] [cuerdas.core :as str] - [integrant.core :as ig]) + [integrant.core :as ig] + [promesa.exec :as px]) (:import org.zeromq.SocketType org.zeromq.ZMQ$Socket @@ -24,38 +26,56 @@ (declare prepare) (declare start-rcv-loop) -(s/def ::endpoint ::us/string) - -(defmethod ig/pre-init-spec ::receiver [_] - (s/keys :opt-un [::endpoint])) - (defmethod ig/init-key ::receiver - [_ {:keys [endpoint] :as cfg}] - (l/info :msg "initializing ZMQ receiver" :bind endpoint) - (let [buffer (a/chan 1) + [_ cfg] + (let [uri (cf/get :loggers-zmq-uri) + buffer (a/chan 1) output (a/chan 1 (comp (filter map?) (keep prepare))) - mult (a/mult output)] - (when endpoint - (let [thread (Thread. #(start-rcv-loop {:out buffer :endpoint endpoint}))] - (.setDaemon thread false) - (.setName thread "penpot/zmq-logger-receiver") - (.start thread))) + mult (a/mult output) + thread (when uri + (px/thread + {:name "penpot/zmq-receiver" + :daemon false} + (l/info :hint "receiver started") + (try + (start-rcv-loop buffer uri) + (catch InterruptedException _ + (l/debug :hint "receiver interrupted")) + (catch java.lang.IllegalStateException cause + (if (= "errno 4" (ex-message cause)) + (l/debug :hint "receiver interrupted") + (l/error :hint "unhandled error" :cause cause))) + (catch Throwable cause + (l/error :hint "unhandled error" :cause cause)) + (finally + (l/info :hint "receiver terminated")))))] (a/pipe buffer output) - (with-meta - (fn [cmd ch] - (case cmd - :sub (a/tap mult ch) - :unsub (a/untap mult ch)) - ch) - {::output output - ::buffer buffer - ::mult mult}))) + (-> cfg + (assoc ::receiver/mult mult) + (assoc ::receiver/thread thread) + (assoc ::receiver/output output) + (assoc ::receiver/buffer buffer)))) + +(s/def ::receiver/mult some?) +(s/def ::receiver/thread #(instance? Thread %)) +(s/def ::receiver/output some?) +(s/def ::receiver/buffer some?) +(s/def ::receiver + (s/keys :req [::receiver/mult + ::receiver/thread + ::receiver/output + ::receiver/buffer])) + +(defn sub! + [{:keys [::receiver/mult]} ch] + (a/tap mult ch)) (defmethod ig/halt-key! ::receiver - [_ f] - (a/close! (::buffer (meta f)))) + [_ {:keys [::receiver/buffer ::receiver/thread]}] + (some-> thread px/interrupt!) + (some-> buffer a/close!)) (def ^:private json-mapper (json/mapper @@ -63,23 +83,23 @@ :decode-key-fn (comp keyword str/kebab)})) (defn- start-rcv-loop - ([] (start-rcv-loop nil)) - ([{:keys [out endpoint] :or {endpoint "tcp://localhost:5556"}}] - (let [out (or out (a/chan 1)) - zctx (ZContext. 1) - socket (.. zctx (createSocket SocketType/SUB))] - (.. socket (connect ^String endpoint)) - (.. socket (subscribe "")) - (.. socket (setReceiveTimeOut 5000)) - (loop [] - (let [msg (.recv ^ZMQ$Socket socket) - msg (ex/ignoring (json/read msg json-mapper)) - msg (if (nil? msg) :empty msg)] - (if (a/>!! out msg) - (recur) - (do - (.close ^java.lang.AutoCloseable socket) - (.destroy ^ZContext zctx)))))))) + [output endpoint] + (let [zctx (ZContext. 1) + socket (.. zctx (createSocket SocketType/SUB))] + (try + (.. socket (connect ^String endpoint)) + (.. socket (subscribe "")) + (.. socket (setReceiveTimeOut 5000)) + (loop [] + (let [msg (.recv ^ZMQ$Socket socket) + msg (ex/ignoring (json/read msg json-mapper)) + msg (if (nil? msg) :empty msg)] + (when (a/>!! output msg) + (recur)))) + + (finally + (.close ^java.lang.AutoCloseable socket) + (.destroy ^ZContext zctx))))) (s/def ::logger-name string?) (s/def ::level string?) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 2c4adb13e7..0ba5ab622b 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -6,10 +6,16 @@ (ns app.main (:require - [app.auth.oidc] + [app.auth.oidc :as-alias oidc] + [app.auth.oidc.providers :as-alias oidc.providers] [app.common.logging :as l] [app.config :as cf] [app.db :as-alias db] + [app.http.client :as-alias http.client] + [app.http.session :as-alias http.session] + [app.loggers.audit :as-alias audit] + [app.loggers.audit.tasks :as-alias audit.tasks] + [app.loggers.zmq :as-alias lzmq] [app.metrics :as-alias mtx] [app.metrics.definition :as-alias mdef] [app.redis :as-alias rds] @@ -100,6 +106,24 @@ ::mdef/labels ["name"] ::mdef/type :summary} + :audit-http-handler-queue-size + {::mdef/name "penpot_audit_http_handler_queue_size" + ::mdef/help "Current number of queued submissions on the audit log http handler" + ::mdef/labels [] + ::mdef/type :gauge} + + :audit-http-handler-concurrency + {::mdef/name "penpot_audit_http_handler_concurrency" + ::mdef/help "Current number of used concurrency capacity on the audit log http handler" + ::mdef/labels [] + ::mdef/type :gauge} + + :audit-http-handler-timing + {::mdef/name "penpot_audit_http_handler_timing" + ::mdef/help "Summary of the time between queuing and executing on the audit log http handler" + ::mdef/labels [] + ::mdef/type :summary} + :executors-active-threads {::mdef/name "penpot_executors_active_threads" ::mdef/help "Current number of threads available in the executor service." @@ -178,8 +202,8 @@ ::sto/gc-touched-task {:pool (ig/ref ::db/pool)} - :app.http/client - {:executor (ig/ref ::wrk/executor)} + ::http.client/client + {::wrk/executor (ig/ref ::wrk/executor)} :app.http.session/manager {:pool (ig/ref ::db/pool) @@ -191,10 +215,10 @@ :max-age (cf/get :auth-token-cookie-max-age)} :app.http.awsns/handler - {:sprops (ig/ref :app.setup/props) - :pool (ig/ref ::db/pool) - :http-client (ig/ref :app.http/client) - :executor (ig/ref ::wrk/executor)} + {::props (ig/ref :app.setup/props) + ::db/pool (ig/ref ::db/pool) + ::http.client/client (ig/ref ::http.client/client) + ::wrk/executor (ig/ref ::wrk/executor)} :app.http/server {:port (cf/get :http-server-port) @@ -220,51 +244,30 @@ :bind-password (cf/get :ldap-bind-password) :enabled? (contains? cf/flags :login-with-ldap)} - :app.auth.oidc/google-provider - {:enabled? (contains? cf/flags :login-with-google) - :client-id (cf/get :google-client-id) - :client-secret (cf/get :google-client-secret)} + ::oidc.providers/google + {} - :app.auth.oidc/github-provider - {:enabled? (contains? cf/flags :login-with-github) - :http-client (ig/ref :app.http/client) - :client-id (cf/get :github-client-id) - :client-secret (cf/get :github-client-secret)} + ::oidc.providers/github + {::http.client/client (ig/ref ::http.client/client)} - :app.auth.oidc/gitlab-provider - {:enabled? (contains? cf/flags :login-with-gitlab) - :base-uri (cf/get :gitlab-base-uri "https://gitlab.com") - :client-id (cf/get :gitlab-client-id) - :client-secret (cf/get :gitlab-client-secret)} + ::oidc.providers/gitlab + {} - :app.auth.oidc/generic-provider - {:enabled? (contains? cf/flags :login-with-oidc) - :http-client (ig/ref :app.http/client) + ::oidc.providers/generic + {::http.client/client (ig/ref ::http.client/client)} - :client-id (cf/get :oidc-client-id) - :client-secret (cf/get :oidc-client-secret) + ::oidc/routes + {::http.client/client (ig/ref ::http.client/client) + ::db/pool (ig/ref ::db/pool) + ::props (ig/ref :app.setup/props) + ::wrk/executor (ig/ref ::wrk/executor) + ::oidc/providers {:google (ig/ref ::oidc.providers/google) + :github (ig/ref ::oidc.providers/github) + :gitlab (ig/ref ::oidc.providers/gitlab) + :oidc (ig/ref ::oidc.providers/generic)} + ::audit/collector (ig/ref ::audit/collector) + ::http.session/session (ig/ref :app.http.session/manager)} - :base-uri (cf/get :oidc-base-uri) - - :token-uri (cf/get :oidc-token-uri) - :auth-uri (cf/get :oidc-auth-uri) - :user-uri (cf/get :oidc-user-uri) - - :scopes (cf/get :oidc-scopes) - :roles-attr (cf/get :oidc-roles-attr) - :roles (cf/get :oidc-roles)} - - :app.auth.oidc/routes - {:providers {:google (ig/ref :app.auth.oidc/google-provider) - :github (ig/ref :app.auth.oidc/github-provider) - :gitlab (ig/ref :app.auth.oidc/gitlab-provider) - :oidc (ig/ref :app.auth.oidc/generic-provider)} - :sprops (ig/ref :app.setup/props) - :http-client (ig/ref :app.http/client) - :pool (ig/ref ::db/pool) - :session (ig/ref :app.http.session/manager) - :public-uri (cf/get :public-uri) - :executor (ig/ref ::wrk/executor)} ;; TODO: revisit the dependencies of this service, looks they are too much unused of them :app.http/router @@ -273,12 +276,12 @@ :session (ig/ref :app.http.session/manager) :awsns-handler (ig/ref :app.http.awsns/handler) :debug-routes (ig/ref :app.http.debug/routes) - :oidc-routes (ig/ref :app.auth.oidc/routes) + :oidc-routes (ig/ref ::oidc/routes) :ws (ig/ref :app.http.websocket/handler) :metrics (ig/ref ::mtx/metrics) :public-uri (cf/get :public-uri) :storage (ig/ref ::sto/storage) - :audit-handler (ig/ref :app.loggers.audit/http-handler) + :audit-handler (ig/ref ::audit/http-handler) :rpc-routes (ig/ref :app.rpc/routes) :doc-routes (ig/ref :app.rpc.doc/routes) :executor (ig/ref ::wrk/executor)} @@ -315,21 +318,22 @@ :scheduled-executor (ig/ref ::wrk/scheduled-executor)} :app.rpc/methods - {:pool (ig/ref ::db/pool) - :session (ig/ref :app.http.session/manager) - :sprops (ig/ref :app.setup/props) - :metrics (ig/ref ::mtx/metrics) - :storage (ig/ref ::sto/storage) - :msgbus (ig/ref :app.msgbus/msgbus) - :public-uri (cf/get :public-uri) - :redis (ig/ref ::rds/redis) - :audit (ig/ref :app.loggers.audit/collector) - :ldap (ig/ref :app.auth.ldap/provider) - :http-client (ig/ref :app.http/client) - :climit (ig/ref :app.rpc/climit) - :rlimit (ig/ref :app.rpc/rlimit) - :executor (ig/ref ::wrk/executor) - :templates (ig/ref :app.setup/builtin-templates) + {::audit/collector (ig/ref ::audit/collector) + ::http.client/client (ig/ref ::http.client/client) + :pool (ig/ref ::db/pool) + :session (ig/ref :app.http.session/manager) + :sprops (ig/ref :app.setup/props) + :metrics (ig/ref ::mtx/metrics) + :storage (ig/ref ::sto/storage) + :msgbus (ig/ref :app.msgbus/msgbus) + :public-uri (cf/get :public-uri) + :redis (ig/ref ::rds/redis) + :ldap (ig/ref :app.auth.ldap/provider) + :http-client (ig/ref ::http.client/client) + :climit (ig/ref :app.rpc/climit) + :rlimit (ig/ref :app.rpc/rlimit) + :executor (ig/ref ::wrk/executor) + :templates (ig/ref :app.setup/builtin-templates) } :app.rpc.doc/routes @@ -350,8 +354,8 @@ :tasks-gc (ig/ref :app.tasks.tasks-gc/handler) :telemetry (ig/ref :app.tasks.telemetry/handler) :session-gc (ig/ref :app.http.session/gc-task) - :audit-log-archive (ig/ref :app.loggers.audit/archive-task) - :audit-log-gc (ig/ref :app.loggers.audit/gc-task)}} + :audit-log-archive (ig/ref ::audit.tasks/archive) + :audit-log-gc (ig/ref ::audit.tasks/gc)}} :app.emails/sendmail @@ -383,52 +387,49 @@ {:pool (ig/ref ::db/pool)} :app.tasks.telemetry/handler - {:pool (ig/ref ::db/pool) - :version (:full cf/version) - :uri (cf/get :telemetry-uri) - :sprops (ig/ref :app.setup/props) - :http-client (ig/ref :app.http/client)} + {::db/pool (ig/ref ::db/pool) + ::http.client/client (ig/ref ::http.client/client) + ::props (ig/ref :app.setup/props)} :app.srepl/server {:port (cf/get :srepl-port) :host (cf/get :srepl-host)} :app.setup/builtin-templates - {:http-client (ig/ref :app.http/client)} + {::http.client/client (ig/ref ::http.client/client)} :app.setup/props {:pool (ig/ref ::db/pool) :key (cf/get :secret-key)} - :app.loggers.zmq/receiver - {:endpoint (cf/get :loggers-zmq-uri)} + ::lzmq/receiver + {} - :app.loggers.audit/http-handler - {:pool (ig/ref ::db/pool) - :executor (ig/ref ::wrk/executor)} + ::audit/http-handler + {::db/pool (ig/ref ::db/pool) + ::wrk/executor (ig/ref ::wrk/executor) + ::mtx/metrics (ig/ref ::mtx/metrics)} - :app.loggers.audit/collector - {:pool (ig/ref ::db/pool) - :executor (ig/ref ::wrk/executor)} + ::audit/collector + {::db/pool (ig/ref ::db/pool) + ::wrk/executor (ig/ref ::wrk/executor) + ::mtx/metrics (ig/ref ::mtx/metrics)} - :app.loggers.audit/archive-task - {:uri (cf/get :audit-log-archive-uri) - :sprops (ig/ref :app.setup/props) - :pool (ig/ref ::db/pool) - :http-client (ig/ref :app.http/client)} + ::audit.tasks/archive + {::props (ig/ref :app.setup/props) + ::db/pool (ig/ref ::db/pool) + ::http.client/client (ig/ref ::http.client/client)} - :app.loggers.audit/gc-task - {:pool (ig/ref ::db/pool)} + ::audit.tasks/gc + {::db/pool (ig/ref ::db/pool)} :app.loggers.loki/reporter - {:uri (cf/get :loggers-loki-uri) - :receiver (ig/ref :app.loggers.zmq/receiver) - :http-client (ig/ref :app.http/client)} + {::lzmq/receiver (ig/ref ::lzmq/receiver) + ::http.client/client (ig/ref ::http.client/client)} :app.loggers.mattermost/reporter - {:uri (cf/get :error-report-webhook) - :receiver (ig/ref :app.loggers.zmq/receiver) - :http-client (ig/ref :app.http/client)} + {::lzmq/receiver (ig/ref ::lzmq/receiver) + ::http.client/client (ig/ref ::http.client/client)} :app.loggers.database/reporter {:receiver (ig/ref :app.loggers.zmq/receiver) @@ -502,7 +503,8 @@ ::db/pool (ig/ref ::db/pool)} ::wrk/worker - {::wrk/parallelism (cf/get ::worker-parallelism 3) + {::wrk/parallelism (cf/get ::worker-parallelism 1) + ;; FIXME: read queues from configuration ::wrk/queue "default" ::rds/redis (ig/ref ::rds/redis) ::wrk/registry (ig/ref ::wrk/registry) @@ -521,7 +523,7 @@ (merge worker-config)) (ig/prep) (ig/init)))) - (l/info :msg "welcome to penpot" + (l/info :hint "welcome to penpot" :flags (str/join "," (map name cf/flags)) :worker? (contains? cf/flags :backend-worker) :version (:full cf/version))) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index adeeceb9e4..9eaa08014e 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -6,12 +6,15 @@ (ns app.rpc (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.spec :as us] + [app.common.uuid :as uuid] [app.db :as db] [app.http :as-alias http] - [app.http.session :as-alias session] + [app.http.client :as-alias http.client] + [app.http.session :as-alias http.session] [app.loggers.audit :as audit] [app.metrics :as mtx] [app.msgbus :as-alias mbus] @@ -84,7 +87,7 @@ internal async flow into ring async flow." [methods {:keys [profile-id session-id params] :as request} respond raise] (let [type (keyword (:type params)) - data (into {::request request} params) + data (into {::http/request request} params) data (if profile-id (assoc data :profile-id profile-id ::session-id session-id) (dissoc data :profile-id)) @@ -103,7 +106,7 @@ [methods {:keys [profile-id session-id params] :as request} respond raise] (let [cmd (keyword (:command params)) etag (yrq/get-header request "if-none-match") - data (into {::request request ::cond/key etag} params) + data (into {::http/request request ::cond/key etag} params) data (if profile-id (assoc data :profile-id profile-id ::session-id session-id) (dissoc data :profile-id)) @@ -143,30 +146,36 @@ mdata)) (defn- wrap-audit - [{:keys [audit] :as cfg} f mdata] - (if audit - (with-meta - (fn [cfg {:keys [::request] :as params}] - (p/finally (f cfg params) - (fn [result _] - (when result - (let [resultm (meta result) - profile-id (or (::audit/profile-id resultm) - (:profile-id result) - (:profile-id params)) - props (or (::audit/replace-props resultm) - (-> params - (merge (::audit/props resultm)) - (dissoc :type)))] - (audit :cmd :submit - :type (or (::audit/type resultm) + [cfg f mdata] + (if-let [collector (::audit/collector cfg)] + (letfn [(handle-audit [params result] + (let [resultm (meta result) + request (::http/request params) + profile-id (or (::audit/profile-id resultm) + (:profile-id result) + (:profile-id params) + uuid/zero) + props (or (::audit/replace-props resultm) + (-> params + (merge (::audit/props resultm)) + (dissoc :profile-id) + (dissoc :type))) + event {:type (or (::audit/type resultm) (::type cfg)) :name (or (::audit/name resultm) (::sv/name mdata)) :profile-id profile-id :ip-addr (some-> request audit/parse-client-ip) - :props (dissoc props ::request))))))) - mdata) + :props (d/without-qualified props)}] + (audit/submit! collector event))) + + (handle-request [cfg params] + (->> (f cfg params) + (p/mcat (fn [result] + (->> (handle-audit params result) + (p/map (constantly result)))))))] + + (with-meta handle-request mdata)) f)) (defn- wrap @@ -251,8 +260,6 @@ (map (partial process-method cfg)) (into {})))) -(s/def ::audit (s/nilable fn?)) -(s/def ::http-client fn?) (s/def ::ldap (s/nilable map?)) (s/def ::msgbus ::mbus/msgbus) (s/def ::climit (s/nilable ::climit/climit)) @@ -262,13 +269,13 @@ (s/def ::sprops map?) (defmethod ig/pre-init-spec ::methods [_] - (s/keys :req-un [::sto/storage - ::session/session + (s/keys :req [::audit/collector + ::http.client/client] + :req-un [::sto/storage + ::http.session/session ::sprops - ::audit ::public-uri ::msgbus - ::http-client ::rlimit ::climit ::wrk/executor diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index 46f2c5d720..d34a55ab2f 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -15,7 +15,6 @@ [app.emails :as eml] [app.http.session :as session] [app.loggers.audit :as audit] - [app.rpc :as-alias rpc] [app.rpc.climit :as climit] [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] @@ -138,8 +137,8 @@ (-> response (rph/with-transform (session/create-fn session (:id profile))) - (vary-meta merge {::audit/props (audit/profile->props profile) - ::audit/profile-id (:id profile)})))))) + (rph/with-meta {::audit/props (audit/profile->props profile) + ::audit/profile-id (:id profile)})))))) (s/def ::login-with-password (s/keys :req-un [::email ::password] @@ -163,8 +162,7 @@ {:auth false ::doc/added "1.15"} [{:keys [session] :as cfg} _] - (with-meta {} - {::rpc/transform-response (session/delete-fn session)})) + (rph/with-transform {} (session/delete-fn session))) ;; ---- COMMAND: Recover Profile @@ -378,8 +376,6 @@ (create-profile conn) (create-profile-relations conn) (profile/decode-profile-row))) - audit-fn (:audit cfg) - invitation (when-let [token (:invitation-token params)] (tokens/verify sprops {:token token :iss :team-invitation}))] @@ -388,10 +384,11 @@ ;; accordingly. (when-let [id (:profile-id claims)] (db/update! conn :profile {:modified-at (dt/now)} {:id id}) - (audit-fn :cmd :submit - :type "fact" - :name "register-profile-retry" - :profile-id id)) + (when-let [collector (::audit/collector cfg)] + (audit/submit! collector + {:type "fact" + :name "register-profile-retry" + :profile-id id}))) (cond ;; If invitation token comes in params, this is because the @@ -404,33 +401,33 @@ (let [claims (assoc invitation :member-id (:id profile)) token (tokens/generate sprops claims) resp {:invitation-token token}] - (with-meta resp - {::rpc/transform-response (session/create-fn session (:id profile)) - ::audit/replace-props (audit/profile->props profile) - ::audit/profile-id (:id profile)})) + (-> resp + (rph/with-transform (session/create-fn session (:id profile))) + (rph/with-meta {::audit/replace-props (audit/profile->props profile) + ::audit/profile-id (:id profile)}))) ;; If auth backend is different from "penpot" means user is ;; registering using third party auth mechanism; in this case ;; we need to mark this session as logged. (not= "penpot" (:auth-backend profile)) - (with-meta (profile/strip-private-attrs profile) - {::rpc/transform-response (session/create-fn session (:id profile)) - ::audit/replace-props (audit/profile->props profile) - ::audit/profile-id (:id profile)}) + (-> (profile/strip-private-attrs profile) + (rph/with-transform (session/create-fn session (:id profile))) + (rph/with-meta {::audit/replace-props (audit/profile->props profile) + ::audit/profile-id (:id profile)})) ;; If the `:enable-insecure-register` flag is set, we proceed ;; to sign in the user directly, without email verification. (true? is-active) - (with-meta (profile/strip-private-attrs profile) - {::rpc/transform-response (session/create-fn session (:id profile)) - ::audit/replace-props (audit/profile->props profile) - ::audit/profile-id (:id profile)}) + (-> (profile/strip-private-attrs profile) + (rph/with-transform (session/create-fn session (:id profile))) + (rph/with-meta {::audit/replace-props (audit/profile->props profile) + ::audit/profile-id (:id profile)})) ;; In all other cases, send a verification email. :else (do (send-email-verification! conn sprops profile) - (with-meta profile + (rph/with-meta profile {::audit/replace-props (audit/profile->props profile) ::audit/profile-id (:id profile)}))))) diff --git a/backend/src/app/rpc/commands/ldap.clj b/backend/src/app/rpc/commands/ldap.clj index 8e1b464579..485194f6c4 100644 --- a/backend/src/app/rpc/commands/ldap.clj +++ b/backend/src/app/rpc/commands/ldap.clj @@ -12,9 +12,9 @@ [app.db :as db] [app.http.session :as session] [app.loggers.audit :as-alias audit] - [app.rpc :as-alias rpc] [app.rpc.commands.auth :as cmd.auth] [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.rpc.queries.profile :as profile] [app.util.services :as sv] [clojure.spec.alpha :as s])) @@ -63,15 +63,16 @@ :member-id (:id profile) :member-email (:email profile)) token (tokens :generate claims)] - (with-meta {:invitation-token token} - {::rpc/transform-response (session/create-fn session (:id profile)) - ::audit/props (:props profile) - ::audit/profile-id (:id profile)})) - (with-meta profile - {::rpc/transform-response (session/create-fn session (:id profile)) - ::audit/props (:props profile) - ::audit/profile-id (:id profile)}))))) + (-> {:invitation-token token} + (rph/with-transform (session/create-fn session (:id profile))) + (rph/with-meta {::audit/props (:props profile) + ::audit/profile-id (:id profile)}))) + + (-> profile + (rph/with-transform (session/create-fn session (:id profile))) + (rph/with-meta {::audit/props (:props profile) + ::audit/profile-id (:id profile)})))))) (defn- login-or-register [{:keys [pool] :as cfg} info] diff --git a/backend/src/app/rpc/commands/verify_token.clj b/backend/src/app/rpc/commands/verify_token.clj index 75e0b25f12..9b5df458f1 100644 --- a/backend/src/app/rpc/commands/verify_token.clj +++ b/backend/src/app/rpc/commands/verify_token.clj @@ -11,8 +11,8 @@ [app.db :as db] [app.http.session :as session] [app.loggers.audit :as audit] - [app.rpc :as-alias rpc] [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] [app.tokens :as tokens] @@ -48,7 +48,7 @@ {:email email} {:id profile-id}) - (with-meta claims + (rph/with-meta claims {::audit/name "update-profile-email" ::audit/props {:email email} ::audit/profile-id profile-id})) @@ -68,11 +68,11 @@ {:is-active true} {:id (:id profile)})) - (with-meta claims - {::rpc/transform-response (session/create-fn session profile-id) - ::audit/name "verify-profile-email" - ::audit/props (audit/profile->props profile) - ::audit/profile-id (:id profile)}))) + (-> claims + (rph/with-transform (session/create-fn session profile-id)) + (rph/with-meta {::audit/name "verify-profile-email" + ::audit/props (audit/profile->props profile) + ::audit/profile-id (:id profile)})))) (defmethod process-token :auth [{:keys [conn] :as cfg} _params {:keys [profile-id] :as claims}] @@ -148,14 +148,13 @@ ;; proceed with accepting the invitation and joining the ;; current profile to the invited team. (let [profile (accept-invitation cfg claims invitation profile)] - (with-meta - (assoc claims :state :created) - {::audit/name "accept-team-invitation" - ::audit/props (merge - (audit/profile->props profile) - {:team-id (:team-id claims) - :role (:role claims)}) - ::audit/profile-id profile-id})) + (-> (assoc claims :state :created) + (rph/with-meta {::audit/name "accept-team-invitation" + ::audit/props (merge + (audit/profile->props profile) + {:team-id (:team-id claims) + :role (:role claims)}) + ::audit/profile-id profile-id}))) (ex/raise :type :validation :code :invalid-token @@ -171,15 +170,14 @@ {:email member-email}) {:columns [:id :email]})] (let [profile (accept-invitation cfg claims invitation member)] - (with-meta - (assoc claims :state :created) - {::rpc/transform-response (session/create-fn session (:id profile)) - ::audit/name "accept-team-invitation" - ::audit/props (merge - (audit/profile->props profile) - {:team-id (:team-id claims) - :role (:role claims)}) - ::audit/profile-id member-id})) + (-> (assoc claims :state :created) + (rph/with-transform (session/create-fn session (:id profile))) + (rph/with-meta {::audit/name "accept-team-invitation" + ::audit/props (merge + (audit/profile->props profile) + {:team-id (:team-id claims) + :role (:role claims)}) + ::audit/profile-id member-id}))) {:invitation-token token :iss :team-invitation diff --git a/backend/src/app/rpc/helpers.clj b/backend/src/app/rpc/helpers.clj index d68282c087..aef80d7547 100644 --- a/backend/src/app/rpc/helpers.clj +++ b/backend/src/app/rpc/helpers.clj @@ -6,6 +6,7 @@ (ns app.rpc.helpers "General purpose RPC helpers." + (:refer-clojure :exclude [with-meta]) (:require [app.common.data.macros :as dm] [app.http :as-alias http] @@ -59,6 +60,10 @@ [mdw hook-fn] (vary-meta mdw update ::rpc/before-complete-fns conj hook-fn)) +(defn with-meta + [mdw mdata] + (vary-meta mdw merge mdata)) + (defn with-http-cache [mdw max-age] (vary-meta mdw update ::rpc/response-transform-fns conj diff --git a/backend/src/app/rpc/mutations/fonts.clj b/backend/src/app/rpc/mutations/fonts.clj index 61f4b3ee54..674254df30 100644 --- a/backend/src/app/rpc/mutations/fonts.clj +++ b/backend/src/app/rpc/mutations/fonts.clj @@ -11,9 +11,11 @@ [app.common.spec :as us] [app.common.uuid :as uuid] [app.db :as db] + [app.loggers.audit :as-alias audit] [app.media :as media] [app.rpc.climit :as-alias climit] [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.rpc.queries.teams :as teams] [app.storage :as sto] [app.util.services :as sv] @@ -104,10 +106,13 @@ :ttf-file-id (:id ttf)})) ] - (-> (generate-fonts data) - (p/then validate-data) - (p/then persist-fonts executor) - (p/then insert-into-db executor)))) + (->> (generate-fonts data) + (p/map validate-data) + (p/mcat executor persist-fonts) + (p/map executor insert-into-db) + (p/map (fn [result] + (let [params (update params :data (comp vec keys))] + (rph/with-meta result {::audit/replace-props params}))))))) ;; --- UPDATE FONT FAMILY diff --git a/backend/src/app/rpc/mutations/media.clj b/backend/src/app/rpc/mutations/media.clj index 2971d64bca..fb02982dbb 100644 --- a/backend/src/app/rpc/mutations/media.clj +++ b/backend/src/app/rpc/mutations/media.clj @@ -13,6 +13,7 @@ [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] + [app.http.client :as http] [app.media :as media] [app.rpc.climit :as climit] [app.rpc.queries.teams :as teams] @@ -186,7 +187,7 @@ (create-file-media-object-from-url cfg params))) (defn- create-file-media-object-from-url - [{:keys [http-client] :as cfg} {:keys [url name] :as params}] + [cfg {:keys [url name] :as params}] (letfn [(parse-and-validate-size [headers] (let [size (some-> (get headers "content-length") d/parse-integer) mtype (get headers "content-type") @@ -215,7 +216,7 @@ :format format})) (download-media [uri] - (-> (http-client {:method :get :uri uri} {:response-type :input-stream}) + (-> (http/req! cfg {:method :get :uri uri} {:response-type :input-stream}) (p/then process-response))) (process-response [{:keys [body headers] :as response}] diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index da087c8c14..c41a3501c7 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -19,6 +19,7 @@ [app.rpc.climit :as-alias climit] [app.rpc.commands.auth :as cmd.auth] [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] [app.storage :as sto] @@ -48,6 +49,7 @@ :opt-un [::lang ::theme])) (sv/defmethod ::update-profile + {::doc/added "1.0"} [{:keys [pool] :as cfg} {:keys [profile-id fullname lang theme] :as params}] (db/with-atomic [conn pool] ;; NOTE: we need to retrieve the profile independently if we use @@ -70,8 +72,11 @@ :props (db/tjson (:props profile))} {:id profile-id}) - (with-meta (-> profile profile/strip-private-attrs d/without-nils) - {::audit/props (audit/profile->props profile)})))) + (-> profile + profile/strip-private-attrs + d/without-nils + (rph/with-meta {::audit/props (audit/profile->props profile)}))))) + ;; --- MUTATION: Update Password @@ -133,7 +138,7 @@ (update-profile-photo cfg params))) (defn update-profile-photo - [{:keys [pool storage executor] :as cfg} {:keys [profile-id] :as params}] + [{:keys [pool storage executor] :as cfg} {:keys [profile-id file] :as params}] (p/let [profile (px/with-dispatch executor (db/get-by-id pool :profile profile-id)) photo (teams/upload-photo cfg params)] @@ -146,7 +151,13 @@ (db/update! pool :profile {:photo-id (:id photo)} {:id profile-id}) - nil)) + + (-> (rph/wrap) + (rph/with-meta {::audit/replace-props + {:file-name (:filename file) + :file-size (:size file) + :file-path (str (:path file)) + :file-mtype (:mtype file)}})))) ;; --- MUTATION: Request Email Change @@ -278,8 +289,7 @@ {:deleted-at deleted-at} {:id profile-id}) - (with-meta {} - {::rpc/transform-response (session/delete-fn session)})))) + (rph/with-transform {} (session/delete-fn session))))) (def sql:owned-teams "with owner_teams as ( @@ -298,77 +308,3 @@ (defn- get-owned-teams-with-participants [conn profile-id] (db/exec! conn [sql:owned-teams profile-id profile-id])) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; DEPRECATED METHODS (TO BE REMOVED ON 1.16.x) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; --- MUTATION: Login - -(s/def ::login ::cmd.auth/login-with-password) - -(sv/defmethod ::login - {:auth false - ::climit/queue :auth - ::doc/added "1.0" - ::doc/deprecated "1.15"} - [cfg params] - (cmd.auth/login-with-password cfg params)) - -;; --- MUTATION: Logout - -(s/def ::logout ::cmd.auth/logout) - -(sv/defmethod ::logout - {:auth false - ::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [session] :as cfg} _] - (with-meta {} - {::rpc/transform-response (session/delete-fn session)})) - -;; --- MUTATION: Recover Profile - -(s/def ::recover-profile ::cmd.auth/recover-profile) - -(sv/defmethod ::recover-profile - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [cfg params] - (cmd.auth/recover-profile cfg params)) - -;; --- MUTATION: Prepare Register - -(s/def ::prepare-register-profile ::cmd.auth/prepare-register-profile) - -(sv/defmethod ::prepare-register-profile - {:auth false - ::doc/added "1.0" - ::doc/deprecated "1.15"} - [cfg params] - (cmd.auth/prepare-register cfg params)) - -;; --- MUTATION: Register Profile - -(s/def ::register-profile ::cmd.auth/register-profile) - -(sv/defmethod ::register-profile - {:auth false - ::climit/queue :auth - ::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} params] - (db/with-atomic [conn pool] - (-> (assoc cfg :conn conn) - (cmd.auth/register-profile params)))) - -;; --- MUTATION: Request Profile Recovery - -(s/def ::request-profile-recovery ::cmd.auth/request-profile-recovery) - -(sv/defmethod ::request-profile-recovery - {:auth false - ::doc/added "1.0" - ::doc/deprecated "1.15"} - [cfg params] - (cmd.auth/request-profile-recovery cfg params)) diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index 83c68c1e8f..7da456c58a 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -474,7 +474,6 @@ [{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}] (db/with-atomic [conn pool] (let [team (create-team conn params) - audit-fn (:audit cfg) profile (db/get-by-id conn :profile profile-id)] ;; Create invitations for all provided emails. @@ -490,14 +489,15 @@ (-> team (vary-meta assoc ::audit/props {:invitations (count emails)}) (rph/with-defer - #(audit-fn :cmd :submit - :type "mutation" - :name "invite-team-member" - :profile-id profile-id - :props {:emails emails - :role role + #(when-let [collector (::audit/collector cfg)] + (audit/submit! collector + {:type "mutation" + :name "invite-team-member" :profile-id profile-id - :invitations (count emails)})))))) + :props {:emails emails + :role role + :profile-id profile-id + :invitations (count emails)}}))))))) ;; --- Mutation: Update invitation role diff --git a/backend/src/app/setup.clj b/backend/src/app/setup.clj index 074b973854..d5cb1a2585 100644 --- a/backend/src/app/setup.clj +++ b/backend/src/app/setup.clj @@ -8,8 +8,10 @@ "Initial data setup of instance." (:require [app.common.logging :as l] + [app.common.spec :as us] [app.common.uuid :as uuid] [app.db :as db] + [app.main :as-alias main] [app.setup.builtin-templates] [app.setup.keys :as keys] [buddy.core.codecs :as bc] @@ -48,6 +50,9 @@ :cause cause)))) instance-id))) +(s/def ::main/props + (s/map-of ::us/keyword some?)) + (defmethod ig/pre-init-spec ::props [_] (s/keys :req-un [::db/pool])) diff --git a/backend/src/app/setup/builtin_templates.clj b/backend/src/app/setup/builtin_templates.clj index 35658e75dd..23b6875aa4 100644 --- a/backend/src/app/setup/builtin_templates.clj +++ b/backend/src/app/setup/builtin_templates.clj @@ -29,10 +29,8 @@ (s/keys :req-un [::id ::name ::thumbnail-uri ::file-uri] :opt-un [::path])) -(s/def ::http-client ::http/client) - (defmethod ig/pre-init-spec :app.setup/builtin-templates [_] - (s/keys :req-un [::http-client])) + (s/keys :req [::http/client])) (defmethod ig/init-key :app.setup/builtin-templates [_ cfg] @@ -43,7 +41,7 @@ (defn- download-preset! [cfg {:keys [path file-uri] :as preset}] - (let [response (http/req! (:http-client cfg) + (let [response (http/req! cfg {:method :get :uri file-uri} {:response-type :input-stream diff --git a/backend/src/app/tasks/telemetry.clj b/backend/src/app/tasks/telemetry.clj index b94dfe1e39..e6323ebdbd 100644 --- a/backend/src/app/tasks/telemetry.clj +++ b/backend/src/app/tasks/telemetry.clj @@ -11,13 +11,14 @@ (:require [app.common.data :as d] [app.common.exceptions :as ex] - [app.common.spec :as us] [app.config :as cf] [app.db :as db] - [app.util.async :refer [thread-sleep]] + [app.http.client :as http] + [app.main :as-alias main] [app.util.json :as json] [clojure.spec.alpha :as s] - [integrant.core :as ig])) + [integrant.core :as ig] + [promesa.exec :as px])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TASK ENTRY POINT @@ -28,18 +29,13 @@ (declare get-subscriptions-newsletter-updates) (declare get-subscriptions-newsletter-news) -(s/def ::http-client fn?) -(s/def ::version ::us/string) -(s/def ::uri ::us/string) -(s/def ::instance-id ::us/uuid) -(s/def ::sprops - (s/keys :req-un [::instance-id])) - (defmethod ig/pre-init-spec ::handler [_] - (s/keys :req-un [::db/pool ::http-client ::version ::uri ::sprops])) + (s/keys :req [::http/client + ::db/pool + ::main/props])) (defmethod ig/init-key ::handler - [_ {:keys [pool sprops version] :as cfg}] + [_ {:keys [::db/pool ::main/props] :as cfg}] (fn [{:keys [send? enabled?] :or {send? true enabled? false}}] (let [subs {:newsletter-updates (get-subscriptions-newsletter-updates pool) :newsletter-news (get-subscriptions-newsletter-news pool)} @@ -48,15 +44,15 @@ (cf/get :telemetry-enabled)) data {:subscriptions subs - :version version - :instance-id (:instance-id sprops)}] + :version (:full cf/version) + :instance-id (:instance-id props)}] (cond ;; If we have telemetry enabled, then proceed the normal ;; operation. enabled? (let [data (merge data (get-stats pool))] (when send? - (thread-sleep (rand-int 10000)) + (px/sleep (rand-int 10000)) (send! cfg data)) data) @@ -68,7 +64,7 @@ (seq subs) (do (when send? - (thread-sleep (rand-int 10000)) + (px/sleep (rand-int 10000)) (send! cfg data)) data) @@ -80,12 +76,13 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defn- send! - [{:keys [http-client uri] :as cfg} data] - (let [response (http-client {:method :post - :uri uri - :headers {"content-type" "application/json"} - :body (json/write-str data)} - {:sync? true})] + [cfg data] + (let [response (http/req! cfg + {:method :post + :uri (cf/get :telemetry-uri) + :headers {"content-type" "application/json"} + :body (json/write-str data)} + {:sync? true})] (when (> (:status response) 206) (ex/raise :type :internal :code :invalid-response diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index c6efd2df91..607ba1809f 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -288,10 +288,10 @@ ::queue ::registry])) +;; FIXME: define queue as set (defmethod ig/prep-key ::worker [_ cfg] - (merge {::queue "default" - ::parallelism 1} + (merge {::queue "default" ::parallelism 1} (d/without-nils cfg))) (defmethod ig/init-key ::worker @@ -666,10 +666,10 @@ props (-> options extract-props db/tjson) id (uuid/next)] - (l/debug :action "submit task" + (l/debug :hint "submit task" :name (d/name task) :queue queue - :in duration) + :in (dt/format-duration duration)) (db/exec-one! conn [sql:insert-new-task id (d/name task) props queue priority max-retries interval]) diff --git a/backend/test/backend_tests/bounce_handling_test.clj b/backend/test/backend_tests/bounce_handling_test.clj index 2938ecc9f1..e0c094969d 100644 --- a/backend/test/backend_tests/bounce_handling_test.clj +++ b/backend/test/backend_tests/bounce_handling_test.clj @@ -101,9 +101,9 @@ (t/deftest test-parse-bounce-report (let [profile (th/create-profile* 1) - sprops (:app.setup/props th/*system*) - cfg {:sprops sprops} - report (bounce-report {:token (tokens/generate sprops + props (:app.setup/props th/*system*) + cfg {:app.main/props props} + report (bounce-report {:token (tokens/generate props {:iss :profile-identity :profile-id (:id profile)})}) result (#'awsns/parse-notification cfg report)] @@ -118,9 +118,9 @@ (t/deftest test-parse-complaint-report (let [profile (th/create-profile* 1) - sprops (:app.setup/props th/*system*) - cfg {:sprops sprops} - report (complaint-report {:token (tokens/generate sprops + props (:app.setup/props th/*system*) + cfg {:app.main/props props} + report (complaint-report {:token (tokens/generate props {:iss :profile-identity :profile-id (:id profile)})}) result (#'awsns/parse-notification cfg report)] @@ -133,8 +133,8 @@ )) (t/deftest test-parse-complaint-report-without-token - (let [sprops (:app.setup/props th/*system*) - cfg {:sprops sprops} + (let [props (:app.setup/props th/*system*) + cfg {:app.main/props props} report (complaint-report {:token ""}) result (#'awsns/parse-notification cfg report)] (t/is (= "complaint" (:type result))) @@ -146,10 +146,10 @@ (t/deftest test-process-bounce-report (let [profile (th/create-profile* 1) - sprops (:app.setup/props th/*system*) + props (:app.setup/props th/*system*) pool (:app.db/pool th/*system*) - cfg {:sprops sprops :pool pool} - report (bounce-report {:token (tokens/generate sprops + cfg {:app.main/props props :app.db/pool pool} + report (bounce-report {:token (tokens/generate props {:iss :profile-identity :profile-id (:id profile)})}) report (#'awsns/parse-notification cfg report)] @@ -175,10 +175,11 @@ (t/deftest test-process-complaint-report (let [profile (th/create-profile* 1) - sprops (:app.setup/props th/*system*) + props (:app.setup/props th/*system*) pool (:app.db/pool th/*system*) - cfg {:sprops sprops :pool pool} - report (complaint-report {:token (tokens/generate sprops + cfg {:app.main/props props + :app.db/pool pool} + report (complaint-report {:token (tokens/generate props {:iss :profile-identity :profile-id (:id profile)})}) report (#'awsns/parse-notification cfg report)] @@ -206,11 +207,11 @@ (t/deftest test-process-bounce-report-to-self (let [profile (th/create-profile* 1) - sprops (:app.setup/props th/*system*) + props (:app.setup/props th/*system*) pool (:app.db/pool th/*system*) - cfg {:sprops sprops :pool pool} + cfg {:app.main/props props :app.db/pool pool} report (bounce-report {:email (:email profile) - :token (tokens/generate sprops + :token (tokens/generate props {:iss :profile-identity :profile-id (:id profile)})}) report (#'awsns/parse-notification cfg report)] @@ -228,11 +229,11 @@ (t/deftest test-process-complaint-report-to-self (let [profile (th/create-profile* 1) - sprops (:app.setup/props th/*system*) + props (:app.setup/props th/*system*) pool (:app.db/pool th/*system*) - cfg {:sprops sprops :pool pool} + cfg {:app.main/props props :app.db/pool pool} report (complaint-report {:email (:email profile) - :token (tokens/generate sprops + :token (tokens/generate props {:iss :profile-identity :profile-id (:id profile)})}) report (#'awsns/parse-notification cfg report)] diff --git a/backend/test/backend_tests/rpc_profile_test.clj b/backend/test/backend_tests/rpc_profile_test.clj index 1c97a904b5..940b2e7a67 100644 --- a/backend/test/backend_tests/rpc_profile_test.clj +++ b/backend/test/backend_tests/rpc_profile_test.clj @@ -56,10 +56,10 @@ ;; Test with good credentials but profile already activated (t/deftest profile-login-success (let [profile (th/create-profile* 1 {:is-active true}) - data {::th/type :login + data {::th/type :login-with-password :email "profile1.test@nodomain.com" :password "123123"} - out (th/mutation! data)] + out (th/command! data)] ;; (th/print-result! out) (t/is (nil? (:error out))) (t/is (= (:id profile) (get-in out [:result :id]))))) @@ -198,7 +198,7 @@ (let [data {::th/type :prepare-register-profile :email "user@example.com" :password "foobar"} - out (th/mutation! data) + out (th/command! data) token (get-in out [:result :token])] (t/is (string? token)) @@ -207,7 +207,7 @@ (let [data {::th/type :register-profile :fullname "foobar" :accept-terms-and-privacy true} - out (th/mutation! data)] + out (th/command! data)] (let [error (:error out)] (t/is (th/ex-info? error)) (t/is (th/ex-of-type? error :validation)) @@ -219,7 +219,8 @@ :fullname "foobar" :accept-terms-and-privacy true :accept-newsletter-subscription true}] - (let [{:keys [result error]} (th/mutation! data)] + (let [{:keys [result error] :as out} (th/command! data)] + ;; (th/print-result! out) (t/is (nil? error)))) )) @@ -302,7 +303,7 @@ :email "user@example.com" :password "foobar"} - {:keys [result error] :as out} (th/mutation! data)] + {:keys [result error] :as out} (th/command! data)] (t/is (nil? error)) (t/is (map? result)) (t/is (string? (:token result))) @@ -312,7 +313,7 @@ :token rtoken :fullname "foobar"} - {:keys [result error] :as out} (th/mutation! data)] + {:keys [result error] :as out} (th/command! data)] ;; (th/print-result! out) (t/is (nil? error)) (t/is (map? result)) @@ -467,7 +468,7 @@ ;; with valid email inactive user (let [data (assoc data :email (:email profile1)) - out (th/mutation! data) + out (th/command! data) error (:error out)] (t/is (= 0 (:call-count @mock))) (t/is (th/ex-info? error)) @@ -476,7 +477,7 @@ ;; with valid email and active user (let [data (assoc data :email (:email profile2)) - out (th/mutation! data)] + out (th/command! data)] ;; (th/print-result! out) (t/is (nil? (:result out))) (t/is (= 1 (:call-count @mock)))) @@ -484,7 +485,7 @@ ;; with valid email and active user with global complaints (th/create-global-complaint-for pool {:type :complaint :email (:email profile2)}) (let [data (assoc data :email (:email profile2)) - out (th/mutation! data)] + out (th/command! data)] ;; (th/print-result! out) (t/is (nil? (:result out))) (t/is (= 2 (:call-count @mock)))) @@ -492,7 +493,7 @@ ;; with valid email and active user with global bounce (th/create-global-complaint-for pool {:type :bounce :email (:email profile2)}) (let [data (assoc data :email (:email profile2)) - out (th/mutation! data) + out (th/command! data) error (:error out)] ;; (th/print-result! out) (t/is (= 2 (:call-count @mock))) diff --git a/common/src/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc index 5041e8b9f7..7be8efb342 100644 --- a/common/src/app/common/exceptions.cljc +++ b/common/src/app/common/exceptions.cljc @@ -41,6 +41,11 @@ [& exprs] `(try* (^:once fn* [] ~@exprs) identity)) +(defn cause + "Retrieve chained cause if available of the exception." + [^Throwable throwable] + (.getCause throwable)) + (defn ex-info? [v] (instance? #?(:clj clojure.lang.ExceptionInfo :cljs cljs.core.ExceptionInfo) v)) diff --git a/common/test/common_tests/types_shape_interactions_test.cljc b/common/test/common_tests/types_shape_interactions_test.cljc index aa77c913a9..c3202394ba 100644 --- a/common/test/common_tests/types_shape_interactions_test.cljc +++ b/common/test/common_tests/types_shape_interactions_test.cljc @@ -30,8 +30,8 @@ (t/is (= :mouse-press (:event-type new-interaction))))) (t/testing "Set after delay on non-frame" - (let [result (ex/try - (ctsi/set-event-type interaction :after-delay shape))] + (let [result (ex/try! + (ctsi/set-event-type interaction :after-delay shape))] (t/is (ex/exception? result)))) (t/testing "Set after delay on frame" diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 2363a9a3bf..65dda9071f 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -7,14 +7,12 @@ (ns app.main.data.workspace.svg-upload (:require [app.common.data :as d] - [app.common.spec :as us] - [app.common.exceptions :as ex] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.pages.changes-builder :as pcb] [app.common.pages.helpers :as cph] - [app.common.spec :refer [max-safe-int min-safe-int]] + [app.common.spec :as us :refer [max-safe-int min-safe-int]] [app.common.types.shape :as cts] [app.common.types.shape-tree :as ctst] [app.common.uuid :as uuid] diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index 62da2dbe88..30ee9415ec 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -7,7 +7,6 @@ (ns app.util.color "Color conversion utils." (:require - [app.common.exceptions :as ex] [app.util.object :as obj] [cuerdas.core :as str] [goog.color :as gcolor])) From 861328af3e9004e66e64676ea05e8dc8896cb7bd Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 5 Dec 2022 09:06:24 +0100 Subject: [PATCH 304/682] :arrow_up: Update promesa library to 10.0.570 --- backend/src/app/loggers/audit.clj | 6 +++--- backend/src/app/redis.clj | 24 ++++++++++++------------ backend/src/app/rpc/climit.clj | 27 +++++++++++++-------------- common/deps.edn | 2 +- 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 629da4824c..fa4f677217 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -254,9 +254,9 @@ "Submit audit event to the collector." [{:keys [::wrk/executor ::db/pool]} params] (->> (px/submit! executor (partial persist-event! pool (d/without-nils params))) - (p/err (fn [cause] - (l/error :hint "audit: unexpected error processing event" :cause cause) - (p/resolved nil))))) + (p/merr (fn [cause] + (l/error :hint "audit: unexpected error processing event" :cause cause) + (p/resolved nil))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; TASK: ARCHIVE diff --git a/backend/src/app/redis.clj b/backend/src/app/redis.clj index dcf9b7ea29..0ada4b0b9a 100644 --- a/backend/src/app/redis.clj +++ b/backend/src/app/redis.clj @@ -360,18 +360,18 @@ ^ScriptOutputType ScriptOutputType/MULTI ^"[Ljava.lang.String;" keys ^"[Ljava.lang.String;" vals) - (p/map (fn [result] - (let [elapsed (tpoint)] - (mtx/run! metrics {:id :redis-eval-timing - :labels [(name sname)] - :val (inst-ms elapsed)}) - (l/trace :hint "eval script" - :name (name sname) - :sha sha - :params (str/join "," (::rscript/vals script)) - :elapsed (dt/format-duration elapsed)) - result))) - (p/error on-error)))) + (p/fmap (fn [result] + (let [elapsed (tpoint)] + (mtx/run! metrics {:id :redis-eval-timing + :labels [(name sname)] + :val (inst-ms elapsed)}) + (l/trace :hint "eval script" + :name (name sname) + :sha sha + :params (str/join "," (::rscript/vals script)) + :elapsed (dt/format-duration elapsed)) + result))) + (p/merr on-error)))) (read-script [] (-> script ::rscript/path io/resource slurp)) diff --git a/backend/src/app/rpc/climit.clj b/backend/src/app/rpc/climit.clj index 8b97c4cce2..8ccf07300a 100644 --- a/backend/src/app/rpc/climit.clj +++ b/backend/src/app/rpc/climit.clj @@ -39,22 +39,21 @@ (defn invoke! [limiter f] - (p/handle - (px/submit! limiter f) - (fn [result cause] - (cond - (capacity-exception? cause) - (p/rejected - (ex/error :type :internal - :code :concurrency-limit-reached - :queue (-> limiter meta :bkey name) - :cause cause)) + (->> (px/submit! limiter f) + (p/hcat (fn [result cause] + (cond + (capacity-exception? cause) + (p/rejected + (ex/error :type :internal + :code :concurrency-limit-reached + :queue (-> limiter meta :bkey name) + :cause cause)) - (some? cause) - (p/rejected cause) + (some? cause) + (p/rejected cause) - :else - (p/resolved result))))) + :else + (p/resolved result)))))) (defn- create-limiter [{:keys [executor metrics concurrency queue-size bkey skey]}] diff --git a/common/deps.edn b/common/deps.edn index 435fffa59f..6e6c6929dc 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -23,7 +23,7 @@ com.cognitect/transit-cljs {:mvn/version "0.8.280"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} - funcool/promesa {:mvn/version "9.2.542"} + funcool/promesa {:mvn/version "10.0.570"} funcool/cuerdas {:mvn/version "2022.06.16-403"} lambdaisland/uri {:mvn/version "1.13.95" From d8bb62c49828290748b7be3b4c094c77b6c8126b Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Wed, 30 Nov 2022 16:20:50 +0100 Subject: [PATCH 305/682] :bug: Fix layer drag enabled on chrome when is readonly --- frontend/src/app/main/ui/hooks.cljs | 2 +- .../app/main/ui/workspace/sidebar/assets.cljs | 32 ++++++++++++------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index 9b6bdb6049..8d08610405 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -110,7 +110,7 @@ on-drag-start (fn [event] - (if disabled + (if (or disabled (not draggable?)) (dom/prevent-default event) (do (dom/stop-propagation event) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 8534520633..9746e04384 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -414,9 +414,11 @@ on-component-drag-start (mf/use-fn - (mf/deps component selected-components item-ref on-drag-start) + (mf/deps component selected-components item-ref on-drag-start workspace-read-only?) (fn [event] - (on-asset-drag-start event component selected-components item-ref :components on-drag-start)))] + (if workspace-read-only? + (dom/prevent-default event) + (on-asset-drag-start event component selected-components item-ref :components on-drag-start))))] [:div {:ref item-ref :class (dom/classnames @@ -795,9 +797,11 @@ on-grahic-drag-start (mf/use-fn - (mf/deps object selected-objects item-ref on-drag-start) + (mf/deps object selected-objects item-ref on-drag-start workspace-read-only?) (fn [event] - (on-asset-drag-start event object selected-objects item-ref :graphics on-drag-start)))] + (if workspace-read-only? + (dom/prevent-default event) + (on-asset-drag-start event object selected-objects item-ref :graphics on-drag-start))))] [:div {:ref item-ref :class-name (dom/classnames @@ -1190,10 +1194,10 @@ (if (or multi-colors? multi-assets?) (on-assets-delete) (let [undo-id (uuid/next)] - (st/emit! (dwu/start-undo-transaction undo-id) - (dwl/delete-color color) - (dwl/sync-file file-id file-id :colors (:id color)) - (dwu/commit-undo-transaction undo-id)))))) + (st/emit! (dwu/start-undo-transaction undo-id) + (dwl/delete-color color) + (dwl/sync-file file-id file-id :colors (:id color)) + (dwu/commit-undo-transaction undo-id)))))) rename-color-clicked (fn [event] @@ -1262,9 +1266,11 @@ on-color-drag-start (mf/use-fn - (mf/deps color selected-colors item-ref) + (mf/deps color selected-colors item-ref workspace-read-only?) (fn [event] - (on-asset-drag-start event color selected-colors item-ref :colors identity)))] + (if workspace-read-only? + (dom/prevent-default event) + (on-asset-drag-start event color selected-colors item-ref :colors identity))))] (mf/use-effect (mf/deps (:editing @state)) @@ -1574,9 +1580,11 @@ on-typography-drag-start (mf/use-fn - (mf/deps typography selected-typographies item-ref) + (mf/deps typography selected-typographies item-ref workspace-read-only?) (fn [event] - (on-asset-drag-start event typography selected-typographies item-ref :typographies identity)))] + (if workspace-read-only? + (dom/prevent-default event) + (on-asset-drag-start event typography selected-typographies item-ref :typographies identity))))] [:div.typography-container {:ref item-ref :draggable (not workspace-read-only?) From 39b9daa3a71620e1b1fb872c094ac65852773900 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 30 Nov 2022 15:38:48 +0100 Subject: [PATCH 306/682] :tada: Add webhooks rpc API --- backend/src/app/db.clj | 2 + backend/src/app/main.clj | 31 ++-- backend/src/app/migrations.clj | 3 + .../migrations/sql/0085-add-webhook-table.sql | 25 +++ backend/src/app/rpc.clj | 5 +- backend/src/app/rpc/commands/webhooks.clj | 120 ++++++++++++++ backend/test/backend_tests/helpers.clj | 8 +- .../test/backend_tests/rpc_webhooks_test.clj | 146 ++++++++++++++++++ 8 files changed, 324 insertions(+), 16 deletions(-) create mode 100644 backend/src/app/migrations/sql/0085-add-webhook-table.sql create mode 100644 backend/src/app/rpc/commands/webhooks.clj create mode 100644 backend/test/backend_tests/rpc_webhooks_test.clj diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 90b960c4aa..e897535dae 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -296,6 +296,7 @@ (let [row (get* ds table params opts)] (when (and (not row) check-deleted?) (ex/raise :type :not-found + :code :object-not-found :table table :hint "database object not found")) row))) @@ -308,6 +309,7 @@ (let [row (get* ds table params (assoc opts :check-deleted? check-not-found))] (when (and (not row) check-not-found) (ex/raise :type :not-found + :code :object-not-found :table table :hint "database object not found")) row))) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 0ba5ab622b..25956efbb5 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -320,20 +320,23 @@ :app.rpc/methods {::audit/collector (ig/ref ::audit/collector) ::http.client/client (ig/ref ::http.client/client) - :pool (ig/ref ::db/pool) - :session (ig/ref :app.http.session/manager) - :sprops (ig/ref :app.setup/props) - :metrics (ig/ref ::mtx/metrics) - :storage (ig/ref ::sto/storage) - :msgbus (ig/ref :app.msgbus/msgbus) - :public-uri (cf/get :public-uri) - :redis (ig/ref ::rds/redis) - :ldap (ig/ref :app.auth.ldap/provider) - :http-client (ig/ref ::http.client/client) - :climit (ig/ref :app.rpc/climit) - :rlimit (ig/ref :app.rpc/rlimit) - :executor (ig/ref ::wrk/executor) - :templates (ig/ref :app.setup/builtin-templates) + ::db/pool (ig/ref ::db/pool) + ::wrk/executor (ig/ref ::wrk/executor) + + :pool (ig/ref ::db/pool) + :session (ig/ref :app.http.session/manager) + :sprops (ig/ref :app.setup/props) + :metrics (ig/ref ::mtx/metrics) + :storage (ig/ref ::sto/storage) + :msgbus (ig/ref :app.msgbus/msgbus) + :public-uri (cf/get :public-uri) + :redis (ig/ref ::rds/redis) + :ldap (ig/ref :app.auth.ldap/provider) + :http-client (ig/ref ::http.client/client) + :climit (ig/ref :app.rpc/climit) + :rlimit (ig/ref :app.rpc/rlimit) + :executor (ig/ref ::wrk/executor) + :templates (ig/ref :app.setup/builtin-templates) } :app.rpc.doc/routes diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index 7b2d22bc9c..c0694e103a 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -262,6 +262,9 @@ {:name "0084-add-features-column-to-file-change-table" :fn (mg/resource "app/migrations/sql/0084-add-features-column-to-file-change-table.sql")} + + {:name "0085-add-webhook-table" + :fn (mg/resource "app/migrations/sql/0085-add-webhook-table.sql")} ]) diff --git a/backend/src/app/migrations/sql/0085-add-webhook-table.sql b/backend/src/app/migrations/sql/0085-add-webhook-table.sql new file mode 100644 index 0000000000..ae33514a91 --- /dev/null +++ b/backend/src/app/migrations/sql/0085-add-webhook-table.sql @@ -0,0 +1,25 @@ +CREATE TABLE webhook ( + id uuid PRIMARY KEY, + team_id uuid NOT NULL REFERENCES team(id) ON DELETE CASCADE DEFERRABLE, + + created_at timestamptz NOT NULL DEFAULT now(), + updated_at timestamptz NOT NULL DEFAULT now(), + + uri text NOT NULL, + mtype text NOT NULL, + + error_code text NULL, + error_count smallint DEFAULT 0, + + is_active boolean DEFAULT true, + secret_key text NULL +); + +ALTER TABLE webhook + ALTER COLUMN uri SET STORAGE external, + ALTER COLUMN mtype SET STORAGE external, + ALTER COLUMN error_code SET STORAGE external, + ALTER COLUMN secret_key SET STORAGE external; + + +CREATE INDEX webhook__team_id__idx ON webhook (team_id); diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 9eaa08014e..7494bc4c23 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -253,6 +253,7 @@ 'app.rpc.commands.auth 'app.rpc.commands.ldap 'app.rpc.commands.demo + 'app.rpc.commands.webhooks 'app.rpc.commands.files 'app.rpc.commands.files.update 'app.rpc.commands.files.create @@ -270,7 +271,9 @@ (defmethod ig/pre-init-spec ::methods [_] (s/keys :req [::audit/collector - ::http.client/client] + ::http.client/client + ::db/pool + ::wrk/executor] :req-un [::sto/storage ::http.session/session ::sprops diff --git a/backend/src/app/rpc/commands/webhooks.clj b/backend/src/app/rpc/commands/webhooks.clj new file mode 100644 index 0000000000..69e9e987de --- /dev/null +++ b/backend/src/app/rpc/commands/webhooks.clj @@ -0,0 +1,120 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.commands.webhooks + (:require + [app.common.exceptions :as ex] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.db :as db] + [app.http.client :as http] + [app.rpc.doc :as-alias doc] + [app.rpc.queries.teams :refer [check-edition-permissions!]] + [app.util.services :as sv] + [app.util.time :as dt] + [app.worker :as-alias wrk] + [clojure.spec.alpha :as s] + [cuerdas.core :as str] + [promesa.core :as p])) + +;; --- Mutation: Create Webhook + +(s/def ::profile-id ::us/uuid) +(s/def ::team-id ::us/uuid) +(s/def ::uri ::us/not-empty-string) +(s/def ::is-active ::us/boolean) +(s/def ::mtype + #{"application/json" + "application/x-www-form-urlencoded" + "application/transit+json"}) + +(s/def ::create-webhook + (s/keys :req-un [::profile-id ::team-id ::uri ::mtype] + :opt-un [::is-active])) + +;; FIXME: validate +;; FIXME: default ratelimit +;; FIXME: quotes + +(defn- validate-webhook! + [cfg whook params] + (letfn [(handle-exception [exception] + (cond + (instance? java.util.concurrent.CompletionException exception) + (handle-exception (ex/cause exception)) + + (instance? javax.net.ssl.SSLHandshakeException exception) + (ex/raise :type :validation + :code :webhook-validation + :hint "ssl-validaton") + + :else + (ex/raise :type :validation + :code :webhook-validation + :hint "unknown" + :cause exception))) + + (handle-response [{:keys [status] :as response}] + (when (not= status 200) + (ex/raise :type :validation + :code :webhook-validation + :hint (str/ffmt "unexpected-status-%" (:status response)))))] + + (if (not= (:uri whook) (:uri params)) + (->> (http/req! cfg {:method :head + :uri (:uri params) + :timeout (dt/duration "2s")}) + (p/hmap (fn [response exception] + (if exception + (handle-exception exception) + (handle-response response))))) + (p/resolved nil)))) + +(sv/defmethod ::create-webhook + {::doc/added "1.17"} + [{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id team-id uri mtype is-active] :as params}] + (check-edition-permissions! pool profile-id team-id) + (letfn [(insert-webhook [_] + (db/insert! pool :webhook + {:id (uuid/next) + :team-id team-id + :uri uri + :is-active is-active + :mtype mtype}))] + (->> (validate-webhook! cfg nil params) + (p/fmap executor insert-webhook)))) + +(s/def ::update-webhook + (s/keys :req-un [::id ::uri ::mtype ::is-active])) + +(sv/defmethod ::update-webhook + {::doc/added "1.17"} + [{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id id uri mtype is-active] :as params}] + (let [whook (db/get pool :webhook {:id id}) + update-fn (fn [_] + (db/update! pool :webhook + {:uri uri + :is-active is-active + :mtype mtype + :error-code nil + :error-count 0} + {:id id}))] + (check-edition-permissions! pool profile-id (:team-id whook)) + + (->> (validate-webhook! cfg whook params) + (p/fmap executor update-fn)))) + +(s/def ::delete-webhook + (s/keys :req-un [::profile-id ::id])) + +(sv/defmethod ::delete-webhook + {::doc/added "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id id]}] + (db/with-atomic [conn pool] + (let [whook (db/get conn :webhook {:id id})] + (check-edition-permissions! conn profile-id (:team-id whook)) + (db/delete! conn :webhook {:id id}) + nil))) diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj index e5ff498466..1ab57e5231 100644 --- a/backend/test/backend_tests/helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -431,4 +431,10 @@ (defn reset-mock! [m] - (reset! m @(mk/make-mock {}))) + (swap! m (fn [m] + (-> m + (assoc :called? false) + (assoc :call-count 0) + (assoc :return-list []) + (assoc :call-args nil) + (assoc :call-args-list []))))) diff --git a/backend/test/backend_tests/rpc_webhooks_test.clj b/backend/test/backend_tests/rpc_webhooks_test.clj new file mode 100644 index 0000000000..37c1c83a0b --- /dev/null +++ b/backend/test/backend_tests/rpc_webhooks_test.clj @@ -0,0 +1,146 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns backend-tests.rpc-webhooks-test + (:require + [app.common.uuid :as uuid] + [app.db :as db] + [app.http :as http] + [app.storage :as sto] + [backend-tests.helpers :as th] + [clojure.test :as t] + [datoteka.fs :as fs] + [datoteka.io :as io] + [mockery.core :refer [with-mocks]])) + +(t/use-fixtures :once th/state-init) +(t/use-fixtures :each th/database-reset) + +(t/deftest webhook-crud + (with-mocks [http-mock {:target 'app.http.client/req! + :return {:status 200}}] + + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + proj-id (:default-project-id prof) + whook (volatile! nil)] + + (t/testing "create webhook" + (let [params {::th/type :create-webhook + :profile-id (:id prof) + :team-id team-id + :uri "http://example.com" + :mtype "application/json"} + out (th/command! params)] + + (t/is (nil? (:error out))) + (t/is (= 1 (:call-count @http-mock))) + + (let [result (:result out)] + (t/is (contains? result :id)) + (t/is (contains? result :team-id)) + (t/is (contains? result :created-at)) + (t/is (contains? result :updated-at)) + (t/is (contains? result :uri)) + (t/is (contains? result :mtype)) + + (t/is (= (:uri params) (:uri result))) + (t/is (= (:team-id params) (:team-id result))) + (t/is (= (:mtype params) (:mtype result))) + (vreset! whook result)))) + + + (th/reset-mock! http-mock) + + (t/testing "update webhook 1 (success)" + (let [params {::th/type :update-webhook + :profile-id (:id prof) + :id (:id @whook) + :uri (:uri @whook) + :mtype "application/transit+json" + :is-active false} + out (th/command! params)] + + (t/is (nil? (:error out))) + (t/is (= 0 (:call-count @http-mock))) + + (let [result (:result out)] + (t/is (contains? result :id)) + (t/is (contains? result :team-id)) + (t/is (contains? result :created-at)) + (t/is (contains? result :updated-at)) + (t/is (contains? result :uri)) + (t/is (contains? result :mtype)) + + (t/is (= (:id params) (:id result))) + (t/is (= (:id @whook) (:id result))) + (t/is (= (:uri params) (:uri result))) + (t/is (= (:team-id @whook) (:team-id result))) + (t/is (= (:mtype params) (:mtype result)))))) + + (th/reset-mock! http-mock) + + (t/testing "update webhook 2 (change uri)" + (let [params {::th/type :update-webhook + :profile-id (:id prof) + :id (:id @whook) + :uri (str (:uri @whook) "/test") + :mtype "application/transit+json" + :is-active false} + out (th/command! params)] + + (t/is (nil? (:error out))) + (t/is (map? (:result out))) + (t/is (= 1 (:call-count @http-mock))))) + + (th/reset-mock! http-mock) + + (t/testing "update webhook 3 (not authorized)" + (let [params {::th/type :update-webhook + :profile-id uuid/zero + :id (:id @whook) + :uri (str (:uri @whook) "/test") + :mtype "application/transit+json" + :is-active false} + out (th/command! params)] + + (t/is (= 0 (:call-count @http-mock))) + (let [error (:error out) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :not-found)) + (t/is (= (:code error-data) :object-not-found))))) + + (th/reset-mock! http-mock) + + (t/testing "delete webhook (success)" + (let [params {::th/type :delete-webhook + :profile-id (:id prof) + :id (:id @whook)} + out (th/command! params)] + + (t/is (= 0 (:call-count @http-mock))) + (t/is (nil? (:error out))) + (t/is (nil? (:result out))) + + (let [rows (th/db-exec! ["select * from webhook"])] + (t/is (= 0 (count rows)))))) + + (t/testing "delete webhook (unauthorozed)" + (let [params {::th/type :delete-webhook + :profile-id uuid/zero + :id (:id @whook)} + out (th/command! params)] + + ;; (th/print-result! out) + (t/is (= 0 (:call-count @http-mock))) + (let [error (:error out) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :not-found)) + (t/is (= (:code error-data) :object-not-found))))) + + ))) From baade567ca6c73e2ea71f5996c306f891c185fd2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 5 Dec 2022 15:16:36 +0100 Subject: [PATCH 307/682] :paperclip: Improve bundle run template script --- backend/scripts/run.template.sh | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/backend/scripts/run.template.sh b/backend/scripts/run.template.sh index 33c8eda2ed..acc2212cf9 100644 --- a/backend/scripts/run.template.sh +++ b/backend/scripts/run.template.sh @@ -1,19 +1,21 @@ #!/usr/bin/env bash -set +e -JAVA_CMD=$(type -p java) -set -e if [[ ! -n "$JAVA_CMD" ]]; then - if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then - JAVA_CMD="$JAVA_HOME/bin/java" - else - >&2 echo "Couldn't find 'java'. Please set JAVA_HOME." - exit 1 - fi + if [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then + JAVA_CMD="$JAVA_HOME/bin/java" + else + set +e + JAVA_CMD=$(type -p java) + set -e + if [[ ! -n "$JAVA_CMD" ]]; then + >&2 echo "Couldn't find 'java'. Please set JAVA_HOME." + exit 1 + fi + fi fi if [ -f ./environ ]; then - source ./environ + source ./environ fi set -x From d97afa0e6d6f0c898e497b7d23d6c7720ffd0e7a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 5 Dec 2022 15:17:01 +0100 Subject: [PATCH 308/682] :paperclip: Add helper/devenv script for kill nonresponsive repl --- backend/scripts/kill-repl.sh | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 backend/scripts/kill-repl.sh diff --git a/backend/scripts/kill-repl.sh b/backend/scripts/kill-repl.sh new file mode 100755 index 0000000000..7832deb1c0 --- /dev/null +++ b/backend/scripts/kill-repl.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -x + +jcmd |grep "rebel" |sed -nE 's/^([0-9]+).*$/\1/p' | xargs kill -9 From cdbfec4f19bdb03b9620acde1a43f3ca2a776a2a Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Fri, 2 Dec 2022 13:05:21 +0100 Subject: [PATCH 309/682] :tada: Add team webhooks section --- backend/src/app/rpc/commands/webhooks.clj | 20 +- .../styles/main/partials/dashboard-team.scss | 161 +++++++++++- frontend/src/app/main/data/dashboard.cljs | 81 ++++++ frontend/src/app/main/refs.cljs | 3 + frontend/src/app/main/ui.cljs | 1 + frontend/src/app/main/ui/dashboard.cljs | 5 +- .../src/app/main/ui/dashboard/sidebar.cljs | 2 + frontend/src/app/main/ui/dashboard/team.cljs | 240 +++++++++++++++++- frontend/src/app/main/ui/routes.cljs | 1 + frontend/translations/en.po | 84 ++++++ frontend/translations/es.po | 84 ++++++ 11 files changed, 677 insertions(+), 5 deletions(-) diff --git a/backend/src/app/rpc/commands/webhooks.clj b/backend/src/app/rpc/commands/webhooks.clj index 69e9e987de..66ae149652 100644 --- a/backend/src/app/rpc/commands/webhooks.clj +++ b/backend/src/app/rpc/commands/webhooks.clj @@ -12,7 +12,7 @@ [app.db :as db] [app.http.client :as http] [app.rpc.doc :as-alias doc] - [app.rpc.queries.teams :refer [check-edition-permissions!]] + [app.rpc.queries.teams :refer [check-edition-permissions! check-read-permissions!]] [app.util.services :as sv] [app.util.time :as dt] [app.worker :as-alias wrk] @@ -49,7 +49,7 @@ (instance? javax.net.ssl.SSLHandshakeException exception) (ex/raise :type :validation :code :webhook-validation - :hint "ssl-validaton") + :hint "ssl-validation") :else (ex/raise :type :validation @@ -118,3 +118,19 @@ (check-edition-permissions! conn profile-id (:team-id whook)) (db/delete! conn :webhook {:id id}) nil))) + +;; --- Query: Webhooks + +(s/def ::team-id ::us/uuid) +(s/def ::get-webhooks + (s/keys :req-un [::profile-id ::team-id])) + +(def sql:get-webhooks + "select id, uri, mtype, is_active, error_code, error_count + from webhook where team_id = ? order by uri") + +(sv/defmethod ::get-webhooks + [{:keys [pool] :as cfg} {:keys [profile-id team-id]}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id team-id) + (db/exec! conn [sql:get-webhooks team-id]))) \ No newline at end of file diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss index 2082ec3616..24c41c34d0 100644 --- a/frontend/resources/styles/main/partials/dashboard-team.scss +++ b/frontend/resources/styles/main/partials/dashboard-team.scss @@ -93,7 +93,8 @@ } .dashboard-team-members, -.dashboard-team-invitations { +.dashboard-team-invitations, +.dashboard-team-webhooks { .empty-invitations { height: 156px; max-width: 1040px; @@ -197,6 +198,71 @@ } } } + + &.uri, + &.active { + width: 48%; + min-width: 300px; + } + + &.last-delivery { + display: flex; + justify-content: center; + width: 50px; + position: relative; + .success svg { + fill: $color-primary; + width: 16px; + height: 16px; + } + .failure svg { + fill: $color-warning; + width: 16px; + height: 16px; + } + + .icon-container { + width: 16px; + height: 16px; + overflow-x: visible; + } + + .icon { + padding: 0; + } + } + + .tooltip { + display: none; + position: absolute; + top: -58px; + left: 50%; + transform: translate(-50%, 0); + text-align: center; + + .label { + border-radius: 3px; + color: $color-white; + background-color: $color-black; + white-space: nowrap; + padding: 12px 20px; + } + + .arrow-down { + margin: 0 auto; + width: 0; + height: 0; + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 8px solid $color-black; + } + } + + .last-delivery-icon:hover { + .tooltip { + display: block; + } + } } .dropdown { @@ -380,3 +446,96 @@ } } } + +.dashboard-team-webhooks { + display: flex; + flex-direction: column; + align-items: center; + + .webhooks-hero-container { + max-width: 1000px; + width: 100%; + display: flex; + flex-direction: column; + + .upload-button { + width: 100px; + } + + .btn-secondary { + margin-left: 10px; + } + } + + .webhooks-hero { + font-size: $fs14; + + padding: $size-6; + background-color: $color-white; + margin-top: $size-6; + display: flex; + justify-content: space-between; + + .banner { + background-color: unset; + + display: flex; + + .icon { + display: flex; + align-items: center; + padding-left: 0px; + padding-right: 10px; + svg { + fill: $color-info; + } + } + } + + .desc { + h2 { + margin-bottom: $size-4; + color: $color-black; + } + width: 80%; + color: $color-gray-40; + p { + font-size: 16px; + } + } + + .btn-primary { + flex-shrink: 0; + } + } + + .webhooks-empty { + text-align: center; + max-width: 1000px; + width: 100%; + padding: $size-6; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + border: 1px dashed $color-gray-20; + color: $color-gray-40; + margin-top: 12px; + min-height: 136px; + } +} + +.webhooks-modal { + .action-buttons { + gap: 10px; + } + .input-checkbox label { + font-size: 14px; + color: $color-black; + } + + .explain { + font-size: 12px; + color: $color-gray-40; + } +} diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 0b4d26e8da..dd5e27f123 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -149,6 +149,24 @@ (->> (rp/query! :team-invitations {:team-id team-id}) (rx/map team-invitations-fetched)))))) +;; --- EVENT: fetch-team-webhooks + +(defn team-webhooks-fetched + [webhooks] + (ptk/reify ::team-webhooks-fetched + ptk/UpdateEvent + (update [_ state] + (assoc state :dashboard-team-webhooks webhooks)))) + +(defn fetch-team-webhooks + [] + (ptk/reify ::fetch-team-webhooks + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state)] + (->> (rp/command! :get-webhooks {:team-id team-id}) + (rx/map team-webhooks-fetched)))))) + ;; --- EVENT: fetch-projects (defn projects-fetched @@ -522,6 +540,61 @@ (rx/tap on-success) (rx/catch on-error)))))) +(defn delete-team-webhook + [{:keys [id] :as params}] + (us/assert ::us/uuid id) + (ptk/reify ::delete-team-webhook + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state) + params (assoc params :team-id team-id) + {:keys [on-success on-error] + :or {on-success identity + on-error rx/throw}} (meta params)] + (->> (rp/command! :delete-webhook params) + (rx/tap on-success) + (rx/catch on-error)))))) + +(s/def ::mtype + #{"application/json" + "application/x-www-form-urlencoded" + "application/transit+json"}) + +(defn update-team-webhook + [{:keys [id uri mtype is-active] :as params}] + (us/assert ::us/uuid id) + (us/assert ::us/uri uri) + (us/assert ::mtype mtype) + (us/assert ::us/boolean is-active) + (ptk/reify ::update-team-webhook + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state) + params (assoc params :team-id team-id) + {:keys [on-success on-error] + :or {on-success identity + on-error rx/throw}} (meta params)] + (->> (rp/command! :update-webhook params) + (rx/tap on-success) + (rx/catch on-error)))))) + +(defn create-team-webhook + [{:keys [uri mtype is-active] :as params}] + (us/assert ::us/uri uri) + (us/assert ::mtype mtype) + (us/assert ::us/boolean is-active) + (ptk/reify ::create-team-webhook + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state) + params (assoc params :team-id team-id) + {:keys [on-success on-error] + :or {on-success identity + on-error rx/throw}} (meta params)] + (->> (rp/command! :create-webhook params) + (rx/tap on-success) + (rx/catch on-error)))))) + ;; --- EVENT: delete-team (defn delete-team @@ -913,6 +986,14 @@ (let [team-id (:current-team-id state)] (rx/of (rt/nav :dashboard-team-invitations {:team-id team-id})))))) +(defn go-to-team-webhooks + [] + (ptk/reify ::go-to-team-webhooks + ptk/WatchEvent + (watch [_ state _] + (let [team-id (:current-team-id state)] + (rx/of (rt/nav :dashboard-team-webhooks {:team-id team-id})))))) + (defn go-to-team-settings [] (ptk/reify ::go-to-team-settings diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 4bc06f8d08..6998b831e5 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -74,6 +74,9 @@ (def dashboard-team-invitations (l/derived :dashboard-team-invitations st/state)) +(def dashboard-team-webhooks + (l/derived :dashboard-team-webhooks st/state)) + (def dashboard-selected-project (l/derived (fn [state] (dm/get-in state [:dashboard-local :selected-project])) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 5b1f41d935..5569d827e8 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -72,6 +72,7 @@ :dashboard-font-providers :dashboard-team-members :dashboard-team-invitations + :dashboard-team-webhooks :dashboard-team-settings) [:* diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index be423286fb..646859c9f8 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -25,7 +25,7 @@ [app.main.ui.dashboard.projects :refer [projects-section]] [app.main.ui.dashboard.search :refer [search-page]] [app.main.ui.dashboard.sidebar :refer [sidebar]] - [app.main.ui.dashboard.team :refer [team-settings-page team-members-page team-invitations-page]] + [app.main.ui.dashboard.team :refer [team-settings-page team-members-page team-invitations-page team-webhooks-page]] [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] [app.util.dom :as dom] @@ -236,6 +236,9 @@ :dashboard-team-invitations [:& team-invitations-page {:team team}] + :dashboard-team-webhooks + [:& team-webhooks-page {:team team}] + :dashboard-team-settings [:& team-settings-page {:team team :profile profile}] diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index ea23c1908b..73df4dcb2d 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -245,6 +245,7 @@ [{:keys [team profile] :as props}] (let [go-members #(st/emit! (dd/go-to-team-members)) go-invitations #(st/emit! (dd/go-to-team-invitations)) + go-webhooks #(st/emit! (dd/go-to-team-webhooks)) go-settings #(st/emit! (dd/go-to-team-settings)) members-map (mf/deref refs/dashboard-team-members) @@ -323,6 +324,7 @@ [:ul.dropdown.options-dropdown [:li {:on-click go-members :data-test "team-members"} (tr "labels.members")] [:li {:on-click go-invitations :data-test "team-invitations"} (tr "labels.invitations")] + [:li {:on-click go-webhooks :data-test "team-webhooks"} (tr "labels.webhooks")] [:li {:on-click go-settings :data-test "team-settings"} (tr "labels.settings")] [:hr] (when can-rename? diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 3967627d93..aa66bd2306 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -27,6 +27,7 @@ [app.util.i18n :as i18n :refer [tr]] [beicon.core :as rx] [cljs.spec.alpha :as s] + [cuerdas.core :as str] [rumext.v2 :as mf])) (mf/defc header @@ -35,13 +36,15 @@ (let [go-members (mf/use-fn #(st/emit! (dd/go-to-team-members))) go-settings (mf/use-fn #(st/emit! (dd/go-to-team-settings))) go-invitations (mf/use-fn #(st/emit! (dd/go-to-team-invitations))) - invite-member (mf/use-fn + go-webhooks (mf/use-fn #(st/emit! (dd/go-to-team-webhooks))) + invite-member (mf/use-fn (mf/deps team) #(st/emit! (modal/show {:type :invite-members :team team :origin :team}))) members-section? (= section :dashboard-team-members) settings-section? (= section :dashboard-team-settings) invitations-section? (= section :dashboard-team-invitations) + webhooks-section? (= section :dashboard-team-webhooks) permissions (:permissions team)] [:header.dashboard-header.team @@ -50,6 +53,7 @@ members-section? (tr "labels.members") settings-section? (tr "labels.settings") invitations-section? (tr "labels.invitations") + webhooks-section? (tr "labels.webhooks") :else nil)]] [:nav.dashboard-header-menu [:ul.dashboard-header-options @@ -57,6 +61,8 @@ [:a {:on-click go-members} (tr "labels.members")]] [:li {:class (when invitations-section? "active")} [:a {:on-click go-invitations} (tr "labels.invitations")]] + [:li {:class (when webhooks-section? "active")} + [:a {:on-click go-webhooks} (tr "labels.webhooks")]] [:li {:class (when settings-section? "active")} [:a {:on-click go-settings} (tr "labels.settings")]]]] [:div.dashboard-buttons @@ -571,6 +577,238 @@ [:& invitation-section {:team team :invitations invitations}]]])) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; WEBHOOKS SECTION +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(s/def ::uri ::us/not-empty-string) +(s/def ::mtype ::us/not-empty-string) +(s/def ::webhook-form + (s/keys :req-un [::uri ::mtype])) + +(mf/defc webhook-modal {::mf/register modal/components + ::mf/register-as :webhook} + [{:keys [webhook] :as props}] + (let [initial (mf/use-memo (fn [] (or webhook {:is-active false :mtype "application/json"}))) + form (fm/use-form :spec ::webhook-form + :initial initial) + mtypes [{:label "application/json" :value "application/json"} + {:label "application/x-www-form-urlencoded" :value "application/x-www-form-urlencoded"} + {:label "application/transit+json" :value "application/transit+json"}] + + on-success + (fn [message] + (st/emit! (dd/fetch-team-webhooks) + (msg/success message) + (modal/hide))) + + on-error + (fn [message {:keys [type code hint] :as error}] + (let [message (if (and (= type :validation) (= code :webhook-validation)) + (str message " " + (case hint + "ssl-validation" (tr "errors.webhooks.ssl-validation") + "")) ;; TODO Add more error codes when back defines them + message)] + (rx/of (msg/error message)))) + + on-create-submit + (fn [] + (let [mdata {:on-success #(on-success (tr "dashboard.webhooks.create.success")) + :on-error (partial on-error (tr "dashboard.webhooks.create.error"))} + webhook {:uri (get-in @form [:clean-data :uri]) + :mtype (get-in @form [:clean-data :mtype]) + :is-active (get-in @form [:clean-data :is-active])}] + (st/emit! (dd/create-team-webhook (with-meta webhook mdata))))) + + on-update-submit + (fn [] + (let [mdata {:on-success #(on-success (tr "dashboard.webhooks.update.success")) + :on-error (partial on-error (tr "dashboard.webhooks.update.error"))} + webhook (get @form :clean-data)] + (st/emit! (dd/update-team-webhook (with-meta webhook mdata))))) + + on-submit + #(let [data (:clean-data @form)] + (if (:id data) + (on-update-submit) + (on-create-submit)))] + + [:div.modal-overlay + [:div.modal-container.webhooks-modal + [:& fm/form {:form form :on-submit on-submit} + + [:div.modal-header + [:div.modal-header-title + (if webhook + [:h2 (tr "modals.edit-webhook.title")] + [:h2 (tr "modals.create-webhook.title")])] + + [:div.modal-close-button + {:on-click #(st/emit! (modal/hide))} i/close]] + + [:div.modal-content.generic-form + [:div.fields-container + [:div.fields-row + [:& fm/input {:type "text" + :auto-focus? true + :form form + :name :uri + :label (tr "modals.create-webhook.url.label") + :placeholder (tr "modals.create-webhook.url.placeholder")}]] + + [:div.fields-row + [:& fm/select {:options mtypes + :label (tr "dashboard.webhooks.content-type") + :default "application/json" + :name :mtype}]]] + [:div.fields-row + [:div.input-checkbox.check-primary + [:& fm/input {:type "checkbox" + :form form + :name :is-active + :label (tr "dashboard.webhooks.active")}] + ] + [:div.explain (tr "dashboard.webhooks.active.explain")]]] + + + + [:div.modal-footer + [:div.action-buttons + [:input.btn-gray.btn-large + {:type "button" + :value (tr "labels.cancel") + :on-click #(modal/hide!)}] + [:& fm/submit-button + {:label (if webhook + (tr "modals.edit-webhook.submit-label") + (tr "modals.create-webhook.submit-label"))}]]]]]])) + + +(mf/defc webhooks-hero + [] + [:div.banner + [:div.title (tr "labels.webhooks") + [:div.description (tr "dashboard.webhooks.description")]] + [:div.create-container + [:div.create (tr "dashboard.webhooks.create")]]] + + [:div.webhooks-hero-container + [:div.webhooks-hero + [:div.desc + [:h2 (tr "labels.webhooks")] + [:& i18n/tr-html {:label "dashboard.webhooks.description"}]] + + [:div.btn-primary + {:on-click #(st/emit! (modal/show :webhook {}))} + [:span (tr "dashboard.webhooks.create")]]]]) + + + (mf/defc webhook-actions + [{:keys [on-edit on-delete] :as props}] + (let [show? (mf/use-state false)] + [:* + [:span.icon {:on-click #(reset! show? true)} [i/actions]] + [:& dropdown {:show @show? + :on-close #(reset! show? false)} + [:ul.dropdown.actions-dropdown + [:li {:on-click on-edit} (tr "labels.edit")] + [:li {:on-click on-delete} (tr "labels.delete")]]]])) + + (mf/defc last-delivery-icon + [{:keys [success? text] :as props}] + [:div.last-delivery-icon + [:div.tooltip + [:div.label text] + [:div.arrow-down]] + (if success? + [:span.icon.success i/msg-success] + [:span.icon.failure i/msg-warning])]) + + (mf/defc webhook-item + {::mf/wrap [mf/memo]} + [{:keys [webhook] :as props}] + (let [on-edit #(st/emit! (modal/show :webhook {:webhook webhook})) + error-code (:error-code webhook) + extract-status + (fn [error-code] + (let [status (-> error-code + (str/split "-") + last + parse-long)] + (if (nil? status) + "" + status))) + delete-fn + (fn [] + (let [params {:id (:id webhook)} + mdata {:on-success #(st/emit! (dd/fetch-team-webhooks))}] + (st/emit! (dd/delete-team-webhook (with-meta params mdata))))) + on-delete #(st/emit! (modal/show + {:type :confirm + :title (tr "modals.delete-webhook.title") + :message (tr "modals.delete-webhook.message") + :accept-label (tr "modals.delete-webhook.accept") + :on-accept delete-fn})) + last-delivery-text (cond + (nil? error-code) + (tr "webhooks.last-delivery.success") + + (= error-code "ssl-validation") + (str (tr "errors.webhooks.last-delivery") " " (tr "errors.webhooks.ssl-validation")) + + (str/starts-with? error-code "unexpected-status") + (str (tr "errors.webhooks.last-delivery") + " " + (tr "errors.webhooks.unexpected-status" (extract-status error-code))) + + :else + (tr "errors.webhooks.last-delivery"))] + [:div.table-row + [:div.table-field.last-delivery + [:div.icon-container + [:& last-delivery-icon {:success? (nil? error-code) :text last-delivery-text}]]] + [:div.table-field.uri + [:div (:uri webhook)]] + [:div.table-field.active + [:div (if (:is-active webhook) (tr "labels.active") (tr "labels.inactive"))]] + [:div.table-field.actions + [:& webhook-actions {:on-edit on-edit + :on-delete on-delete}]]])) + + +(mf/defc webhooks-list + [{:keys [webhooks] :as props}] + [:div.dashboard-table + [:div.table-rows + (for [webhook webhooks] + [:& webhook-item {:webhook webhook :key (:id webhook)}])]]) + +(mf/defc team-webhooks-page + [{:keys [team] :as props}] + (let [webhooks (mf/deref refs/dashboard-team-webhooks)] + + (mf/with-effect [team] + (dom/set-html-title + (tr "title.team-webhooks" + (if (:is-default team) + (tr "dashboard.your-penpot") + (:name team))))) + + (mf/with-effect [team] + (st/emit! (dd/fetch-team-webhooks))) + + [:* + [:& header {:team team :section :dashboard-team-webhooks}] + [:section.dashboard-container.dashboard-team-webhooks + [:div + [:& webhooks-hero] + (if (empty? webhooks) + [:div.webhooks-empty + [:div (tr "dashboard.webhooks.empty.no-webhooks")] + [:div (tr "dashboard.webhooks.empty.add-one")]] + [:& webhooks-list {:webhooks webhooks}])]]])) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; SETTINGS SECTION ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/ui/routes.cljs b/frontend/src/app/main/ui/routes.cljs index f916a52bcc..0f0aefd2fe 100644 --- a/frontend/src/app/main/ui/routes.cljs +++ b/frontend/src/app/main/ui/routes.cljs @@ -66,6 +66,7 @@ ["/dashboard/team/:team-id" ["/members" :dashboard-team-members] ["/invitations" :dashboard-team-invitations] + ["/webhooks" :dashboard-team-webhooks] ["/settings" :dashboard-team-settings] ["/projects" :dashboard-projects] ["/search" :dashboard-search] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a113b8b820..c0c7d771f2 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -669,6 +669,51 @@ msgstr "Your name" msgid "dashboard.your-penpot" msgstr "Your Penpot" +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." + +msgid "dashboard.webhooks.create" +msgstr "Create webhook" + +msgid "dashboard.webhooks.empty.no-webhooks" +msgstr "No webhooks created so far." + +msgid "dashboard.webhooks.empty.add-one" +msgstr "Press the button \"Add webhook\" to add one." + +msgid "dashboard.webhooks.content-type" +msgstr "Content type" + +msgid "dashboard.webhooks.active" +msgstr "Is active" + +msgid "dashboard.webhooks.active.explain" +msgstr "When this hook is triggered event details will be delivered" + +msgid "webhooks.last-delivery.success" +msgstr "Last delivery was successfull." + +msgid "errors.webhooks.last-delivery" +msgstr "Last delivery was not successfull." + +msgid "errors.webhooks.ssl-validation" +msgstr "Error on SSL validation." + +msgid "errors.webhooks.unexpected-status" +msgstr "Unexpected status %s" + +msgid "dashboard.webhooks.update.error" +msgstr "Error on updating webhook." + +msgid "dashboard.webhooks.update.success" +msgstr "Webhook updated successfully." + +msgid "dashboard.webhooks.create.error" +msgstr "Error on creating webhook." + +msgid "dashboard.webhooks.create.success" +msgstr "Webhook created successfully." + #: src/app/main/ui/alert.cljs msgid "ds.alert-ok" msgstr "Ok" @@ -1468,6 +1513,15 @@ msgstr "(you)" msgid "labels.your-account" msgstr "Your account" +msgid "labels.webhooks" +msgstr "Webhooks" + +msgid "labels.active" +msgstr "Active" + +msgid "labels.inactive" +msgstr "Inactive" + #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" msgstr "Loading image…" @@ -1682,6 +1736,33 @@ msgstr "Are you sure you want to delete this member from the team?" msgid "modals.delete-team-member-confirm.title" msgstr "Delete team member" +msgid "modals.delete-webhook.title" +msgstr "Deleting webhook" + +msgid "modals.delete-webhook.message" +msgstr "Are you sure you want to delete this webhook?" + +msgid "modals.delete-webhook.accept" +msgstr "Delete webhook" + +msgid "modals.create-webhook.title" +msgstr "Create webhook" + +msgid "modals.create-webhook.submit-label" +msgstr "Create webhook" + +msgid "modals.create-webhook.url.label" +msgstr "Payload URL" + +msgid "modals.create-webhook.url.placeholder" +msgstr "https://example.com/postreceive" + +msgid "modals.edit-webhook.title" +msgstr "Edit webhook" + +msgid "modals.edit-webhook.submit-label" +msgstr "Edit webhook" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-member-confirm.accept" msgstr "Send invitation" @@ -2489,6 +2570,9 @@ msgstr "Profile - Penpot" msgid "title.team-invitations" msgstr "Invitations - %s - Penpot" +msgid "title.team-webhooks" +msgstr "Webhooks - %s - Penpot" + #: src/app/main/ui/dashboard/team.cljs msgid "title.team-members" msgstr "Members - %s - Penpot" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 5e9e36dc18..4cc50f736c 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -713,6 +713,51 @@ msgstr "Tu nombre" msgid "dashboard.your-penpot" msgstr "Tu Penpot" +msgid "dashboard.webhooks.description" +msgstr "Los webhooks son una forma simple de permitir notificar a otros sitios web y aplicaciones cuando ocurren ciertos eventos en Penpot. Enviaremos una petición POST a cada una de las URLs que indiques." + +msgid "dashboard.webhooks.create" +msgstr "Crear webhook" + +msgid "dashboard.webhooks.empty.no-webhooks" +msgstr "No hay ningún webhook aún." + +msgid "dashboard.webhooks.empty.add-one" +msgstr "Pulsa el botón \"Crear webhook\" para añadir uno." + +msgid "dashboard.webhooks.content-type" +msgstr "Tipo de contenido" + +msgid "dashboard.webhooks.active" +msgstr "Activo" + +msgid "dashboard.webhooks.active.explain" +msgstr "Cuando se active este webhook se enviarán detalles del evento" + +msgid "webhooks.last-delivery.success" +msgstr "El último envío fue correcto." + +msgid "errors.webhooks.last-delivery" +msgstr "Hubo un problema en el último envío." + +msgid "errors.webhooks.ssl-validation" +msgstr "Error en la validación SSL." + +msgid "errors.webhooks.unexpected-status" +msgstr "Estado inesperado %s" + +msgid "dashboard.webhooks.update.error" +msgstr "Error modificando el webhook" + +msgid "dashboard.webhooks.update.success" +msgstr "Webhook modificado con éxito" + +msgid "dashboard.webhooks.create.error" +msgstr "Error creando con éxito" + +msgid "dashboard.webhooks.create.success" +msgstr "Webhook creado con éxito" + #: src/app/main/ui/alert.cljs msgid "ds.alert-ok" msgstr "Ok" @@ -1653,6 +1698,15 @@ msgstr "(tú)" msgid "labels.your-account" msgstr "Tu cuenta" +msgid "labels.webhooks" +msgstr "Webhooks" + +msgid "labels.active" +msgstr "Activo" + +msgid "labels.inactive" +msgstr "Inactivo" + #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "media.loading" msgstr "Cargando imagen…" @@ -1880,6 +1934,33 @@ msgstr "¿Seguro que quieres eliminar este integrante del equipo?" msgid "modals.delete-team-member-confirm.title" msgstr "Eliminar integrante del equipo" +msgid "modals.delete-webhook.title" +msgstr "Borrando webhook" + +msgid "modals.delete-webhook.message" +msgstr "¿Seguro que quieres borrar este webhook?" + +msgid "modals.delete-webhook.accept" +msgstr "Borrar webhook" + +msgid "modals.create-webhook.title" +msgstr "Crear webhook" + +msgid "modals.create-webhook.submit-label" +msgstr "Crear webhook" + +msgid "modals.create-webhook.url.label" +msgstr "Payload URL" + +msgid "modals.create-webhook.url.placeholder" +msgstr "https://example.com/postreceive" + +msgid "modals.edit-webhook.title" +msgstr "Modificar webhook" + +msgid "modals.edit-webhook.submit-label" +msgstr "Modificar webhook" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-member-confirm.accept" msgstr "Enviar invitacion" @@ -2818,6 +2899,9 @@ msgstr "Perfil - Penpot" msgid "title.team-invitations" msgstr "Invitaciones - %s - Penpot" +msgid "title.team-webhooks" +msgstr "Webhooks - %s - Penpot" + #: src/app/main/ui/dashboard/team.cljs msgid "title.team-members" msgstr "Integrantes - %s - Penpot" From 6c2d2e142b59653a5bca1e4910c978ec3d56cd55 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 2 Dec 2022 12:17:32 +0100 Subject: [PATCH 310/682] :sparkles: Improve reflow texts --- .../src/app/common/pages/changes_builder.cljc | 15 +++++++ frontend/src/app/main/data/workspace.cljs | 10 +++-- .../src/app/main/data/workspace/groups.cljs | 3 +- .../app/main/data/workspace/modifiers.cljs | 39 ++++++++++-------- .../app/main/data/workspace/path/edition.cljs | 3 +- .../app/main/data/workspace/selection.cljs | 3 +- .../app/main/data/workspace/shape_layout.cljs | 40 ++++++++++++++++--- .../src/app/main/data/workspace/shapes.cljs | 7 ++-- .../data/workspace/shapes_update_layout.cljs | 24 ----------- .../src/app/main/data/workspace/texts.cljs | 4 +- .../shapes/text/viewport_texts_html.cljs | 5 ++- frontend/src/debug.cljs | 4 ++ 12 files changed, 95 insertions(+), 62 deletions(-) delete mode 100644 frontend/src/app/main/data/workspace/shapes_update_layout.cljs diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 77e232f3bd..8375fc4513 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -276,6 +276,21 @@ (update :undo-changes #(reduce mk-undo-change % shapes)) (apply-changes-local))))) + +(defn changed-attrs + [object update-fn {:keys [attrs]}] + (let [changed? + (fn [old new attr] + (let [old-val (get old attr) + new-val (get new attr)] + (not= old-val new-val)))] + (let [new-obj (update-fn object)] + (if (= object new-obj) + '() + + (let [attrs (or attrs (d/concat-set (keys object) (keys new-obj)))] + (filter (partial changed? object new-obj) attrs)))))) + (defn update-shapes "Calculate the changes and undos to be done when a function is applied to a single object" diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index af4f881608..a14b0454d6 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -56,7 +56,7 @@ [app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.shapes :as dwsh] - [app.main.data.workspace.shapes-update-layout :as dwul] + [app.main.data.workspace.shape-layout :as dwsl] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.transforms :as dwt] @@ -177,6 +177,7 @@ (->> (rx/merge ;; Initialize notifications & load team fonts (rx/of (dwn/initialize team-id id) + (dwsl/initialize) (df/load-team-fonts team-id)) ;; Load all pages, independently if they are pointers or already @@ -316,7 +317,8 @@ ptk/WatchEvent (watch [_ _ _] - (rx/of (dwn/finalize file-id))))) + (rx/of (dwn/finalize file-id) + (dwsl/finalize))))) (declare go-to-page) (declare ^:private preload-data-uris) @@ -808,7 +810,7 @@ (rx/of (dch/commit-changes changes) (dwco/expand-collapse parent-id) - (dwul/update-layout-positions layouts-to-update)))))) + (ptk/data-event :layout/update layouts-to-update)))))) (defn relocate-selected-shapes [parent-id to-index] @@ -1554,7 +1556,7 @@ (rx/of (dch/commit-changes changes) (dws/select-shapes selected) - (dwul/update-layout-positions [frame-id]))))] + (ptk/data-event :layout/update [frame-id]))))] (ptk/reify ::paste-shape ptk/WatchEvent diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index 4a5fad453b..e009b6f26e 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -14,7 +14,6 @@ [app.common.types.shape-tree :as ctt] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.selection :as dws] - [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [beicon.core :as rx] [potok.core :as ptk])) @@ -190,7 +189,7 @@ :origin it}] (rx/of (dch/commit-changes changes) - (dwul/update-layout-positions parents)))))) + (ptk/data-event :layout/update parents)))))) (def mask-group (ptk/reify ::mask-group diff --git a/frontend/src/app/main/data/workspace/modifiers.cljs b/frontend/src/app/main/data/workspace/modifiers.cljs index f6de2ab145..808798457c 100644 --- a/frontend/src/app/main/data/workspace/modifiers.cljs +++ b/frontend/src/app/main/data/workspace/modifiers.cljs @@ -113,7 +113,7 @@ (reduce set-child ignore-tree children)))) -(defn- update-grow-type +(defn update-grow-type [shape old-shape] (let [auto-width? (= :auto-width (:grow-type shape)) auto-height? (= :auto-height (:grow-type shape)) @@ -226,6 +226,22 @@ (recur (rest modifiers) (update objects id apply-path-modifier path-modifier))))))) +(defn- calculate-modifiers + ([state modif-tree] + (calculate-modifiers state false false modif-tree)) + + ([state ignore-constraints ignore-snap-pixel modif-tree] + (let [objects + (wsh/lookup-page-objects state) + + snap-pixel? + (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid))] + + (as-> objects $ + (apply-text-modifiers $ (get state :workspace-text-modifier)) + ;;(apply-path-modifiers $ (get-in state [:workspace-local :edit-path])) + (gsh/set-objects-modifiers modif-tree $ ignore-constraints snap-pixel?))))) + (defn set-modifiers ([modif-tree] (set-modifiers modif-tree false)) @@ -237,19 +253,7 @@ (ptk/reify ::set-modifiers ptk/UpdateEvent (update [_ state] - (let [objects - (wsh/lookup-page-objects state) - - snap-pixel? - (and (not ignore-snap-pixel) (contains? (:workspace-layout state) :snap-pixel-grid)) - - modif-tree - (as-> objects $ - (apply-text-modifiers $ (get state :workspace-text-modifier)) - ;;(apply-path-modifiers $ (get-in state [:workspace-local :edit-path])) - (gsh/set-objects-modifiers modif-tree $ ignore-constraints snap-pixel?))] - - (assoc state :workspace-modifiers modif-tree)))))) + (assoc state :workspace-modifiers (calculate-modifiers state ignore-constraints ignore-snap-pixel modif-tree)))))) ;; Rotation use different algorithm to calculate children modifiers (and do not use child constraints). (defn set-rotation-modifiers @@ -282,12 +286,14 @@ ([] (apply-modifiers nil)) - ([{:keys [undo-transation?] :or {undo-transation? true}}] + ([{:keys [undo-transation? modifiers] :or {undo-transation? true}}] (ptk/reify ::apply-modifiers ptk/WatchEvent (watch [_ state _] (let [objects (wsh/lookup-page-objects state) - object-modifiers (get state :workspace-modifiers) + object-modifiers (if modifiers + (calculate-modifiers state modifiers) + (get state :workspace-modifiers)) ids (or (keys object-modifiers) []) ids-with-children (into (vec ids) (mapcat #(cph/get-children-ids objects %)) ids) @@ -326,7 +332,6 @@ :transform :transform-inverse :rotation - :position-data :flip-x :flip-y :grow-type diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index a5b4a30a5b..a112da6575 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -21,7 +21,6 @@ [app.main.data.workspace.path.state :as st] [app.main.data.workspace.path.streams :as streams] [app.main.data.workspace.path.undo :as undo] - [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] [app.util.path.tools :as upt] @@ -316,7 +315,7 @@ ptk/WatchEvent (watch [_ _ _] - (rx/of (dwul/update-layout-positions [id]))))) + (rx/of (ptk/data-event :layout/update [id]))))) (defn split-segments [{:keys [from-p to-p t]}] diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 8791777c11..3ef4a4bf6c 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -21,7 +21,6 @@ [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.collapse :as dwc] - [app.main.data.workspace.shapes-update-layout :as dwul] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwt] [app.main.data.workspace.zoom :as dwz] @@ -565,7 +564,7 @@ ;; Warning: This order is important for the focus mode. (rx/of (dch/commit-changes changes) (select-shapes new-selected) - (dwul/update-layout-positions frames) + (ptk/data-event :layout/update frames) (memorize-duplicated id-original id-duplicated)))))))))) (defn change-hover-state diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 3e14d82279..8f1b69a9b9 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -9,13 +9,14 @@ [app.common.colors :as clr] [app.common.data :as d] [app.common.pages.helpers :as cph] + [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dwc] [app.main.data.workspace.colors :as cl] + [app.main.data.workspace.modifiers :as dwm] [app.main.data.workspace.selection :as dwse] [app.main.data.workspace.shapes :as dws] - [app.main.data.workspace.shapes-update-layout :as wsul] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [beicon.core :as rx] @@ -64,9 +65,38 @@ (let [objects (wsh/lookup-page-objects state) children-ids (into [] (mapcat #(get-in objects [% :shapes])) ids)] (rx/of (dwc/update-shapes ids (get-layout-initializer type)) - (wsul/update-layout-positions ids) + (ptk/data-event :layout/update ids) (dwc/update-shapes children-ids #(dissoc % :constraints-h :constraints-v))))))) + +;; Never call this directly but through the data-event `:layout/update` +;; Otherwise a lot of cycle dependencies could be generated +(defn- update-layout-positions + [ids] + (ptk/reify ::update-layout-positions + ptk/WatchEvent + (watch [_ _ _] + (if (d/not-empty? ids) + (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))] + (rx/of (dwm/apply-modifiers {:modifiers modif-tree}))) + (rx/empty))))) + +(defn initialize + [] + (ptk/reify ::initialize + ptk/WatchEvent + (watch [_ _ stream] + (let [stopper (rx/filter (ptk/type? ::finalize) stream)] + (->> stream + (rx/filter (ptk/type? :layout/update)) + (rx/map deref) + (rx/map #(update-layout-positions %)) + (rx/take-until stopper)))))) + +(defn finalize + [] + (ptk/reify ::finalize)) + (defn create-layout-from-selection [type] (ptk/reify ::create-layout-from-selection @@ -115,7 +145,7 @@ (rx/of (dwu/start-undo-transaction undo-id) (dwc/update-shapes ids #(apply dissoc % layout-keys)) - (wsul/update-layout-positions ids) + (ptk/data-event :layout/update ids) (dwu/commit-undo-transaction undo-id)))))) (defn create-layout @@ -163,7 +193,7 @@ ptk/WatchEvent (watch [_ _ _] (rx/of (dwc/update-shapes ids #(d/deep-merge % changes)) - (wsul/update-layout-positions ids))))) + (ptk/data-event :layout/update ids))))) (defn update-layout-child [ids changes] @@ -174,4 +204,4 @@ parent-ids (->> ids (map #(cph/get-parent-id objects %))) layout-ids (->> ids (filter (comp ctl/layout? (d/getf objects))))] (rx/of (dwc/update-shapes ids #(d/deep-merge (or % {}) changes)) - (wsul/update-layout-positions (d/concat-vec layout-ids parent-ids))))))) + (ptk/data-event :layout/update (d/concat-vec layout-ids parent-ids))))))) diff --git a/frontend/src/app/main/data/workspace/shapes.cljs b/frontend/src/app/main/data/workspace/shapes.cljs index 9fde82d4be..95adb579a0 100644 --- a/frontend/src/app/main/data/workspace/shapes.cljs +++ b/frontend/src/app/main/data/workspace/shapes.cljs @@ -24,7 +24,6 @@ [app.main.data.workspace.changes :as dch] [app.main.data.workspace.edition :as dwe] [app.main.data.workspace.selection :as dws] - [app.main.data.workspace.shapes-update-layout :as dwsul] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] [app.main.features :as features] @@ -105,7 +104,7 @@ (rx/concat (rx/of (dch/commit-changes changes) - (dwsul/update-layout-positions [(:parent-id shape)]) + (ptk/data-event :layout/update [(:parent-id shape)]) (when-not no-select? (dws/select-shapes (d/ordered-set id)))) (when (= :text (:type attrs)) @@ -310,9 +309,9 @@ (reduce ctp/remove-flow flows))))))] (rx/of (dc/detach-comment-thread ids) - (dwsul/update-layout-positions all-parents) + (ptk/data-event :layout/update all-parents) (dch/commit-changes changes) - (dwsul/update-layout-positions layout-ids)))) + (ptk/data-event :layout/update layout-ids)))) (defn create-and-add-shape [type frame-x frame-y data] diff --git a/frontend/src/app/main/data/workspace/shapes_update_layout.cljs b/frontend/src/app/main/data/workspace/shapes_update_layout.cljs deleted file mode 100644 index 710fd10c69..0000000000 --- a/frontend/src/app/main/data/workspace/shapes_update_layout.cljs +++ /dev/null @@ -1,24 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.main.data.workspace.shapes-update-layout - (:require - [app.common.data :as d] - [app.common.types.modifiers :as ctm] - [app.main.data.workspace.modifiers :as dwm] - [beicon.core :as rx] - [potok.core :as ptk])) - -(defn update-layout-positions - [ids] - (ptk/reify ::update-layout-positions - ptk/WatchEvent - (watch [_ _ _] - (if (d/not-empty? ids) - (let [modif-tree (dwm/create-modif-tree ids (ctm/reflow-modifiers))] - (rx/of (dwm/set-modifiers modif-tree) - (dwm/apply-modifiers))) - (rx/empty))))) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index 77b44c2962..d5bde20916 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -343,7 +343,9 @@ (when (or (and (not-changed? (:width shape) new-width) (= (:grow-type shape) :auto-width)) (and (not-changed? (:height shape) new-height) (or (= (:grow-type shape) :auto-height) (= (:grow-type shape) :auto-width)))) - (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false})))))))) + (rx/of (dch/update-shapes [id] update-fn {:reg-objects? true :save-undo? false}) + (ptk/data-event :layout/update [id])))))))) + (defn save-font [data] diff --git a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs index 405b3f1b06..b32b3dc06e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/viewport_texts_html.cljs @@ -15,6 +15,7 @@ [app.common.pages.helpers :as cph] [app.common.text :as txt] [app.common.types.modifiers :as ctm] + [app.main.data.workspace.modifiers :as mdwm] [app.main.data.workspace.texts :as dwt] [app.main.fonts :as fonts] [app.main.refs :as refs] @@ -40,7 +41,9 @@ ;; We need to remove the movement because the dynamic modifiers will have move it deltav (gpt/to-vec (gpt/point (:selrect shape')) (gpt/point (:selrect shape)))] - (gsh/transform-shape shape (ctm/move modifier deltav)))) + (-> shape + (gsh/transform-shape (ctm/move modifier deltav)) + (mdwm/update-grow-type shape)))) (defn process-shape [modifiers {:keys [id] :as shape}] (let [modifier (dm/get-in modifiers [id :modifiers])] diff --git a/frontend/src/debug.cljs b/frontend/src/debug.cljs index f0b61abd76..cfb8b1c40a 100644 --- a/frontend/src/debug.cljs +++ b/frontend/src/debug.cljs @@ -91,6 +91,10 @@ ;; These events are excluded when we activate the :events flag (def debug-exclude-events #{:app.main.data.workspace.notifications/handle-pointer-update + :app.main.data.workspace.notifications/handle-pointer-send + :app.main.data.workspace.persistence/update-persistence-status + :app.main.data.workspace.changes/update-indices + :app.main.data.websocket/send-message :app.main.data.workspace.selection/change-hover-state}) (defonce ^:dynamic *debug* (atom #{#_:events})) From 7dbe39b1b53e0001b2b70e94a9b12cb05be2ce68 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Dec 2022 13:21:06 +0100 Subject: [PATCH 311/682] :bug: Fix problems with cache and thumbnails --- .../app/common/geom/shapes/constraints.cljc | 17 +++++++++-- .../src/app/common/pages/changes_builder.cljc | 12 ++++---- frontend/src/app/main/data/workspace.cljs | 2 +- frontend/src/app/util/cache.cljs | 29 +++++++++++-------- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/common/src/app/common/geom/shapes/constraints.cljc b/common/src/app/common/geom/shapes/constraints.cljc index 6275e673fb..bd158ccd54 100644 --- a/common/src/app/common/geom/shapes/constraints.cljc +++ b/common/src/app/common/geom/shapes/constraints.cljc @@ -13,6 +13,7 @@ [app.common.geom.shapes.transforms :as gtr] [app.common.math :as mth] [app.common.types.modifiers :as ctm] + [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid])) ;; Auxiliary methods to work in an specifica axis @@ -285,13 +286,25 @@ (let [modifiers (ctm/select-child modifiers) constraints-h - (if-not ignore-constraints + (cond + (ctl/layout? parent) + :left + + (not ignore-constraints) (:constraints-h child (default-constraints-h child)) + + :else :scale) constraints-v - (if-not ignore-constraints + (cond + (ctl/layout? parent) + :top + + (not ignore-constraints) (:constraints-v child (default-constraints-v child)) + + :else :scale)] (if (and (= :scale constraints-h) (= :scale constraints-v)) diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index 8375fc4513..e27b7f3a3a 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -283,13 +283,13 @@ (fn [old new attr] (let [old-val (get old attr) new-val (get new attr)] - (not= old-val new-val)))] - (let [new-obj (update-fn object)] - (if (= object new-obj) - '() + (not= old-val new-val))) + new-obj (update-fn object)] + (if (= object new-obj) + '() - (let [attrs (or attrs (d/concat-set (keys object) (keys new-obj)))] - (filter (partial changed? object new-obj) attrs)))))) + (let [attrs (or attrs (d/concat-set (keys object) (keys new-obj)))] + (filter (partial changed? object new-obj) attrs))))) (defn update-shapes "Calculate the changes and undos to be done when a function is applied to a diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index a14b0454d6..74d1e1c1a6 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -55,8 +55,8 @@ [app.main.data.workspace.path.shapes-to-path :as dwps] [app.main.data.workspace.persistence :as dwp] [app.main.data.workspace.selection :as dws] - [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.shape-layout :as dwsl] + [app.main.data.workspace.shapes :as dwsh] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.thumbnails :as dwth] [app.main.data.workspace.transforms :as dwt] diff --git a/frontend/src/app/util/cache.cljs b/frontend/src/app/util/cache.cljs index 90c675bb05..fcb54c9174 100644 --- a/frontend/src/app/util/cache.cljs +++ b/frontend/src/app/util/cache.cljs @@ -29,15 +29,20 @@ :else (let [subject (rx/subject)] - (swap! pending assoc key subject) - (->> observable - (rx/catch #(do (rx/error! subject %) - (swap! pending dissoc key) - (rx/throw %))) - (rx/tap - (fn [data] - (let [entry {:created-at (dt/now) :data data}] - (swap! cache assoc key entry)) - (rx/push! subject data) - (rx/end! subject) - (swap! pending dissoc key)))))))) + (do + (swap! pending assoc key subject) + + (rx/subscribe + observable + + (fn [data] + (let [entry {:created-at (dt/now) :data data}] + (swap! cache assoc key entry)) + (swap! pending dissoc key) + (rx/push! subject data) + (rx/end! subject)) + + #(do + (swap! pending dissoc key) + (rx/error! subject %)))) + subject)))) From 4b55c7a8e0dec5e1e44f933e681742e74afb10f2 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Dec 2022 07:52:58 +0100 Subject: [PATCH 312/682] :bug: Fix problem with text not growing the hug container --- .../src/app/common/geom/shapes/modifiers.cljc | 4 +- common/src/app/common/types/modifiers.cljc | 156 ++++++++++++------ .../src/app/main/data/workspace/texts.cljs | 36 ++-- 3 files changed, 129 insertions(+), 67 deletions(-) diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index b4fa223bd3..5470730d7b 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -121,7 +121,7 @@ (update-in [child-id :modifiers] ctm/add-modifiers child-modifiers)) (rest children))))))))) -(defn- process-layout-children +#_(defn- process-layout-children [modif-tree objects bounds parent transformed-parent-bounds] (letfn [(process-child [modif-tree child] (let [child-id (:id child) @@ -275,7 +275,7 @@ (set-children-modifiers objects bounds parent transformed-parent-bounds ignore-constraints) layout? - (-> (process-layout-children objects bounds parent transformed-parent-bounds) + (-> #_(process-layout-children objects bounds parent transformed-parent-bounds) (set-layout-modifiers objects bounds parent transformed-parent-bounds))) ;; Auto-width/height can change the positions in the parent so we need to recalculate diff --git a/common/src/app/common/types/modifiers.cljc b/common/src/app/common/types/modifiers.cljc index 22a428b262..ece78a0e59 100644 --- a/common/src/app/common/types/modifiers.cljc +++ b/common/src/app/common/types/modifiers.cljc @@ -40,13 +40,15 @@ ;; * change-properties (defrecord Modifiers - [geometry-parent + [last-order ;; Last `order` attribute in the geometry list + geometry-parent geometry-child structure-parent structure-child]) (defrecord GeometricOperation - [type + [order ;; Need the order to keep consistent between geometry-parent and geometry-child + type vector origin transform @@ -62,41 +64,42 @@ ;; Record constructors -(defn move-op - [vector] - (GeometricOperation. :move vector nil nil nil nil nil)) +(defn- move-op + [order vector] + (GeometricOperation. order :move vector nil nil nil nil nil)) -(defn resize-op - ([vector origin] - (GeometricOperation. :resize vector origin nil nil nil nil)) - ([vector origin transform transform-inverse] - (GeometricOperation. :resize vector origin transform transform-inverse nil nil))) +(defn- resize-op + ([order vector origin] + (GeometricOperation. order :resize vector origin nil nil nil nil)) -(defn rotation-geom-op - [center angle] - (GeometricOperation. :rotation nil nil nil nil angle center)) + ([order vector origin transform transform-inverse] + (GeometricOperation. order :resize vector origin transform transform-inverse nil nil))) -(defn rotation-struct-op +(defn- rotation-geom-op + [order center angle] + (GeometricOperation. order :rotation nil nil nil nil angle center)) + +(defn- rotation-struct-op [angle] (StructureOperation. :rotation nil angle nil)) -(defn remove-children-op +(defn- remove-children-op [shapes] (StructureOperation. :remove-children nil shapes nil)) -(defn add-children-op +(defn- add-children-op [shapes index] (StructureOperation. :add-children nil shapes index)) -(defn reflow-op +(defn- reflow-op [] (StructureOperation. :reflow nil nil nil)) -(defn scale-content-op +(defn- scale-content-op [value] (StructureOperation. :scale-content nil value nil)) -(defn change-property-op +(defn- change-property-op [property value] (StructureOperation. :change-property property value nil)) @@ -142,8 +145,9 @@ (defn- merge-move [op1 op2] (let [vector-op1 (dm/get-prop op1 :vector) - vector-op2 (dm/get-prop op2 :vector)] - (move-op (gpt/add vector-op1 vector-op2)))) + vector-op2 (dm/get-prop op2 :vector) + vector (gpt/add vector-op1 vector-op2)] + (assoc op1 :vector vector))) (defn- merge-resize [op1 op2] @@ -197,7 +201,7 @@ ;; Public builder API (defn empty [] - (Modifiers. [] [] [] [])) + (Modifiers. 0 [] [] [] [])) (defn move-parent ([modifiers x y] @@ -205,22 +209,31 @@ ([modifiers vector] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> (or modifiers (empty)) - (move-vec? vector) - (update :geometry-parent maybe-add-move (move-op vector))))) + (let [modifiers (or modifiers (empty)) + order (inc (dm/get-prop modifiers :last-order)) + modifiers (assoc modifiers :last-order order)] + (cond-> modifiers + (move-vec? vector) + (update :geometry-parent maybe-add-move (move-op order vector)))))) (defn resize-parent ([modifiers vector origin] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> (or modifiers (empty)) - (resize-vec? vector) - (update :geometry-parent maybe-add-resize (resize-op vector origin)))) + (let [modifiers (or modifiers (empty)) + order (inc (dm/get-prop modifiers :last-order)) + modifiers (assoc modifiers :last-order order)] + (cond-> modifiers + (resize-vec? vector) + (update :geometry-parent maybe-add-resize (resize-op order vector origin))))) ([modifiers vector origin transform transform-inverse] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> (or modifiers (empty)) - (resize-vec? vector) - (update :geometry-parent maybe-add-resize (resize-op vector origin transform transform-inverse))))) + (let [modifiers (or modifiers (empty)) + order (inc (dm/get-prop modifiers :last-order)) + modifiers (assoc modifiers :last-order order)] + (cond-> modifiers + (resize-vec? vector) + (update :geometry-parent maybe-add-resize (resize-op order vector origin transform transform-inverse)))))) (defn move ([modifiers x y] @@ -228,29 +241,41 @@ ([modifiers vector] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> (or modifiers (empty)) - (move-vec? vector) - (update :geometry-child maybe-add-move (move-op vector))))) + (let [modifiers (or modifiers (empty)) + order (inc (dm/get-prop modifiers :last-order)) + modifiers (assoc modifiers :last-order order)] + (cond-> modifiers + (move-vec? vector) + (update :geometry-child maybe-add-move (move-op order vector)))))) (defn resize ([modifiers vector origin] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> (or modifiers (empty)) - (resize-vec? vector) - (update :geometry-child maybe-add-resize (resize-op vector origin)))) + (let [modifiers (or modifiers (empty)) + order (inc (dm/get-prop modifiers :last-order)) + modifiers (assoc modifiers :last-order order)] + (cond-> modifiers + (resize-vec? vector) + (update :geometry-child maybe-add-resize (resize-op order vector origin))))) ([modifiers vector origin transform transform-inverse] (assert (valid-vector? vector) (dm/str "Invalid move vector: " (:x vector) "," (:y vector))) - (cond-> (or modifiers (empty)) - (resize-vec? vector) - (update :geometry-child maybe-add-resize (resize-op vector origin transform transform-inverse))))) + (let [modifiers (or modifiers (empty)) + order (inc (dm/get-prop modifiers :last-order)) + modifiers (assoc modifiers :last-order order)] + (cond-> modifiers + (resize-vec? vector) + (update :geometry-child maybe-add-resize (resize-op order vector origin transform transform-inverse)))))) (defn rotation [modifiers center angle] - (cond-> (or modifiers (empty)) - (not (mth/close? angle 0)) - (-> (update :structure-child conj (rotation-struct-op angle)) - (update :geometry-child conj (rotation-geom-op center angle))))) + (let [modifiers (or modifiers (empty)) + order (inc (dm/get-prop modifiers :last-order)) + modifiers (assoc modifiers :last-order order)] + (cond-> modifiers + (not (mth/close? angle 0)) + (-> (update :structure-child conj (rotation-struct-op angle)) + (update :geometry-child conj (rotation-geom-op order center angle)))))) (defn remove-children [modifiers shapes] @@ -279,8 +304,8 @@ (-> (or modifiers (empty)) (update :structure-child conj (change-property-op property value)))) -(defn- merge-geometry - [operations other] +(defn- concat-geometry + [operations other merge?] (cond (c/empty? operations) @@ -297,10 +322,10 @@ (let [current (first operations) result (cond - (= :move (dm/get-prop current :type)) + (and merge? (= :move (dm/get-prop current :type))) (maybe-add-move result current) - (= :resize (dm/get-prop current :type)) + (and merge? (= :resize (dm/get-prop current :type))) (maybe-add-resize result current) :else @@ -308,15 +333,37 @@ (recur result (rest operations))))))) +(defn increase-order + [operations last-order] + (->> operations + (mapv #(update % :order + last-order)))) + (defn add-modifiers [modifiers new-modifiers] + (let [modifiers (or modifiers (empty)) - new-modifiers (or new-modifiers (empty))] + new-modifiers (or new-modifiers (empty)) + last-order (dm/get-prop modifiers :last-order) + new-last-order (dm/get-prop new-modifiers :last-order) + + + old-geom-child (dm/get-prop modifiers :geometry-child) + new-geom-child (-> (dm/get-prop new-modifiers :geometry-child) + (increase-order last-order)) + + old-geom-parent (dm/get-prop modifiers :geometry-parent) + new-geom-parent (-> (dm/get-prop new-modifiers :geometry-parent) + (increase-order last-order)) + + ;; We can only merge if the result will respect the global order in modifiers + merge-child? (and (c/empty? new-geom-parent) (c/empty? old-geom-parent)) + merge-parent? (and (c/empty? new-geom-child) (c/empty? old-geom-child))] (-> modifiers - (update :geometry-child merge-geometry (dm/get-prop new-modifiers :geometry-child)) - (update :geometry-parent merge-geometry (dm/get-prop new-modifiers :geometry-parent)) + (assoc :last-order (+ last-order new-last-order)) + (update :geometry-child #(concat-geometry % new-geom-child merge-child?)) + (update :geometry-parent #(concat-geometry % new-geom-parent merge-parent?)) (update :structure-parent #(d/concat-vec [] % (dm/get-prop new-modifiers :structure-parent))) - (update :structure-child #(d/concat-vec [] % (dm/get-prop new-modifiers :structure-child)))))) + (update :structure-child #(d/concat-vec [] % (dm/get-prop new-modifiers :structure-child)))))) ;; These are convenience methods to create single operation modifiers without the builder @@ -522,8 +569,9 @@ (defn modifiers->transform "Given a set of modifiers returns its transformation matrix" [modifiers] - (let [modifiers (concat (dm/get-prop modifiers :geometry-parent) - (dm/get-prop modifiers :geometry-child))] + (let [modifiers (->> (concat (dm/get-prop modifiers :geometry-parent) + (dm/get-prop modifiers :geometry-child)) + (sort-by :order))] (loop [matrix (gmt/matrix) modifiers (seq modifiers)] diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index d5bde20916..f24868f55b 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -8,6 +8,7 @@ (:require [app.common.attrs :as attrs] [app.common.data :as d] + [app.common.data.macros :as dm] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] @@ -380,23 +381,36 @@ new-shape)) +(defn update-text-modifier-state + [id props] + (ptk/reify ::update-text-modifier-state + ptk/UpdateEvent + (update [_ state] + (update-in state [:workspace-text-modifier id] (fnil merge {}) props)))) + (defn update-text-modifier [id props] (ptk/reify ::update-text-modifier - ptk/UpdateEvent - (update [_ state] - (update-in state [:workspace-text-modifier id] (fnil merge {}) props)) - ptk/WatchEvent (watch [_ state _] - (let [shape (wsh/lookup-shape state id)] - (when (or (and (some? (:width props)) - (not (mth/close? (:width props) (:width shape)))) - (and (some? (:height props)) - (not (mth/close? (:height props) (:height shape))))) + (let [shape (wsh/lookup-shape state id) - (let [modif-tree (dwm/create-modif-tree [id] (ctm/reflow-modifiers))] - (rx/of (dwm/set-modifiers modif-tree)))))))) + text-modifier (dm/get-in state [:workspace-text-modifier id]) + + current-width (or (:width text-modifier) (:width shape)) + current-height (or (:height text-modifier) (:height shape))] + (rx/concat + (rx/of (update-text-modifier-state id props)) + + (if (or (and (some? (:width props)) + (not (mth/close? (:width props) current-width))) + (and (some? (:height props)) + (not (mth/close? (:height props) current-height)))) + + (let [modif-tree (dwm/create-modif-tree [id] (ctm/reflow-modifiers))] + (->> (rx/of (dwm/set-modifiers modif-tree)) + (rx/observe-on :async))) + (rx/empty))))))) (defn clean-text-modifier [id] From 172f4c142b439ceaaf72b9a12db3bc6f9d94bba4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Dec 2022 10:36:35 +0100 Subject: [PATCH 313/682] :sparkles: Remove unused functions --- .../app/common/geom/shapes/flex_layout.cljc | 2 +- .../geom/shapes/flex_layout/modifiers.cljc | 29 ------------------- .../src/app/common/geom/shapes/modifiers.cljc | 20 +------------ 3 files changed, 2 insertions(+), 49 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout.cljc b/common/src/app/common/geom/shapes/flex_layout.cljc index 9379752468..9119d8dab0 100644 --- a/common/src/app/common/geom/shapes/flex_layout.cljc +++ b/common/src/app/common/geom/shapes/flex_layout.cljc @@ -18,4 +18,4 @@ (dm/export fdr/layout-drop-areas) (dm/export fli/calc-layout-data) (dm/export fmo/layout-child-modifiers) -(dm/export fmo/normalize-child-modifiers) + diff --git a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc index 481d27390f..29eea82f0d 100644 --- a/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/modifiers.cljc @@ -6,41 +6,12 @@ (ns app.common.geom.shapes.flex-layout.modifiers (:require - [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.flex-layout.positions :as fpo] [app.common.geom.shapes.points :as gpo] - [app.common.geom.shapes.transforms :as gtr] [app.common.types.modifiers :as ctm] [app.common.types.shape.layout :as ctl])) -(defn normalize-child-modifiers - "Apply the modifiers and then normalized them against the parent coordinates" - [modifiers {:keys [transform transform-inverse] :as parent} child-bounds parent-bounds transformed-parent-bounds] - - (let [transformed-child-bounds (gtr/transform-bounds child-bounds modifiers) - - child-bb-before (gpo/parent-coords-bounds child-bounds parent-bounds) - child-bb-after (gpo/parent-coords-bounds transformed-child-bounds transformed-parent-bounds) - - scale-x (/ (gpo/width-points child-bb-before) (gpo/width-points child-bb-after)) - scale-y (/ (gpo/height-points child-bb-before) (gpo/height-points child-bb-after)) - - resize-vector (gpt/point scale-x scale-y) - modif-transform (or (ctm/modifiers->transform modifiers) (gmt/matrix)) - modif-transform-inverse (gmt/inverse modif-transform) - resize-transform (gmt/multiply modif-transform transform) - resize-transform-inverse (gmt/multiply transform-inverse modif-transform-inverse) - resize-origin (gpo/origin transformed-child-bounds)] - - (-> modifiers - (ctm/select-child) - (ctm/resize - resize-vector - resize-origin - resize-transform - resize-transform-inverse)))) - (defn calc-fill-width-data "Calculates the size and modifiers for the width of an auto-fill child" [{:keys [transform transform-inverse] :as parent} diff --git a/common/src/app/common/geom/shapes/modifiers.cljc b/common/src/app/common/geom/shapes/modifiers.cljc index 5470730d7b..887681e756 100644 --- a/common/src/app/common/geom/shapes/modifiers.cljc +++ b/common/src/app/common/geom/shapes/modifiers.cljc @@ -121,23 +121,6 @@ (update-in [child-id :modifiers] ctm/add-modifiers child-modifiers)) (rest children))))))))) -#_(defn- process-layout-children - [modif-tree objects bounds parent transformed-parent-bounds] - (letfn [(process-child [modif-tree child] - (let [child-id (:id child) - parent-id (:id parent) - modifiers (dm/get-in modif-tree [parent-id :modifiers]) - child-bounds @(get bounds child-id) - parent-bounds @(get bounds parent-id) - child-modifiers (-> modifiers - (ctm/select-child-geometry-modifiers) - (gcl/normalize-child-modifiers parent child-bounds parent-bounds @transformed-parent-bounds))] - (cond-> modif-tree - (not (ctm/empty? child-modifiers)) - (update-in [child-id :modifiers] ctm/add-modifiers child-modifiers))))] - (let [children (map (d/getf objects) (:shapes parent))] - (reduce process-child modif-tree children)))) - (defn get-group-bounds [objects bounds modif-tree shape] (let [shape-id (:id shape) @@ -275,8 +258,7 @@ (set-children-modifiers objects bounds parent transformed-parent-bounds ignore-constraints) layout? - (-> #_(process-layout-children objects bounds parent transformed-parent-bounds) - (set-layout-modifiers objects bounds parent transformed-parent-bounds))) + (set-layout-modifiers objects bounds parent transformed-parent-bounds)) ;; Auto-width/height can change the positions in the parent so we need to recalculate (cond-> autolayouts auto? (conj (:id parent)))])) From a64d92b005765e4ed00b1f05e48f436913175014 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Dec 2022 15:05:29 +0100 Subject: [PATCH 314/682] :sparkles: Change default parameters on selection to layout --- .../src/app/common/pages/changes_builder.cljc | 5 +- .../app/main/data/workspace/shape_layout.cljs | 62 ++++++++++++++++++- 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/common/src/app/common/pages/changes_builder.cljc b/common/src/app/common/pages/changes_builder.cljc index e27b7f3a3a..bb669eeb1b 100644 --- a/common/src/app/common/pages/changes_builder.cljc +++ b/common/src/app/common/pages/changes_builder.cljc @@ -278,6 +278,7 @@ (defn changed-attrs + "Returns the list of attributes that will change when `update-fn` is applied" [object update-fn {:keys [attrs]}] (let [changed? (fn [old new attr] @@ -285,9 +286,7 @@ new-val (get new attr)] (not= old-val new-val))) new-obj (update-fn object)] - (if (= object new-obj) - '() - + (when-not (= object new-obj) (let [attrs (or attrs (d/concat-set (keys object) (keys new-obj)))] (filter (partial changed? object new-obj) attrs))))) diff --git a/frontend/src/app/main/data/workspace/shape_layout.cljs b/frontend/src/app/main/data/workspace/shape_layout.cljs index 8f1b69a9b9..b0eb038125 100644 --- a/frontend/src/app/main/data/workspace/shape_layout.cljs +++ b/frontend/src/app/main/data/workspace/shape_layout.cljs @@ -8,8 +8,12 @@ (:require [app.common.colors :as clr] [app.common.data :as d] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm] + [app.common.types.shape-tree :as ctt] [app.common.types.shape.layout :as ctl] [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dwc] @@ -38,7 +42,7 @@ (def initial-flex-layout {:layout :flex :layout-flex-dir :row - :layout-gap-type :simple + :layout-gap-type :multiple :layout-gap {:row-gap 0 :column-gap 0} :layout-align-items :start :layout-justify-content :start @@ -97,6 +101,41 @@ [] (ptk/reify ::finalize)) +(defn shapes->flex-params + "Given the shapes calculate its flex parameters (horizontal vs vertical etc)" + [objects shapes] + + (let [points + (->> shapes + (map :id) + (ctt/sort-z-index objects) + (map (comp gsh/center-shape (d/getf objects)))) + + start (first points) + end (reduce (fn [acc p] (gpt/add acc (gpt/to-vec start p))) points) + + angle (gpt/signed-angle-with-other + (gpt/to-vec start end) + (gpt/point 1 0)) + + angle (mod angle 360) + + t1 (min (abs (- angle 0)) (abs (- angle 360))) + t2 (abs (- angle 90)) + t3 (abs (- angle 180)) + t4 (abs (- angle 270)) + + tmin (min t1 t2 t3 t4) + + direction + (cond + (mth/close? tmin t1) :row + (mth/close? tmin t2) :reverse-column + (mth/close? tmin t3) :reverse-row + (mth/close? tmin t4) :column)] + + {:layout-flex-dir direction})) + (defn create-layout-from-selection [type] (ptk/reify ::create-layout-from-selection @@ -110,6 +149,7 @@ single? (= (count selected-shapes) 1) has-group? (->> selected-shapes (d/seek cph/group-shape?)) is-group? (and single? has-group?)] + (if is-group? (let [new-shape-id (uuid/next) parent-id (:parent-id (first selected-shapes)) @@ -123,17 +163,35 @@ (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1}) (create-layout-from-id [new-shape-id] type) + (dwc/update-shapes + [new-shape-id] + (fn [shape] + (-> shape + (assoc :layout-item-h-sizing :auto + :layout-item-v-sizing :auto)))) + + (ptk/data-event :layout/update [new-shape-id]) (dws/delete-shapes page-id selected) (dwu/commit-undo-transaction undo-id))) (let [new-shape-id (uuid/next) - undo-id (uuid/next)] + undo-id (uuid/next) + flex-params (shapes->flex-params objects selected-shapes)] (rx/of (dwu/start-undo-transaction undo-id) (dws/create-artboard-from-selection new-shape-id) (cl/remove-all-fills [new-shape-id] {:color clr/black :opacity 1}) (create-layout-from-id [new-shape-id] type) + (dwc/update-shapes + [new-shape-id] + (fn [shape] + (-> shape + (merge flex-params) + (assoc :layout-item-h-sizing :auto + :layout-item-v-sizing :auto)))) + + (ptk/data-event :layout/update [new-shape-id]) (dwu/commit-undo-transaction undo-id)))))))) (defn remove-layout From 415a3cad7baf286c3523dea6864dd283b46c5328 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 9 Dec 2022 11:01:24 +0100 Subject: [PATCH 315/682] :sparkles: Calculate selrect from points --- common/src/app/common/geom/shapes/points.cljc | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/common/src/app/common/geom/shapes/points.cljc b/common/src/app/common/geom/shapes/points.cljc index f8ea543690..dba17591e3 100644 --- a/common/src/app/common/geom/shapes/points.cljc +++ b/common/src/app/common/geom/shapes/points.cljc @@ -7,7 +7,9 @@ (ns app.common.geom.shapes.points (:require [app.common.geom.point :as gpt] + [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.intersect :as gsi] + [app.common.geom.shapes.rect :as gre] [app.common.math :as mth])) (defn origin @@ -134,3 +136,10 @@ (defn merge-parent-coords-bounds [bounds parent-bounds] (parent-coords-bounds (flatten bounds) parent-bounds)) + +(defn points->selrect + [points] + (let [width (width-points points) + height (height-points points) + center (gco/center-points points)] + (gre/center->selrect center width height))) From d283c6418e2a484e038dace1f0d02564afd27e97 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Fri, 9 Dec 2022 13:00:23 +0100 Subject: [PATCH 316/682] :tada: Add flag for enabling webhooks --- common/src/app/common/flags.cljc | 3 ++- frontend/src/app/main/ui/dashboard/sidebar.cljs | 3 ++- frontend/src/app/main/ui/dashboard/team.cljs | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/flags.cljc b/common/src/app/common/flags.cljc index 8a5d1ea6e3..4cde8026e4 100644 --- a/common/src/app/common/flags.cljc +++ b/common/src/app/common/flags.cljc @@ -12,7 +12,8 @@ (def default "A common flags that affects both: backend and frontend." [:enable-registration - :enable-login]) + :enable-login + :enable-webhooks]) (defn parse [& flags] diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index 73df4dcb2d..320eada9b0 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -324,7 +324,8 @@ [:ul.dropdown.options-dropdown [:li {:on-click go-members :data-test "team-members"} (tr "labels.members")] [:li {:on-click go-invitations :data-test "team-invitations"} (tr "labels.invitations")] - [:li {:on-click go-webhooks :data-test "team-webhooks"} (tr "labels.webhooks")] + (when (contains? @cf/flags :webhooks) + [:li {:on-click go-webhooks :data-test "team-webhooks"} (tr "labels.webhooks")]) [:li {:on-click go-settings :data-test "team-settings"} (tr "labels.settings")] [:hr] (when can-rename? diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index aa66bd2306..9e1c50c922 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -61,8 +61,9 @@ [:a {:on-click go-members} (tr "labels.members")]] [:li {:class (when invitations-section? "active")} [:a {:on-click go-invitations} (tr "labels.invitations")]] - [:li {:class (when webhooks-section? "active")} - [:a {:on-click go-webhooks} (tr "labels.webhooks")]] + (when (contains? @cfg/flags :webhooks) + [:li {:class (when webhooks-section? "active")} + [:a {:on-click go-webhooks} (tr "labels.webhooks")]]) [:li {:class (when settings-section? "active")} [:a {:on-click go-settings} (tr "labels.settings")]]]] [:div.dashboard-buttons From c411ce248e2ac3bc1c5ffd48c7baaa3a8d598bda Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Fri, 9 Dec 2022 13:25:28 -0500 Subject: [PATCH 317/682] :bug: Fix moving comment threads Moving comment threads was failing with assert errors because the speced type is gpt/point whereas update-comment-thread-position was passing a raw map of coordinates. Signed-off-by: Ryan Breen --- frontend/src/app/main/data/workspace/comments.cljs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs index 7fddf1c78a..9c93ec83b1 100644 --- a/frontend/src/app/main/data/workspace/comments.cljs +++ b/frontend/src/app/main/data/workspace/comments.cljs @@ -123,10 +123,10 @@ page-id (:id page) objects (wsh/lookup-page-objects state page-id) new-frame-id (if (nil? frame-id) - (ctst/frame-id-by-position objects {:x new-x :y new-y}) + (ctst/frame-id-by-position objects (gpt/point new-x new-y)) (:frame-id thread)) thread (assoc thread - :position {:x new-x :y new-y} + :position (gpt/point new-x new-y) :frame-id new-frame-id) changes From 8609308cb4319685937a8a7eb8fe810b7ea21090 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Sat, 10 Dec 2022 00:08:01 +0100 Subject: [PATCH 318/682] :bug: Fix problems with empty thumbnails --- frontend/src/app/main/data/workspace/thumbnails.cljs | 6 ++++-- frontend/src/app/main/ui/shapes/custom_stroke.cljs | 2 +- .../main/ui/workspace/shapes/frame/thumbnail_render.cljs | 7 +++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 27cc69fb7f..63c290f770 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -34,7 +34,7 @@ (fn [subs] ;; We look in the DOM a canvas that 1) matches the id and 2) that it's not empty ;; will be empty on first rendering before drawing the thumbnail and we don't want to store that - (let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-empty='false']" object-id))] + (let [node (dom/query (dm/fmt "canvas.thumbnail-canvas[data-object-id='%'][data-ready='true']" object-id))] (if (some? node) (.toBlob node (fn [blob] (rx/push! subs blob) @@ -79,7 +79,9 @@ (let [params {:file-id file-id :object-id object-id :data data}] (rx/merge ;; Update the local copy of the thumbnails so we don't need to request it again - (rx/of #(update % :workspace-thumbnails assoc object-id data)) + (if (some? data) + (rx/of #(update % :workspace-thumbnails assoc object-id data)) + (rx/empty)) (->> (rp/cmd! :upsert-file-object-thumbnail params) (rx/ignore)))) diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 373be81003..2c0be8a8b7 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -431,7 +431,7 @@ stroke-id (dm/fmt "strokes-%" (:id shape)) stroke-props (-> (obj/create) (obj/set! "id" stroke-id) - (obj/set! "class" "strokes") + (obj/set! "className" "strokes") (cond-> ;; There is a blur (and (:blur shape) (not (cph/frame-shape? shape)) (-> shape :blur :hidden not)) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index 5f13ae5fbc..6e60fb6b60 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -32,6 +32,7 @@ canvas-height (.-height canvas-node)] (.clearRect canvas-context 0 0 canvas-width canvas-height) (.drawImage canvas-context img-node 0 0 canvas-width canvas-height) + (dom/set-property! canvas-node "data-ready" "true") true)) (catch :default err (.error js/console err) @@ -95,11 +96,13 @@ on-image-load (mf/use-callback + (mf/deps @show-frame-thumbnail) (fn [] (ts/raf #(let [canvas-node (mf/ref-val frame-canvas-ref) img-node (mf/ref-val frame-image-ref)] (when (draw-thumbnail-canvas! canvas-node img-node) + (reset! image-url nil) (when @show-frame-thumbnail (reset! show-frame-thumbnail false)) ;; If we don't have the thumbnail data saved (normally the first load) we update the data @@ -129,12 +132,13 @@ (dom/set-property! "fill" "none") (obj/set! "innerHTML" (dm/str style-str frame-html))) img-src (-> svg-node dom/node->xml dom/svg->data-uri)] + (reset! image-url img-src)))) on-change-frame (mf/use-callback (fn [] - (when (and (some? @node-ref) @regenerate-thumbnail) + (when (and (some? @node-ref) @rendered? @regenerate-thumbnail) (let [loading-images? (some? (dom/query @node-ref "[data-loading='true']")) loading-fonts? (some? (dom/query (dm/str "#frame-container-" (:id shape) " > style[data-loading='true']")))] (when (and (not loading-images?) (not loading-fonts?)) @@ -237,7 +241,6 @@ {:key (dm/str "thumbnail-canvas-" (:id shape)) :ref frame-canvas-ref :data-object-id (dm/str page-id (:id shape)) - :data-empty @show-frame-thumbnail :width fixed-width :height fixed-height ;; DEBUG From f607540f2360e4c49e50ccfde6e2ea30afa07f14 Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Sat, 10 Dec 2022 06:51:58 -0500 Subject: [PATCH 319/682] :lipstick: Remove duplicate require in backend/dev/user.clj Cleanup a duplicate require of srepl Signed-off-by: Ryan Breen --- backend/dev/user.clj | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/dev/user.clj b/backend/dev/user.clj index 80824130a0..c7d590235c 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -19,7 +19,6 @@ [app.main :as main] [app.srepl.helpers] [app.srepl.main :as srepl] - [app.srepl.main :as srepl] [app.util.blob :as blob] [app.util.fressian :as fres] [app.util.json :as json] From 67682fe21186265be07677301b7feb5bb687faab Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Dec 2022 17:05:49 +0100 Subject: [PATCH 320/682] :bug: Fix shape exportation --- common/deps.edn | 2 +- frontend/src/app/render.cljs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common/deps.edn b/common/deps.edn index 6e6c6929dc..4b18aad90e 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -23,7 +23,7 @@ com.cognitect/transit-cljs {:mvn/version "0.8.280"} java-http-clj/java-http-clj {:mvn/version "0.4.3"} - funcool/promesa {:mvn/version "10.0.570"} + funcool/promesa {:mvn/version "10.0.571"} funcool/cuerdas {:mvn/version "2022.06.16-403"} lambdaisland/uri {:mvn/version "1.13.95" diff --git a/frontend/src/app/render.cljs b/frontend/src/app/render.cljs index 1276b4775a..534da24881 100644 --- a/frontend/src/app/render.cljs +++ b/frontend/src/app/render.cljs @@ -103,10 +103,10 @@ (let [features (cond-> #{} components-v2 (conj "components/v2"))] (->> (rx/zip (repo/query! :font-variants {:file-id file-id}) - (repo/cmd! :page {:file-id file-id - :page-id page-id - :object-id object-id - :features features})) + (repo/cmd! :get-page {:file-id file-id + :page-id page-id + :object-id object-id + :features features})) (rx/tap (fn [[fonts]] (when (seq fonts) (st/emit! (df/fonts-fetched fonts))))) From de6cba8c0b77c72d5ebc9c93521faea325f8eb0c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 9 Dec 2022 14:59:38 +0100 Subject: [PATCH 321/682] :sparkles: Changes to the flex UI --- .../common/geom/shapes/flex_layout/lines.cljc | 8 +- .../images/icons/auto-padding-both-sides.svg | 3 + .../resources/styles/common/framework.scss | 4 + .../partials/sidebar-element-options.scss | 43 ++++++- frontend/src/app/main/ui/formats.cljs | 2 +- frontend/src/app/main/ui/icons.cljs | 1 + .../options/menus/layout_container.cljs | 120 +++++++++++------- 7 files changed, 127 insertions(+), 54 deletions(-) create mode 100644 frontend/resources/images/icons/auto-padding-both-sides.svg diff --git a/common/src/app/common/geom/shapes/flex_layout/lines.cljc b/common/src/app/common/geom/shapes/flex_layout/lines.cljc index 664b91737e..7e445ab9fd 100644 --- a/common/src/app/common/geom/shapes/flex_layout/lines.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/lines.cljc @@ -15,13 +15,9 @@ (def conjv (fnil conj [])) (defn layout-bounds - [{:keys [layout-padding layout-padding-type] :as shape} shape-bounds] + [{:keys [layout-padding] :as shape} shape-bounds] (let [;; Add padding to the bounds - {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding - [pad-top pad-right pad-bottom pad-left] - (if (= layout-padding-type :multiple) - [pad-top pad-right pad-bottom pad-left] - [pad-top pad-top pad-top pad-top])] + {pad-top :p1 pad-right :p2 pad-bottom :p3 pad-left :p4} layout-padding] (gpo/pad-points shape-bounds pad-top pad-right pad-bottom pad-left))) (defn init-layout-lines diff --git a/frontend/resources/images/icons/auto-padding-both-sides.svg b/frontend/resources/images/icons/auto-padding-both-sides.svg new file mode 100644 index 0000000000..a9702f96d8 --- /dev/null +++ b/frontend/resources/images/icons/auto-padding-both-sides.svg @@ -0,0 +1,3 @@ + + + diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index 888b2d101d..0a7c0032fb 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -396,6 +396,10 @@ ul.slider-dots { width: 43px; } + &.auto { + width: auto; + } + // Input amounts &.pixels { diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 7701d5f23b..fb3f29de71 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -1612,11 +1612,23 @@ align-items: center; margin-right: 5px; font-family: "worksans", sans-serif; + + &.justify-content { + align-items: start; + margin-top: 4px; + } } .btn-wrapper { display: flex; width: 100%; max-width: 185px; + + &.justify-content { + display: flex; + flex-direction: column; + gap: 5px; + } + .direction, .wrap-type, .align-items-style, @@ -1711,7 +1723,8 @@ margin-bottom: 8px; height: 26px; .gap-row, - .gap-column { + .gap-column, + .padding-row { display: flex; justify-content: center; align-items: center; @@ -1751,6 +1764,13 @@ border: none; cursor: pointer; border-radius: $br-small; + + &.lock { + border: 1px solid $color-gray-60; + border-radius: $br-small; + width: 30px; + height: 30px; + } &.active { svg { fill: $color-primary; @@ -1765,10 +1785,17 @@ } } + .padding-row { + grid-template-columns: auto 30px; + } + + .margin-row { + grid-template-columns: 65px auto; + } + .padding-row, .margin-row { display: grid; - grid-template-columns: 65px auto; height: 26px; .padding-icons, .margin-icons { @@ -1799,15 +1826,27 @@ fill: $color-gray-30; } } + :last-child { border: none; } } + + .padding-icons { + margin-bottom: 8px; + margin-top: 3px; + margin-right: 1px; + height: 30px; + width: 30px; + } + .wrapper { display: flex; height: 26px; + .input-element { margin: 0; + margin-top: 3px; height: 26px; } } diff --git a/frontend/src/app/main/ui/formats.cljs b/frontend/src/app/main/ui/formats.cljs index cb02882b7c..8bbeca1f98 100644 --- a/frontend/src/app/main/ui/formats.cljs +++ b/frontend/src/app/main/ui/formats.cljs @@ -61,7 +61,7 @@ {:p1 p1 :p2 p2 :p3 p3} :else - {:p1 p1 :p2 p2 :p3 p3}))) + {:p1 p1 :p2 p2 :p3 p3 :p4 p4}))) (defn format-size [type value shape] (let [sizing (if (= type :width) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index e6c073122e..a8dec1705d 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -68,6 +68,7 @@ (def auto-margin (icon-xref :auto-margin)) (def auto-padding (icon-xref :auto-padding)) (def auto-padding-side (icon-xref :auto-padding-side)) +(def auto-padding-both-sides (icon-xref :auto-padding-both-sides)) (def auto-width (icon-xref :auto-width)) (def auto-wrap (icon-xref :auto-wrap)) (def bool-difference (icon-xref :boolean-difference)) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index e8111f373b..fe63d9831c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -12,7 +12,6 @@ [app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [tr]] [cuerdas.core :as str] [rumext.v2 :as mf])) @@ -154,53 +153,70 @@ (mf/defc justify-content-row [{:keys [is-col? justify-content set-justify] :as props}] - [:div.justify-content-style - (for [justify [:start :center :end :space-around :space-between]] - [:button.justify.tooltip - {:class (dom/classnames :active (= justify-content justify) - :tooltip-bottom-left (not= justify :start) - :tooltip-bottom (= justify :start)) - :alt (dm/str "Justify content " (d/name justify)) - :on-click #(set-justify justify) - :key (dm/str "justify-content" (d/name justify))} - (get-layout-flex-icon :justify-content justify is-col?)])]) + [:* + [:div.justify-content-style + (for [justify [:start :center :end]] + [:button.justify.tooltip + {:class (dom/classnames :active (= justify-content justify) + :tooltip-bottom-left (not= justify :start) + :tooltip-bottom (= justify :start)) + :alt (dm/str "Justify content " (d/name justify)) + :on-click #(set-justify justify) + :key (dm/str "justify-content" (d/name justify))} + (get-layout-flex-icon :justify-content justify is-col?)])] + [:div.justify-content-style + (for [justify [:space-around :space-between]] + [:button.justify.tooltip + {:class (dom/classnames :active (= justify-content justify) + :tooltip-bottom-left (not= justify :start) + :tooltip-bottom (= justify :start)) + :alt (dm/str "Justify content " (d/name justify)) + :on-click #(set-justify justify) + :key (dm/str "justify-content" (d/name justify))} + (get-layout-flex-icon :justify-content justify is-col?)])]]) (mf/defc padding-section [{:keys [values on-change-style on-change] :as props}] (let [padding-type (:layout-padding-type values) - rx (if (and (not (= :multiple (:layout-padding values))) - (apply = (vals (:layout-padding values)))) - (:p1 (:layout-padding values)) + p1 (if (and (not (= :multiple (:layout-padding values))) + (= (dm/get-in values [:layout-padding :p1]) + (dm/get-in values [:layout-padding :p3]))) + (dm/get-in values [:layout-padding :p1]) + "--") + + p2 (if (and (not (= :multiple (:layout-padding values))) + (= (dm/get-in values [:layout-padding :p2]) + (dm/get-in values [:layout-padding :p4]))) + (dm/get-in values [:layout-padding :p2]) "--")] [:div.padding-row - [:div.padding-icons - [:div.padding-icon.tooltip.tooltip-bottom - {:class (dom/classnames :selected (= padding-type :simple)) - :alt "Padding" - :on-click #(on-change-style :simple)} - i/auto-padding] - [:div.padding-icon.tooltip.tooltip-bottom - {:class (dom/classnames :selected (= padding-type :multiple)) - :alt "Padding - sides" - :on-click #(on-change-style :multiple)} - i/auto-padding-side]] - [:div.wrapper - (cond - (= padding-type :simple) - [:div.tooltip.tooltip-bottom - {:alt (tr "workspace.options.layout.padding-all")} - [:div.input-element.mini + (cond + (= padding-type :simple) - [:> numeric-input - {:placeholder "--" - :on-click #(dom/select-target %) - :on-change (partial on-change :simple) - :value rx}]]] + [:div.gap-group + [:div.gap-row.tooltip.tooltip-bottom-left + {:alt "Vertical padding"} + [:span.icon.rotated i/auto-padding-both-sides] + [:> numeric-input + {:placeholder "--" + :on-click #(dom/select-target %) + :on-change (partial on-change :simple :p1) + :value p1}]] - (= padding-type :multiple) + [:div.gap-column.tooltip.tooltip-bottom-left + {:alt "Horizontal padding"} + [:span.icon i/auto-padding-both-sides] + [:> numeric-input + {:placeholder "--" + :on-click #(dom/select-target %) + :on-change (partial on-change :simple :p2) + :value p2}]]] + + (= padding-type :multiple) + [:div.wrapper (for [num [:p1 :p2 :p3 :p4]] [:div.tooltip.tooltip-bottom {:key (dm/str "padding-" (d/name num)) @@ -209,12 +225,19 @@ :p2 "Right" :p3 "Bottom" :p4 "Left")} - [:div.input-element.mini + [:div.input-element.auto [:> numeric-input {:placeholder "--" :on-click #(dom/select-target %) - :on-change (partial on-change num) - :value (num (:layout-padding values))}]]]))]])) + :on-change (partial on-change :multiple num) + :value (num (:layout-padding values))}]]])]) + + [:div.padding-icons + [:div.padding-icon.tooltip.tooltip-bottom + {:class (dom/classnames :selected (= padding-type :multiple)) + :alt "Padding - multiple" + :on-click #(on-change-style (if (= padding-type :multiple) :simple :multiple))} + i/auto-padding-side]]])) (mf/defc gap-section [{:keys [gap-selected? set-gap gap-value toggle-gap-type gap-type]}] @@ -358,10 +381,17 @@ (st/emit! (dwsl/update-layout ids {:layout-padding-type type}))) on-padding-change - (fn [type val] - (if (= type :simple) - (st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p2 val :p3 val :p4 val}})) - (st/emit! (dwsl/update-layout ids {:layout-padding {type val}}))))] + (fn [type prop val] + (prn "??" type prop val) + (cond + (and (= type :simple) (= prop :p1)) + (st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}})) + + (and (= type :simple) (= prop :p2)) + (st/emit! (dwsl/update-layout ids {:layout-padding {:p2 val :p4 val}})) + + :else + (st/emit! (dwsl/update-layout ids {:layout-padding {prop val}}))))] [:div.element-set [:div.element-set-title @@ -415,7 +445,7 @@ [:div.layout-row [:div.justify-content.row-title "Justify"] - [:div.btn-wrapper + [:div.btn-wrapper.justify-content [:& justify-content-row {:is-col? is-col? :justify-content justify-content :set-justify set-justify-content}]]] From 95a18fce8d7c97d6e0b2d3f7489b729224a49f4d Mon Sep 17 00:00:00 2001 From: Eva Date: Mon, 28 Nov 2022 08:44:19 +0100 Subject: [PATCH 322/682] :sparkles: Improve login accessibility --- frontend/resources/images/login-pink.svg | 896 +++++++++++++++--- frontend/resources/styles/common/base.scss | 10 +- .../resources/styles/common/framework.scss | 14 +- .../resources/styles/main/layouts/login.scss | 18 +- .../styles/main/partials/dashboard.scss | 11 +- .../resources/styles/main/partials/forms.scss | 7 +- frontend/src/app/main/ui/auth.cljs | 4 +- frontend/src/app/main/ui/auth/login.cljs | 99 +- frontend/src/app/main/ui/auth/recovery.cljs | 4 +- .../app/main/ui/auth/recovery_request.cljs | 20 +- frontend/src/app/main/ui/auth/register.cljs | 38 +- .../app/main/ui/components/button_link.cljs | 21 + .../src/app/main/ui/components/forms.cljs | 16 +- frontend/src/app/main/ui/components/link.cljs | 20 + frontend/src/app/main/ui/messages.cljs | 10 +- .../app/main/ui/settings/change_email.cljs | 4 +- .../src/app/main/ui/settings/password.cljs | 4 +- frontend/src/app/util/forms.cljs | 25 +- frontend/translations/en.po | 4 + frontend/translations/es.po | 4 + 20 files changed, 978 insertions(+), 251 deletions(-) create mode 100644 frontend/src/app/main/ui/components/button_link.cljs create mode 100644 frontend/src/app/main/ui/components/link.cljs diff --git a/frontend/resources/images/login-pink.svg b/frontend/resources/images/login-pink.svg index f633fd411d..de7e40a93e 100644 --- a/frontend/resources/images/login-pink.svg +++ b/frontend/resources/images/login-pink.svg @@ -1,158 +1,790 @@ - - - - - - - + + + + + + + + + + - - - - + + + + - - - - - - + + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - + + + + - - - - - + + + + + - - - - - - - - + + + + + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - + + + + - - - - + + + + - - - - - + + + + + - - - - - - - + + + + + + + - - - - - + + + + + - - - - - + + + + + - - - - - - - - + + + + + + + + - - - - + + + + - - + + - - - - - - - - - + + + + + + + + + - - - - - - - - - + + + + + + + + + - - - S - - - V - - - G - + + + + - - + + diff --git a/frontend/resources/styles/common/base.scss b/frontend/resources/styles/common/base.scss index f49c000644..4dafb79d54 100644 --- a/frontend/resources/styles/common/base.scss +++ b/frontend/resources/styles/common/base.scss @@ -54,17 +54,13 @@ svg { } } -*:focus { - outline: none; - box-shadow: 0; -} - a { cursor: pointer; - color: $color-primary-dark; + font-weight: 500; + color: $color-gray-50; &:hover { - color: $color-primary; + text-decoration: underline; } } diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index 0a7c0032fb..df56e139dc 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -14,6 +14,7 @@ border-radius: 3px; cursor: pointer; display: flex; + font-family: "worksans", sans-serif; font-size: $fs12; height: 30px; justify-content: center; @@ -42,7 +43,8 @@ @extend %btn; background: $color-primary; color: $color-black; - &:hover { + &:hover, + &:focus { background: $color-black; color: $color-primary; } @@ -503,16 +505,6 @@ input[type="checkbox"] { margin-top: 1px 0 0; } -input:focus, -select:focus, -textarea:focus, -input[type="file"]:focus, -input[type="radio"]:focus, -input[type="checkbox"]:focus { - box-shadow: none; - outline: none; -} - .form-errors { color: $color-danger; } diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss index 3d88adb378..7fdc92ed5f 100644 --- a/frontend/resources/styles/main/layouts/login.scss +++ b/frontend/resources/styles/main/layouts/login.scss @@ -87,7 +87,6 @@ .btn-large { flex-grow: 1; font-size: 14px; - font-family: sourcesanspro; font-style: normal; font-weight: normal; } @@ -102,7 +101,8 @@ height: 20px; margin-right: 1rem; } - &:hover { + &:hover, + &:focus { background-color: #2065d7; color: $color-white; } @@ -120,7 +120,8 @@ margin-right: 1rem; } - &:hover { + &:hover, + &:focus { background-color: #ee5f18; color: $color-white; } @@ -138,7 +139,8 @@ margin-right: 1rem; } - &:hover { + &:hover, + &:focus { background-color: #2f2f2f; color: $color-white; } @@ -186,7 +188,12 @@ margin-bottom: 10px; a { font-size: $fs14; - color: $color-primary-dark; + font-weight: 500; + color: $color-gray-50; + &:hover, + &:focus { + text-decoration: underline; + } } } } @@ -198,6 +205,7 @@ span { margin: 0 $size-2; + color: $color-gray-40; } } } diff --git a/frontend/resources/styles/main/partials/dashboard.scss b/frontend/resources/styles/main/partials/dashboard.scss index 8fe5e2ebe6..f18499919f 100644 --- a/frontend/resources/styles/main/partials/dashboard.scss +++ b/frontend/resources/styles/main/partials/dashboard.scss @@ -89,9 +89,9 @@ margin-bottom: 8px; } .info { - color: $color-gray-30; + color: $color-gray-50; margin-bottom: 20px; - font-size: $fs16; + font-size: $fs14; } } .action { @@ -551,6 +551,10 @@ width: 16px; height: 16px; } + span { + font-weight: 500; + font-size: $fs14; + } } .template-link { @@ -567,7 +571,8 @@ .template-link-text { font-size: 12px; - color: $color-gray-30; + margin-top: $size-2; + color: $color-gray-50; } &:hover { diff --git a/frontend/resources/styles/main/partials/forms.scss b/frontend/resources/styles/main/partials/forms.scss index 7be087539e..47891adbc8 100644 --- a/frontend/resources/styles/main/partials/forms.scss +++ b/frontend/resources/styles/main/partials/forms.scss @@ -99,7 +99,9 @@ textarea { } a { - text-decoration: underline; + &:hover { + text-decoration: underline; + } } p { @@ -146,7 +148,7 @@ textarea { label { font-size: $fs12; - color: $color-gray-30; + color: $color-gray-50; position: absolute; left: 15px; top: 6px; @@ -216,6 +218,7 @@ textarea { } .hint { + color: $color-gray-40; padding: 4px; font-size: $fs12; } diff --git a/frontend/src/app/main/ui/auth.cljs b/frontend/src/app/main/ui/auth.cljs index 67a8e8d6f0..03d1061d3f 100644 --- a/frontend/src/app/main/ui/auth.cljs +++ b/frontend/src/app/main/ui/auth.cljs @@ -41,9 +41,9 @@ (mf/use-effect #(dom/set-html-title (tr "title.default"))) - [:div.auth + [:main.auth [:section.auth-sidebar - [:a.logo {:href "#/"} i/logo] + [:span.logo {:aria-hidden "true"} i/logo] [:span.tagline (tr "auth.sidebar-tagline")]] [:section.auth-content diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index 29273386d5..baac56f6fe 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -6,17 +6,21 @@ (ns app.main.ui.auth.login (:require + [app.common.data :as d] [app.common.spec :as us] [app.config :as cf] [app.main.data.messages :as dm] [app.main.data.users :as du] [app.main.repo :as rp] [app.main.store :as st] + [app.main.ui.components.button-link :as bl] [app.main.ui.components.forms :as fm] + [app.main.ui.components.link :as lk] [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] [app.util.dom :as dom] [app.util.i18n :refer [tr]] + [app.util.keyboard :as k] [app.util.router :as rt] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -29,14 +33,6 @@ :login-with-gitlab :login-with-oidc])) -(s/def ::email ::us/email) -(s/def ::password ::us/not-empty-string) -(s/def ::invitation-token ::us/not-empty-string) - -(s/def ::login-form - (s/keys :req-un [::email ::password] - :opt-un [::invitation-token])) - (defn- login-with-oidc [event provider params] (dom/prevent-default event) @@ -74,13 +70,30 @@ :else (st/emit! (dm/error (tr "errors.generic"))))))))) +(s/def ::email ::us/email) +(s/def ::password ::us/not-empty-string) +(s/def ::invitation-token ::us/not-empty-string) + +(s/def ::login-form + (s/keys :req-un [::email ::password] + :opt-un [::invitation-token])) + +(defn handle-error-messages + [errors _data] + (d/update-when errors :email + (fn [{:keys [code] :as error}] + (cond-> error + (= code ::us/email) + (assoc :message (tr "errors.email-invalid")))))) (mf/defc login-form [{:keys [params on-success-callback] :as props}] (let [initial (mf/use-memo (mf/deps params) (constantly params)) error (mf/use-state false) - form (fm/use-form :spec ::login-form :initial initial) + form (fm/use-form :spec ::login-form + :validators [handle-error-messages] + :initial initial) on-error (fn [cause] @@ -109,8 +122,7 @@ (fn [data] (if (nil? on-success-callback) (on-success-default data) - (on-success-callback) - )) + (on-success-callback))) on-submit (mf/use-callback @@ -136,21 +148,21 @@ {:type :warning :content message :on-close #(reset! error nil) - :data-test "login-banner"}]) + :data-test "login-banner" + :role "alert"}]) [:& fm/form {:on-submit on-submit :form form} [:div.fields-row [:& fm/input {:name :email :type "email" - :tab-index "2" :help-icon i/at :label (tr "auth.email")}]] + [:div.fields-row [:& fm/input {:type "password" :name :password - :tab-index "3" :help-icon i/eye :label (tr "auth.password")}]] @@ -169,34 +181,38 @@ [{:keys [params] :as props}] [:div.auth-buttons (when (contains? @cf/flags :login-with-google) - [:a.btn-primary.btn-large.btn-google-auth - {:on-click #(login-with-oidc % :google params)} - [:span.logo i/brand-google] - (tr "auth.login-with-google-submit")]) + [:& bl/button-link {:action #(login-with-oidc % :google params) + :icon i/brand-google + :name (tr "auth.login-with-google-submit") + :klass "btn-google-auth"}]) (when (contains? @cf/flags :login-with-github) - [:a.btn-primary.btn-large.btn-github-auth - {:on-click #(login-with-oidc % :github params)} - [:span.logo i/brand-github] - (tr "auth.login-with-github-submit")]) + [:& bl/button-link {:action #(login-with-oidc % :github params) + :icon i/brand-github + :name (tr "auth.login-with-github-submit") + :klass "btn-github-auth"}]) (when (contains? @cf/flags :login-with-gitlab) - [:a.btn-primary.btn-large.btn-gitlab-auth - {:on-click #(login-with-oidc % :gitlab params)} - [:span.logo i/brand-gitlab] - (tr "auth.login-with-gitlab-submit")]) + [:& bl/button-link {:action #(login-with-oidc % :gitlab params) + :icon i/brand-gitlab + :name (tr "auth.login-with-gitlab-submit") + :klass "btn-gitlab-auth"}]) (when (contains? @cf/flags :login-with-oidc) - [:a.btn-primary.btn-large.btn-github-auth - {:on-click #(login-with-oidc % :oidc params)} - [:span.logo i/brand-openid] - (tr "auth.login-with-oidc-submit")])]) + [:& bl/button-link {:action #(login-with-oidc % :oidc params) + :icon i/brand-openid + :name (tr "auth.login-with-oidc-submit") + :klass "btn-github-auth"}])]) (mf/defc login-button-oidc [{:keys [params] :as props}] (when (contains? @cf/flags :login-with-oidc) [:div.link-entry.link-oidc - [:a {:on-click #(login-with-oidc % :oidc params)} + [:a {:tab-index "0" + :on-key-down (fn [event] + (when (k/enter? event) + (login-with-oidc event :oidc params))) + :on-click #(login-with-oidc % :oidc params)} (tr "auth.login-with-oidc-submit")]])) (mf/defc login-methods @@ -209,8 +225,7 @@ [:span.text (tr "labels.continue-with")] [:span.line]] - [:div.buttons - [:& login-buttons {:params params}]] + [:& login-buttons {:params params}] (when (or (contains? @cf/flags :login) (contains? @cf/flags :login-with-ldap)) @@ -234,21 +249,21 @@ [:div.links (when (contains? @cf/flags :login) [:div.link-entry - [:a {:on-click #(st/emit! (rt/nav :auth-recovery-request)) - :data-test "forgot-password"} - (tr "auth.forgot-password")]]) + [:& lk/link {:action #(st/emit! (rt/nav :auth-recovery-request)) + :name (tr "auth.forgot-password") + :data-test "forgot-password"}]]) (when (contains? @cf/flags :registration) [:div.link-entry [:span (tr "auth.register") " "] - [:a {:on-click #(st/emit! (rt/nav :auth-register {} params)) - :data-test "register-submit"} - (tr "auth.register-submit")]])] + [:& lk/link {:action #(st/emit! (rt/nav :auth-register {} params)) + :name (tr "auth.register-submit") + :data-test "register-submit"}]])] (when (contains? @cf/flags :demo-users) [:div.links.demo [:div.link-entry [:span (tr "auth.create-demo-profile") " "] - [:a {:on-click #(st/emit! (du/create-demo-profile)) - :data-test "demo-account-link"} - (tr "auth.create-demo-account")]]])]]) + [:& lk/link {:action #(st/emit! (du/create-demo-profile)) + :name (tr "auth.create-demo-account") + :data-test "demo-account-link"}]]])]]) diff --git a/frontend/src/app/main/ui/auth/recovery.cljs b/frontend/src/app/main/ui/auth/recovery.cljs index f72bf420aa..3f752f7f62 100644 --- a/frontend/src/app/main/ui/auth/recovery.cljs +++ b/frontend/src/app/main/ui/auth/recovery.cljs @@ -25,10 +25,10 @@ ::password-2])) (defn- password-equality - [data] + [errors data] (let [password-1 (:password-1 data) password-2 (:password-2 data)] - (cond-> {} + (cond-> errors (and password-1 password-2 (not= password-1 password-2)) (assoc :password-2 {:message "errors.password-invalid-confirmation"}) diff --git a/frontend/src/app/main/ui/auth/recovery_request.cljs b/frontend/src/app/main/ui/auth/recovery_request.cljs index 783d2fb958..6356c5de2c 100644 --- a/frontend/src/app/main/ui/auth/recovery_request.cljs +++ b/frontend/src/app/main/ui/auth/recovery_request.cljs @@ -6,11 +6,13 @@ (ns app.main.ui.auth.recovery-request (:require + [app.common.data :as d] [app.common.spec :as us] [app.main.data.messages :as dm] [app.main.data.users :as du] [app.main.store :as st] [app.main.ui.components.forms :as fm] + [app.main.ui.components.link :as lk] [app.main.ui.icons :as i] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] @@ -20,10 +22,19 @@ (s/def ::email ::us/email) (s/def ::recovery-request-form (s/keys :req-un [::email])) +(defn handle-error-messages + [errors _data] + (d/update-when errors :email + (fn [{:keys [code] :as error}] + (cond-> error + (= code :missing) + (assoc :message (tr "errors.email-invalid")))))) (mf/defc recovery-form [{:keys [on-success-callback] :as props}] - (let [form (fm/use-form :spec ::recovery-request-form :initial {}) + (let [form (fm/use-form :spec ::recovery-request-form + :validators [handle-error-messages] + :initial {}) submitted (mf/use-state false) default-success-finish #(st/emit! (dm/info (tr "auth.notifications.recovery-token-sent"))) @@ -87,9 +98,8 @@ [:h1 (tr "auth.recovery-request-title")] [:div.subtitle (tr "auth.recovery-request-subtitle")] [:& recovery-form {:params params :on-success-callback on-success-callback}] - [:div.links [:div.link-entry - [:a {:on-click go-back - :data-test "go-back-link"} - (tr "labels.go-back")]]]]])) + [:& lk/link {:action go-back + :name (tr "labels.go-back") + :data-test "go-back-link"}]]]]])) diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 87ab67dde5..38d9bf3ca2 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.auth.register (:require + [app.common.data :as d] [app.common.spec :as us] [app.config :as cf] [app.main.data.messages :as dm] @@ -14,6 +15,7 @@ [app.main.store :as st] [app.main.ui.auth.login :as login] [app.main.ui.components.forms :as fm] + [app.main.ui.components.link :as lk] [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] [app.util.i18n :refer [tr]] @@ -31,11 +33,17 @@ ;; --- PAGE: Register (defn- validate - [data] + [errors data] (let [password (:password data)] - (cond-> {} + (cond-> errors (> 8 (count password)) - (assoc :password {:message "errors.password-too-short"})))) + (assoc :password {:message "errors.password-too-short"}) + :always + (d/update-when :email + (fn [{:keys [code] :as error}] + (cond-> error + (= code ::us/email) + (assoc :message (tr "errors.email-invalid")))))))) (s/def ::fullname ::us/not-empty-string) (s/def ::password ::us/not-empty-string) @@ -106,13 +114,11 @@ [:div.fields-row [:& fm/input {:type "email" :name :email - :tab-index "2" :help-icon i/at :label (tr "auth.email") :data-test "email-input"}]] [:div.fields-row [:& fm/input {:name :password - :tab-index "3" :hint (tr "auth.password-length-hint") :label (tr "auth.password") :type "password"}]] @@ -133,8 +139,7 @@ [:span.text (tr "labels.continue-with")] [:span.line]] - [:div.buttons - [:& login/login-buttons {:params params}]] + [:& login/login-buttons {:params params}] (when (or (contains? @cf/flags :login) (contains? @cf/flags :login-with-ldap)) @@ -160,17 +165,16 @@ [:div.links [:div.link-entry [:span (tr "auth.already-have-account") " "] - [:a {:on-click #(st/emit! (rt/nav :auth-login {} params)) - :tab-index "4" - :data-test "login-here-link"} - (tr "auth.login-here")]] + + [:& lk/link {:action #(st/emit! (rt/nav :auth-login {} params)) + :name (tr "auth.login-here") + :data-test "login-here-link"}]] (when (contains? @cf/flags :demo-users) [:div.link-entry [:span (tr "auth.create-demo-profile") " "] - [:a {:on-click #(st/emit! (du/create-demo-profile)) - :tab-index "5"} - (tr "auth.create-demo-account")]])]]) + [:& lk/link {:action #(st/emit! (du/create-demo-profile)) + :name (tr "auth.create-demo-account")}]])]]) ;; --- PAGE: register validation @@ -237,7 +241,6 @@ :form form} [:div.fields-row [:& fm/input {:name :fullname - :tab-index "1" :label (tr "auth.fullname") :type "text"}]] @@ -268,9 +271,8 @@ [:div.links [:div.link-entry - [:a {:on-click #(st/emit! (rt/nav :auth-register {} {})) - :tab-index "4"} - (tr "labels.go-back")]]]]) + [:& lk/link {:action #(st/emit! (rt/nav :auth-register {} {})) + :name (tr "labels.go-back")}]]]]) (mf/defc register-success-page [{:keys [params] :as props}] diff --git a/frontend/src/app/main/ui/components/button_link.cljs b/frontend/src/app/main/ui/components/button_link.cljs new file mode 100644 index 0000000000..66cd2c395b --- /dev/null +++ b/frontend/src/app/main/ui/components/button_link.cljs @@ -0,0 +1,21 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.components.button-link + (:require + [app.util.keyboard :as kbd] + [rumext.v2 :as mf])) + +(mf/defc button-link [{:keys [action icon name klass]}] + [:a.btn-primary.btn-large.button-link + {:class klass + :tabindex "0" + :on-click action + :on-key-down (fn [event] + (when (kbd/enter? event) + (action event)))} + [:span.logo icon] + name]) diff --git a/frontend/src/app/main/ui/components/forms.cljs b/frontend/src/app/main/ui/components/forms.cljs index 4102c36d9d..176f99d78f 100644 --- a/frontend/src/app/main/ui/components/forms.cljs +++ b/frontend/src/app/main/ui/components/forms.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.components.forms (:require [app.common.data :as d] + [app.common.data.macros :as dm] [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] [app.util.dom :as dom] @@ -42,7 +43,6 @@ touched? (get-in @form [:touched input-name]) error (get-in @form [:errors input-name]) - value (get-in @form [:data input-name] "") help-icon' (cond @@ -103,8 +103,11 @@ :on-blur on-blur :placeholder label :on-change on-change - :type @type') - (cond-> (and value is-checkbox?) (assoc :default-checked value)) + :type @type' + :tabindex "0") + (cond-> (and value is-checkbox?) (assoc :default-checked value)) + (cond-> (and touched? (:message error)) (assoc "aria-invalid" "true" + "aria-describedby" (dm/str "error-" input-name))) (obj/clj->props))] [:div @@ -126,7 +129,8 @@ help-icon']) (cond (and touched? (:message error)) - [:span.error {:data-test (clojure.string/join [data-test "-error"]) }(tr (:message error))] + [:span.error {:id (dm/str "error-" input-name) + :data-test (clojure.string/join [data-test "-error"]) }(tr (:message error))] (string? hint) [:span.hint hint])]])) @@ -220,7 +224,11 @@ {:name "submit" :class (when (or (not (:valid @form)) (true? disabled)) "btn-disabled") :disabled (or (not (:valid @form)) (true? disabled)) + :tabindex "0" :on-click on-click + :on-key-down (fn [event] + (when (kbd/enter? event) + (on-click))) :value label :data-test data-test :type "submit"}])) diff --git a/frontend/src/app/main/ui/components/link.cljs b/frontend/src/app/main/ui/components/link.cljs new file mode 100644 index 0000000000..2b9c19184d --- /dev/null +++ b/frontend/src/app/main/ui/components/link.cljs @@ -0,0 +1,20 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.main.ui.components.link + (:require + [app.util.keyboard :as kbd] + [rumext.v2 :as mf])) + +(mf/defc link [{:keys [action name klass data-test]}] + [:a {:on-click action + :klass klass + :on-key-down (fn [event] + (when (kbd/enter? event) + (action event))) + :tabindex "0" + :data-test data-test} + name]) diff --git a/frontend/src/app/main/ui/messages.cljs b/frontend/src/app/main/ui/messages.cljs index 49be805dd7..ae7e53930e 100644 --- a/frontend/src/app/main/ui/messages.cljs +++ b/frontend/src/app/main/ui/messages.cljs @@ -15,7 +15,7 @@ [rumext.v2 :as mf])) (mf/defc banner - [{:keys [type position status controls content actions on-close data-test] :as props}] + [{:keys [type position status controls content actions on-close data-test role] :as props}] [:div.banner {:class (dom/classnames :warning (= type :warning) :error (= type :error) @@ -35,7 +35,8 @@ [:div.content {:class (dom/classnames :inline-actions (= controls :inline-actions) :bottom-actions (= controls :bottom-actions)) - :data-test data-test} + :data-test data-test + :role role} content (when (or (= controls :bottom-actions) (= controls :inline-actions)) [:div.actions @@ -60,7 +61,7 @@ (mf/defc inline-banner {::mf/wrap [mf/memo]} - [{:keys [type content on-close actions data-test] :as props}] + [{:keys [type content on-close actions data-test role] :as props}] [:& banner {:type type :position :inline :status :visible @@ -72,5 +73,6 @@ :content content :on-close on-close :actions actions - :data-test data-test}]) + :data-test data-test + :role role}]) diff --git a/frontend/src/app/main/ui/settings/change_email.cljs b/frontend/src/app/main/ui/settings/change_email.cljs index 468f18dde9..3bca85224f 100644 --- a/frontend/src/app/main/ui/settings/change_email.cljs +++ b/frontend/src/app/main/ui/settings/change_email.cljs @@ -24,10 +24,10 @@ (s/def ::email-2 ::us/email) (defn- email-equality - [data] + [errors data] (let [email-1 (:email-1 data) email-2 (:email-2 data)] - (cond-> {} + (cond-> errors (and email-1 email-2 (not= email-1 email-2)) (assoc :email-2 {:message (tr "errors.email-invalid-confirmation")})))) diff --git a/frontend/src/app/main/ui/settings/password.cljs b/frontend/src/app/main/ui/settings/password.cljs index ce93a8af7d..940e4fd8fa 100644 --- a/frontend/src/app/main/ui/settings/password.cljs +++ b/frontend/src/app/main/ui/settings/password.cljs @@ -48,11 +48,11 @@ (s/def ::password-old ::us/not-empty-string) (defn- password-equality - [data] + [errors data] (let [password-1 (:password-1 data) password-2 (:password-2 data)] - (cond-> {} + (cond-> errors (and password-1 password-2 (not= password-1 password-2)) (assoc :password-2 {:message (tr "errors.password-invalid-confirmation")}) diff --git a/frontend/src/app/util/forms.cljs b/frontend/src/app/util/forms.cljs index 561eaa8032..d29d119735 100644 --- a/frontend/src/app/util/forms.cljs +++ b/frontend/src/app/util/forms.cljs @@ -21,12 +21,16 @@ (and (empty? path) (list? pred) (= (first (last pred)) 'cljs.core/contains?)) - (let [path (conj path (last (last pred)))] - (assoc-in acc path {:code ::missing :type :builtin})) + (let [field (last (last pred)) + path (conj path field) + root (first via)] + (assoc-in acc path {:code :missing :type :builtin :root root :field field})) - (and (seq path) - (seq via)) - (assoc-in acc path {:code (last via) :type :builtin}) + (and (seq path) (seq via)) + (let [field (first path) + code (last via) + root (first via)] + (assoc-in acc path {:code code :type :builtin :root root :field field})) :else acc)) @@ -64,11 +68,12 @@ problems (when (= ::s/invalid cleaned) (::s/problems (s/explain-data spec (:data state)))) - errors (merge (reduce interpret-problem {} problems) - (reduce (fn [errors vf] - (merge errors (vf (:data state)))) - {} validators) - (:errors state))] + errors (reduce interpret-problem {} problems) + errors (reduce (fn [errors vf] + (merge errors (vf errors (:data state)))) + errors + validators) + errors (merge errors (:errors state))] (assoc state :errors errors diff --git a/frontend/translations/en.po b/frontend/translations/en.po index c0c7d771f2..9f5933d538 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -767,6 +767,10 @@ msgstr "Looks like you are opening a file that has the feature '%s' enabled bug msgid "errors.email-already-exists" msgstr "Email already used" +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs, src/app/main/ui/auth/recovery_request.cljs +msgid "errors.email-invalid" +msgstr "Enter a valid email please" + #: src/app/main/ui/auth/verify_token.cljs msgid "errors.email-already-validated" msgstr "Email already validated." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 4cc50f736c..63e11b6120 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -812,6 +812,10 @@ msgstr "Este fichero ya se ha usado con los Componentes V2 activados." msgid "errors.email-already-exists" msgstr "Este correo ya está en uso" +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs, src/app/main/ui/auth/recovery_request.cljs +msgid "errors.email-invalid" +msgstr "Por favor, escribe un email válido" + #: src/app/main/ui/auth/verify_token.cljs msgid "errors.email-already-validated" msgstr "Este correo ya está validado." From 76a19a82c3002583ec2d33fa4d6084426990415b Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Fri, 9 Dec 2022 16:20:01 +0100 Subject: [PATCH 323/682] :tada: Add placeholder for empty inspect tab --- .../styles/main/partials/sidebar.scss | 36 +++++++++++++++++++ .../main/ui/viewer/handoff/right_sidebar.cljs | 14 ++++++-- frontend/translations/en.po | 10 ++++++ frontend/translations/es.po | 9 +++++ 4 files changed, 66 insertions(+), 3 deletions(-) diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 15d63f034f..1ac66ac895 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -174,6 +174,42 @@ } } + .empty { + color: $color-gray-20; + font-size: 12px; + line-height: 1.5; + text-align: center; + padding: 0 15px; + display: flex; + flex-direction: column; + gap: 20px; + margin-top: 12px; + + .tool-window-bar-icon { + height: 32px; + display: flex; + align-items: center; + justify-content: center; + margin-top: 16px; + } + + svg { + width: 32px; + height: 32px; + fill: $color-gray-30; + } + + .btn-primary { + margin-top: 10px; + background-color: $color-gray-60; + color: $color-gray-10; + &:hover { + background-color: $color-primary; + color: $color-black; + } + } + } + & > .resize-area { position: absolute; width: 8px; diff --git a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs index 629cc80304..63db1d487c 100644 --- a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs @@ -13,6 +13,7 @@ [app.main.ui.viewer.handoff.attributes :refer [attributes]] [app.main.ui.viewer.handoff.code :refer [code]] [app.main.ui.viewer.handoff.selection-feedback :refer [resolve-shapes]] + [app.util.dom :as dom] [app.util.i18n :refer [tr]] [rumext.v2 :as mf])) @@ -30,7 +31,7 @@ [:aside.settings-bar.settings-bar-right {:class (when @expanded "expanded")} [:div.settings-bar-inside - (when (seq shapes) + (if (seq shapes) [:div.tool-window [:div.tool-window-bar.big (if (> (count shapes) 1) @@ -69,8 +70,15 @@ [:& tab-element {:id :code :title (tr "handoff.tabs.code")} [:& code {:frame frame :shapes shapes - :on-expand (fn[] + :on-expand (fn [] (when (= from :workspace) (dw/set-inspect-expanded (not expanded))) (swap! expanded not)) - :from from}]]]]])]])) + :from from}]]]]] + [:div.empty + [:span.tool-window-bar-icon i/code] + [:div (tr "handoff.empty.select")] + [:span.tool-window-bar-icon i/help] + [:div (tr "handoff.empty.help")] + [:button.btn-primary.action {:on-click #(dom/open-new-window "https://help.penpot.app/user-guide/inspect/")} (tr "handoff.empty.more-info")] + ])]])) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index c0c7d771f2..f3b21bac3a 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1114,6 +1114,15 @@ msgstr "Text" msgid "handoff.tabs.info" msgstr "Info" +msgid "handoff.empty.select" +msgstr "Select a shape, board or group to inspect their properties and code" + +msgid "handoff.empty.help" +msgstr "If you want to know more about design inspect visit Penpot's help center" + +msgid "handoff.empty.more-info" +msgstr "More info about inspect" + #: src/app/main/ui/workspace/header.cljs msgid "label.shortcuts" msgstr "Shortcuts" @@ -4445,3 +4454,4 @@ msgstr "The font %s could not be loaded" msgid "errors.bad-font-plural" msgstr "The fonts %s could not be loaded" + diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 4cc50f736c..730c7d13cb 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1221,6 +1221,15 @@ msgstr "Texto" msgid "handoff.tabs.info" msgstr "Información" +msgid "handoff.empty.select" +msgstr "Elige una forma, tablero o grupo para inspeccionar sus propiedades y código" + +msgid "handoff.empty.help" +msgstr "Si quieres saber más sobre la inspección puedes visitar el centro de ayuda de Penpot" + +msgid "handoff.empty.more-info" +msgstr "Más información sobre la inspección" + msgid "history.alert-message" msgstr "Estás viendo la versión %s" From d0d63169e283e380acdab75e2f97138d2a18342c Mon Sep 17 00:00:00 2001 From: Eva Date: Mon, 12 Dec 2022 16:53:05 +0100 Subject: [PATCH 324/682] :bug: Fix home icon in login page --- frontend/resources/styles/main/layouts/login.scss | 6 ++++++ frontend/src/app/main/ui/auth.cljs | 6 ++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss index 7fdc92ed5f..e4b95c76bc 100644 --- a/frontend/resources/styles/main/layouts/login.scss +++ b/frontend/resources/styles/main/layouts/login.scss @@ -43,6 +43,12 @@ max-width: 11vw; height: 80px; } + .hidden-name { + visibility: hidden; + width: 0; + height: 0; + float: left; + } } } diff --git a/frontend/src/app/main/ui/auth.cljs b/frontend/src/app/main/ui/auth.cljs index 03d1061d3f..92bfbcd792 100644 --- a/frontend/src/app/main/ui/auth.cljs +++ b/frontend/src/app/main/ui/auth.cljs @@ -39,11 +39,13 @@ params (:query-params route)] (mf/use-effect - #(dom/set-html-title (tr "title.default"))) + #(dom/set-html-title (tr "title.default"))) [:main.auth [:section.auth-sidebar - [:span.logo {:aria-hidden "true"} i/logo] + [:a.logo {:href "#/"} + [:span {:aria-hidden true} i/logo] + [:span.hidden-name "Home"]] [:span.tagline (tr "auth.sidebar-tagline")]] [:section.auth-content From 05e437ee06299fe00d4c64b650160b1b246d0fbe Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Mon, 12 Dec 2022 16:56:25 +0100 Subject: [PATCH 325/682] :bug: Fix flex elemen info doesn't show on inspect tab --- frontend/src/app/main/refs.cljs | 11 +++++++++++ .../src/app/main/ui/viewer/handoff/attributes.cljs | 5 +++-- .../handoff/attributes/layout_flex_element.cljs | 8 ++++---- frontend/src/app/main/ui/viewer/handoff/code.cljs | 8 +++++--- .../src/app/main/ui/viewer/handoff/right_sidebar.cljs | 3 ++- 5 files changed, 25 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 6998b831e5..df141a2d3b 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -384,6 +384,17 @@ (def workspace-focus-selected (l/derived :workspace-focus-selected st/state)) +(defn workspace-get-flex-child + [ids] + (l/derived + (fn [state] + (let [objects (wsh/lookup-page-objects state)] + (into [] + (comp (map (d/getf objects)) + (filter (partial ctl/layout-child? objects))) + ids))) + st/state =)) + ;; Remove this when deprecating components-v2 (def remove-graphics (l/derived :remove-graphics st/state)) diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes.cljs index 91e803da18..610ed323f3 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes.cljs @@ -32,7 +32,7 @@ :text [:layout :text :shadow :blur :stroke :layout-flex-item]}) (mf/defc attributes - [{:keys [page-id file-id shapes frame]}] + [{:keys [page-id file-id shapes frame from]}] (let [shapes (hooks/use-equal-memo shapes) shapes (mf/with-memo [shapes] (mapv #(gsh/translate-to-frame % frame) shapes)) @@ -52,7 +52,8 @@ :text text-panel :svg svg-panel) {:shapes shapes - :frame frame}]) + :frame frame + :from from}]) [:& exports {:shapes shapes :type type diff --git a/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs index 9cdb46a02f..9b6edd9288 100644 --- a/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/attributes/layout_flex_element.cljs @@ -90,7 +90,7 @@ [:div.attributes-label "Margin"] [:& manage-margin {:margin merged-margin :type "margin"}] [:& copy-button {:data (copy-data shape :layout-item-margin)}]]) - + (when (:layout-item-h-sizing shape) [:div.attributes-unit-row [:div.attributes-label "Horizontal sizing"] @@ -102,7 +102,7 @@ [:div.attributes-label "Vertical sizing"] [:div.attributes-value (manage-sizing (:layout-item-v-sizing shape) :v)] [:& copy-button {:data (copy-data shape :layout-item-v-sizing)}]]) - + (when (= :fill (:layout-item-h-sizing shape)) [:* (when (some? (:layout-item-max-w shape)) @@ -132,10 +132,10 @@ [:& copy-button {:data (copy-data shape :layout-item-min-h)}]])])])) (mf/defc layout-flex-element-panel - [{:keys [shapes]}] + [{:keys [shapes from]}] (let [route (mf/deref refs/route) page-id (:page-id (:query-params route)) - mod-shapes (cd/get-flex-elements page-id shapes) + mod-shapes (cd/get-flex-elements page-id shapes from) shape (first mod-shapes) has-margin? (some? (:layout-item-margin shape)) has-values? (or (some? (:layout-item-max-w shape)) diff --git a/frontend/src/app/main/ui/viewer/handoff/code.cljs b/frontend/src/app/main/ui/viewer/handoff/code.cljs index 1f4a9e7f7b..02a425a4ac 100644 --- a/frontend/src/app/main/ui/viewer/handoff/code.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/code.cljs @@ -46,10 +46,12 @@ (cond-> code (= type "svg") (beautify/html #js {"indent_size" 2})))) -(defn get-flex-elements [page-id shapes] +(defn get-flex-elements [page-id shapes from] (let [ids (mapv :id shapes) ids (hooks/use-equal-memo ids) - get-layout-children-refs (mf/use-memo (mf/deps ids page-id) #(refs/get-flex-child-viewer ids page-id))] + get-layout-children-refs (mf/use-memo (mf/deps ids page-id from) #(if (= from :workspace) + (refs/workspace-get-flex-child ids) + (refs/get-flex-child-viewer ids page-id)))] (mf/deref get-layout-children-refs))) @@ -61,7 +63,7 @@ (map #(gsh/translate-to-frame % frame))) route (mf/deref refs/route) page-id (:page-id (:query-params route)) - flex-items (get-flex-elements page-id shapes) + flex-items (get-flex-elements page-id shapes from) shapes (map #(assoc % :flex-items flex-items) shapes) style-code (-> (cg/generate-style-code @style-type shapes) (format-code "css")) diff --git a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs index 63db1d487c..aa1c23986e 100644 --- a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs @@ -65,7 +65,8 @@ [:& attributes {:page-id page-id :file-id file-id :frame frame - :shapes shapes}]] + :shapes shapes + :from from}]] [:& tab-element {:id :code :title (tr "handoff.tabs.code")} [:& code {:frame frame From e1de3ba5e7f4c59d32fbd645f79648bd4f265182 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 9 Dec 2022 12:47:59 +0100 Subject: [PATCH 326/682] :recycle: Changed transform calculation --- common/deps.edn | 1 + common/src/app/common/geom/matrix.cljc | 17 +- common/src/app/common/geom/shapes.cljc | 2 +- .../app/common/geom/shapes/transforms.cljc | 227 +++++++++--------- .../test/common_tests/geom_shapes_test.cljc | 60 ++++- .../app/main/data/workspace/svg_upload.cljs | 22 +- 6 files changed, 179 insertions(+), 150 deletions(-) diff --git a/common/deps.edn b/common/deps.edn index 4b18aad90e..de4ef462ad 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -33,6 +33,7 @@ funcool/datoteka {:mvn/version "3.0.66"} com.sun.mail/jakarta.mail {:mvn/version "2.0.1"} + org.la4j/la4j {:mvn/version "0.6.0"} ;; exception printing fipp/fipp {:mvn/version "0.6.26"} diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index 0158fe7df2..f6fb4af36c 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -288,14 +288,15 @@ (defn inverse "Gets the inverse of the affinity transform `mtx`" [{:keys [a b c d e f] :as mtx}] - (let [det (determinant mtx) - a' (/ d det) - b' (/ (- b) det) - c' (/ (- c) det) - d' (/ a det) - e' (/ (- (* c f) (* d e)) det) - f' (/ (- (* b e) (* a f)) det)] - (Matrix. a' b' c' d' e' f'))) + (let [det (determinant mtx)] + (when-not (mth/almost-zero? det) + (let [a' (/ d det) + b' (/ (- b) det) + c' (/ (- c) det) + d' (/ a det) + e' (/ (- (* c f) (* d e)) det) + f' (/ (- (* b e) (* a f)) det)] + (Matrix. a' b' c' d' e' f'))))) (defn round [mtx] diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index b4aa9ef50f..0c2e9043ac 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -168,7 +168,7 @@ (dm/export gtr/transform-str) (dm/export gtr/inverse-transform-matrix) (dm/export gtr/transform-rect) -(dm/export gtr/calculate-adjust-matrix) +(dm/export gtr/calculate-geometry) (dm/export gtr/update-group-selrect) (dm/export gtr/update-mask-selrect) (dm/export gtr/update-bool-selrect) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 2e5cc59540..6028c6a097 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -5,21 +5,24 @@ ;; Copyright (c) KALEIDOS INC (ns app.common.geom.shapes.transforms + #?(:clj (:import (org.la4j Matrix LinearAlgebra)) + :cljs (:import goog.math.Matrix)) + (:require [app.common.data :as d] [app.common.data.macros :as dm] + [app.common.exceptions :as ex] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.bool :as gshb] [app.common.geom.shapes.common :as gco] [app.common.geom.shapes.path :as gpa] [app.common.geom.shapes.rect :as gpr] - [app.common.math :as mth] [app.common.pages.helpers :as cph] [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) -(def ^:dynamic *skip-adjust* false) +#?(:clj (set! *warn-on-reflection* true)) ;; --- Relative Movement @@ -76,21 +79,8 @@ dy (- (d/check-num y) (-> shape :selrect :y))] (move shape (gpt/point dx dy)))) - ; ---- Geometric operations -(defn- calculate-skew-angle - "Calculates the skew angle of the parallelogram given by the points" - [[p1 _ p3 p4]] - (let [v1 (gpt/to-vec p3 p4) - v2 (gpt/to-vec p4 p1)] - ;; If one of the vectors is zero it's a rectangle with 0 height or width - ;; We don't skew these - (if (or (gpt/almost-zero? v1) - (gpt/almost-zero? v2)) - 0 - (- 90 (gpt/angle-with-other v1 v2))))) - (defn- calculate-height "Calculates the height of a parallelogram given by the points" [[p1 _ _ p4]] @@ -104,31 +94,6 @@ (-> (gpt/to-vec p1 p2) (gpt/length))) -(defn- calculate-rotation - "Calculates the rotation between two shapes given the resize vector direction" - [center points-shape1 points-shape2 flip-x flip-y] - - (let [idx-1 0 - idx-2 (cond (and flip-x (not flip-y)) 1 - (and flip-x flip-y) 2 - (and (not flip-x) flip-y) 3 - :else 0) - p1 (nth points-shape1 idx-1) - p2 (nth points-shape2 idx-2) - v1 (gpt/to-vec center p1) - v2 (gpt/to-vec center p2) - - rot-angle (gpt/angle-with-other v1 v2) - rot-sign (gpt/angle-sign v1 v2)] - (* rot-sign rot-angle))) - -(defn- calculate-dimensions - [[p1 p2 p3 _]] - (let [width (gpt/distance p1 p2) - height (gpt/distance p2 p3)] - {:width width :height height})) - - ;; --- Transformation matrix operations (defn transform-matrix @@ -147,9 +112,12 @@ (cond-> (some? transform) (gmt/multiply transform)) - (cond-> - (and (not no-flip) flip-x) (gmt/scale (gpt/point -1 1)) - (and (not no-flip) flip-y) (gmt/scale (gpt/point 1 -1))) + (cond-> (and flip-x (not no-flip)) + (gmt/scale (gpt/point -1 1))) + + (cond-> (and flip-y (not no-flip)) + (gmt/scale (gpt/point 1 -1))) + (gmt/translate (gpt/negate shape-center))))) (defn transform-str @@ -186,74 +154,92 @@ (gco/transform-points matrix))] (gpr/points->rect points))) -(defn calculate-adjust-matrix - "Calculates a matrix that is a series of transformations we have to do to the transformed rectangle so that - after applying them the end result is the `shape-path-temp`. - This is compose of three transformations: skew, resize and rotation" - [points-temp points-rec flip-x flip-y] - (let [center (gco/center-bounds points-temp) +(defn transform-points-matrix + "Calculate the transform matrix to convert from the selrect to the points bounds + TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM)" + [{:keys [x1 y1 x2 y2]} [d1 d2 _ d4]] + #?(:clj + ;; NOTE: the source matrix may not be invertible we can't + ;; calculate the transform, so on exception we return `nil` + (ex/ignoring + (let [target-points-matrix + (->> (list (:x d1) (:x d2) (:x d4) + (:y d1) (:y d2) (:y d4) + 1 1 1 ) + (into-array Double/TYPE) + (Matrix/from1DArray 3 3)) - stretch-matrix (gmt/matrix) + source-points-matrix + (->> (list x1 x2 x1 + y1 y1 y2 + 1 1 1) + (into-array Double/TYPE) + (Matrix/from1DArray 3 3)) - skew-angle (calculate-skew-angle points-temp) + ;; May throw an exception if the matrix is not invertible + source-points-matrix-inv + (.. source-points-matrix + (withInverter LinearAlgebra/GAUSS_JORDAN) + (inverse)) - ;; When one of the axis is flipped we have to reverse the skew - ;; skew-angle (if (neg? (* (:x resize-vector) (:y resize-vector))) (- skew-angle) skew-angle ) - skew-angle (if (and (or flip-x flip-y) - (not (and flip-x flip-y))) (- skew-angle) skew-angle ) - skew-angle (if (mth/nan? skew-angle) 0 skew-angle) + transform-jvm + (.. target-points-matrix + (multiply source-points-matrix-inv))] - stretch-matrix (gmt/multiply stretch-matrix (gmt/skew-matrix skew-angle 0)) + (gmt/matrix (.get transform-jvm 0 0) + (.get transform-jvm 1 0) + (.get transform-jvm 0 1) + (.get transform-jvm 1 1) + (.get transform-jvm 0 2) + (.get transform-jvm 1 2)))) - h1 (max 1 (calculate-height points-temp)) - h2 (max 1 (calculate-height (gco/transform-points points-rec center stretch-matrix))) - h3 (if-not (mth/almost-zero? h2) (/ h1 h2) 1) - h3 (if (mth/nan? h3) 1 h3) + :cljs + (let [target-points-matrix + (Matrix. #js [#js [(:x d1) (:x d2) (:x d4)] + #js [(:y d1) (:y d2) (:y d4)] + #js [ 1 1 1]]) - w1 (max 1 (calculate-width points-temp)) - w2 (max 1 (calculate-width (gco/transform-points points-rec center stretch-matrix))) - w3 (if-not (mth/almost-zero? w2) (/ w1 w2) 1) - w3 (if (mth/nan? w3) 1 w3) + source-points-matrix + (Matrix. #js [#js [x1 x2 x1] + #js [y1 y1 y2] + #js [ 1 1 1]]) - stretch-matrix (gmt/multiply stretch-matrix (gmt/scale-matrix (gpt/point w3 h3))) + ;; returns nil if not invertible + source-points-matrix-inv (.getInverse source-points-matrix) - rotation-angle (calculate-rotation - center - (gco/transform-points points-rec (gco/center-points points-rec) stretch-matrix) - points-temp - flip-x - flip-y) + ;; TargetM = SourceM * Transform ==> Transform = TargetM * inv(SourceM) + transform-js + (when source-points-matrix-inv + (.multiply target-points-matrix source-points-matrix-inv))] - stretch-matrix (gmt/multiply (gmt/rotate-matrix rotation-angle) stretch-matrix) + (when transform-js + (gmt/matrix (.getValueAt transform-js 0 0) + (.getValueAt transform-js 1 0) + (.getValueAt transform-js 0 1) + (.getValueAt transform-js 1 1) + (.getValueAt transform-js 0 2) + (.getValueAt transform-js 1 2)))))) - ;; This is the inverse to be able to remove the transformation - stretch-matrix-inverse - (gmt/multiply (gmt/scale-matrix (gpt/point (/ 1 w3) (/ 1 h3))) - (gmt/skew-matrix (- skew-angle) 0) - (gmt/rotate-matrix (- rotation-angle)))] - [stretch-matrix stretch-matrix-inverse rotation-angle])) +(defn calculate-geometry + [points] + (let [width (calculate-width points) + height (calculate-height points) + center (gco/center-points points) + sr (gpr/center->selrect center width height) -(defn- adjust-rotated-transform - [{:keys [transform transform-inverse flip-x flip-y]} points] - (let [center (gco/center-bounds points) + points-transform-mtx (transform-points-matrix sr points) - points-temp (cond-> points - (some? transform-inverse) - (gco/transform-points center transform-inverse)) - points-temp-dim (calculate-dimensions points-temp) + ;; Calculate the transform by move the transformation to the center + transform + (when points-transform-mtx + (gmt/multiply + (gmt/translate-matrix (gpt/negate center)) + points-transform-mtx + (gmt/translate-matrix center))) - ;; This rectangle is the new data for the current rectangle. We want to change our rectangle - ;; to have this width, height, x, y - new-width (max 0.01 (:width points-temp-dim)) - new-height (max 0.01 (:height points-temp-dim)) - selrect (gpr/center->selrect center new-width new-height) + transform-inverse (when transform (gmt/inverse transform))] - rect-points (gpr/rect->points selrect) - [matrix matrix-inverse] (calculate-adjust-matrix points-temp rect-points flip-x flip-y)] - - [selrect - (if transform (gmt/multiply transform matrix) matrix) - (if transform-inverse (gmt/multiply matrix-inverse transform-inverse) matrix-inverse)])) + [sr transform transform-inverse])) (defn- adjust-shape-flips "After some tranformations the flip-x/flip-y flags can change we need @@ -315,33 +301,36 @@ bool? (= (:type shape) :bool) path? (= (:type shape) :path) - [selrect transform transform-inverse] - (adjust-rotated-transform shape points) + [selrect transform transform-inverse] (calculate-geometry points) base-rotation (or (:rotation shape) 0) modif-rotation (or (get-in shape [:modifiers :rotation]) 0) rotation (mod (+ base-rotation modif-rotation) 360)] - (-> shape - (cond-> bool? - (update :bool-content gpa/transform-content transform-mtx)) - (cond-> path? - (update :content gpa/transform-content transform-mtx)) - (cond-> (not path?) - (assoc :x (:x selrect) - :y (:y selrect) - :width (:width selrect) - :height (:height selrect))) - (cond-> transform - (-> (assoc :transform transform) - (assoc :transform-inverse transform-inverse))) - (cond-> (not transform) - (dissoc :transform :transform-inverse)) - (cond-> (some? selrect) - (assoc :selrect selrect)) - (cond-> (d/not-empty? points) - (assoc :points points)) - (assoc :rotation rotation)))) + (if-not (and transform transform-inverse) + ;; When we cannot calculate the transformation we leave the shape as it was + shape + (-> shape + (cond-> bool? + (update :bool-content gpa/transform-content transform-mtx)) + (cond-> path? + (update :content gpa/transform-content transform-mtx)) + (cond-> (not path?) + (assoc :x (:x selrect) + :y (:y selrect) + :width (:width selrect) + :height (:height selrect))) + (cond-> transform + (-> (assoc :transform transform) + (assoc :transform-inverse transform-inverse))) + (cond-> (not transform) + (dissoc :transform :transform-inverse)) + (cond-> (some? selrect) + (assoc :selrect selrect)) + + (cond-> (d/not-empty? points) + (assoc :points points)) + (assoc :rotation rotation))))) (defn- apply-transform "Given a new set of points transformed, set up the rectangle so it keeps diff --git a/common/test/common_tests/geom_shapes_test.cljc b/common/test/common_tests/geom_shapes_test.cljc index 07f971e6f4..0b9dac36fd 100644 --- a/common/test/common_tests/geom_shapes_test.cljc +++ b/common/test/common_tests/geom_shapes_test.cljc @@ -9,6 +9,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.geom.shapes.transforms :as gsht] [app.common.math :as mth :refer [close?]] [app.common.types.modifiers :as ctm] [app.common.types.shape :as cts] @@ -128,13 +129,10 @@ (let [modifiers (ctm/resize-modifiers (gpt/point 0 0) (gpt/point 0 0)) shape-before (create-test-shape type {:modifiers modifiers}) shape-after (gsh/transform-shape shape-before)] - (t/is (> (get-in shape-before [:selrect :width]) - (get-in shape-after [:selrect :width]))) - (t/is (> (get-in shape-after [:selrect :width]) 0)) - - (t/is (> (get-in shape-before [:selrect :height]) - (get-in shape-after [:selrect :height]))) - (t/is (> (get-in shape-after [:selrect :height]) 0))) + (t/is (close? (get-in shape-before [:selrect :width]) + (get-in shape-after [:selrect :width]))) + (t/is (close? (get-in shape-before [:selrect :height]) + (get-in shape-after [:selrect :height])))) :rect :path)) (t/testing "Transform shape with rotation modifiers" @@ -195,6 +193,50 @@ (t/is (= (:x expect) (:x result))) (t/is (= (:y expect) (:y result))) (t/is (= (:width expect) (:width result))) - (t/is (= (:height expect) (:height result))) - )) + (t/is (= (:height expect) (:height result))))) +(def g45 (mth/radians 45)) + +(t/deftest points-transform-matrix + (t/testing "Transform matrix" + (t/are [selrect points expected] + (let [result (gsht/transform-points-matrix selrect points)] + (t/is (gmt/close? expected result))) + + ;; No transformation + (gsh/make-selrect 0 0 10 10) + (-> (gsh/make-selrect 0 0 10 10) + (gsh/rect->points)) + (gmt/matrix) + + ;; Displacement + (gsh/make-selrect 0 0 10 10) + (-> (gsh/make-selrect 20 20 10 10) + (gsh/rect->points )) + (gmt/matrix 1 0 0 1 20 20) + + ;; Resize + (gsh/make-selrect 0 0 10 10) + (-> (gsh/make-selrect 0 0 20 40) + (gsh/rect->points)) + (gmt/matrix 2 0 0 4 0 0) + + ;; Displacement+Resize + (gsh/make-selrect 0 0 10 10) + (-> (gsh/make-selrect 10 10 20 40) + (gsh/rect->points)) + (gmt/matrix 2 0 0 4 10 10) + + ;; Rotation + (gsh/make-selrect 0 0 10 10) + (-> (gsh/make-selrect 0 0 10 10) + (gsh/rect->points) + (gsh/transform-points (gmt/rotate-matrix 45))) + (gmt/matrix (mth/cos g45) (mth/sin g45) (- (mth/sin g45)) (mth/cos g45) 0 0) + + ;; Rotation + Resize + (gsh/make-selrect 0 0 10 10) + (-> (gsh/make-selrect 0 0 20 40) + (gsh/rect->points) + (gsh/transform-points (gmt/rotate-matrix 45))) + (gmt/matrix (* (mth/cos g45) 2) (* (mth/sin g45) 2) (* (- (mth/sin g45)) 4) (* (mth/cos g45) 4) 0 0)))) diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index 65dda9071f..b0b483f5d6 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -249,20 +249,16 @@ (let [points (-> (gsh/rect->points rect-data) (gsh/transform-points transform)) - center (gsh/center-points points) - rect-shape (gsh/center->rect center (:width rect-data) (:height rect-data)) - selrect (gsh/rect->selrect rect-shape) - rect-points (gsh/rect->points rect-shape) + [selrect transform transform-inverse] (gsh/calculate-geometry points)] - [shape-transform shape-transform-inv rotation] - (gsh/calculate-adjust-matrix points rect-points (neg? (:a transform)) (neg? (:d transform)))] - - (merge rect-shape - {:selrect selrect - :points points - :rotation rotation - :transform shape-transform - :transform-inverse shape-transform-inv}))) + {:x (:x selrect) + :y (:y selrect) + :width (:width selrect) + :height (:height selrect) + :selrect selrect + :points points + :transform transform + :transform-inverse transform-inverse})) (defn create-rect-shape [name frame-id svg-data {:keys [attrs] :as data}] From 90bc9943bcbf08dc8c07c67f5b0d92203686cf00 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 13 Dec 2022 12:46:53 +0100 Subject: [PATCH 327/682] :bug: Fix expand right sidebar on workspace inspect --- frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs index 63db1d487c..55529d0624 100644 --- a/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs +++ b/frontend/src/app/main/ui/viewer/handoff/right_sidebar.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.viewer.handoff.right-sidebar (:require [app.main.data.workspace :as dw] + [app.main.store :as st] [app.main.ui.components.shape-icon :as si] [app.main.ui.components.tab-container :refer [tab-container tab-element]] [app.main.ui.icons :as i] @@ -72,7 +73,7 @@ :shapes shapes :on-expand (fn [] (when (= from :workspace) - (dw/set-inspect-expanded (not expanded))) + (st/emit! (dw/set-inspect-expanded (not @expanded)))) (swap! expanded not)) :from from}]]]]] [:div.empty @@ -80,5 +81,4 @@ [:div (tr "handoff.empty.select")] [:span.tool-window-bar-icon i/help] [:div (tr "handoff.empty.help")] - [:button.btn-primary.action {:on-click #(dom/open-new-window "https://help.penpot.app/user-guide/inspect/")} (tr "handoff.empty.more-info")] - ])]])) + [:button.btn-primary.action {:on-click #(dom/open-new-window "https://help.penpot.app/user-guide/inspect/")} (tr "handoff.empty.more-info")]])]])) From cd6aa8f69118b009eb46fff42950c8425a29f0de Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 13 Dec 2022 12:57:32 +0100 Subject: [PATCH 328/682] :bug: Fix can't select a board in inspect mode --- frontend/src/app/main/ui/workspace/viewport.cljs | 2 +- frontend/src/app/main/ui/workspace/viewport/actions.cljs | 6 +++--- frontend/src/app/main/ui/workspace/viewport/widgets.cljs | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 5d55889f27..e144ed7312 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -154,7 +154,7 @@ on-frame-enter (actions/on-frame-enter frame-hover) on-frame-leave (actions/on-frame-leave frame-hover) - on-frame-select (actions/on-frame-select selected) + on-frame-select (actions/on-frame-select selected workspace-read-only?) disable-events? (contains? layout :comments) show-comments? (= drawing-tool :comments) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 4316ee8559..70a66fd226 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -125,16 +125,16 @@ (st/emit! (dw/start-move-selected)))))))) (defn on-frame-select - [selected] + [selected workspace-read-only?] (mf/use-callback - (mf/deps selected) + (mf/deps selected workspace-read-only?) (fn [event id] (let [shift? (kbd/shift? event) selected? (contains? selected id) selected-drawtool (deref refs/selected-drawing-tool)] (st/emit! (when (or shift? (not selected?)) (dw/select-shape id shift?)) - (when (and (nil? selected-drawtool) (not shift?)) + (when (and (nil? selected-drawtool) (not shift?) (not workspace-read-only?)) (dw/start-move-selected))))))) (defn on-frame-enter diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 8e6fae8f13..e57d682561 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -97,8 +97,7 @@ (when (= 1 (.-which event)) (dom/prevent-default event) (dom/stop-propagation event) - (when-not workspace-read-only? - (on-frame-select event (:id frame))))))) + (on-frame-select event (:id frame)))))) on-double-click (mf/use-callback From 7ca74c04677257b0ca2bec826f270364dc24768a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 12:58:57 +0100 Subject: [PATCH 329/682] :paperclip: Fix unexpected linter issue --- common/dev/user.clj | 2 +- common/src/app/common/geom/shapes/transforms.cljc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/common/dev/user.clj b/common/dev/user.clj index fc379cc343..3f56de5f4e 100644 --- a/common/dev/user.clj +++ b/common/dev/user.clj @@ -38,7 +38,7 @@ ;; --- Development Stuff (defn- run-tests - ([] (run-tests #"^common-tests.test-.*$")) + ([] (run-tests #"^common-tests.*-test$")) ([o] (repl/refresh) (cond diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 6028c6a097..582c59167c 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -22,7 +22,8 @@ [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) -#?(:clj (set! *warn-on-reflection* true)) +#?(:clj (set! *warn-on-reflection* true) + :cljs (ex/ignoring nil)) ;; --- Relative Movement From fb0cf6fcbc93181f7eb0087ba4b1549da4c0f372 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 13:14:44 +0100 Subject: [PATCH 330/682] :paperclip: Revert some hacky code from previous commit --- common/src/app/common/geom/shapes/transforms.cljc | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 582c59167c..4383d92bb5 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -7,11 +7,10 @@ (ns app.common.geom.shapes.transforms #?(:clj (:import (org.la4j Matrix LinearAlgebra)) :cljs (:import goog.math.Matrix)) - (:require + #?(:clj [app.common.exceptions :as ex]) [app.common.data :as d] [app.common.data.macros :as dm] - [app.common.exceptions :as ex] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes.bool :as gshb] @@ -22,8 +21,7 @@ [app.common.types.modifiers :as ctm] [app.common.uuid :as uuid])) -#?(:clj (set! *warn-on-reflection* true) - :cljs (ex/ignoring nil)) +#?(:clj (set! *warn-on-reflection* true)) ;; --- Relative Movement From 7f589b09cafcad8bbc26af5bb6b46131495f596e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 5 Dec 2022 17:16:16 +0100 Subject: [PATCH 331/682] :recycle: Move audit http handler to RPC --- backend/resources/climit.edn | 4 +- backend/src/app/http.clj | 4 - backend/src/app/loggers/audit.clj | 111 +----------------- backend/src/app/main.clj | 6 - backend/src/app/rpc.clj | 7 +- backend/src/app/rpc/commands/audit.clj | 86 ++++++++++++++ backend/test/backend_tests/rpc_audit_test.clj | 92 +++++++++++++++ frontend/src/app/main/data/events.cljs | 13 +- 8 files changed, 199 insertions(+), 124 deletions(-) create mode 100644 backend/src/app/rpc/commands/audit.clj create mode 100644 backend/test/backend_tests/rpc_audit_test.clj diff --git a/backend/resources/climit.edn b/backend/resources/climit.edn index 697d165392..755568713d 100644 --- a/backend/resources/climit.edn +++ b/backend/resources/climit.edn @@ -4,4 +4,6 @@ {:update-file {:concurrency 1 :queue-size 3} :auth {:concurrency 128} :process-font {:concurrency 4 :queue-size 32} - :process-image {:concurrency 8 :queue-size 32}} + :process-image {:concurrency 8 :queue-size 32} + :push-audit-events + {:concurrency 1 :queue-size 3}} diff --git a/backend/src/app/http.clj b/backend/src/app/http.clj index 58df5e81c6..976f42d6ef 100644 --- a/backend/src/app/http.clj +++ b/backend/src/app/http.clj @@ -116,7 +116,6 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (s/def ::assets map?) -(s/def ::audit-handler fn?) (s/def ::awsns-handler fn?) (s/def ::debug-routes (s/nilable vector?)) (s/def ::doc-routes (s/nilable vector?)) @@ -138,7 +137,6 @@ ::awsns-handler ::debug-routes ::oidc-routes - ::audit-handler ::rpc-routes ::doc-routes])) @@ -173,8 +171,6 @@ ["/api" {:middleware [[mw/cors] [session/middleware-2 session]]} - ["/audit/events" {:handler (:audit-handler cfg) - :allowed-methods #{:post}}] ["/feedback" {:handler feedback :allowed-methods #{:post}}] (:doc-routes cfg) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index fa4f677217..c8ef0d7963 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -28,9 +28,7 @@ [lambdaisland.uri :as u] [promesa.core :as p] [promesa.exec :as px] - [promesa.exec.bulkhead :as pxb] - [yetti.request :as yrq] - [yetti.response :as yrs])) + [yetti.request :as yrq])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; HELPERS @@ -56,7 +54,6 @@ (assoc (->> sk str/kebab (keyword "penpot")) v))))] (reduce-kv process-param {} params))) - (def ^:private profile-props [:id @@ -105,111 +102,12 @@ (s/def ::name ::us/string) (s/def ::type ::us/string) (s/def ::props (s/map-of ::us/keyword any?)) -(s/def ::timestamp dt/instant?) -(s/def ::context (s/map-of ::us/keyword any?)) - -(s/def ::frontend-event - (s/keys :req-un [::type ::name ::props ::timestamp ::profile-id] - :opt-un [::context])) - -(s/def ::frontend-events (s/every ::frontend-event)) - (s/def ::ip-addr ::us/string) -(s/def ::backend-event + +(s/def ::event (s/keys :req-un [::type ::name ::profile-id] :opt-un [::ip-addr ::props])) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; HTTP HANDLER -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -(s/def ::concurrency ::us/integer) - -(defmethod ig/pre-init-spec ::http-handler [_] - (s/keys :req [::wrk/executor ::db/pool ::mtx/metrics ::concurrency])) - -(defmethod ig/prep-key ::http-handler - [_ cfg] - (merge {::concurrency (cf/get :audit-log-http-handler-concurrency 8)} - (d/without-nils cfg))) - -(defmethod ig/init-key ::http-handler - [_ {:keys [::wrk/executor ::db/pool ::mtx/metrics ::concurrency] :as cfg}] - (if (or (db/read-only? pool) - (not (contains? cf/flags :audit-log))) - (do - (l/warn :hint "audit: http handler disabled or db is read-only") - (fn [_ respond _] - (respond (yrs/response 204)))) - - (letfn [(event->row [event] - [(uuid/next) - (:name event) - (:source event) - (:type event) - (:timestamp event) - (:profile-id event) - (db/inet (:ip-addr event)) - (db/tjson (:props event)) - (db/tjson (d/without-nils (:context event)))]) - - (handle-request [{:keys [profile-id] :as request}] - (let [events (->> (:events (:params request)) - (remove #(not= profile-id (:profile-id %))) - (us/conform ::frontend-events)) - ip-addr (parse-client-ip request) - xform (comp - (map #(assoc % :ip-addr ip-addr)) - (map #(assoc % :source "frontend")) - (map event->row)) - - columns [:id :name :source :type :tracked-at - :profile-id :ip-addr :props :context]] - (when (seq events) - (->> (into [] xform events) - (db/insert-multi! pool :audit-log columns))))) - - (report-error! [cause] - (if-let [xdata (us/validation-error? cause)] - (l/error ::l/raw (str "audit: validation error frontend events request\n" (ex/explain xdata))) - (l/error :hint "audit: unexpected error on processing frontend events" :cause cause))) - - (on-queue [instance] - (l/trace :hint "http-handler: enqueued" - :queue-size (get instance ::pxb/current-queue-size) - :concurrency (get instance ::pxb/current-concurrency)) - (mtx/run! metrics - :id :audit-http-handler-queue-size - :val (get instance ::pxb/current-queue-size)) - (mtx/run! metrics - :id :audit-http-handler-concurrency - :val (get instance ::pxb/current-concurrency))) - - (on-run [instance task] - (let [elapsed (- (inst-ms (dt/now)) - (inst-ms task))] - (l/trace :hint "http-handler: execute" - :elapsed (str elapsed "ms")) - (mtx/run! metrics - :id :audit-http-handler-timing - :val elapsed) - (mtx/run! metrics - :id :audit-http-handler-queue-size - :val (get instance ::pxb/current-queue-size)) - (mtx/run! metrics - :id :audit-http-handler-concurrency - :val (get instance ::pxb/current-concurrency))))] - - (let [limiter (pxb/create :executor executor - :concurrency concurrency - :on-queue on-queue - :on-run on-run)] - (fn [request respond _] - (->> (px/submit! limiter (partial handle-request request)) - (p/fnly (fn [_ cause] - (some-> cause report-error!) - (respond (yrs/response 204)))))))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; COLLECTOR ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -239,7 +137,7 @@ (defn- persist-event! [pool event] - (us/verify! ::backend-event event) + (us/verify! ::event event) (db/insert! pool :audit-log {:id (uuid/next) :name (:name event) @@ -335,7 +233,6 @@ {:iss "authentication" :iat (dt/now) :uid uuid/zero}) - ;; FIXME tokens/generate body (t/encode {:events events}) headers {"content-type" "application/transit+json" "origin" (cf/get :public-uri) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 25956efbb5..22d9de0475 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -281,7 +281,6 @@ :metrics (ig/ref ::mtx/metrics) :public-uri (cf/get :public-uri) :storage (ig/ref ::sto/storage) - :audit-handler (ig/ref ::audit/http-handler) :rpc-routes (ig/ref :app.rpc/routes) :doc-routes (ig/ref :app.rpc.doc/routes) :executor (ig/ref ::wrk/executor)} @@ -408,11 +407,6 @@ ::lzmq/receiver {} - ::audit/http-handler - {::db/pool (ig/ref ::db/pool) - ::wrk/executor (ig/ref ::wrk/executor) - ::mtx/metrics (ig/ref ::mtx/metrics)} - ::audit/collector {::db/pool (ig/ref ::db/pool) ::wrk/executor (ig/ref ::wrk/executor) diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 7494bc4c23..51f48ba283 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -167,6 +167,7 @@ :profile-id profile-id :ip-addr (some-> request audit/parse-client-ip) :props (d/without-qualified props)}] + (audit/submit! collector event))) (handle-request [cfg params] @@ -174,8 +175,9 @@ (p/mcat (fn [result] (->> (handle-audit params result) (p/map (constantly result)))))))] - - (with-meta handle-request mdata)) + (if-not (::audit/skip mdata) + (with-meta handle-request mdata) + f)) f)) (defn- wrap @@ -254,6 +256,7 @@ 'app.rpc.commands.ldap 'app.rpc.commands.demo 'app.rpc.commands.webhooks + 'app.rpc.commands.audit 'app.rpc.commands.files 'app.rpc.commands.files.update 'app.rpc.commands.files.create diff --git a/backend/src/app/rpc/commands/audit.clj b/backend/src/app/rpc/commands/audit.clj new file mode 100644 index 0000000000..df692d7f4d --- /dev/null +++ b/backend/src/app/rpc/commands/audit.clj @@ -0,0 +1,86 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.commands.audit + "Audit Log related RPC methods" + (:require + [app.common.data :as d] + [app.common.logging :as l] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cf] + [app.db :as db] + [app.http :as-alias http] + [app.loggers.audit :as audit] + [app.rpc.climit :as-alias climit] + [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] + [app.util.services :as sv] + [app.util.time :as dt] + [app.worker :as wrk] + [clojure.spec.alpha :as s] + [promesa.core :as p] + [promesa.exec :as px])) + +(defn- event->row [event] + [(uuid/next) + (:name event) + (:source event) + (:type event) + (:timestamp event) + (:profile-id event) + (db/inet (:ip-addr event)) + (db/tjson (:props event)) + (db/tjson (d/without-nils (:context event)))]) + +(def ^:private event-columns + [:id :name :source :type :tracked-at + :profile-id :ip-addr :props :context]) + +(defn- handle-events + [{:keys [::db/pool]} {:keys [profile-id events ::http/request] :as params}] + (let [ip-addr (audit/parse-client-ip request) + xform (comp + (map #(assoc % :profile-id profile-id)) + (map #(assoc % :ip-addr ip-addr)) + (map #(assoc % :source "frontend")) + (filter :profile-id) + (map event->row)) + events (sequence xform events)] + (when (seq events) + (db/insert-multi! pool :audit-log event-columns events)))) + +(s/def ::profile-id ::us/uuid) +(s/def ::name ::us/string) +(s/def ::type ::us/string) +(s/def ::props (s/map-of ::us/keyword any?)) +(s/def ::timestamp dt/instant?) +(s/def ::context (s/map-of ::us/keyword any?)) + +(s/def ::event + (s/keys :req-un [::type ::name ::props ::timestamp] + :opt-un [::context])) + +(s/def ::events (s/every ::event)) + +(s/def ::push-audit-events + (s/keys :req-un [::events ::profile-id])) + +(sv/defmethod ::push-audit-events + {::climit/queue :push-audit-events + ::climit/key-fn :profile-id + ::audit/skip true + ::doc/added "1.17"} + [{:keys [::db/pool ::wrk/executor] :as cfg} params] + (if (or (db/read-only? pool) + (not (contains? cf/flags :audit-log))) + (do + (l/warn :hint "audit: http handler disabled or db is read-only") + (rph/wrap nil)) + + (->> (px/submit! executor #(handle-events cfg params)) + (p/fmap (constantly nil))))) + diff --git a/backend/test/backend_tests/rpc_audit_test.clj b/backend/test/backend_tests/rpc_audit_test.clj new file mode 100644 index 0000000000..9df795192a --- /dev/null +++ b/backend/test/backend_tests/rpc_audit_test.clj @@ -0,0 +1,92 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns backend-tests.rpc-audit-test + (:require + [app.common.pprint :as pp] + [app.common.uuid :as uuid] + [app.db :as db] + [app.util.time :as dt] + [backend-tests.helpers :as th] + [clojure.test :as t])) + +(t/use-fixtures :once th/state-init) +(t/use-fixtures :each th/database-reset) + +(defn decode-row + [{:keys [props context] :as row}] + (cond-> row + (db/pgobject? props) (assoc :props (db/decode-transit-pgobject props)) + (db/pgobject? context) (assoc :context (db/decode-transit-pgobject context)))) + +(def http-request + (reify + yetti.request/Request + (get-header [_ name] + (case name + "x-forwarded-for" "127.0.0.44")))) + +(t/deftest push-events-1 + (with-redefs [app.config/flags #{:audit-log}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + proj-id (:default-project-id prof) + + params {::th/type :push-audit-events + :app.http/request http-request + :profile-id (:id prof) + :events [{:name "navigate" + :props {:project-id proj-id + :team-id team-id + :route "dashboard-files"} + :context {:engine "blink"} + :profile-id (:id prof) + :timestamp (dt/now) + :type "action"}]} + out (th/command! params)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (t/is (nil? (:result out))) + + (let [[row :as rows] (->> (th/db-exec! ["select * from audit_log"]) + (mapv decode-row))] + ;; (pp/pprint rows) + (t/is (= 1 (count rows))) + (t/is (= (:id prof) (:profile-id row))) + (t/is (= "navigate" (:name row))) + (t/is (= "frontend" (:source row))))))) + +(t/deftest push-events-2 + (with-redefs [app.config/flags #{:audit-log}] + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + proj-id (:default-project-id prof) + + params {::th/type :push-audit-events + :app.http/request http-request + :profile-id (:id prof) + :events [{:name "navigate" + :props {:project-id proj-id + :team-id team-id + :route "dashboard-files"} + :context {:engine "blink"} + :profile-id uuid/zero + :timestamp (dt/now) + :type "action"}]} + out (th/command! params)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (t/is (nil? (:result out))) + + (let [[row :as rows] (->> (th/db-exec! ["select * from audit_log"]) + (mapv decode-row))] + ;; (pp/pprint rows) + (t/is (= 1 (count rows))) + (t/is (= (:id prof) (:profile-id row))) + (t/is (= "navigate" (:name row))) + (t/is (= "frontend" (:source row))))))) + + diff --git a/frontend/src/app/main/data/events.cljs b/frontend/src/app/main/data/events.cljs index 0f82818661..fea0f0a5f2 100644 --- a/frontend/src/app/main/data/events.cljs +++ b/frontend/src/app/main/data/events.cljs @@ -38,7 +38,7 @@ (defn- collect-context [] (let [uagent (UAParser.)] - (d/merge + (merge {:app-version (:full @cf/version) :locale @i18n/locale} (let [browser (.getBrowser uagent)] @@ -215,12 +215,17 @@ (defn- persist-events [events] (if (seq events) - (let [uri (u/join @cf/public-uri "api/audit/events") + (let [uri (u/join @cf/public-uri "api/rpc/command/push-audit-events") params {:uri uri :method :post + :credentials "include" :body (http/transit-data {:events events})}] (->> (http/send! params) - (rx/mapcat rp/handle-response))) + (rx/mapcat rp/handle-response) + (rx/catch (fn [cause] + (l/error :hint "unexpected error on persisting audit events") + (rx/of nil))))) + (rx/of nil))) (defn initialize @@ -274,7 +279,7 @@ (rx/map (fn [event] (let [session* (or @session (dt/now)) context (-> @context - (d/merge (:context event)) + (merge (:context event)) (assoc :session session*))] (reset! session session*) (-> event From c0a4b7dc76ae52d03ba05aaeafee50d6578f7d69 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 5 Dec 2022 23:01:53 +0100 Subject: [PATCH 332/682] :sparkles: Improve worker queue management and add specific worker instance for webhooks --- backend/src/app/config.clj | 7 +- backend/src/app/main.clj | 18 ++-- backend/src/app/worker.clj | 165 ++++++++++++++++++++----------------- 3 files changed, 106 insertions(+), 84 deletions(-) diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 32b01ca862..95a221b3aa 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -108,7 +108,9 @@ (s/def ::default-executor-parallelism ::us/integer) (s/def ::scheduled-executor-parallelism ::us/integer) -(s/def ::worker-parallelism ::us/integer) + +(s/def ::worker-default-parallelism ::us/integer) +(s/def ::worker-webhook-parallelism ::us/integer) (s/def ::authenticated-cookie-domain ::us/string) (s/def ::authenticated-cookie-name ::us/string) @@ -222,7 +224,8 @@ ::error-report-webhook ::default-executor-parallelism ::scheduled-executor-parallelism - ::worker-parallelism + ::worker-default-parallelism + ::worker-webhook-parallelism ::file-change-snapshot-every ::file-change-snapshot-timeout ::user-feedback-destination diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 22d9de0475..1c36c9d038 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -494,20 +494,28 @@ {:cron #app/cron "30 */5 * * * ?" ;; every 5m :task :audit-log-gc})]} - ::wrk/scheduler + ::wrk/dispatcher {::rds/redis (ig/ref ::rds/redis) ::mtx/metrics (ig/ref ::mtx/metrics) ::db/pool (ig/ref ::db/pool)} - ::wrk/worker - {::wrk/parallelism (cf/get ::worker-parallelism 1) - ;; FIXME: read queues from configuration - ::wrk/queue "default" + [::default ::wrk/worker] + {::wrk/parallelism (cf/get ::worker-default-parallelism 1) + ::wrk/queue :default + ::rds/redis (ig/ref ::rds/redis) + ::wrk/registry (ig/ref ::wrk/registry) + ::mtx/metrics (ig/ref ::mtx/metrics) + ::db/pool (ig/ref ::db/pool)} + + [::webhook ::wrk/worker] + {::wrk/parallelism (cf/get ::worker-webhook-parallelism 1) + ::wrk/queue :webhooks ::rds/redis (ig/ref ::rds/redis) ::wrk/registry (ig/ref ::wrk/registry) ::mtx/metrics (ig/ref ::mtx/metrics) ::db/pool (ig/ref ::db/pool)}}) + (def system nil) (defn start diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index 607ba1809f..b4305a3b91 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -14,6 +14,7 @@ [app.common.spec :as us] [app.common.transit :as t] [app.common.uuid :as uuid] + [app.config :as cf] [app.db :as db] [app.metrics :as mtx] [app.redis :as rds] @@ -174,63 +175,62 @@ (db/pgobject? props) (assoc :props (db/decode-transit-pgobject props)))) -(s/def ::queue ::us/string) (s/def ::wait-duration ::dt/duration) -(defmethod ig/pre-init-spec ::scheduler [_] +(defmethod ig/pre-init-spec ::dispatcher [_] (s/keys :req [::mtx/metrics ::db/pool ::rds/redis] :opt [::wait-duration ::batch-size])) -(defmethod ig/prep-key ::scheduler +(defmethod ig/prep-key ::dispatcher [_ cfg] - (merge {::batch-size 1 - ::wait-duration (dt/duration "2s")} + (merge {::batch-size 100 + ::wait-duration (dt/duration "5s")} (d/without-nils cfg))) (def ^:private sql:select-next-tasks - "select * from task as t + "select id, queue from task as t where t.scheduled_at <= now() and (t.status = 'new' or t.status = 'retry') + and queue ~~* ?::text order by t.priority desc, t.scheduled_at limit ? for update skip locked") -(defn- format-queue - [queue] - (str/ffmt "penpot-tasks-queue:%" queue)) - -(defmethod ig/init-key ::scheduler +(defmethod ig/init-key ::dispatcher [_ {:keys [::db/pool ::rds/redis ::batch-size] :as cfg}] - (letfn [(get-tasks-batch [conn] - (->> (db/exec! conn [sql:select-next-tasks batch-size]) - (map decode-task-row) - (seq))) + (letfn [(get-tasks [conn] + (let [prefix (str (cf/get :tenant) ":%")] + (seq (db/exec! conn [sql:select-next-tasks prefix batch-size])))) - (queue-task [conn rconn {:keys [id queue] :as task}] - (db/update! conn :task {:status "ready"} {:id id}) - (let [queue (format-queue queue) - payload (t/encode id) - result (rds/rpush! rconn queue payload)] - (l/debug :hist "scheduler: task pushed to redis" - :task-id id - :key queue - :queued result))) + (push-tasks! [conn rconn [queue tasks]] + (let [ids (mapv :id tasks) + key (str/ffmt "taskq:%" queue) + res (rds/rpush! rconn key (mapv t/encode ids)) + sql [(str "update task set status = 'scheduled'" + " where id = ANY(?)") + (db/create-array conn "uuid" ids)]] - (run-batch [rconn] + (db/exec-one! conn sql) + (l/debug :hist "dispatcher: push tasks to redis" + :queue queue + :tasks (count ids) + :queued res))) + + (run-batch! [rconn] (db/with-atomic [conn pool] - (when-let [tasks (get-tasks-batch conn)] - (run! (partial queue-task conn rconn) tasks) - true))) - ] + (when-let [tasks (get-tasks conn)] + (->> (group-by :queue tasks) + (run! (partial push-tasks! conn rconn))) + true)))] (if (db/read-only? pool) - (l/warn :hint "scheduler: not started (db is read-only)") + (l/warn :hint "dispatcher: not started (db is read-only)") (px/thread - {:name "penpot/scheduler"} - (l/info :hint "scheduler: started") + {:name "penpot/worker-dispatcher"} + (l/info :hint "dispatcher: started") (try (dm/with-open [rconn (rds/connect redis)] (loop [] @@ -238,7 +238,7 @@ (throw (InterruptedException. "interrumpted"))) (try - (when-not (run-batch rconn) + (when-not (run-batch! rconn) (px/sleep (::wait-duration cfg))) (catch InterruptedException cause (throw cause)) @@ -246,29 +246,29 @@ (cond (rds/exception? cause) (do - (l/warn :hint "scheduler: redis exception (will retry in an instant)" :cause cause) + (l/warn :hint "dispatcher: redis exception (will retry in an instant)" :cause cause) (px/sleep (::rds/timeout rconn))) (db/sql-exception? cause) (do - (l/warn :hint "scheduler: database exception (will retry in an instant)" :cause cause) + (l/warn :hint "dispatcher: database exception (will retry in an instant)" :cause cause) (px/sleep (::rds/timeout rconn))) :else (do - (l/error :hint "scheduler: unhandled exception (will retry in an instant)" :cause cause) + (l/error :hint "dispatcher: unhandled exception (will retry in an instant)" :cause cause) (px/sleep (::rds/timeout rconn)))))) (recur))) (catch InterruptedException _ - (l/debug :hint "scheduler: interrupted")) + (l/debug :hint "dispatcher: interrupted")) (catch Throwable cause - (l/error :hint "scheduler: unexpected exception" :cause cause)) + (l/error :hint "dispatcher: unexpected exception" :cause cause)) (finally - (l/info :hint "scheduler: terminated"))))))) + (l/info :hint "dispatcher: terminated"))))))) -(defmethod ig/halt-key! ::scheduler +(defmethod ig/halt-key! ::dispatcher [_ thread] (some-> thread px/interrupt!)) @@ -288,36 +288,38 @@ ::queue ::registry])) -;; FIXME: define queue as set (defmethod ig/prep-key ::worker [_ cfg] - (merge {::queue "default" ::parallelism 1} + (merge {::parallelism 1} (d/without-nils cfg))) (defmethod ig/init-key ::worker [_ {:keys [::db/pool ::queue ::parallelism] :as cfg}] - (if (db/read-only? pool) - (l/warn :hint "workers: not started (db is read-only)" :queue queue) - (doall - (->> (range parallelism) - (map #(assoc cfg ::worker-id %)) - (map start-worker!))))) + (let [queue (d/name queue) + cfg (assoc cfg ::queue queue)] + (if (db/read-only? pool) + (l/warn :hint "worker: not started (db is read-only)" :queue queue :parallelism parallelism) + (doall + (->> (range parallelism) + (map #(assoc cfg ::worker-id %)) + (map start-worker!)))))) (defmethod ig/halt-key! ::worker [_ threads] (run! px/interrupt! threads)) (defn- start-worker! - [{:keys [::rds/redis ::worker-id] :as cfg}] + [{:keys [::rds/redis ::worker-id ::queue] :as cfg}] (px/thread {:name (format "penpot/worker/%s" worker-id)} - (l/info :hint "worker: started" :worker-id worker-id) + (l/info :hint "worker: started" :worker-id worker-id :queue queue) (try (dm/with-open [rconn (rds/connect redis)] - (let [cfg (-> cfg - (update ::queue format-queue) - (assoc ::rds/rconn rconn) - (assoc ::timeout (dt/duration "5s")))] + (let [tenant (cf/get :tenant "main") + cfg (-> cfg + (assoc ::queue (str/ffmt "taskq:%:%" tenant queue)) + (assoc ::rds/rconn rconn) + (assoc ::timeout (dt/duration "5s")))] (loop [] (when (px/interrupted?) (throw (InterruptedException. "interrupted"))) @@ -327,13 +329,17 @@ (catch InterruptedException _ (l/debug :hint "worker: interrupted" - :worker-id worker-id)) + :worker-id worker-id + :queue queue)) (catch Throwable cause (l/error :hint "worker: unexpected exception" :worker-id worker-id + :queue queue :cause cause)) (finally - (l/info :hint "worker: terminated" :worker-id worker-id))))) + (l/info :hint "worker: terminated" + :worker-id worker-id + :queue queue))))) (defn- run-worker-loop! [{:keys [::db/pool ::rds/rconn ::timeout ::queue ::registry ::worker-id]}] @@ -631,9 +637,26 @@ ;; SUBMIT API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(s/def ::task keyword?) +(defn- extract-props + [options] + (let [cns (namespace ::sample)] + (persistent! + (reduce-kv (fn [res k v] + (cond-> res + (not= (namespace k) cns) + (assoc! k v))) + (transient {}) + options)))) + +(def ^:private sql:insert-new-task + "insert into task (id, name, props, queue, priority, max_retries, scheduled_at) + values (?, ?, ?, ?, ?, ?, now() + ?) + returning id") + +(s/def ::task (s/or :kw keyword? :str string?)) +(s/def ::queue (s/or :kw keyword? :str string?)) (s/def ::delay (s/or :int ::us/integer :duration dt/duration?)) -(s/def ::conn some?) +(s/def ::conn (s/or :pool ::db/pool :connection some?)) (s/def ::priority ::us/integer) (s/def ::max-retries ::us/integer) @@ -641,36 +664,24 @@ (s/keys :req [::task ::conn] :opt [::delay ::queue ::priority ::max-retries])) -(defn- extract-props - [options] - (persistent! - (reduce-kv (fn [res k v] - (cond-> res - (not (qualified-keyword? k)) - (assoc! k v))) - (transient {}) - options))) - -(def ^:private sql:insert-new-task - "insert into task (id, name, props, queue, priority, max_retries, scheduled_at) - values (?, ?, ?, ?, ?, ?, now() + ?) - returning id") - (defn submit! [& {:keys [::task ::delay ::queue ::priority ::max-retries ::conn] - :or {delay 0 queue "default" priority 100 max-retries 3} + :or {delay 0 queue :default priority 100 max-retries 3} :as options}] (us/verify ::submit-options options) (let [duration (dt/duration delay) interval (db/interval duration) props (-> options extract-props db/tjson) - id (uuid/next)] + id (uuid/next) + tenant (cf/get :tenant) + task (d/name task) + queue (str/ffmt "%:%" tenant (d/name queue))] (l/debug :hint "submit task" - :name (d/name task) + :name task :queue queue :in (dt/format-duration duration)) - (db/exec-one! conn [sql:insert-new-task id (d/name task) props + (db/exec-one! conn [sql:insert-new-task id task props queue priority max-retries interval]) id)) From 9debfa3b276b33f8d3b7c638c782648dcd8c1489 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 5 Dec 2022 23:03:30 +0100 Subject: [PATCH 333/682] :paperclip: Minor cange on exception formating --- common/src/app/common/exceptions.cljc | 2 -- 1 file changed, 2 deletions(-) diff --git a/common/src/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc index 7be8efb342..333af74723 100644 --- a/common/src/app/common/exceptions.cljc +++ b/common/src/app/common/exceptions.cljc @@ -169,9 +169,7 @@ (print-all [cause] (print-summary cause) - (newline) (println "DETAIL:") - (when trace? (print-trace cause)) From d584ae5a0f36367286116da3f1474074017c27a5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Dec 2022 14:43:07 +0100 Subject: [PATCH 334/682] :sparkles: Improve json encode/decode api --- backend/src/app/auth/oidc.clj | 8 ++++---- backend/src/app/db.clj | 9 +++++---- backend/src/app/http/middleware.clj | 2 +- backend/src/app/loggers/loki.clj | 2 +- backend/src/app/loggers/mattermost.clj | 2 +- backend/src/app/loggers/zmq.clj | 2 +- backend/src/app/tasks/telemetry.clj | 2 +- backend/src/app/util/json.clj | 27 +++++++++++++------------- 8 files changed, 27 insertions(+), 27 deletions(-) diff --git a/backend/src/app/auth/oidc.clj b/backend/src/app/auth/oidc.clj index 6463c70758..8440b650e2 100644 --- a/backend/src/app/auth/oidc.clj +++ b/backend/src/app/auth/oidc.clj @@ -64,7 +64,7 @@ nil) (= 200 (:status response)) - (let [data (json/read (:body response))] + (let [data (json/decode (:body response))] {:token-uri (get data :token_endpoint) :auth-uri (get data :authorization_endpoint) :user-uri (get data :userinfo_endpoint)}) @@ -172,7 +172,7 @@ :hint "unable to retrieve github emails" :http-status status :http-body body)) - (->> response :body json/read (filter :primary) first :email)))))) + (->> response :body json/decode (filter :primary) first :email)))))) (defmethod ig/pre-init-spec ::providers/github [_] (s/keys :req [::http/client])) @@ -278,7 +278,7 @@ (->> (http/req! cfg req) (p/map (fn [{:keys [status body] :as res}] (if (= status 200) - (let [data (json/read body)] + (let [data (json/decode body)] {:token (get data :access_token) :type (get data :token_type)}) (ex/raise :type :internal @@ -316,7 +316,7 @@ (get info attr-kw))) (process-response [response] - (p/let [info (-> response :body json/read) + (p/let [info (-> response :body json/decode) email (get-email info)] {:backend (:name provider) :email email diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index e897535dae..9848a943a3 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -427,7 +427,7 @@ val (.getValue o)] (if (or (= typ "json") (= typ "jsonb")) - (json/read val) + (json/decode val) val)))) (defn decode-transit-pgobject @@ -462,9 +462,10 @@ (defn json "Encode as plain json." [data] - (doto (org.postgresql.util.PGobject.) - (.setType "jsonb") - (.setValue (json/write-str data)))) + (when data + (doto (org.postgresql.util.PGobject.) + (.setType "jsonb") + (.setValue (json/encode-str data))))) ;; --- Locks diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index ce0471aff3..5f687be12e 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -46,7 +46,7 @@ (str/starts-with? header "application/json") (with-open [is (yrq/body request)] - (let [params (json/read is)] + (let [params (json/decode is)] (-> request (assoc :body-params params) (update :params merge params)))) diff --git a/backend/src/app/loggers/loki.clj b/backend/src/app/loggers/loki.clj index 3f95d3ad68..68f5ee74a6 100644 --- a/backend/src/app/loggers/loki.clj +++ b/backend/src/app/loggers/loki.clj @@ -73,7 +73,7 @@ :timeout 3000 :method :post :headers {"content-type" "application/json"} - :body (json/write payload)} + :body (json/encode payload)} {:sync? true})) (defn- handle-event diff --git a/backend/src/app/loggers/mattermost.clj b/backend/src/app/loggers/mattermost.clj index 15c51d044b..f7a1efb490 100644 --- a/backend/src/app/loggers/mattermost.clj +++ b/backend/src/app/loggers/mattermost.clj @@ -29,7 +29,7 @@ {:uri (cf/get :error-report-webhook) :method :post :headers {"content-type" "application/json"} - :body (json/write-str {:text text})} + :body (json/encode-str {:text text})} {:sync? true})] (when (not= 200 (:status resp)) diff --git a/backend/src/app/loggers/zmq.clj b/backend/src/app/loggers/zmq.clj index 19a7e98008..77b7de5494 100644 --- a/backend/src/app/loggers/zmq.clj +++ b/backend/src/app/loggers/zmq.clj @@ -92,7 +92,7 @@ (.. socket (setReceiveTimeOut 5000)) (loop [] (let [msg (.recv ^ZMQ$Socket socket) - msg (ex/ignoring (json/read msg json-mapper)) + msg (ex/ignoring (json/decode msg json-mapper)) msg (if (nil? msg) :empty msg)] (when (a/>!! output msg) (recur)))) diff --git a/backend/src/app/tasks/telemetry.clj b/backend/src/app/tasks/telemetry.clj index e6323ebdbd..7754f699e7 100644 --- a/backend/src/app/tasks/telemetry.clj +++ b/backend/src/app/tasks/telemetry.clj @@ -81,7 +81,7 @@ {:method :post :uri (cf/get :telemetry-uri) :headers {"content-type" "application/json"} - :body (json/write-str data)} + :body (json/encode-str data)} {:sync? true})] (when (> (:status response) 206) (ex/raise :type :internal diff --git a/backend/src/app/util/json.clj b/backend/src/app/util/json.clj index 3547bfebd2..abcb4154bf 100644 --- a/backend/src/app/util/json.clj +++ b/backend/src/app/util/json.clj @@ -5,7 +5,6 @@ ;; Copyright (c) KALEIDOS INC (ns app.util.json - (:refer-clojure :exclude [read]) (:require [jsonista.core :as j])) @@ -13,23 +12,23 @@ [params] (j/object-mapper params)) -(defn write +(defn read! + ([from] (j/read-value from j/keyword-keys-object-mapper)) + ([from mapper] (j/read-value from mapper))) + +(defn write! + ([to v] (j/write-value to v j/keyword-keys-object-mapper)) + ([to v mapper] (j/write-value to v mapper))) + +(defn encode ([v] (j/write-value-as-bytes v j/keyword-keys-object-mapper)) ([v mapper] (j/write-value-as-bytes v mapper))) -(defn write-str - ([v] (j/write-value-as-string v j/keyword-keys-object-mapper)) - ([v mapper] (j/write-value-as-string v mapper))) - -(defn read +(defn decode ([v] (j/read-value v j/keyword-keys-object-mapper)) ([v mapper] (j/read-value v mapper))) -(defn encode - [v] - (j/write-value-as-bytes v j/keyword-keys-object-mapper)) - -(defn decode - [v] - (j/read-value v j/keyword-keys-object-mapper)) +(defn encode-str + ([v] (j/write-value-as-string v j/keyword-keys-object-mapper)) + ([v mapper] (j/write-value-as-string v mapper))) From d768711caae6bc18e828b92885bc9b3e65bcdb46 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Dec 2022 14:43:35 +0100 Subject: [PATCH 335/682] :sparkles: Improve null handling on more db helpers --- backend/src/app/db.clj | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 9848a943a3..6e4d12061f 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -442,22 +442,25 @@ (defn inet [ip-addr] - (doto (org.postgresql.util.PGobject.) - (.setType "inet") - (.setValue (str ip-addr)))) + (when ip-addr + (doto (org.postgresql.util.PGobject.) + (.setType "inet") + (.setValue (str ip-addr))))) (defn decode-inet [^PGobject o] - (if (= "inet" (.getType o)) - (.getValue o) - nil)) + (when o + (if (= "inet" (.getType o)) + (.getValue o) + nil))) (defn tjson "Encode as transit json." [data] - (doto (org.postgresql.util.PGobject.) - (.setType "jsonb") - (.setValue (t/encode-str data {:type :json-verbose})))) + (when data + (doto (org.postgresql.util.PGobject.) + (.setType "jsonb") + (.setValue (t/encode-str data {:type :json-verbose}))))) (defn json "Encode as plain json." From 5b9f0ed0b19cc3a072494f622d1dfc1be2ac2b88 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Dec 2022 14:47:32 +0100 Subject: [PATCH 336/682] :tada: Add webhook processing worker --- backend/src/app/loggers/audit.clj | 48 +++-- backend/src/app/loggers/webhooks.clj | 171 ++++++++++++++++++ backend/src/app/main.clj | 16 +- backend/src/app/migrations.clj | 3 + .../sql/0086-add-webhook-delivery-table.sql | 16 ++ backend/src/app/rpc.clj | 9 +- backend/src/app/rpc/commands/webhooks.clj | 87 +++++---- backend/src/app/rpc/helpers.clj | 4 + backend/src/app/rpc/mutations/projects.clj | 18 +- 9 files changed, 308 insertions(+), 64 deletions(-) create mode 100644 backend/src/app/loggers/webhooks.clj create mode 100644 backend/src/app/migrations/sql/0086-add-webhook-delivery-table.sql diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index c8ef0d7963..d004fe5d41 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -17,6 +17,7 @@ [app.db :as db] [app.http.client :as http] [app.loggers.audit.tasks :as-alias tasks] + [app.loggers.webhooks :as-alias webhooks] [app.main :as-alias main] [app.metrics :as mtx] [app.tokens :as tokens] @@ -103,10 +104,11 @@ (s/def ::type ::us/string) (s/def ::props (s/map-of ::us/keyword any?)) (s/def ::ip-addr ::us/string) +(s/def ::webhooks/event? ::us/boolean) (s/def ::event (s/keys :req-un [::type ::name ::profile-id] - :opt-un [::ip-addr ::props])) + :opt-un [::ip-addr ::props ::webhooks/event?])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; COLLECTOR @@ -117,8 +119,7 @@ ;; an external storage and data cleared. (s/def ::collector - (s/nilable - (s/keys :req [::wrk/executor ::db/pool]))) + (s/keys :req [::wrk/executor ::db/pool])) (defmethod ig/pre-init-spec ::collector [_] (s/keys :req [::db/pool ::wrk/executor ::mtx/metrics])) @@ -126,11 +127,8 @@ (defmethod ig/init-key ::collector [_ {:keys [::db/pool] :as cfg}] (cond - (not (contains? cf/flags :audit-log)) - (l/info :hint "audit: log collection disabled") - (db/read-only? pool) - (l/warn :hint "audit: log collection disabled (db is read-only)") + (l/warn :hint "audit: disabled (db is read-only)") :else cfg)) @@ -138,19 +136,35 @@ (defn- persist-event! [pool event] (us/verify! ::event event) - (db/insert! pool :audit-log - {:id (uuid/next) - :name (:name event) - :type (:type event) - :profile-id (:profile-id event) - :tracked-at (dt/now) - :ip-addr (some-> (:ip-addr event) db/inet) - :props (db/tjson (:props event)) - :source "backend"})) + (let [params {:id (uuid/next) + :name (:name event) + :type (:type event) + :profile-id (:profile-id event) + :tracked-at (dt/now) + :ip-addr (:ip-addr event) + :props (:props event)}] + + (when (contains? cf/flags :audit-log) + (db/insert! pool :audit-log + (-> params + (update :props db/tjson) + (update :ip-addr db/inet) + (assoc :source "backend")))) + + (when (and (contains? cf/flags :webhooks) + (::webhooks/event? event)) + (wrk/submit! ::wrk/conn pool + ::wrk/task :process-webhook-event + ::wrk/queue :webhooks + ::wrk/max-retries 0 + ::webhooks/event (-> params + (dissoc :ip-addr) + (dissoc :type)))))) (defn submit! "Submit audit event to the collector." - [{:keys [::wrk/executor ::db/pool]} params] + [{:keys [::wrk/executor ::db/pool] :as collector} params] + (us/assert! ::collector collector) (->> (px/submit! executor (partial persist-event! pool (d/without-nils params))) (p/merr (fn [cause] (l/error :hint "audit: unexpected error processing event" :cause cause) diff --git a/backend/src/app/loggers/webhooks.clj b/backend/src/app/loggers/webhooks.clj new file mode 100644 index 0000000000..28331540f5 --- /dev/null +++ b/backend/src/app/loggers/webhooks.clj @@ -0,0 +1,171 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.loggers.webhooks + "A mattermost integration for error reporting." + (:require + [app.common.data :as d] + [app.common.logging :as l] + [app.common.transit :as t] + [app.common.uri :as uri] + [app.db :as db] + [app.http.client :as http] + [app.util.json :as json] + [app.util.time :as dt] + [app.worker :as wrk] + [clojure.spec.alpha :as s] + [cuerdas.core :as str] + [integrant.core :as ig])) + +;; --- PROC + +(defn lookup-webhooks-by-team + [pool team-id] + (db/exec! pool ["select * from webhook where team_id=? and is_active=true" team-id])) + +(defn lookup-webhooks-by-project + [pool project-id] + (let [sql [(str "select * from webhook as w" + " join project as p on (p.team_id = w.team_id)" + " where p.id = ? and w.is_active = true") + project-id]] + (db/exec! pool sql))) + +(defn lookup-webhooks-by-file + [pool file-id] + (let [sql [(str "select * from webhook as w" + " join project as p on (p.team_id = w.team_id)" + " join file as f on (f.project_id = p.id)" + " where f.id = ? and w.is_active = true") + file-id]] + (db/exec! pool sql))) + +(defn lookup-webhooks + [{:keys [::db/pool]} {:keys [props] :as event}] + (or (some->> (:team-id props) (lookup-webhooks-by-team pool)) + (some->> (:project-id props) (lookup-webhooks-by-project pool)) + (some->> (:file-id props) (lookup-webhooks-by-file pool)))) + +(defmethod ig/pre-init-spec ::process-event-handler [_] + (s/keys :req [::db/pool])) + +(defmethod ig/init-key ::process-event-handler + [_ {:keys [::db/pool] :as cfg}] + (fn [{:keys [props] :as task}] + (let [event (::event props)] + (l/debug :hint "process webhook event" + :name (:name event)) + + (when-let [items (lookup-webhooks cfg event)] + ;; (app.common.pprint/pprint items) + (l/trace :hint "webhooks found for event" :total (count items)) + + (db/with-atomic [conn pool] + (doseq [item items] + (wrk/submit! ::wrk/conn conn + ::wrk/task :run-webhook + ::wrk/queue :webhooks + ::wrk/max-retries 3 + ::event event + ::config item))))))) + +;; --- RUN + +(declare interpret-exception) +(declare interpret-response) + +(def ^:private mapper + (json/mapper + {:encode-key-fn str/camel + :decode-key-fn (comp keyword str/kebab) + :pretty true})) + +(defmethod ig/pre-init-spec ::run-webhook-handler [_] + (s/keys :req [::http/client ::db/pool])) + +(defmethod ig/prep-key ::run-webhook-handler + [_ cfg] + (merge {::max-errors 3} (d/without-nils cfg))) + +(defmethod ig/init-key ::run-webhook-handler + [_ {:keys [::db/pool ::max-errors] :as cfg}] + (letfn [(update-webhook! [whook err] + (if err + (let [sql [(str "update webhook " + " set error_code=?, " + " error_count=error_count+1 " + " where id=?") + err + (:id whook)] + res (db/exec-one! pool sql {:return-keys true})] + (when (>= (:error-count res) max-errors) + (db/update! pool :webhook {:is-active false} {:id (:id whook)}))) + + (db/update! pool :webhook + {:updated-at (dt/now) + :error-code nil + :error-count 0} + {:id (:id whook)}))) + + (report-delivery! [whook req rsp err] + (db/insert! pool :webhook-delivery + {:webhook-id (:id whook) + :created-at (dt/now) + :error-code err + :req-data (db/tjson req) + :rsp-data (db/tjson rsp)}))] + + (fn [{:keys [props] :as task}] + (let [event (::event props) + whook (::config props) + + body (case (:mtype whook) + "application/json" (json/encode-str event mapper) + "application/transit+json" (t/encode-str event) + "application/x-www-form-urlencoded" (uri/map->query-string event))] + + (l/debug :hint "run webhook" + :event-name (:name event) + :webhook-id (:id whook) + :webhook-uri (:uri whook) + :webhook-mtype (:mtype whook)) + + (let [req {:uri (:uri whook) + :headers {"content-type" (:mtype whook)} + :timeout (dt/duration "4s") + :method :post + :body body}] + (try + (let [rsp (http/req! cfg req {:response-type :input-stream :sync? true}) + err (interpret-response rsp)] + (report-delivery! whook req rsp err) + (update-webhook! whook err)) + (catch Throwable cause + (let [err (interpret-exception cause)] + (report-delivery! whook req nil err) + (update-webhook! whook err) + (when (= err "unknown") + (l/error :hint "unknown error on webhook request" + :cause cause)))))))))) + +(defn interpret-response + [{:keys [status] :as response}] + (when-not (or (= 200 status) + (= 204 status)) + (str/ffmt "unexpected-status:%" status))) + +(defn interpret-exception + [cause] + (cond + (instance? javax.net.ssl.SSLHandshakeException cause) + "ssl-validation-error" + + (instance? java.net.ConnectException cause) + "connection-error" + + (instance? java.net.http.HttpConnectTimeoutException cause) + "timeout" + )) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 1c36c9d038..b2145aeb46 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -15,6 +15,7 @@ [app.http.session :as-alias http.session] [app.loggers.audit :as-alias audit] [app.loggers.audit.tasks :as-alias audit.tasks] + [app.loggers.webhooks :as-alias webhooks] [app.loggers.zmq :as-alias lzmq] [app.metrics :as-alias mtx] [app.metrics.definition :as-alias mdef] @@ -357,7 +358,12 @@ :telemetry (ig/ref :app.tasks.telemetry/handler) :session-gc (ig/ref :app.http.session/gc-task) :audit-log-archive (ig/ref ::audit.tasks/archive) - :audit-log-gc (ig/ref ::audit.tasks/gc)}} + :audit-log-gc (ig/ref ::audit.tasks/gc) + + :process-webhook-event + (ig/ref ::webhooks/process-event-handler) + :run-webhook + (ig/ref ::webhooks/run-webhook-handler)}} :app.emails/sendmail @@ -420,6 +426,14 @@ ::audit.tasks/gc {::db/pool (ig/ref ::db/pool)} + ::webhooks/process-event-handler + {::db/pool (ig/ref ::db/pool) + ::http.client/client (ig/ref ::http.client/client)} + + ::webhooks/run-webhook-handler + {::db/pool (ig/ref ::db/pool) + ::http.client/client (ig/ref ::http.client/client)} + :app.loggers.loki/reporter {::lzmq/receiver (ig/ref ::lzmq/receiver) ::http.client/client (ig/ref ::http.client/client)} diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index c0694e103a..dda506b11c 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -265,6 +265,9 @@ {:name "0085-add-webhook-table" :fn (mg/resource "app/migrations/sql/0085-add-webhook-table.sql")} + + {:name "0086-add-webhook-delivery-table" + :fn (mg/resource "app/migrations/sql/0086-add-webhook-delivery-table.sql")} ]) diff --git a/backend/src/app/migrations/sql/0086-add-webhook-delivery-table.sql b/backend/src/app/migrations/sql/0086-add-webhook-delivery-table.sql new file mode 100644 index 0000000000..3fead5b51c --- /dev/null +++ b/backend/src/app/migrations/sql/0086-add-webhook-delivery-table.sql @@ -0,0 +1,16 @@ +CREATE TABLE webhook_delivery ( + webhook_id uuid NOT NULL REFERENCES webhook(id) ON DELETE CASCADE DEFERRABLE, + created_at timestamptz NOT NULL DEFAULT now(), + + error_code text NULL, + + req_data jsonb NULL, + rsp_data jsonb NULL, + + PRIMARY KEY (webhook_id, created_at) +); + +ALTER TABLE webhook_delivery + ALTER COLUMN error_code SET STORAGE external, + ALTER COLUMN req_data SET STORAGE external, + ALTER COLUMN rsp_data SET STORAGE external; diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 51f48ba283..df4fd4ea91 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -16,6 +16,7 @@ [app.http.client :as-alias http.client] [app.http.session :as-alias http.session] [app.loggers.audit :as audit] + [app.loggers.webhooks :as-alias webhooks] [app.metrics :as mtx] [app.msgbus :as-alias mbus] [app.rpc.climit :as climit] @@ -155,18 +156,24 @@ (:profile-id result) (:profile-id params) uuid/zero) + props (or (::audit/replace-props resultm) (-> params + (d/without-qualified) (merge (::audit/props resultm)) (dissoc :profile-id) (dissoc :type))) + event {:type (or (::audit/type resultm) (::type cfg)) :name (or (::audit/name resultm) (::sv/name mdata)) :profile-id profile-id :ip-addr (some-> request audit/parse-client-ip) - :props (d/without-qualified props)}] + :props props + ::webhooks/event? (or (::webhooks/event? mdata) + (::webhooks/event? resultm) + false)}] (audit/submit! collector event))) diff --git a/backend/src/app/rpc/commands/webhooks.clj b/backend/src/app/rpc/commands/webhooks.clj index 66ae149652..f1af961758 100644 --- a/backend/src/app/rpc/commands/webhooks.clj +++ b/backend/src/app/rpc/commands/webhooks.clj @@ -11,6 +11,7 @@ [app.common.uuid :as uuid] [app.db :as db] [app.http.client :as http] + [app.loggers.webhooks :as webhooks] [app.rpc.doc :as-alias doc] [app.rpc.queries.teams :refer [check-edition-permissions! check-read-permissions!]] [app.util.services :as sv] @@ -35,77 +36,83 @@ (s/keys :req-un [::profile-id ::team-id ::uri ::mtype] :opt-un [::is-active])) -;; FIXME: validate -;; FIXME: default ratelimit -;; FIXME: quotes +;; NOTE: for now the quote is hardcoded but this need to be solved in +;; a more universal way for handling properly object quotes +(def max-hooks-for-team 8) (defn- validate-webhook! [cfg whook params] (letfn [(handle-exception [exception] - (cond - (instance? java.util.concurrent.CompletionException exception) - (handle-exception (ex/cause exception)) - - (instance? javax.net.ssl.SSLHandshakeException exception) + (if-let [hint (webhooks/interpret-exception exception)] (ex/raise :type :validation :code :webhook-validation - :hint "ssl-validation") - - :else - (ex/raise :type :validation + :hint hint) + (ex/raise :type :internal :code :webhook-validation - :hint "unknown" :cause exception))) - (handle-response [{:keys [status] :as response}] - (when (not= status 200) + (handle-response [response] + (when-let [hint (webhooks/interpret-response response)] (ex/raise :type :validation :code :webhook-validation - :hint (str/ffmt "unexpected-status-%" (:status response)))))] + :hint hint)))] (if (not= (:uri whook) (:uri params)) (->> (http/req! cfg {:method :head :uri (:uri params) - :timeout (dt/duration "2s")}) + :timeout (dt/duration "3s")}) (p/hmap (fn [response exception] (if exception (handle-exception exception) (handle-response response))))) (p/resolved nil)))) +(defn- validate-quotes! + [{:keys [::db/pool]} {:keys [team-id]}] + (let [sql ["select count(*) as total from webhook where team_id = ?" team-id] + total (:total (db/exec-one! pool sql))] + (when (>= total max-hooks-for-team) + (ex/raise :type :restriction + :code :webhooks-quote-reached + :hint (str/ffmt "can't create more than % webhooks per team" max-hooks-for-team))))) + +(defn- insert-webhook! + [{:keys [::db/pool]} {:keys [team-id uri mtype is-active] :as params}] + (db/insert! pool :webhook + {:id (uuid/next) + :team-id team-id + :uri uri + :is-active is-active + :mtype mtype})) + +(defn- update-webhook! + [{:keys [::db/pool] :as cfg} {:keys [id] :as wook} {:keys [uri mtype is-active] :as params}] + (db/update! pool :webhook + {:uri uri + :is-active is-active + :mtype mtype + :error-code nil + :error-count 0} + {:id id})) + (sv/defmethod ::create-webhook {::doc/added "1.17"} - [{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id team-id uri mtype is-active] :as params}] + [{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id team-id] :as params}] (check-edition-permissions! pool profile-id team-id) - (letfn [(insert-webhook [_] - (db/insert! pool :webhook - {:id (uuid/next) - :team-id team-id - :uri uri - :is-active is-active - :mtype mtype}))] - (->> (validate-webhook! cfg nil params) - (p/fmap executor insert-webhook)))) + (->> (validate-webhook! cfg nil params) + (p/fmap executor (fn [_] (validate-quotes! cfg params))) + (p/fmap executor (fn [_] (insert-webhook! cfg params))))) (s/def ::update-webhook (s/keys :req-un [::id ::uri ::mtype ::is-active])) (sv/defmethod ::update-webhook {::doc/added "1.17"} - [{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id id uri mtype is-active] :as params}] - (let [whook (db/get pool :webhook {:id id}) - update-fn (fn [_] - (db/update! pool :webhook - {:uri uri - :is-active is-active - :mtype mtype - :error-code nil - :error-count 0} - {:id id}))] + [{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [id profile-id] :as params}] + (let [whook (db/get pool :webhook {:id id})] (check-edition-permissions! pool profile-id (:team-id whook)) - (->> (validate-webhook! cfg whook params) - (p/fmap executor update-fn)))) + (p/fmap executor (fn [_] (update-webhook! cfg whook params)))))) (s/def ::delete-webhook (s/keys :req-un [::profile-id ::id])) @@ -133,4 +140,4 @@ [{:keys [pool] :as cfg} {:keys [profile-id team-id]}] (with-open [conn (db/open pool)] (check-read-permissions! conn profile-id team-id) - (db/exec! conn [sql:get-webhooks team-id]))) \ No newline at end of file + (db/exec! conn [sql:get-webhooks team-id]))) diff --git a/backend/src/app/rpc/helpers.clj b/backend/src/app/rpc/helpers.clj index aef80d7547..1f4d7bbf91 100644 --- a/backend/src/app/rpc/helpers.clj +++ b/backend/src/app/rpc/helpers.clj @@ -64,6 +64,10 @@ [mdw mdata] (vary-meta mdw merge mdata)) +(defn assoc-meta + [mdw k v] + (vary-meta mdw assoc k v)) + (defn with-http-cache [mdw max-age] (vary-meta mdw update ::rpc/response-transform-fns conj diff --git a/backend/src/app/rpc/mutations/projects.clj b/backend/src/app/rpc/mutations/projects.clj index 35e598ae4a..95c36d9572 100644 --- a/backend/src/app/rpc/mutations/projects.clj +++ b/backend/src/app/rpc/mutations/projects.clj @@ -9,6 +9,10 @@ [app.common.spec :as us] [app.common.uuid :as uuid] [app.db :as db] + [app.loggers.audit :as-alias audit] + [app.loggers.webhooks :as-alias webhooks] + [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] [app.rpc.permissions :as perms] [app.rpc.queries.projects :as proj] [app.rpc.queries.teams :as teams] @@ -22,7 +26,6 @@ (s/def ::name ::us/string) (s/def ::profile-id ::us/uuid) - ;; --- Mutation: Create Project (declare create-project) @@ -35,6 +38,8 @@ :opt-un [::id])) (sv/defmethod ::create-project + {::doc/added "1.0" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] (db/with-atomic [conn pool] (teams/check-edition-permissions! conn profile-id team-id) @@ -122,10 +127,13 @@ ;; this is not allowed. (sv/defmethod ::delete-project + {::doc/added "1.0" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (proj/check-edition-permissions! conn profile-id id) - (db/update! conn :project - {:deleted-at (dt/now)} - {:id id :is-default false}) - nil)) + (let [project (db/update! conn :project + {:deleted-at (dt/now)} + {:id id :is-default false})] + (rph/with-meta (rph/wrap) + {::audit/props {:team-id (:team-id project)}})))) From edaa62b05bbef8b1b0ad171096e36c33163ca19d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Dec 2022 14:52:07 +0100 Subject: [PATCH 337/682] :lipstick: Replace us/assert with us/assert! on dashboard data ns --- frontend/src/app/main/data/dashboard.cljs | 86 ++++++++++++----------- 1 file changed, 44 insertions(+), 42 deletions(-) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index dd5e27f123..16feb70149 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -64,7 +64,7 @@ (defn initialize [{:keys [id] :as params}] - (us/assert ::us/uuid id) + (us/assert! ::us/uuid id) (ptk/reify ::initialize ptk/UpdateEvent (update [_ state] @@ -201,7 +201,7 @@ (defn search [params] - (us/assert ::search params) + (us/assert! ::search params) (ptk/reify ::search ptk/UpdateEvent (update [_ state] @@ -236,7 +236,7 @@ (defn fetch-files [{:keys [project-id] :as params}] - (us/assert ::us/uuid project-id) + (us/assert! ::us/uuid project-id) (ptk/reify ::fetch-files ptk/WatchEvent (watch [_ _ _] @@ -347,7 +347,7 @@ (defn toggle-file-select [{:keys [id project-id] :as file}] - (us/assert ::file file) + (us/assert! ::file file) (ptk/reify ::toggle-file-select ptk/UpdateEvent (update [_ state] @@ -377,7 +377,7 @@ (defn create-team [{:keys [name] :as params}] - (us/assert string? name) + (us/assert! ::us/string name) (ptk/reify ::create-team ptk/WatchEvent (watch [_ _ _] @@ -394,7 +394,7 @@ (defn create-team-with-invitations [{:keys [name emails role] :as params}] - (us/assert string? name) + (us/assert! ::us/string name) (ptk/reify ::create-team-with-invitations ptk/WatchEvent (watch [_ _ _] @@ -413,7 +413,7 @@ (defn update-team [{:keys [id name] :as params}] - (us/assert ::team params) + (us/assert! ::team params) (ptk/reify ::update-team ptk/UpdateEvent (update [_ state] @@ -426,7 +426,7 @@ (defn update-team-photo [{:keys [file] :as params}] - (us/assert ::di/file file) + (us/assert! ::di/file file) (ptk/reify ::update-team-photo ptk/WatchEvent (watch [_ state _] @@ -447,8 +447,8 @@ (defn update-team-member-role [{:keys [role member-id] :as params}] - (us/assert ::us/uuid member-id) - (us/assert ::us/keyword role) + (us/assert! ::us/uuid member-id) + (us/assert! ::us/keyword role) (ptk/reify ::update-team-member-role ptk/WatchEvent (watch [_ state _] @@ -461,7 +461,7 @@ (defn delete-team-member [{:keys [member-id] :as params}] - (us/assert ::us/uuid member-id) + (us/assert! ::us/uuid member-id) (ptk/reify ::delete-team-member ptk/WatchEvent (watch [_ state _] @@ -474,7 +474,9 @@ (defn leave-team [{:keys [reassign-to] :as params}] - (us/assert (s/nilable ::us/uuid) reassign-to) + (us/assert! + :spec (s/nilable ::us/uuid) + :val reassign-to) (ptk/reify ::leave-team ptk/WatchEvent (watch [_ state _] @@ -510,9 +512,9 @@ (defn update-team-invitation-role [{:keys [email team-id role] :as params}] - (us/assert ::us/email email) - (us/assert ::us/uuid team-id) - (us/assert ::us/keyword role) + (us/assert! ::us/email email) + (us/assert! ::us/uuid team-id) + (us/assert! ::us/keyword role) (ptk/reify ::update-team-invitation-role IDeref (-deref [_] {:role role}) @@ -528,8 +530,8 @@ (defn delete-team-invitation [{:keys [email team-id] :as params}] - (us/assert ::us/email email) - (us/assert ::us/uuid team-id) + (us/assert! ::us/email email) + (us/assert! ::us/uuid team-id) (ptk/reify ::delete-team-invitation ptk/WatchEvent (watch [_ _ _] @@ -542,7 +544,7 @@ (defn delete-team-webhook [{:keys [id] :as params}] - (us/assert ::us/uuid id) + (us/assert! ::us/uuid id) (ptk/reify ::delete-team-webhook ptk/WatchEvent (watch [_ state _] @@ -562,10 +564,10 @@ (defn update-team-webhook [{:keys [id uri mtype is-active] :as params}] - (us/assert ::us/uuid id) - (us/assert ::us/uri uri) - (us/assert ::mtype mtype) - (us/assert ::us/boolean is-active) + (us/assert! ::us/uuid id) + (us/assert! ::us/uri uri) + (us/assert! ::mtype mtype) + (us/assert! ::us/boolean is-active) (ptk/reify ::update-team-webhook ptk/WatchEvent (watch [_ state _] @@ -580,9 +582,9 @@ (defn create-team-webhook [{:keys [uri mtype is-active] :as params}] - (us/assert ::us/uri uri) - (us/assert ::mtype mtype) - (us/assert ::us/boolean is-active) + (us/assert! ::us/uri uri) + (us/assert! ::mtype mtype) + (us/assert! ::us/boolean is-active) (ptk/reify ::create-team-webhook ptk/WatchEvent (watch [_ state _] @@ -599,7 +601,7 @@ (defn delete-team [{:keys [id] :as params}] - (us/assert ::team params) + (us/assert! ::team params) (ptk/reify ::delete-team ptk/WatchEvent (watch [_ _ _] @@ -652,7 +654,7 @@ (defn duplicate-project [{:keys [id name] :as params}] - (us/assert ::us/uuid id) + (us/assert! ::us/uuid id) (ptk/reify ::duplicate-project ptk/WatchEvent (watch [_ _ _] @@ -669,8 +671,8 @@ (defn move-project [{:keys [id team-id] :as params}] - (us/assert ::us/uuid id) - (us/assert ::us/uuid team-id) + (us/assert! ::us/uuid id) + (us/assert! ::us/uuid team-id) (ptk/reify ::move-project IDeref (-deref [_] @@ -688,7 +690,7 @@ (defn toggle-project-pin [{:keys [id is-pinned] :as project}] - (us/assert ::project project) + (us/assert! ::project project) (ptk/reify ::toggle-project-pin ptk/UpdateEvent (update [_ state] @@ -705,7 +707,7 @@ (defn rename-project [{:keys [id name] :as params}] - (us/assert ::project params) + (us/assert! ::project params) (ptk/reify ::rename-project ptk/UpdateEvent (update [_ state] @@ -723,7 +725,7 @@ (defn delete-project [{:keys [id] :as params}] - (us/assert ::project params) + (us/assert! ::project params) (ptk/reify ::delete-project ptk/UpdateEvent (update [_ state] @@ -745,7 +747,7 @@ (defn delete-file [{:keys [id project-id] :as params}] - (us/assert ::file params) + (us/assert! ::file params) (ptk/reify ::delete-file ptk/UpdateEvent (update [_ state] @@ -764,7 +766,7 @@ (defn rename-file [{:keys [id name] :as params}] - (us/assert ::file params) + (us/assert! ::file params) (ptk/reify ::rename-file IDeref (-deref [_] @@ -787,7 +789,7 @@ (defn set-file-shared [{:keys [id is-shared] :as params}] - (us/assert ::file params) + (us/assert! ::file params) (ptk/reify ::set-file-shared IDeref (-deref [_] @@ -828,7 +830,7 @@ (defn create-file [{:keys [project-id] :as params}] - (us/assert ::us/uuid project-id) + (us/assert! ::us/uuid project-id) (ptk/reify ::create-file IDeref @@ -857,8 +859,8 @@ (defn duplicate-file [{:keys [id name] :as params}] - (us/assert ::us/uuid id) - (us/assert ::name name) + (us/assert! ::us/uuid id) + (us/assert! ::name name) (ptk/reify ::duplicate-file ptk/WatchEvent (watch [_ _ _] @@ -877,8 +879,8 @@ (defn move-files [{:keys [ids project-id] :as params}] - (us/assert ::us/set-of-uuid ids) - (us/assert ::us/uuid project-id) + (us/assert! ::us/set-of-uuid ids) + (us/assert! ::us/uuid project-id) (ptk/reify ::move-files IDeref (-deref [_] @@ -898,7 +900,7 @@ ;; --- EVENT: clone-template (defn clone-template [{:keys [template-id project-id] :as params}] - (us/assert ::us/uuid project-id) + (us/assert! ::us/uuid project-id) (ptk/reify ::clone-template IDeref (-deref [_] @@ -920,7 +922,7 @@ (defn go-to-workspace [{:keys [id project-id] :as file}] - (us/assert ::file file) + (us/assert! ::file file) (ptk/reify ::go-to-workspace ptk/WatchEvent (watch [_ _ _] From 21abd98b95c2378e95d2325c71840236ddad50fd Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Dec 2022 15:51:53 +0100 Subject: [PATCH 338/682] :sparkles: Integrate error handling for webhooks UI --- frontend/src/app/main/data/events.cljs | 2 +- frontend/src/app/main/ui/dashboard/team.cljs | 223 ++++++++++--------- frontend/translations/en.po | 27 ++- frontend/translations/es.po | 27 ++- 4 files changed, 153 insertions(+), 126 deletions(-) diff --git a/frontend/src/app/main/data/events.cljs b/frontend/src/app/main/data/events.cljs index fea0f0a5f2..e4644d8757 100644 --- a/frontend/src/app/main/data/events.cljs +++ b/frontend/src/app/main/data/events.cljs @@ -222,7 +222,7 @@ :body (http/transit-data {:events events})}] (->> (http/send! params) (rx/mapcat rp/handle-response) - (rx/catch (fn [cause] + (rx/catch (fn [_] (l/error :hint "unexpected error on persisting audit events") (rx/of nil))))) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 9e1c50c922..0a333b1f6d 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -587,53 +587,78 @@ (s/def ::webhook-form (s/keys :req-un [::uri ::mtype])) -(mf/defc webhook-modal {::mf/register modal/components - ::mf/register-as :webhook} +(def valid-webhook-mtypes + [{:label "application/json" :value "application/json"} + {:label "application/x-www-form-urlencoded" :value "application/x-www-form-urlencoded"} + {:label "application/transit+json" :value "application/transit+json"}]) + +(defn- extract-status + [error-code] + (-> error-code (str/split #":") second)) + +(mf/defc webhook-modal + {::mf/register modal/components + ::mf/register-as :webhook} [{:keys [webhook] :as props}] (let [initial (mf/use-memo (fn [] (or webhook {:is-active false :mtype "application/json"}))) form (fm/use-form :spec ::webhook-form :initial initial) - mtypes [{:label "application/json" :value "application/json"} - {:label "application/x-www-form-urlencoded" :value "application/x-www-form-urlencoded"} - {:label "application/transit+json" :value "application/transit+json"}] - on-success - (fn [message] - (st/emit! (dd/fetch-team-webhooks) - (msg/success message) - (modal/hide))) + (mf/use-fn + (fn [_] + (let [message (tr "dashboard.webhooks.create.success")] + (st/emit! (dd/fetch-team-webhooks) + (msg/success message) + (modal/hide))))) on-error - (fn [message {:keys [type code hint] :as error}] - (let [message (if (and (= type :validation) (= code :webhook-validation)) - (str message " " - (case hint - "ssl-validation" (tr "errors.webhooks.ssl-validation") - "")) ;; TODO Add more error codes when back defines them - message)] - (rx/of (msg/error message)))) + (mf/use-fn + (fn [form {:keys [type code hint] :as error}] + (if (and (= type :validation) + (= code :webhook-validation)) + (let [message (cond + (= hint "unknown") + (tr "errors.webhooks.unexpected") + (= hint "ssl-validation-error") + (tr "errors.webhooks.ssl-validation") + (= hint "timeout") + (tr "errors.webhooks.timeout") + (= hint "connection-error") + (tr "errors.webhooks.connection") + (str/starts-with? hint "unexpected-status") + (tr "errors.webhooks.unexpected-status" (extract-status hint)))] + (swap! form assoc-in [:errors :uri] {:message message})) + (rx/throw error)))) on-create-submit - (fn [] - (let [mdata {:on-success #(on-success (tr "dashboard.webhooks.create.success")) - :on-error (partial on-error (tr "dashboard.webhooks.create.error"))} - webhook {:uri (get-in @form [:clean-data :uri]) - :mtype (get-in @form [:clean-data :mtype]) - :is-active (get-in @form [:clean-data :is-active])}] - (st/emit! (dd/create-team-webhook (with-meta webhook mdata))))) + (mf/use-fn + (fn [form] + (let [cdata (:clean-data @form) + mdata {:on-success (partial on-success form) + :on-error (partial on-error form)} + params {:uri (:uri cdata) + :mtype (:mtype cdata) + :is-active (:is-active cdata)}] + (st/emit! (dd/create-team-webhook + (with-meta params mdata)))))) on-update-submit - (fn [] - (let [mdata {:on-success #(on-success (tr "dashboard.webhooks.update.success")) - :on-error (partial on-error (tr "dashboard.webhooks.update.error"))} - webhook (get @form :clean-data)] - (st/emit! (dd/update-team-webhook (with-meta webhook mdata))))) + (mf/use-fn + (fn [form] + (let [params (:clean-data @form) + mdata {:on-success (partial on-success form) + :on-error (partial on-error form)}] + (st/emit! (dd/update-team-webhook + (with-meta params mdata)))))) on-submit - #(let [data (:clean-data @form)] - (if (:id data) - (on-update-submit) - (on-create-submit)))] + (mf/use-fn + (fn [form] + (prn @form) + (let [data (:clean-data @form)] + (if (:id data) + (on-update-submit form) + (on-create-submit form)))))] [:div.modal-overlay [:div.modal-container.webhooks-modal @@ -659,7 +684,7 @@ :placeholder (tr "modals.create-webhook.url.placeholder")}]] [:div.fields-row - [:& fm/select {:options mtypes + [:& fm/select {:options valid-webhook-mtypes :label (tr "dashboard.webhooks.content-type") :default "application/json" :name :mtype}]]] @@ -704,79 +729,75 @@ {:on-click #(st/emit! (modal/show :webhook {}))} [:span (tr "dashboard.webhooks.create")]]]]) +(mf/defc webhook-actions + [{:keys [on-edit on-delete] :as props}] + (let [show? (mf/use-state false)] + [:* + [:span.icon {:on-click #(reset! show? true)} [i/actions]] + [:& dropdown {:show @show? + :on-close #(reset! show? false)} + [:ul.dropdown.actions-dropdown + [:li {:on-click on-edit} (tr "labels.edit")] + [:li {:on-click on-delete} (tr "labels.delete")]]]])) - (mf/defc webhook-actions - [{:keys [on-edit on-delete] :as props}] - (let [show? (mf/use-state false)] - [:* - [:span.icon {:on-click #(reset! show? true)} [i/actions]] - [:& dropdown {:show @show? - :on-close #(reset! show? false)} - [:ul.dropdown.actions-dropdown - [:li {:on-click on-edit} (tr "labels.edit")] - [:li {:on-click on-delete} (tr "labels.delete")]]]])) +(mf/defc last-delivery-icon + [{:keys [success? text] :as props}] + [:div.last-delivery-icon + [:div.tooltip + [:div.label text] + [:div.arrow-down]] + (if success? + [:span.icon.success i/msg-success] + [:span.icon.failure i/msg-warning])]) - (mf/defc last-delivery-icon - [{:keys [success? text] :as props}] - [:div.last-delivery-icon - [:div.tooltip - [:div.label text] - [:div.arrow-down]] - (if success? - [:span.icon.success i/msg-success] - [:span.icon.failure i/msg-warning])]) +(mf/defc webhook-item + {::mf/wrap [mf/memo]} + [{:keys [webhook] :as props}] + (let [on-edit #(st/emit! (modal/show :webhook {:webhook webhook})) + error-code (:error-code webhook) - (mf/defc webhook-item - {::mf/wrap [mf/memo]} - [{:keys [webhook] :as props}] - (let [on-edit #(st/emit! (modal/show :webhook {:webhook webhook})) - error-code (:error-code webhook) - extract-status - (fn [error-code] - (let [status (-> error-code - (str/split "-") - last - parse-long)] - (if (nil? status) - "" - status))) - delete-fn - (fn [] - (let [params {:id (:id webhook)} - mdata {:on-success #(st/emit! (dd/fetch-team-webhooks))}] - (st/emit! (dd/delete-team-webhook (with-meta params mdata))))) - on-delete #(st/emit! (modal/show - {:type :confirm - :title (tr "modals.delete-webhook.title") - :message (tr "modals.delete-webhook.message") - :accept-label (tr "modals.delete-webhook.accept") - :on-accept delete-fn})) - last-delivery-text (cond - (nil? error-code) - (tr "webhooks.last-delivery.success") + delete-fn + (fn [] + (let [params {:id (:id webhook)} + mdata {:on-success #(st/emit! (dd/fetch-team-webhooks))}] + (st/emit! (dd/delete-team-webhook (with-meta params mdata))))) - (= error-code "ssl-validation") - (str (tr "errors.webhooks.last-delivery") " " (tr "errors.webhooks.ssl-validation")) + on-delete + (fn [] + (st/emit! (modal/show + {:type :confirm + :title (tr "modals.delete-webhook.title") + :message (tr "modals.delete-webhook.message") + :accept-label (tr "modals.delete-webhook.accept") + :on-accept delete-fn}))) - (str/starts-with? error-code "unexpected-status") - (str (tr "errors.webhooks.last-delivery") - " " - (tr "errors.webhooks.unexpected-status" (extract-status error-code))) + last-delivery-text + (if (nil? error-code) + (tr "webhooks.last-delivery.success") + (str (tr "errors.webhooks.last-delivery") + (cond + (= error-code "ssl-validation-error") + (dm/str " " (tr "errors.webhooks.ssl-validation")) - :else - (tr "errors.webhooks.last-delivery"))] - [:div.table-row - [:div.table-field.last-delivery - [:div.icon-container - [:& last-delivery-icon {:success? (nil? error-code) :text last-delivery-text}]]] - [:div.table-field.uri - [:div (:uri webhook)]] - [:div.table-field.active - [:div (if (:is-active webhook) (tr "labels.active") (tr "labels.inactive"))]] - [:div.table-field.actions - [:& webhook-actions {:on-edit on-edit - :on-delete on-delete}]]])) + (str/starts-with? error-code "unexpected-status") + (dm/str " " (tr "errors.webhooks.unexpected-status" (extract-status error-code))))))] + [:div.table-row + [:div.table-field.last-delivery + [:div.icon-container + [:& last-delivery-icon + {:success? (nil? error-code) + :text last-delivery-text}]]] + [:div.table-field.uri + [:div (:uri webhook)]] + [:div.table-field.active + [:div (if (:is-active webhook) + (tr "labels.active") + (tr "labels.inactive"))]] + [:div.table-field.actions + [:& webhook-actions + {:on-edit on-edit + :on-delete on-delete}]]])) (mf/defc webhooks-list [{:keys [webhooks] :as props}] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index c5085a0925..de91460a86 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -690,6 +690,21 @@ msgstr "Is active" msgid "dashboard.webhooks.active.explain" msgstr "When this hook is triggered event details will be delivered" +msgid "dashboard.webhooks.update.success" +msgstr "Webhook updated successfully." + +msgid "dashboard.webhooks.create.success" +msgstr "Webhook created successfully." + +msgid "errors.webhooks.unexpected" +msgstr "Unexpected error on validating" + +msgid "errors.webhooks.timeout" +msgstr "Timeout" + +msgid "errors.webhooks.connection" +msgstr "Connection error, url not reacheable" + msgid "webhooks.last-delivery.success" msgstr "Last delivery was successfull." @@ -702,18 +717,6 @@ msgstr "Error on SSL validation." msgid "errors.webhooks.unexpected-status" msgstr "Unexpected status %s" -msgid "dashboard.webhooks.update.error" -msgstr "Error on updating webhook." - -msgid "dashboard.webhooks.update.success" -msgstr "Webhook updated successfully." - -msgid "dashboard.webhooks.create.error" -msgstr "Error on creating webhook." - -msgid "dashboard.webhooks.create.success" -msgstr "Webhook created successfully." - #: src/app/main/ui/alert.cljs msgid "ds.alert-ok" msgstr "Ok" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index b66865f718..f06309bb07 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -737,6 +737,21 @@ msgstr "Cuando se active este webhook se enviarán detalles del evento" msgid "webhooks.last-delivery.success" msgstr "El último envío fue correcto." +msgid "dashboard.webhooks.update.success" +msgstr "Webhook modificado con éxito" + +msgid "dashboard.webhooks.create.success" +msgstr "Webhook creado con éxito" + +msgid "errors.webhooks.timeout" +msgstr "Timeout" + +msgid "errors.webhooks.unexpected" +msgstr "Error inesperado al validar" + +msgid "errors.webhooks.connection" +msgstr "Error de conexion, la url no es alcanzable" + msgid "errors.webhooks.last-delivery" msgstr "Hubo un problema en el último envío." @@ -746,18 +761,6 @@ msgstr "Error en la validación SSL." msgid "errors.webhooks.unexpected-status" msgstr "Estado inesperado %s" -msgid "dashboard.webhooks.update.error" -msgstr "Error modificando el webhook" - -msgid "dashboard.webhooks.update.success" -msgstr "Webhook modificado con éxito" - -msgid "dashboard.webhooks.create.error" -msgstr "Error creando con éxito" - -msgid "dashboard.webhooks.create.success" -msgstr "Webhook creado con éxito" - #: src/app/main/ui/alert.cljs msgid "ds.alert-ok" msgstr "Ok" From f2b60261f8b209ae5357a459b340cfc8606e36d2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 12 Dec 2022 08:31:48 +0100 Subject: [PATCH 339/682] :tada: Add tests for webhooks rpc and logger --- backend/src/app/loggers/webhooks.clj | 12 +- backend/src/app/rpc/commands/webhooks.clj | 4 +- backend/test/backend_tests/helpers.clj | 17 +++ .../backend_tests/loggers_webhooks_test.clj | 120 ++++++++++++++++++ .../test/backend_tests/rpc_webhooks_test.clj | 41 +++++- 5 files changed, 183 insertions(+), 11 deletions(-) create mode 100644 backend/test/backend_tests/loggers_webhooks_test.clj diff --git a/backend/src/app/loggers/webhooks.clj b/backend/src/app/loggers/webhooks.clj index 28331540f5..a5849c6da2 100644 --- a/backend/src/app/loggers/webhooks.clj +++ b/backend/src/app/loggers/webhooks.clj @@ -22,11 +22,11 @@ ;; --- PROC -(defn lookup-webhooks-by-team +(defn- lookup-webhooks-by-team [pool team-id] (db/exec! pool ["select * from webhook where team_id=? and is_active=true" team-id])) -(defn lookup-webhooks-by-project +(defn- lookup-webhooks-by-project [pool project-id] (let [sql [(str "select * from webhook as w" " join project as p on (p.team_id = w.team_id)" @@ -34,7 +34,7 @@ project-id]] (db/exec! pool sql))) -(defn lookup-webhooks-by-file +(defn- lookup-webhooks-by-file [pool file-id] (let [sql [(str "select * from webhook as w" " join project as p on (p.team_id = w.team_id)" @@ -43,7 +43,7 @@ file-id]] (db/exec! pool sql))) -(defn lookup-webhooks +(defn- lookup-webhooks [{:keys [::db/pool]} {:keys [props] :as event}] (or (some->> (:team-id props) (lookup-webhooks-by-team pool)) (some->> (:project-id props) (lookup-webhooks-by-project pool)) @@ -77,7 +77,7 @@ (declare interpret-exception) (declare interpret-response) -(def ^:private mapper +(def ^:private json-mapper (json/mapper {:encode-key-fn str/camel :decode-key-fn (comp keyword str/kebab) @@ -123,7 +123,7 @@ whook (::config props) body (case (:mtype whook) - "application/json" (json/encode-str event mapper) + "application/json" (json/encode-str event json-mapper) "application/transit+json" (t/encode-str event) "application/x-www-form-urlencoded" (uri/map->query-string event))] diff --git a/backend/src/app/rpc/commands/webhooks.clj b/backend/src/app/rpc/commands/webhooks.clj index f1af961758..fdbc30851b 100644 --- a/backend/src/app/rpc/commands/webhooks.clj +++ b/backend/src/app/rpc/commands/webhooks.clj @@ -99,8 +99,8 @@ {::doc/added "1.17"} [{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id team-id] :as params}] (check-edition-permissions! pool profile-id team-id) - (->> (validate-webhook! cfg nil params) - (p/fmap executor (fn [_] (validate-quotes! cfg params))) + (->> (validate-quotes! cfg params) + (p/fmap executor (fn [_] (validate-webhook! cfg nil params))) (p/fmap executor (fn [_] (insert-webhook! cfg params))))) (s/def ::update-webhook diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj index 1ab57e5231..41cf3e1cfa 100644 --- a/backend/test/backend_tests/helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -284,6 +284,19 @@ :session-id session-id :profile-id profile-id}))))) +(defn create-webhook* + ([params] (create-webhook* *pool* params)) + ([pool {:keys [team-id id uri mtype is-active] + :or {is-active true + mtype "application/json" + uri "http://example.com/webhook"}}] + (db/insert! pool :webhook + {:id (or id (uuid/next)) + :team-id team-id + :uri uri + :is-active is-active + :mtype mtype}))) + ;; --- RPC HELPERS (defn handle-error @@ -417,6 +430,10 @@ [& params] (apply db/query *pool* params)) +(defn db-get + [& params] + (apply db/get* *pool* params)) + (defn sleep [ms-or-duration] (Thread/sleep (inst-ms (dt/duration ms-or-duration)))) diff --git a/backend/test/backend_tests/loggers_webhooks_test.clj b/backend/test/backend_tests/loggers_webhooks_test.clj new file mode 100644 index 0000000000..8af5bd780b --- /dev/null +++ b/backend/test/backend_tests/loggers_webhooks_test.clj @@ -0,0 +1,120 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns backend-tests.loggers-webhooks-test + (:require + [app.common.uuid :as uuid] + [app.db :as db] + [app.http :as http] + [app.storage :as sto] + [backend-tests.helpers :as th] + [clojure.test :as t] + [mockery.core :refer [with-mocks]])) + +(t/use-fixtures :once th/state-init) +(t/use-fixtures :each th/database-reset) + +(t/deftest process-event-handler-with-no-webhooks + (with-mocks [submit-mock {:target 'app.worker/submit! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + res (th/run-task! :process-webhook-event + {:props + {:app.loggers.webhooks/event + {:type "mutation" + :name "create-project" + :props {:team-id (:default-team-id prof)}}}})] + + (t/is (= 0 (:call-count @submit-mock))) + (t/is (nil? res))))) + +(t/deftest process-event-handler + (with-mocks [submit-mock {:target 'app.worker/submit! :return nil}] + (let [prof (th/create-profile* 1 {:is-active true}) + whk (th/create-webhook* {:team-id (:default-team-id prof)}) + res (th/run-task! :process-webhook-event + {:props + {:app.loggers.webhooks/event + {:type "mutation" + :name "create-project" + :props {:team-id (:default-team-id prof)}}}})] + + (t/is (= 1 (:call-count @submit-mock))) + (t/is (nil? res))))) + +(t/deftest run-webhook-handler-1 + (with-mocks [http-mock {:target 'app.http.client/req! :return {:status 200}}] + (let [prof (th/create-profile* 1 {:is-active true}) + whk (th/create-webhook* {:team-id (:default-team-id prof)}) + evt {:type "mutation" + :name "create-project" + :props {:team-id (:default-team-id prof)}} + res (th/run-task! :run-webhook + {:props + {:app.loggers.webhooks/event evt + :app.loggers.webhooks/config whk}})] + + (t/is (= 1 (:call-count @http-mock))) + + (let [rows (th/db-exec! ["select * from webhook_delivery where webhook_id=?" + (:id whk)])] + (t/is (= 1 (count rows))) + (t/is (nil? (-> rows first :error-code)))) + + ;; Refresh webhook + (let [whk' (th/db-get :webhook {:id (:id whk)})] + (t/is (nil? (:error-code whk'))) + (prn whk')) + + ))) + +(t/deftest run-webhook-handler-2 + (with-mocks [http-mock {:target 'app.http.client/req! :return {:status 400}}] + (let [prof (th/create-profile* 1 {:is-active true}) + whk (th/create-webhook* {:team-id (:default-team-id prof)}) + evt {:type "mutation" + :name "create-project" + :props {:team-id (:default-team-id prof)}} + res (th/run-task! :run-webhook + {:props + {:app.loggers.webhooks/event evt + :app.loggers.webhooks/config whk}})] + + (t/is (= 1 (:call-count @http-mock))) + + (let [rows (th/db-query :webhook-delivery {:webhook-id (:id whk)})] + (t/is (= 1 (count rows))) + (t/is (= "unexpected-status:400" (-> rows first :error-code)))) + + ;; Refresh webhook + (let [whk' (th/db-get :webhook {:id (:id whk)})] + (t/is (= "unexpected-status:400" (:error-code whk'))) + (t/is (= 1 (:error-count whk')))) + + + ;; RUN 2 times more + + (th/run-task! :run-webhook + {:props + {:app.loggers.webhooks/event evt + :app.loggers.webhooks/config whk}}) + + (th/run-task! :run-webhook + {:props + {:app.loggers.webhooks/event evt + :app.loggers.webhooks/config whk}}) + + + (let [rows (th/db-query :webhook-delivery {:webhook-id (:id whk)})] + (t/is (= 3 (count rows))) + (t/is (= "unexpected-status:400" (-> rows first :error-code)))) + + ;; Refresh webhook + (let [whk' (th/db-get :webhook {:id (:id whk)})] + (t/is (= "unexpected-status:400" (:error-code whk'))) + (t/is (= 3 (:error-count whk'))) + (t/is (false? (:is-active whk')))) + + ))) diff --git a/backend/test/backend_tests/rpc_webhooks_test.clj b/backend/test/backend_tests/rpc_webhooks_test.clj index 37c1c83a0b..6d2f97a170 100644 --- a/backend/test/backend_tests/rpc_webhooks_test.clj +++ b/backend/test/backend_tests/rpc_webhooks_test.clj @@ -12,8 +12,6 @@ [app.storage :as sto] [backend-tests.helpers :as th] [clojure.test :as t] - [datoteka.fs :as fs] - [datoteka.io :as io] [mockery.core :refer [with-mocks]])) (t/use-fixtures :once th/state-init) @@ -52,7 +50,6 @@ (t/is (= (:mtype params) (:mtype result))) (vreset! whook result)))) - (th/reset-mock! http-mock) (t/testing "update webhook 1 (success)" @@ -144,3 +141,41 @@ (t/is (= (:code error-data) :object-not-found))))) ))) + +(t/deftest webhooks-quotes + (with-mocks [http-mock {:target 'app.http.client/req! + :return {:status 200}}] + + (let [prof (th/create-profile* 1 {:is-active true}) + team-id (:default-team-id prof) + params {::th/type :create-webhook + :profile-id (:id prof) + :team-id team-id + :uri "http://example.com" + :mtype "application/json"} + out1 (th/command! params) + out2 (th/command! params) + out3 (th/command! params) + out4 (th/command! params) + out5 (th/command! params) + out6 (th/command! params) + out7 (th/command! params) + out8 (th/command! params) + out9 (th/command! params)] + + (t/is (= 8 (:call-count @http-mock))) + + (t/is (nil? (:error out1))) + (t/is (nil? (:error out2))) + (t/is (nil? (:error out3))) + (t/is (nil? (:error out4))) + (t/is (nil? (:error out5))) + (t/is (nil? (:error out6))) + (t/is (nil? (:error out7))) + (t/is (nil? (:error out8))) + + (let [error (:error out9) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :restriction)) + (t/is (= (:code error-data) :webhooks-quote-reached)))))) From 240e480b2edca0b141db678f5c53fb777bc1fde3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 12 Dec 2022 11:10:22 +0100 Subject: [PATCH 340/682] :tada: Allow application/json on Accept header --- backend/src/app/http/middleware.clj | 49 +++++++++++++++++++++++++++-- frontend/src/app/main/repo.cljs | 3 ++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index 5f687be12e..23e229652c 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -32,6 +32,12 @@ {:name ::params :compile (constantly ymw/wrap-params)}) +(def ^:private json-mapper + (json/mapper + {:encode-key-fn str/camel + :decode-key-fn (comp keyword str/kebab) + :pretty true})) + (defn wrap-parse-request [handler] (letfn [(process-request [request] @@ -46,7 +52,7 @@ (str/starts-with? header "application/json") (with-open [is (yrq/body request)] - (let [params (json/decode is)] + (let [params (json/decode is json-mapper)] (-> request (assoc :body-params params) (update :params merge params)))) @@ -117,7 +123,32 @@ (finally (.close ^OutputStream output-stream)))))) - (format-response [response request] + (json-streamable-body [data] + (reify yrs/StreamableResponseBody + (-write-body-to-stream [_ _ output-stream] + (try + + (with-open [bos (buffered-output-stream output-stream buffer-size)] + (json/write! bos data json-mapper)) + + (catch java.io.IOException _cause + ;; Do nothing, EOF means client closes connection abruptly + nil) + (catch Throwable cause + (l/warn :hint "unexpected error on encoding response" + :cause cause)) + (finally + (.close ^OutputStream output-stream)))))) + + (format-response-with-json [response _] + (let [body (yrs/body response)] + (if (or (boolean? body) (coll? body)) + (-> response + (update :headers assoc "content-type" "application/json") + (assoc :body (json-streamable-body body))) + response))) + + (format-response-with-transit [response request] (let [body (yrs/body response)] (if (or (boolean? body) (coll? body)) (let [qs (yrq/query request) @@ -130,6 +161,20 @@ (assoc :body (transit-streamable-body body opts)))) response))) + (format-response [response request] + (let [accept (yrq/get-header request "accept")] + (cond + (or (= accept "application/transit+json") + (str/includes? accept "application/transit+json")) + (format-response-with-transit response request) + + (or (= accept "application/json") + (str/includes? accept "application/json")) + (format-response-with-json response request) + + :else + (format-response-with-transit response request)))) + (process-response [response request] (cond-> response (map? response) (format-response request)))] diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 67993a4192..aea3ed1691 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -60,6 +60,7 @@ http/conditional-decode-transit)] (->> (http/send! {:method :get :uri (u/join @cf/public-uri "api/rpc/query/" (name id)) + :headers {"accept" "application/transit+json"} :credentials "include" :query params}) (rx/map decode-transit) @@ -71,6 +72,7 @@ [id params] (->> (http/send! {:method :post :uri (u/join @cf/public-uri "api/rpc/mutation/" (name id)) + :headers {"accept" "application/transit+json"} :credentials "include" :body (http/transit-data params)}) (rx/map http/conditional-decode-transit) @@ -88,6 +90,7 @@ (->> (http/send! {:method method :uri (u/join @cf/public-uri "api/rpc/command/" (name id)) :credentials "include" + :headers {"accept" "application/transit+json"} :body (when (= method :post) (if form-data? (http/form-data params) From ae79ee435e62559213d9823e653d1084861208ad Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 12 Dec 2022 13:26:08 +0100 Subject: [PATCH 341/682] :tada: Add many rpc calls to webhooks registry --- backend/src/app/rpc/commands/files.clj | 18 ++++++++------ backend/src/app/rpc/commands/files/create.clj | 6 +++-- backend/src/app/rpc/mutations/fonts.clj | 24 ++++++++++--------- 3 files changed, 28 insertions(+), 20 deletions(-) diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 9b3a6bbc97..27df59f4e1 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -17,6 +17,7 @@ [app.common.types.shape-tree :as ctt] [app.db :as db] [app.db.sql :as sql] + [app.loggers.webhooks :as-alias webhooks] [app.rpc.commands.files.thumbnails :as-alias thumbs] [app.rpc.cond :as-alias cond] [app.rpc.doc :as-alias doc] @@ -762,7 +763,8 @@ (s/keys :req-un [::profile-id ::name ::id])) (sv/defmethod ::rename-file - {::doc/added "1.17"} + {::doc/added "1.17" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (check-edition-permissions! conn profile-id id) @@ -806,7 +808,8 @@ (s/keys :req-un [::profile-id ::id ::is-shared])) (sv/defmethod ::set-file-shared - {::doc/added "1.17"} + {::doc/added "1.17" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [id profile-id is-shared] :as params}] (db/with-atomic [conn pool] (check-edition-permissions! conn profile-id id) @@ -829,14 +832,14 @@ (s/keys :req-un [::id ::profile-id])) (sv/defmethod ::delete-file - {::doc/added "1.17"} + {::doc/added "1.17" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (check-edition-permissions! conn profile-id id) (absorb-library conn params) (mark-file-deleted conn params))) - ;; --- MUTATION COMMAND: link-file-to-library (def sql:link-file-to-library @@ -852,7 +855,8 @@ (s/keys :req-un [::profile-id ::file-id ::library-id])) (sv/defmethod ::link-file-to-library - {::doc/added "1.17"} + {::doc/added "1.17" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [profile-id file-id library-id] :as params}] (when (= file-id library-id) (ex/raise :type :validation @@ -863,7 +867,6 @@ (check-edition-permissions! conn profile-id library-id) (link-file-to-library conn params))) - ;; --- MUTATION COMMAND: unlink-file-from-library (defn unlink-file-from-library @@ -876,7 +879,8 @@ (s/keys :req-un [::profile-id ::file-id ::library-id])) (sv/defmethod ::unlink-file-from-library - {::doc/added "1.17"} + {::doc/added "1.17" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] (check-edition-permissions! conn profile-id file-id) diff --git a/backend/src/app/rpc/commands/files/create.clj b/backend/src/app/rpc/commands/files/create.clj index eb5ebe675a..d0283abca8 100644 --- a/backend/src/app/rpc/commands/files/create.clj +++ b/backend/src/app/rpc/commands/files/create.clj @@ -11,7 +11,8 @@ [app.common.types.file :as ctf] [app.common.uuid :as uuid] [app.db :as db] - [app.loggers.audit :as audit] + [app.loggers.audit :as-alias audit] + [app.loggers.webhooks :as-alias webhooks] [app.rpc.commands.files :as files] [app.rpc.doc :as-alias doc] [app.rpc.permissions :as perms] @@ -75,7 +76,8 @@ ::files/features])) (sv/defmethod ::create-file - {::doc/added "1.17"} + {::doc/added "1.17" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] (db/with-atomic [conn pool] (proj/check-edition-permissions! conn profile-id project-id) diff --git a/backend/src/app/rpc/mutations/fonts.clj b/backend/src/app/rpc/mutations/fonts.clj index 674254df30..b92a3fc864 100644 --- a/backend/src/app/rpc/mutations/fonts.clj +++ b/backend/src/app/rpc/mutations/fonts.clj @@ -12,6 +12,7 @@ [app.common.uuid :as uuid] [app.db :as db] [app.loggers.audit :as-alias audit] + [app.loggers.webhooks :as-alias webhooks] [app.media :as media] [app.rpc.climit :as-alias climit] [app.rpc.doc :as-alias doc] @@ -43,6 +44,8 @@ ::font-id ::font-family ::font-weight ::font-style])) (sv/defmethod ::create-font-variant + {::doc/added "1.3" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [team-id profile-id] :as params}] (let [cfg (update cfg :storage media/configure-assets-storage)] (teams/check-edition-permissions! pool profile-id team-id) @@ -119,19 +122,16 @@ (s/def ::update-font (s/keys :req-un [::profile-id ::team-id ::id ::name])) -(def sql:update-font - "update team_font_variant - set font_family = ? - where team_id = ? - and font_id = ?") - (sv/defmethod ::update-font - {::climit/queue :process-font} + {::doc/added "1.3" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [team-id profile-id id name] :as params}] (db/with-atomic [conn pool] (teams/check-edition-permissions! conn profile-id team-id) - (db/exec-one! conn [sql:update-font name team-id id]) - nil)) + (db/update! conn :team-font-variant + {:font-family name} + {:font-id id + :team-id team-id}))) ;; --- DELETE FONT @@ -139,10 +139,11 @@ (s/keys :req-un [::profile-id ::team-id ::id])) (sv/defmethod ::delete-font + {::doc/added "1.3" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [id team-id profile-id] :as params}] (db/with-atomic [conn pool] (teams/check-edition-permissions! conn profile-id team-id) - (db/update! conn :team-font-variant {:deleted-at (dt/now)} {:font-id id :team-id team-id}) @@ -154,7 +155,8 @@ (s/keys :req-un [::profile-id ::team-id ::id])) (sv/defmethod ::delete-font-variant - {::doc/added "1.3"} + {::doc/added "1.3" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [id team-id profile-id] :as params}] (db/with-atomic [conn pool] (teams/check-edition-permissions! conn profile-id team-id) From 029efefb62ff5fa4e3d952898c294d9e4e60bafe Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 13 Dec 2022 16:43:28 +0100 Subject: [PATCH 342/682] :sparkles: Import/export layout data --- common/src/app/common/pages/common.cljc | 1 - common/src/app/common/types/shape/layout.cljc | 2 - frontend/src/app/main/ui/shapes/export.cljs | 84 +++++++++++++++++-- .../options/menus/layout_container.cljs | 1 - frontend/src/app/util/import/parser.cljs | 53 +++++++++++- 5 files changed, 128 insertions(+), 13 deletions(-) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index de34e6b742..885cdb9678 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -76,7 +76,6 @@ :layout :layout-container :layout-dir :layout-container :layout-gap :layout-container - :layout-type :layout-container :layout-wrap-type :layout-container :layout-padding-type :layout-container :layout-padding :layout-container diff --git a/common/src/app/common/types/shape/layout.cljc b/common/src/app/common/types/shape/layout.cljc index 1fb0f40509..bfe99ed200 100644 --- a/common/src/app/common/types/shape/layout.cljc +++ b/common/src/app/common/types/shape/layout.cljc @@ -50,7 +50,6 @@ (s/def ::row-gap ::us/safe-number) (s/def ::column-gap ::us/safe-number) -(s/def ::layout-type #{:flex :grid}) (s/def ::layout-gap (s/keys :opt-un [::row-gap ::column-gap])) @@ -60,7 +59,6 @@ ::layout-flex-dir ::layout-gap ::layout-gap-type - ::layout-type ::layout-wrap-type ::layout-padding-type ::layout-padding diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 1ea742cebb..00de59a7a6 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -332,16 +332,84 @@ :penpot:preserve-scroll ((d/nilf str) (:preserve-scroll interaction))}])]))) +(defn- export-layout-container-data + [{:keys [layout + layout-flex-dir + layout-gap + layout-gap-type + layout-wrap-type + layout-padding-type + layout-padding + layout-justify-content + layout-align-items + layout-align-content]}] + + (when layout + (mf/html + [:> "penpot:layout" + #js {:penpot:layout (d/name layout) + :penpot:layout-flex-dir (d/name layout-flex-dir) + :penpot:layout-gap-type (d/name layout-gap-type) + :penpot:layout-gap-row (:row-gap layout-gap) + :penpot:layout-gap-column (:column-gap layout-gap) + :penpot:layout-wrap-type (d/name layout-wrap-type) + :penpot:layout-padding-type (d/name layout-padding-type) + :penpot:layout-padding-p1 (:p1 layout-padding) + :penpot:layout-padding-p2 (:p2 layout-padding) + :penpot:layout-padding-p3 (:p3 layout-padding) + :penpot:layout-padding-p4 (:p4 layout-padding) + :penpot:layout-justify-content (d/name layout-justify-content) + :penpot:layout-align-items (d/name layout-align-items) + :penpot:layout-align-content (d/name layout-align-content)}]))) + +(defn- export-layout-item-data + [{:keys [layout-item-margin + layout-item-margin-type + layout-item-h-sizing + layout-item-v-sizing + layout-item-max-h + layout-item-min-h + layout-item-max-w + layout-item-min-w + layout-item-align-self]}] + + (when (or layout-item-margin + layout-item-margin-type + layout-item-h-sizing + layout-item-v-sizing + layout-item-max-h + layout-item-min-h + layout-item-max-w + layout-item-min-w + layout-item-align-self) + (mf/html + [:> "penpot:layout-item" + #js {:penpot:layout-item-margin-m1 (:m1 layout-item-margin) + :penpot:layout-item-margin-m2 (:m2 layout-item-margin) + :penpot:layout-item-margin-m3 (:m3 layout-item-margin) + :penpot:layout-item-margin-m4 (:m4 layout-item-margin) + :penpot:layout-item-margin-type (d/name layout-item-margin-type) + :penpot:layout-item-h-sizing (d/name layout-item-h-sizing) + :penpot:layout-item-v-sizing (d/name layout-item-v-sizing) + :penpot:layout-item-max-h layout-item-max-h + :penpot:layout-item-min-h layout-item-min-h + :penpot:layout-item-max-w layout-item-max-w + :penpot:layout-item-min-w layout-item-min-w + :penpot:layout-item-align-self (d/name layout-item-align-self)}]))) + + (mf/defc export-data [{:keys [shape]}] (let [props (-> (obj/create) (add-data shape) (add-library-refs shape))] [:> "penpot:shape" props - (export-shadow-data shape) - (export-blur-data shape) - (export-exports-data shape) - (export-svg-data shape) - (export-interactions-data shape) - (export-fills-data shape) - (export-strokes-data shape) - (export-grid-data shape)])) + (export-shadow-data shape) + (export-blur-data shape) + (export-exports-data shape) + (export-svg-data shape) + (export-interactions-data shape) + (export-fills-data shape) + (export-strokes-data shape) + (export-grid-data shape) + (export-layout-container-data shape) + (export-layout-item-data shape)])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs index fe63d9831c..bb1d404723 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layout_container.cljs @@ -382,7 +382,6 @@ on-padding-change (fn [type prop val] - (prn "??" type prop val) (cond (and (= type :simple) (= prop :p1)) (st/emit! (dwsl/update-layout ids {:layout-padding {:p1 val :p3 val}})) diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index 34479481ef..cd908e1123 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -869,6 +869,54 @@ :style parse-style)))) +(defn add-layout-container-data [props node] + (if-let [data (get-data node :penpot:layout)] + (merge props + (d/without-nils + {:layout (get-meta data :layout keyword) + :layout-flex-dir (get-meta data :layout-flex-dir keyword) + :layout-wrap-type (get-meta data :layout-wrap-type keyword) + + :layout-gap-type (get-meta data :layout-gap-type keyword) + :layout-gap + (d/without-nils + {:row-gap (get-meta data :layout-gap-row d/parse-double) + :column-gap (get-meta data :layout-gap-column d/parse-double)}) + + :layout-padding-type (get-meta data :layout-padding-type keyword) + :layout-padding + (d/without-nils + {:p1 (get-meta data :layout-padding-p1 d/parse-double) + :p2 (get-meta data :layout-padding-p2 d/parse-double) + :p3 (get-meta data :layout-padding-p3 d/parse-double) + :p4 (get-meta data :layout-padding-p4 d/parse-double)}) + + :layout-justify-content (get-meta data :layout-justify-content keyword) + :layout-align-items (get-meta data :layout-align-items keyword) + :layout-align-content (get-meta data :layout-align-content keyword)})) + props)) + +(defn add-layout-item-data [props node] + (if-let [data (get-data node :penpot:layout-item)] + (merge props + (d/without-nils + {:layout-item-margin + (d/without-nils + {:m1 (get-meta data :layout-item-margin-m1 d/parse-double) + :m2 (get-meta data :layout-item-margin-m2 d/parse-double) + :m3 (get-meta data :layout-item-margin-m3 d/parse-double) + :m4 (get-meta data :layout-item-margin-m4 d/parse-double)}) + + :layout-item-margin-type (get-meta data :layout-item-margin-type keyword) + :layout-item-h-sizing (get-meta data :layout-item-h-sizing keyword) + :layout-item-v-sizing (get-meta data :layout-item-v-sizing keyword) + :layout-item-max-h (get-meta data :layout-item-max-h d/parse-double) + :layout-item-min-h (get-meta data :layout-item-min-h d/parse-double) + :layout-item-max-w (get-meta data :layout-item-max-w d/parse-double) + :layout-item-min-w (get-meta data :layout-item-min-w d/parse-double) + :layout-item-align-self (get-meta data :layout-item-align-self keyword)})) + props)) + (defn parse-data [type node] @@ -894,7 +942,10 @@ (add-svg-content node)) (cond-> (= :frame type) - (add-frame-data node)) + (-> (add-frame-data node) + (add-layout-container-data node))) + + (add-layout-item-data node) (cond-> (= :group type) (add-group-data node)) From 2fbd1d807835d52accb83e6db74d856da616aeed Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 13 Dec 2022 16:46:34 +0100 Subject: [PATCH 343/682] :bug: Fix problem with hug and item margins --- .../geom/shapes/flex_layout/bounds.cljc | 52 +++++++++---------- .../app/main/ui/workspace/viewport/debug.cljs | 4 +- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc index b762090d3e..64e673fd23 100644 --- a/common/src/app/common/geom/shapes/flex_layout/bounds.cljc +++ b/common/src/app/common/geom/shapes/flex_layout/bounds.cljc @@ -8,7 +8,6 @@ (:require [app.common.geom.point :as gpt] [app.common.geom.shapes.points :as gpo] - [app.common.math :as mth] [app.common.types.shape.layout :as ctl])) (defn child-layout-bound-points @@ -55,38 +54,32 @@ (gpt/add (hv (/ width 2))) (and col? h-end?) - (gpt/add (hv width)))] + (gpt/add (hv width))) - (cond-> [base-p] - (and (mth/almost-zero? min-width) (mth/almost-zero? min-height)) - (conj (cond-> base-p - row? - (gpt/add (hv width)) + ;; We need some height/width to calculate the bounds. We stablish the minimum + min-width (max min-width 0.01) + min-height (max min-height 0.01)] - col? - (gpt/add (vv height)))) + (-> [base-p] + (conj (cond-> base-p + (or row? h-start?) + (gpt/add (hv min-width)) - (not (mth/almost-zero? min-width)) - (conj (cond-> base-p - (or row? h-start?) - (gpt/add (hv min-width)) + (and col? h-center?) + (gpt/add (hv (/ min-width 2))) - (and col? h-center?) - (gpt/add (hv (/ min-width 2))) + (and col? h-center?) + (gpt/subtract (hv min-width)))) - (and col? h-center?) - (gpt/subtract (hv min-width)))) + (conj (cond-> base-p + (or col? v-start?) + (gpt/add (vv min-height)) - (not (mth/almost-zero? min-height)) - (conj (cond-> base-p - (or col? v-start?) - (gpt/add (vv min-height)) + (and row? v-center?) + (gpt/add (vv (/ min-height 2))) - (and row? v-center?) - (gpt/add (vv (/ min-height 2))) - - (and row? v-end?) - (gpt/subtract (vv min-height))))))) + (and row? v-end?) + (gpt/subtract (vv min-height))))))) (defn layout-content-bounds [bounds {:keys [layout-padding] :as parent} children] @@ -107,8 +100,11 @@ child-bounds (if (or (ctl/fill-height? child) (ctl/fill-height? child)) (child-layout-bound-points parent child parent-bounds child-bounds) - child-bounds)] - (gpo/parent-coords-bounds child-bounds parent-bounds)))] + child-bounds) + + [margin-top margin-right margin-bottom margin-left] (ctl/child-margins child)] + (-> (gpo/parent-coords-bounds child-bounds parent-bounds) + (gpo/pad-points (- margin-top) (- margin-right) (- margin-bottom) (- margin-left)))))] (as-> children $ (map child-bounds $) diff --git a/frontend/src/app/main/ui/workspace/viewport/debug.cljs b/frontend/src/app/main/ui/workspace/viewport/debug.cljs index b8079c324f..845e38ca41 100644 --- a/frontend/src/app/main/ui/workspace/viewport/debug.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/debug.cljs @@ -75,8 +75,8 @@ points [start-p (-> start-p (gpt/add (xv line-width))) (-> start-p (gpt/add (xv line-width)) (gpt/add (yv line-height))) - (-> start-p (gpt/add (yv line-height))) - ]] + (-> start-p (gpt/add (yv line-height)))]] + [:g.layout-line {:key (dm/str "line-" idx)} [:polygon {:points (->> points (map #(dm/fmt "%, %" (:x %) (:y %))) (str/join " ")) :style {:stroke "red" :stroke-width (/ 2 zoom) :stroke-dasharray (dm/str (/ 10 zoom) " " (/ 5 zoom))}}]]))])))) From 03228a9801dbdeea655f8927fca47711be6ce1a0 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 13 Dec 2022 16:47:28 +0100 Subject: [PATCH 344/682] :bug: Fix problem with snap pixel --- common/src/app/common/geom/shapes/pixel_precision.cljc | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/common/src/app/common/geom/shapes/pixel_precision.cljc b/common/src/app/common/geom/shapes/pixel_precision.cljc index f6451ab022..fa7c501317 100644 --- a/common/src/app/common/geom/shapes/pixel_precision.cljc +++ b/common/src/app/common/geom/shapes/pixel_precision.cljc @@ -32,6 +32,7 @@ ratio-width (/ target-width curr-width) ratio-height (/ target-height curr-height) scalev (gpt/point ratio-width ratio-height)] + (-> modifiers (ctm/resize scalev origin transform transform-inverse)))) @@ -41,7 +42,6 @@ corner (gpt/point bounds) target-corner (gpt/round corner) deltav (gpt/to-vec corner target-corner)] - (ctm/move modifiers deltav))) (defn set-pixel-precision @@ -56,8 +56,10 @@ has-resize? (size-pixel-precision shape points)) points - (cond-> (:points shape) - has-resize? (gco/transform-points (ctm/modifiers->transform modifiers)))] + (if has-resize? + (-> (:points shape) + (gco/transform-points (ctm/modifiers->transform modifiers)) ) + points)] [modifiers points])] (position-pixel-precision modifiers shape points))) From 5aaaab4f801b26f5c5e2a64b3e4c09e809822e72 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 13 Dec 2022 16:48:04 +0100 Subject: [PATCH 345/682] :sparkles: Show ghost when moving elemets from/into layout --- .../app/main/data/workspace/transforms.cljs | 20 ++++++++++--- .../src/app/main/ui/workspace/viewport.cljs | 28 +++++++++++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 14cac6d30b..f6f0801572 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -28,6 +28,7 @@ [app.main.data.workspace.undo :as dwu] [app.main.snap :as snap] [app.main.streams :as ms] + [app.util.dom :as dom] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk])) @@ -415,6 +416,14 @@ (rx/take 1) (rx/map #(start-move from-position)))))) +(defn set-ghost-displacement + [move-vector] + (ptk/reify ::set-ghost-displacement + ptk/EffectEvent + (effect [_ _ _] + (when-let [node (dom/get-element-by-class "ghost-outline")] + (dom/set-property! node "transform" (gmt/translate-matrix move-vector)))))) + (defn- start-move ([from-position] (start-move from-position nil)) ([from-position ids] @@ -501,6 +510,9 @@ (dwm/build-change-frame-modifiers objects selected target-frame drop-index) (dwm/set-modifiers))))) + (->> move-stream + (rx/map (comp set-ghost-displacement first))) + ;; Last event will write the modifiers creating the changes (->> move-stream (rx/last) @@ -508,10 +520,10 @@ (fn [[_ target-frame drop-index]] (let [undo-id (uuid/next)] (rx/of (dwu/start-undo-transaction undo-id) - (move-shapes-to-frame ids target-frame drop-index) - (dwm/apply-modifiers {:undo-transation? false}) - (finish-transform) - (dwu/commit-undo-transaction undo-id)))))))))))))) + (move-shapes-to-frame ids target-frame drop-index) + (dwm/apply-modifiers {:undo-transation? false}) + (finish-transform) + (dwu/commit-undo-transaction undo-id)))))))))))))) (s/def ::direction #{:up :down :right :left}) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index e144ed7312..e6888a28ed 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -11,6 +11,7 @@ [app.common.data.macros :as dm] [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as cph] + [app.common.types.shape.layout :as ctl] [app.main.refs :as refs] [app.main.ui.context :as ctx] [app.main.ui.hooks :as ui-hooks] @@ -293,14 +294,25 @@ :modifiers modifiers}]) (when show-frame-outline? - [:& outline/shape-outlines - {:objects objects-modified - :hover #{(->> @hover-ids - (filter #(cph/frame-shape? (get base-objects %))) - (remove selected) - (first))} - :zoom zoom - :modifiers modifiers}]) + (let [outlined-frame-id + (->> @hover-ids + (filter #(cph/frame-shape? (get base-objects %))) + (remove selected) + (first)) + outlined-frame (get objects outlined-frame-id)] + [:* + [:& outline/shape-outlines + {:objects objects-modified + :hover #{outlined-frame-id} + :zoom zoom + :modifiers modifiers}] + + (when (ctl/layout? outlined-frame) + [:g.ghost-outline + [:& outline/shape-outlines + {:objects base-objects + :selected selected + :zoom zoom}]])])) (when show-outlines? [:& outline/shape-outlines From fd7d189bb74887a726a057fbc4d4db4f7084da90 Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 13 Dec 2022 22:22:21 +0100 Subject: [PATCH 346/682] :lipstick: Change style of cancel button on webhooks modal --- .../styles/main/partials/dashboard-team.scss | 13 +++++++++++++ frontend/src/app/main/ui/dashboard/team.cljs | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss index 24c41c34d0..88c626550e 100644 --- a/frontend/resources/styles/main/partials/dashboard-team.scss +++ b/frontend/resources/styles/main/partials/dashboard-team.scss @@ -528,6 +528,19 @@ .webhooks-modal { .action-buttons { gap: 10px; + + .cancel-button { + border: 1px solid $color-gray-30; + background: $color-canvas; + border-radius: 3px; + padding: 0.5rem 1rem; + cursor: pointer; + margin-right: 8px; + + &:hover { + background: $color-gray-20; + } + } } .input-checkbox label { font-size: 14px; diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 9e1c50c922..71160b21b3 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -676,7 +676,7 @@ [:div.modal-footer [:div.action-buttons - [:input.btn-gray.btn-large + [:input.cancel-button {:type "button" :value (tr "labels.cancel") :on-click #(modal/hide!)}] From d7459db292ea4e3d3fe829a22beed350035abef7 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 23:13:11 +0100 Subject: [PATCH 347/682] :tada: Add task deduplication by label --- backend/src/app/migrations.clj | 3 ++ .../migrations/sql/0087-mod-task-table.sql | 9 ++++ backend/src/app/worker.clj | 52 +++++++++++++------ 3 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 backend/src/app/migrations/sql/0087-mod-task-table.sql diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index dda506b11c..f659d93ee7 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -268,6 +268,9 @@ {:name "0086-add-webhook-delivery-table" :fn (mg/resource "app/migrations/sql/0086-add-webhook-delivery-table.sql")} + + {:name "0087-mod-task-table" + :fn (mg/resource "app/migrations/sql/0087-mod-task-table.sql")} ]) diff --git a/backend/src/app/migrations/sql/0087-mod-task-table.sql b/backend/src/app/migrations/sql/0087-mod-task-table.sql new file mode 100644 index 0000000000..75379eca61 --- /dev/null +++ b/backend/src/app/migrations/sql/0087-mod-task-table.sql @@ -0,0 +1,9 @@ +ALTER TABLE task + ADD COLUMN label text NULL; + +ALTER TABLE task + ALTER COLUMN label SET STORAGE external; + +CREATE INDEX task__label__idx + ON task (label, name, queue) + WHERE status = 'new'; diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index b4305a3b91..9adaa7ee13 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -97,7 +97,7 @@ (l/info :hint "registry initialized" :tasks (count tasks)) (reduce-kv (fn [registry k v] (let [tname (name k)] - (l/debug :hint "register task" :name tname) + (l/trace :hint "register task" :name tname) (assoc registry tname (wrap-task-handler metrics tname v)))) {} tasks)) @@ -214,10 +214,10 @@ (db/create-array conn "uuid" ids)]] (db/exec-one! conn sql) - (l/debug :hist "dispatcher: push tasks to redis" + (l/debug :hist "dispatcher: queue tasks" :queue queue :tasks (count ids) - :queued res))) + :total-queued res))) (run-batch! [rconn] (db/with-atomic [conn pool] @@ -445,7 +445,7 @@ :else (try - (l/trace :hint "worker: executing task" + (l/debug :hint "worker: executing task" :worker-id worker-id :task-id (:id task) :task-name (:name task) @@ -649,39 +649,57 @@ options)))) (def ^:private sql:insert-new-task - "insert into task (id, name, props, queue, priority, max_retries, scheduled_at) - values (?, ?, ?, ?, ?, ?, now() + ?) + "insert into task (id, name, props, queue, label, priority, max_retries, scheduled_at) + values (?, ?, ?, ?, ?, ?, ?, now() + ?) returning id") +(def ^:private + sql:remove-not-started-tasks + "delete from task + where name=? and queue=? and label=? and status = 'new' and scheduled_at > now()") + +(s/def ::label string?) (s/def ::task (s/or :kw keyword? :str string?)) (s/def ::queue (s/or :kw keyword? :str string?)) -(s/def ::delay (s/or :int ::us/integer :duration dt/duration?)) +(s/def ::delay (s/or :int integer? :duration dt/duration?)) (s/def ::conn (s/or :pool ::db/pool :connection some?)) -(s/def ::priority ::us/integer) -(s/def ::max-retries ::us/integer) +(s/def ::priority integer?) +(s/def ::max-retries integer?) +(s/def ::dedupe boolean?) (s/def ::submit-options - (s/keys :req [::task ::conn] - :opt [::delay ::queue ::priority ::max-retries])) + (s/and + (s/keys :req [::task ::conn] + :opt [::label ::delay ::queue ::priority ::max-retries ::dedupe]) + (fn [{:keys [::dedupe ::label] :or {label ""}}] + (if dedupe + (not= label "") + true)))) (defn submit! - [& {:keys [::task ::delay ::queue ::priority ::max-retries ::conn] - :or {delay 0 queue :default priority 100 max-retries 3} + [& {:keys [::task ::delay ::queue ::priority ::max-retries ::conn ::dedupe ::label] + :or {delay 0 queue :default priority 100 max-retries 3 label ""} :as options}] - (us/verify ::submit-options options) + (us/verify! ::submit-options options) (let [duration (dt/duration delay) interval (db/interval duration) props (-> options extract-props db/tjson) id (uuid/next) tenant (cf/get :tenant) task (d/name task) - queue (str/ffmt "%:%" tenant (d/name queue))] + queue (str/ffmt "%:%" tenant (d/name queue)) + deleted (when dedupe + (-> (db/exec-one! conn [sql:remove-not-started-tasks task queue label]) + :next.jdbc/update-count))] (l/debug :hint "submit task" :name task :queue queue + :label label + :dedupe (boolean dedupe) + :deleted (or deleted 0) :in (dt/format-duration duration)) - (db/exec-one! conn [sql:insert-new-task id task props - queue priority max-retries interval]) + (db/exec-one! conn [sql:insert-new-task id task props queue + label priority max-retries interval]) id)) From 782f2ed57d1000b1de434d94b557865f2267d65d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 23:13:48 +0100 Subject: [PATCH 348/682] :tada: Enable comments events on webhooks --- backend/src/app/rpc/commands/comments.clj | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj index 0214e31117..f2aad072d5 100644 --- a/backend/src/app/rpc/commands/comments.clj +++ b/backend/src/app/rpc/commands/comments.clj @@ -10,6 +10,7 @@ [app.common.geom.point :as gpt] [app.common.spec :as us] [app.db :as db] + [app.loggers.webhooks :as-alias webhooks] [app.rpc.commands.files :as files] [app.rpc.doc :as-alias doc] [app.rpc.queries.teams :as teams] @@ -43,6 +44,7 @@ #(or (:file-id %) (:team-id %)))) (sv/defmethod ::get-comment-threads + {::doc/added "1.15"} [{:keys [pool] :as cfg} params] (with-open [conn (db/open pool)] (retrieve-comment-threads conn params))) @@ -245,7 +247,8 @@ (sv/defmethod ::create-comment-thread {::retry/max-retries 3 ::retry/matches retry/conflict-db-insert? - ::doc/added "1.15"} + ::doc/added "1.15" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] (db/with-atomic [conn pool] (files/check-comment-permissions! conn profile-id file-id share-id) @@ -364,7 +367,8 @@ :opt-un [::share-id])) (sv/defmethod ::create-comment - {::doc/added "1.15"} + {::doc/added "1.15" + ::webhooks/event? true} [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] (create-comment conn params))) @@ -483,7 +487,8 @@ (s/keys :req-un [::profile-id ::id])) (sv/defmethod ::delete-comment - {::doc/added "1.15"} + {::doc/added "1.15" + ::webhooks/event? true} [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] (db/with-atomic [conn pool] (let [comment (db/get-by-id conn :comment id {:for-update true})] @@ -529,4 +534,3 @@ :frame-id frame-id} {:id (:id thread)}) nil))) - From d56082307bb0756976cc2dd871195858f9d55416 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 23:14:55 +0100 Subject: [PATCH 349/682] :tada: Add update-file (batched) to webhooks --- backend/src/app/loggers/audit.clj | 32 ++++++++++++++----- backend/src/app/loggers/webhooks.clj | 5 ++- backend/src/app/rpc.clj | 15 +++++++-- backend/src/app/rpc/commands/files/update.clj | 17 ++++++++-- 4 files changed, 54 insertions(+), 15 deletions(-) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index d004fe5d41..55c3339fde 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -104,11 +104,18 @@ (s/def ::type ::us/string) (s/def ::props (s/map-of ::us/keyword any?)) (s/def ::ip-addr ::us/string) + (s/def ::webhooks/event? ::us/boolean) +(s/def ::webhooks/batch-timeout ::dt/duration) +(s/def ::webhooks/batch-key + (s/or :fn fn? :str string? :kw keyword?)) (s/def ::event (s/keys :req-un [::type ::name ::profile-id] - :opt-un [::ip-addr ::props ::webhooks/event?])) + :opt-un [::ip-addr ::props] + :opt [::webhooks/event? + ::webhooks/batch-timeout + ::webhooks/batch-key])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; COLLECTOR @@ -153,13 +160,22 @@ (when (and (contains? cf/flags :webhooks) (::webhooks/event? event)) - (wrk/submit! ::wrk/conn pool - ::wrk/task :process-webhook-event - ::wrk/queue :webhooks - ::wrk/max-retries 0 - ::webhooks/event (-> params - (dissoc :ip-addr) - (dissoc :type)))))) + (let [batch-key (::webhooks/batch-key event) + batch-timeout (::webhooks/batch-timeout event)] + (wrk/submit! ::wrk/conn pool + ::wrk/task :process-webhook-event + ::wrk/queue :webhooks + ::wrk/max-retries 0 + ::wrk/delay (or batch-timeout 0) + ::wrk/label (cond + (fn? batch-key) (batch-key (:props event)) + (keyword? batch-key) (name batch-key) + (string? batch-key) batch-key + :else "default") + ::wrk/dedupe true + ::webhooks/event (-> params + (dissoc :ip-addr) + (dissoc :type))))))) (defn submit! "Submit audit event to the collector." diff --git a/backend/src/app/loggers/webhooks.clj b/backend/src/app/loggers/webhooks.clj index a5849c6da2..b05b815581 100644 --- a/backend/src/app/loggers/webhooks.clj +++ b/backend/src/app/loggers/webhooks.clj @@ -11,6 +11,7 @@ [app.common.logging :as l] [app.common.transit :as t] [app.common.uri :as uri] + [app.config :as cf] [app.db :as db] [app.http.client :as http] [app.util.json :as json] @@ -56,6 +57,7 @@ [_ {:keys [::db/pool] :as cfg}] (fn [{:keys [props] :as task}] (let [event (::event props)] + (l/debug :hint "process webhook event" :name (:name event)) @@ -134,7 +136,8 @@ :webhook-mtype (:mtype whook)) (let [req {:uri (:uri whook) - :headers {"content-type" (:mtype whook)} + :headers {"content-type" (:mtype whook) + "user-agent" (str/ffmt "penpot/%" (:main cf/version))} :timeout (dt/duration "4s") :method :post :body body}] diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index df4fd4ea91..849f8370c2 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -171,9 +171,18 @@ :profile-id profile-id :ip-addr (some-> request audit/parse-client-ip) :props props - ::webhooks/event? (or (::webhooks/event? mdata) - (::webhooks/event? resultm) - false)}] + ::webhooks/batch-key + (or (::webhooks/batch-key mdata) + (::webhooks/batch-key resultm)) + + ::webhooks/batch-timeout + (or (::webhooks/batch-timeout mdata) + (::webhooks/batch-timeout resultm)) + + ::webhooks/event? + (or (::webhooks/event? mdata) + (::webhooks/event? resultm) + false)}] (audit/submit! collector event))) diff --git a/backend/src/app/rpc/commands/files/update.clj b/backend/src/app/rpc/commands/files/update.clj index a590d63e8e..520df28972 100644 --- a/backend/src/app/rpc/commands/files/update.clj +++ b/backend/src/app/rpc/commands/files/update.clj @@ -17,6 +17,7 @@ [app.config :as cf] [app.db :as db] [app.loggers.audit :as audit] + [app.loggers.webhooks :as-alias webhooks] [app.metrics :as mtx] [app.msgbus :as mbus] [app.rpc.climit :as-alias climit] @@ -122,12 +123,18 @@ ;; set is different than the persisted one, update it on the ;; database. +(defn webhook-batch-keyfn + [props] + (str "rpc:update-file:" (:id props))) + (sv/defmethod ::update-file {::climit/queue :update-file ::climit/key-fn :id + ::webhooks/event? true + ::webhooks/batch-timeout (dt/duration "2s") + ::webhooks/batch-key webhook-batch-keyfn ::doc/added "1.17"} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] - (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) (db/xact-lock! conn id) @@ -173,8 +180,12 @@ {:id id}))) (-> (update-fn cfg params) - (vary-meta assoc ::audit/props {:project-id (:project-id file) - :team-id (:team-id file)})))))) + (vary-meta assoc ::audit/replace-props + {:id (:id file) + :name (:name file) + :features (:features file) + :project-id (:project-id file) + :team-id (:team-id file)})))))) (defn- update-file* [{:keys [conn] :as cfg} {:keys [file changes session-id profile-id] :as params}] From 84903ae1f252d44fca5b8b9683b49d1b5266b3ac Mon Sep 17 00:00:00 2001 From: Pablo Alba Date: Tue, 13 Dec 2022 22:13:49 +0100 Subject: [PATCH 350/682] :bug: Fix unable to select text at assets inputs in firefox --- CHANGES.md | 1 + .../src/app/main/ui/workspace/sidebar/assets.cljs | 11 +++++++---- .../workspace/sidebar/options/menus/typography.cljs | 5 ++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 22a8037b54..dfa47c58aa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -19,6 +19,7 @@ - Fix twitter support account link [Taiga #4279](https://tree.taiga.io/project/penpot/issue/4279) - Fix lang autodetect issue [Taiga #4277](https://tree.taiga.io/project/penpot/issue/4277) - Fix adding an extra page on import [Taiga #4543](https://tree.taiga.io/project/penpot/task/4543) +- Fix unable to select text at assets inputs in firefox [Taiga #4572](https://tree.taiga.io/project/penpot/issue/4572) ### :arrow_up: Deps updates ### :heart: Community contributions by (Thank you!) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 9746e04384..2f5434f334 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -1286,7 +1286,7 @@ #(on-asset-click % (:id color) (partial apply-color (:id color)))) :ref item-ref - :draggable (not workspace-read-only?) + :draggable (and (not workspace-read-only?) (not (:editing @state))) :on-drag-start on-color-drag-start :on-drag-enter on-drag-enter :on-drag-leave on-drag-leave @@ -1557,6 +1557,8 @@ (let [item-ref (mf/use-ref) dragging? (mf/use-state false) workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) + editing? (= editing-id (:id typography)) + open? (mf/use-state editing?) on-drop (mf/use-fn (mf/deps typography dragging? selected-typographies selected-typographies-full selected-typographies-paths move-typography) @@ -1587,7 +1589,7 @@ (on-asset-drag-start event typography selected-typographies item-ref :typographies identity))))] [:div.typography-container {:ref item-ref - :draggable (not workspace-read-only?) + :draggable (and (not workspace-read-only?) (not @open?)) :on-drag-start on-typography-drag-start :on-drag-enter on-drag-enter :on-drag-leave on-drag-leave @@ -1603,8 +1605,9 @@ :selected? (contains? selected-typographies (:id typography)) :on-click #(on-asset-click % (:id typography) (partial apply-typography typography)) - :editing? (= editing-id (:id typography)) - :focus-name? (= (:rename-typography local-data) (:id typography))}] + :editing? editing? + :focus-name? (= (:rename-typography local-data) (:id typography)) + :open? open?}] (when @dragging? [:div.dragging])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs index 62c77cb0df..a6fae4c3b6 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/typography.cljs @@ -456,9 +456,8 @@ ;; In summary, this need to a good UX/UI/IMPL rework. (mf/defc typography-entry - [{:keys [typography local? selected? on-click on-change on-detach on-context-menu editing? focus-name? file]}] - (let [open? (mf/use-state editing?) - hover-detach (mf/use-state false) + [{:keys [typography local? selected? on-click on-change on-detach on-context-menu editing? focus-name? file open?]}] + (let [hover-detach (mf/use-state false) name-input-ref (mf/use-ref) on-change-ref (mf/use-ref nil) workspace-read-only? (mf/use-ctx ctx/workspace-read-only?) From e9064611cfd4b3db1fffeb3fa433d667d4ee1b73 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 14 Dec 2022 14:38:56 +0100 Subject: [PATCH 351/682] :sparkles: Improve thumbnail generation --- .../app/main/data/workspace/thumbnails.cljs | 40 ++++++++++--------- .../shapes/frame/dynamic_modifiers.cljs | 2 +- .../shapes/frame/thumbnail_render.cljs | 7 +--- .../app/main/ui/workspace/viewport/hooks.cljs | 3 +- 4 files changed, 25 insertions(+), 27 deletions(-) diff --git a/frontend/src/app/main/data/workspace/thumbnails.cljs b/frontend/src/app/main/data/workspace/thumbnails.cljs index 63c290f770..cb66f59cb3 100644 --- a/frontend/src/app/main/data/workspace/thumbnails.cljs +++ b/frontend/src/app/main/data/workspace/thumbnails.cljs @@ -64,28 +64,30 @@ (watch [_ state _] (let [object-id (dm/str page-id frame-id) file-id (or file-id (:current-file-id state)) - blob-result (thumbnail-stream object-id)] + blob-result (thumbnail-stream object-id) + params {:file-id file-id :object-id object-id :data nil}] - (->> blob-result - (rx/merge-map - (fn [blob] - (if (some? blob) - (wapi/read-file-as-data-url blob) - (rx/of nil)))) + (rx/concat + ;; Delete the thumbnail first so if we interrupt we can regenerate after + (rp/cmd! :upsert-file-object-thumbnail params) + (->> blob-result + (rx/merge-map + (fn [blob] + (if (some? blob) + (wapi/read-file-as-data-url blob) + (rx/of nil)))) - (rx/merge-map - (fn [data] - (if (some? file-id) - (let [params {:file-id file-id :object-id object-id :data data}] - (rx/merge - ;; Update the local copy of the thumbnails so we don't need to request it again - (if (some? data) - (rx/of #(update % :workspace-thumbnails assoc object-id data)) - (rx/empty)) - (->> (rp/cmd! :upsert-file-object-thumbnail params) - (rx/ignore)))) + (rx/merge-map + (fn [data] + (if (some? file-id) + (let [params (assoc params :data data)] + (rx/merge + ;; Update the local copy of the thumbnails so we don't need to request it again + (rx/of #(update % :workspace-thumbnails assoc object-id data)) + (->> (rp/cmd! :upsert-file-object-thumbnail params) + (rx/ignore)))) - (rx/empty)))))))))) + (rx/empty))))))))))) (defn- extract-frame-changes "Process a changes set in a commit to extract the frames that are changing" diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs index c4df18c29f..21bef00465 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/dynamic_modifiers.cljs @@ -232,7 +232,7 @@ (dom/remove-attribute! (dom/get-parent shape-node) "display"))) (doseq [{:keys [frame shape]} add-children] - (let [frame-node (get-shape-node node frame) + (let [frame-node (get-shape-node frame) shape-node (get-shape-node shape) use-node diff --git a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs index 6e60fb6b60..040b944857 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame/thumbnail_render.cljs @@ -149,6 +149,7 @@ (mf/use-callback (fn [] (when (not @disable-ref?) + (reset! render-frame? true) (reset! regenerate-thumbnail true)))) on-load-frame-dom @@ -168,12 +169,6 @@ (when (and (some? prev-thumbnail-data) (nil? thumbnail-data)) (rx/push! updates-str :update)))) - (mf/use-effect - (mf/deps @render-frame? thumbnail-data) - (fn [] - (when (and (some? thumbnail-data) @render-frame?) - (reset! render-frame? false)))) - (mf/use-effect (mf/deps force-render) (fn [] diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index c35b2aedbb..1c0868f36a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -278,8 +278,9 @@ (> zoom 1.3) ;; Zoom >= 25% will show frames hovering + ;; Also, if we're moving a shape over the frame we need to remove the thumbnail (and - (>= zoom 0.25) + (or (= :move transform) (>= zoom 0.25)) (or (contains? hover-ids? id) (contains? @last-hover-ids id))) ;; Otherwise, if it's a selected frame From 44e87e75e6f5cd463434ed042bae7dcb991d4cd5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 12 Dec 2022 15:33:55 +0100 Subject: [PATCH 352/682] :bug: Fix unexpected redirect on invitation acceptation --- frontend/src/app/main.cljs | 1 + frontend/src/app/main/data/users.cljs | 1 + frontend/src/app/main/ui/routes.cljs | 37 ++++++++------------------- 3 files changed, 13 insertions(+), 26 deletions(-) diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index ce313ce90a..5831edea8c 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -60,6 +60,7 @@ (rx/merge (rx/of (ev/initialize) (du/initialize-profile)) + (->> stream (rx/filter du/profile-fetched?) (rx/take 1) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 96507a85ee..51f9626225 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -164,6 +164,7 @@ (swap! storage dissoc :redirect-url) (.replace js/location redirect-url)) (rt/nav' :dashboard-projects {:team-id team-id}))))] + (ptk/reify ::logged-in IDeref (-deref [_] profile) diff --git a/frontend/src/app/main/ui/routes.cljs b/frontend/src/app/main/ui/routes.cljs index 0f0aefd2fe..d6ffde3fed 100644 --- a/frontend/src/app/main/ui/routes.cljs +++ b/frontend/src/app/main/ui/routes.cljs @@ -10,9 +10,9 @@ [app.common.uuid :as uuid] [app.config :as cf] [app.main.data.users :as du] + [app.main.repo :as rp] [app.main.store :as st] [app.util.router :as rt] - [app.util.storage :refer [storage]] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk])) @@ -93,32 +93,17 @@ (defn on-navigate [router path] - (let [match (match-path router path) - profile (:profile @storage) - nopath? (or (= path "") (= path "/")) - path-name (-> match :data :name) - authpath? (some #(= path-name %) '(:auth-login - :auth-register - :auth-register-validate - :auth-register-success - :auth-recovery-request - :auth-recovery)) - authed? (and (not (nil? profile)) - (not= (:id profile) uuid/zero))] + (if-let [match (match-path router path)] + (st/emit! (rt/navigated match)) - (cond - (or (and nopath? authed? (nil? match)) - (and authpath? authed?)) - (st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)})) - - (and (not authed?) (nil? match)) - (st/emit! (rt/nav :auth-login)) - - (nil? match) - (st/emit! (rt/assign-exception {:type :not-found})) - - :else - (st/emit! (rt/navigated match))))) + ;; We just recheck with an additional profile request; this avoids + ;; some race conditions that causes unexpected redirects on + ;; invitations workflows (and probably other cases). + (->> (rp/query! :profile) + (rx/subs (fn [{:keys [id] :as profile}] + (if (= id uuid/zero) + (st/emit! (rt/nav :auth-login)) + (st/emit! (rt/nav :dashboard-projects {:team-id (du/get-current-team-id profile)})))))))) (defn init-routes [] From be5053ce225062980dd830848d2af941b3c789b1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 09:50:52 +0100 Subject: [PATCH 353/682] :sparkles: Use the same value for created_at and tracked_at on audit --- backend/src/app/loggers/audit.clj | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 55c3339fde..3d1d172b67 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -143,11 +143,13 @@ (defn- persist-event! [pool event] (us/verify! ::event event) - (let [params {:id (uuid/next) + (let [now (dt/now) + params {:id (uuid/next) :name (:name event) :type (:type event) :profile-id (:profile-id event) - :tracked-at (dt/now) + :created-at now + :tracked-at now :ip-addr (:ip-addr event) :props (:props event)}] From 7a9172560dec6138c2911b34d90a3e0b19fcfcbc Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 09:52:30 +0100 Subject: [PATCH 354/682] :recycle: Move teams queries and mutations to commands --- backend/src/app/rpc.clj | 1 + backend/src/app/rpc/commands/auth.clj | 2 +- backend/src/app/rpc/commands/comments.clj | 2 +- backend/src/app/rpc/commands/files.clj | 2 +- backend/src/app/rpc/commands/management.clj | 3 +- backend/src/app/rpc/commands/teams.clj | 817 ++++++++++++++++++ backend/src/app/rpc/commands/verify_token.clj | 2 +- backend/src/app/rpc/commands/webhooks.clj | 2 +- backend/src/app/rpc/mutations/fonts.clj | 2 +- backend/src/app/rpc/mutations/media.clj | 2 +- backend/src/app/rpc/mutations/profile.clj | 12 +- backend/src/app/rpc/mutations/projects.clj | 35 +- backend/src/app/rpc/mutations/teams.clj | 412 ++------- backend/src/app/rpc/queries/comments.clj | 2 +- backend/src/app/rpc/queries/files.clj | 68 +- backend/src/app/rpc/queries/fonts.clj | 3 +- backend/src/app/rpc/queries/projects.clj | 2 +- backend/src/app/rpc/queries/teams.clj | 228 +---- backend/test/backend_tests/helpers.clj | 9 +- frontend/src/app/main/data/dashboard.cljs | 29 +- frontend/src/app/main/data/users.cljs | 4 +- frontend/src/app/main/data/workspace.cljs | 2 +- frontend/src/app/main/repo.cljs | 5 + 23 files changed, 993 insertions(+), 653 deletions(-) create mode 100644 backend/src/app/rpc/commands/teams.clj diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 849f8370c2..61c59a58a1 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -268,6 +268,7 @@ 'app.rpc.commands.management 'app.rpc.commands.verify-token 'app.rpc.commands.search + 'app.rpc.commands.teams 'app.rpc.commands.auth 'app.rpc.commands.ldap 'app.rpc.commands.demo diff --git a/backend/src/app/rpc/commands/auth.clj b/backend/src/app/rpc/commands/auth.clj index d34a55ab2f..9ad8cbf1ef 100644 --- a/backend/src/app/rpc/commands/auth.clj +++ b/backend/src/app/rpc/commands/auth.clj @@ -16,9 +16,9 @@ [app.http.session :as session] [app.loggers.audit :as audit] [app.rpc.climit :as climit] + [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] - [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] [app.tokens :as tokens] [app.util.services :as sv] diff --git a/backend/src/app/rpc/commands/comments.clj b/backend/src/app/rpc/commands/comments.clj index f2aad072d5..45d83557ed 100644 --- a/backend/src/app/rpc/commands/comments.clj +++ b/backend/src/app/rpc/commands/comments.clj @@ -12,8 +12,8 @@ [app.db :as db] [app.loggers.webhooks :as-alias webhooks] [app.rpc.commands.files :as files] + [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] - [app.rpc.queries.teams :as teams] [app.rpc.retry :as retry] [app.util.blob :as blob] [app.util.services :as sv] diff --git a/backend/src/app/rpc/commands/files.clj b/backend/src/app/rpc/commands/files.clj index 27df59f4e1..98518feb94 100644 --- a/backend/src/app/rpc/commands/files.clj +++ b/backend/src/app/rpc/commands/files.clj @@ -19,13 +19,13 @@ [app.db.sql :as sql] [app.loggers.webhooks :as-alias webhooks] [app.rpc.commands.files.thumbnails :as-alias thumbs] + [app.rpc.commands.teams :as teams] [app.rpc.cond :as-alias cond] [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] [app.rpc.permissions :as perms] [app.rpc.queries.projects :as projects] [app.rpc.queries.share-link :refer [retrieve-share-link]] - [app.rpc.queries.teams :as teams] [app.util.blob :as blob] [app.util.pointer-map :as pmap] [app.util.services :as sv] diff --git a/backend/src/app/rpc/commands/management.clj b/backend/src/app/rpc/commands/management.clj index 7e56bec6fc..d647c90c48 100644 --- a/backend/src/app/rpc/commands/management.clj +++ b/backend/src/app/rpc/commands/management.clj @@ -15,10 +15,9 @@ [app.db :as db] [app.rpc.commands.binfile :as binfile] [app.rpc.commands.files :as files] + [app.rpc.commands.teams :as teams :refer [create-project-role create-project]] [app.rpc.doc :as-alias doc] - [app.rpc.mutations.projects :refer [create-project-role create-project]] [app.rpc.queries.projects :as proj] - [app.rpc.queries.teams :as teams] [app.util.blob :as blob] [app.util.pointer-map :as pmap] [app.util.services :as sv] diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj new file mode 100644 index 0000000000..ec88484e03 --- /dev/null +++ b/backend/src/app/rpc/commands/teams.clj @@ -0,0 +1,817 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.rpc.commands.teams + (:require + [app.common.data :as d] + [app.common.exceptions :as ex] + [app.common.logging :as l] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.config :as cf] + [app.db :as db] + [app.emails :as eml] + [app.loggers.audit :as audit] + [app.media :as media] + [app.rpc.climit :as climit] + [app.rpc.doc :as-alias doc] + [app.rpc.helpers :as rph] + [app.rpc.permissions :as perms] + [app.rpc.queries.profile :as profile] + [app.storage :as sto] + [app.tokens :as tokens] + [app.util.services :as sv] + [app.util.time :as dt] + [clojure.spec.alpha :as s] + [cuerdas.core :as str] + [promesa.core :as p] + [promesa.exec :as px])) + +;; --- Helpers & Specs + +(s/def ::id ::us/uuid) +(s/def ::name ::us/string) +(s/def ::profile-id ::us/uuid) +(s/def ::file-id ::us/uuid) +(s/def ::team-id ::us/uuid) + +(def ^:private sql:team-permissions + "select tpr.is_owner, + tpr.is_admin, + tpr.can_edit + from team_profile_rel as tpr + join team as t on (t.id = tpr.team_id) + where tpr.profile_id = ? + and tpr.team_id = ? + and t.deleted_at is null") + +(defn get-permissions + [conn profile-id team-id] + (let [rows (db/exec! conn [sql:team-permissions profile-id team-id]) + is-owner (boolean (some :is-owner rows)) + is-admin (boolean (some :is-admin rows)) + can-edit (boolean (some :can-edit rows))] + (when (seq rows) + {:is-owner is-owner + :is-admin (or is-owner is-admin) + :can-edit (or is-owner is-admin can-edit) + :can-read true}))) + +(def has-edit-permissions? + (perms/make-edition-predicate-fn get-permissions)) + +(def has-read-permissions? + (perms/make-read-predicate-fn get-permissions)) + +(def check-edition-permissions! + (perms/make-check-fn has-edit-permissions?)) + +(def check-read-permissions! + (perms/make-check-fn has-read-permissions?)) + +;; --- Query: Teams + +(declare retrieve-teams) + +(s/def ::get-teams + (s/keys :req-un [::profile-id])) + +(sv/defmethod ::get-teams + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id]}] + (with-open [conn (db/open pool)] + (retrieve-teams conn profile-id))) + +(def sql:teams + "select t.*, + tp.is_owner, + tp.is_admin, + tp.can_edit, + (t.id = ?) as is_default + from team_profile_rel as tp + join team as t on (t.id = tp.team_id) + where t.deleted_at is null + and tp.profile_id = ? + order by tp.created_at asc") + +(defn process-permissions + [team] + (let [is-owner (:is-owner team) + is-admin (:is-admin team) + can-edit (:can-edit team) + permissions {:type :membership + :is-owner is-owner + :is-admin (or is-owner is-admin) + :can-edit (or is-owner is-admin can-edit)}] + (-> team + (dissoc :is-owner :is-admin :can-edit) + (assoc :permissions permissions)))) + +(defn retrieve-teams + [conn profile-id] + (let [defaults (profile/retrieve-additional-data conn profile-id)] + (->> (db/exec! conn [sql:teams (:default-team-id defaults) profile-id]) + (mapv process-permissions)))) + +;; --- Query: Team (by ID) + +(declare retrieve-team) + +(s/def ::get-team + (s/keys :req-un [::profile-id ::id])) + +(sv/defmethod ::get-team + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id id]}] + (with-open [conn (db/open pool)] + (retrieve-team conn profile-id id))) + +(defn retrieve-team + [conn profile-id team-id] + (let [defaults (profile/retrieve-additional-data conn profile-id) + sql (str "WITH teams AS (" sql:teams ") SELECT * FROM teams WHERE id=?") + result (db/exec-one! conn [sql (:default-team-id defaults) profile-id team-id])] + (when-not result + (ex/raise :type :not-found + :code :team-does-not-exist)) + (process-permissions result))) + + +;; --- Query: Team Members + +(def sql:team-members + "select tp.*, + p.id, + p.email, + p.fullname as name, + p.fullname as fullname, + p.photo_id, + p.is_active + from team_profile_rel as tp + join profile as p on (p.id = tp.profile_id) + where tp.team_id = ?") + +(defn retrieve-team-members + [conn team-id] + (db/exec! conn [sql:team-members team-id])) + +(s/def ::team-id ::us/uuid) +(s/def ::get-team-members + (s/keys :req-un [::profile-id ::team-id])) + +(sv/defmethod ::get-team-members + {::doc/added "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id team-id]}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id team-id) + (retrieve-team-members conn team-id))) + + +;; --- Query: Team Users + +(declare retrieve-users) +(declare retrieve-team-for-file) + +(s/def ::get-team-users + (s/and (s/keys :req-un [::profile-id] + :opt-un [::team-id ::file-id]) + #(or (:team-id %) (:file-id %)))) + +(sv/defmethod ::get-team-users + {::doc/added "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id team-id file-id]}] + (with-open [conn (db/open pool)] + (if team-id + (do + (check-read-permissions! conn profile-id team-id) + (retrieve-users conn team-id)) + (let [{team-id :id} (retrieve-team-for-file conn file-id)] + (check-read-permissions! conn profile-id team-id) + (retrieve-users conn team-id))))) + +;; This is a similar query to team members but can contain more data +;; because some user can be explicitly added to project or file (not +;; implemented in UI) + +(def sql:team-users + "select pf.id, pf.fullname, pf.photo_id + from profile as pf + inner join team_profile_rel as tpr on (tpr.profile_id = pf.id) + where tpr.team_id = ? + union + select pf.id, pf.fullname, pf.photo_id + from profile as pf + inner join project_profile_rel as ppr on (ppr.profile_id = pf.id) + inner join project as p on (ppr.project_id = p.id) + where p.team_id = ? + union + select pf.id, pf.fullname, pf.photo_id + from profile as pf + inner join file_profile_rel as fpr on (fpr.profile_id = pf.id) + inner join file as f on (fpr.file_id = f.id) + inner join project as p on (f.project_id = p.id) + where p.team_id = ?") + +(def sql:team-by-file + "select p.team_id as id + from project as p + join file as f on (p.id = f.project_id) + where f.id = ?") + +(defn retrieve-users + [conn team-id] + (db/exec! conn [sql:team-users team-id team-id team-id])) + +(defn retrieve-team-for-file + [conn file-id] + (->> [sql:team-by-file file-id] + (db/exec-one! conn))) + +;; --- Query: Team Stats + +(declare retrieve-team-stats) + +(s/def ::get-team-stats + (s/keys :req-un [::profile-id ::team-id])) + +(sv/defmethod ::get-team-stats + {::doc/added "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id team-id]}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id team-id) + (retrieve-team-stats conn team-id))) + +(def sql:team-stats + "select (select count(*) from project where team_id = ?) as projects, + (select count(*) from file as f join project as p on (p.id = f.project_id) where p.team_id = ?) as files") + +(defn retrieve-team-stats + [conn team-id] + (db/exec-one! conn [sql:team-stats team-id team-id])) + + +;; --- Query: Team invitations + +(s/def ::get-team-invitations + (s/keys :req-un [::profile-id ::team-id])) + +(def sql:team-invitations + "select email_to as email, role, (valid_until < now()) as expired + from team_invitation where team_id = ? order by valid_until desc") + +(defn get-team-invitations + [conn team-id] + (->> (db/exec! conn [sql:team-invitations team-id]) + (mapv #(update % :role keyword)))) + +(sv/defmethod ::get-team-invitations + {::doc/added "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id team-id]}] + (with-open [conn (db/open pool)] + (check-read-permissions! conn profile-id team-id) + (get-team-invitations conn team-id))) + +;; --- Mutation: Create Team + +(declare create-team) +(declare create-project) +(declare create-project-role) +(declare ^:private create-team*) +(declare ^:private create-team-role) +(declare ^:private create-team-default-project) + +(s/def ::create-team + (s/keys :req-un [::profile-id ::name] + :opt-un [::id])) + +(sv/defmethod ::create-team + {::doc/added "1.17"} + [{:keys [::db/pool] :as cfg} params] + (db/with-atomic [conn pool] + (create-team conn params))) + +(defn create-team + "This is a complete team creation process, it creates the team + object and all related objects (default role and default project)." + [conn params] + (let [team (create-team* conn params) + params (assoc params + :team-id (:id team) + :role :owner) + project (create-team-default-project conn params)] + (create-team-role conn params) + (assoc team :default-project-id (:id project)))) + +(defn- create-team* + [conn {:keys [id name is-default] :as params}] + (let [id (or id (uuid/next)) + is-default (if (boolean? is-default) is-default false)] + (db/insert! conn :team + {:id id + :name name + :is-default is-default}))) + +(defn- create-team-role + [conn {:keys [team-id profile-id role] :as params}] + (let [params {:team-id team-id + :profile-id profile-id}] + (->> (perms/assign-role-flags params role) + (db/insert! conn :team-profile-rel)))) + +(defn- create-team-default-project + [conn {:keys [team-id profile-id] :as params}] + (let [project {:id (uuid/next) + :team-id team-id + :name "Drafts" + :is-default true} + project (create-project conn project)] + (create-project-role conn {:project-id (:id project) + :profile-id profile-id + :role :owner}) + project)) + +;; NOTE: we have project creation here because there are cyclic +;; dependency between teams and projects namespaces, and the project +;; creation happens in both sides, on team creation and on simple +;; project creation, so it make sense to have this functions in this +;; namespace too. + +(defn create-project + [conn {:keys [id team-id name is-default] :as params}] + (let [id (or id (uuid/next)) + is-default (if (boolean? is-default) is-default false)] + (db/insert! conn :project + {:id id + :name name + :team-id team-id + :is-default is-default}))) + +(defn create-project-role + [conn {:keys [project-id profile-id role]}] + (let [params {:project-id project-id + :profile-id profile-id}] + (->> (perms/assign-role-flags params role) + (db/insert! conn :project-profile-rel)))) + +;; --- Mutation: Update Team + +(s/def ::update-team + (s/keys :req-un [::profile-id ::name ::id])) + +(sv/defmethod ::update-team + {::doc/added "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [id name profile-id] :as params}] + (db/with-atomic [conn pool] + (check-edition-permissions! conn profile-id id) + (db/update! conn :team + {:name name} + {:id id}) + nil)) + + +;; --- Mutation: Leave Team + +(declare role->params) + +(s/def ::reassign-to ::us/uuid) +(s/def ::leave-team + (s/keys :req-un [::profile-id ::id] + :opt-un [::reassign-to])) + +(defn leave-team + [conn {:keys [id profile-id reassign-to]}] + (let [perms (get-permissions conn profile-id id) + members (retrieve-team-members conn id)] + + (cond + ;; we can only proceed if there are more members in the team + ;; besides the current profile + (<= (count members) 1) + (ex/raise :type :validation + :code :no-enough-members-for-leave + :context {:members (count members)}) + + ;; if the `reassign-to` is filled and has a different value + ;; than the current profile-id, we proceed to reassing the + ;; owner role to profile identified by the `reassign-to`. + (and reassign-to (not= reassign-to profile-id)) + (let [member (d/seek #(= reassign-to (:id %)) members)] + (when-not member + (ex/raise :type :not-found :code :member-does-not-exist)) + + ;; unasign owner role to current profile + (db/update! conn :team-profile-rel + {:is-owner false} + {:team-id id + :profile-id profile-id}) + + ;; assign owner role to new profile + (db/update! conn :team-profile-rel + (role->params :owner) + {:team-id id :profile-id reassign-to})) + + ;; and finally, if all other conditions does not match and the + ;; current profile is owner, we dont allow it because there + ;; must always be an owner. + (:is-owner perms) + (ex/raise :type :validation + :code :owner-cant-leave-team + :hint "releasing owner before leave")) + + (db/delete! conn :team-profile-rel + {:profile-id profile-id + :team-id id}) + + nil)) + + +(sv/defmethod ::leave-team + {::doc/added "1.17"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (leave-team conn params))) + +;; --- Mutation: Delete Team + +(s/def ::delete-team + (s/keys :req-un [::profile-id ::id])) + +;; TODO: right now just don't allow delete default team, in future it +;; should raise a specific exception for signal that this action is +;; not allowed. + +(sv/defmethod ::delete-team + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + (db/with-atomic [conn pool] + (let [perms (get-permissions conn profile-id id)] + (when-not (:is-owner perms) + (ex/raise :type :validation + :code :only-owner-can-delete-team)) + + (db/update! conn :team + {:deleted-at (dt/now)} + {:id id :is-default false}) + nil))) + + +;; --- Mutation: Team Update Role + +(s/def ::team-id ::us/uuid) +(s/def ::member-id ::us/uuid) +;; Temporarily disabled viewer role +;; https://tree.taiga.io/project/uxboxproject/issue/1083 +;; (s/def ::role #{:owner :admin :editor :viewer}) +(s/def ::role #{:owner :admin :editor}) + +(defn role->params + [role] + (case role + :admin {:is-owner false :is-admin true :can-edit true} + :editor {:is-owner false :is-admin false :can-edit true} + :owner {:is-owner true :is-admin true :can-edit true} + :viewer {:is-owner false :is-admin false :can-edit false})) + +(defn update-team-member-role + [conn {:keys [team-id profile-id member-id role] :as params}] + ;; We retrieve all team members instead of query the + ;; database for a single member. This is just for + ;; convenience, if this becomes a bottleneck or problematic, + ;; we will change it to more efficient fetch mechanisms. + (let [perms (get-permissions conn profile-id team-id) + members (retrieve-team-members conn team-id) + member (d/seek #(= member-id (:id %)) members) + + is-owner? (:is-owner perms) + is-admin? (:is-admin perms)] + + ;; If no member is found, just 404 + (when-not member + (ex/raise :type :not-found + :code :member-does-not-exist)) + + ;; First check if we have permissions to change roles + (when-not (or is-owner? is-admin?) + (ex/raise :type :validation + :code :insufficient-permissions)) + + ;; Don't allow change role of owner member + (when (:is-owner member) + (ex/raise :type :validation + :code :cant-change-role-to-owner)) + + ;; Don't allow promote to owner to admin users. + (when (and (not is-owner?) (= role :owner)) + (ex/raise :type :validation + :code :cant-promote-to-owner)) + + (let [params (role->params role)] + ;; Only allow single owner on team + (when (= role :owner) + (db/update! conn :team-profile-rel + {:is-owner false} + {:team-id team-id + :profile-id profile-id})) + + (db/update! conn :team-profile-rel + params + {:team-id team-id + :profile-id member-id}) + nil))) + +(s/def ::update-team-member-role + (s/keys :req-un [::profile-id ::team-id ::member-id ::role])) + +(sv/defmethod ::update-team-member-role + {::doc/added "1.17"} + [{:keys [::db/pool] :as cfg} params] + (db/with-atomic [conn pool] + (update-team-member-role conn params))) + + +;; --- Mutation: Delete Team Member + +(s/def ::delete-team-member + (s/keys :req-un [::profile-id ::team-id ::member-id])) + +(sv/defmethod ::delete-team-member + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [team-id profile-id member-id] :as params}] + (db/with-atomic [conn pool] + (let [perms (get-permissions conn profile-id team-id)] + (when-not (or (:is-owner perms) + (:is-admin perms)) + (ex/raise :type :validation + :code :insufficient-permissions)) + + (when (= member-id profile-id) + (ex/raise :type :validation + :code :cant-remove-yourself)) + + (db/delete! conn :team-profile-rel {:profile-id member-id + :team-id team-id}) + + nil))) + +;; --- Mutation: Update Team Photo + +(declare ^:private upload-photo) +(declare ^:private update-team-photo) + +(s/def ::file ::media/upload) +(s/def ::update-team-photo + (s/keys :req-un [::profile-id ::team-id ::file])) + +(sv/defmethod ::update-team-photo + {::doc/added "1.17"} + [cfg {:keys [file] :as params}] + ;; Validate incoming mime type + (media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"}) + (let [cfg (update cfg :storage media/configure-assets-storage)] + (update-team-photo cfg params))) + +(defn update-team-photo + [{:keys [pool storage executor] :as cfg} {:keys [profile-id team-id] :as params}] + (p/let [team (px/with-dispatch executor + (retrieve-team pool profile-id team-id)) + photo (upload-photo cfg params)] + + ;; Mark object as touched for make it ellegible for tentative + ;; garbage collection. + (when-let [id (:photo-id team)] + (sto/touch-object! storage id)) + + ;; Save new photo + (db/update! pool :team + {:photo-id (:id photo)} + {:id team-id}) + + (assoc team :photo-id (:id photo)))) + +(defn upload-photo + [{:keys [storage executor climit] :as cfg} {:keys [file]}] + (letfn [(get-info [content] + (climit/with-dispatch (:process-image climit) + (media/run {:cmd :info :input content}))) + + (generate-thumbnail [info] + (climit/with-dispatch (:process-image climit) + (media/run {:cmd :profile-thumbnail + :format :jpeg + :quality 85 + :width 256 + :height 256 + :input info}))) + + ;; Function responsible of calculating cryptographyc hash of + ;; the provided data. + (calculate-hash [data] + (px/with-dispatch executor + (sto/calculate-hash data)))] + + (p/let [info (get-info file) + thumb (generate-thumbnail info) + hash (calculate-hash (:data thumb)) + content (-> (sto/content (:data thumb) (:size thumb)) + (sto/wrap-with-hash hash))] + (sto/put-object! storage {::sto/content content + ::sto/deduplicate? true + :bucket "profile" + :content-type (:mtype thumb)})))) + +;; --- Mutation: Create Team Invitation + +(def sql:upsert-team-invitation + "insert into team_invitation(team_id, email_to, role, valid_until) + values (?, ?, ?, ?) + on conflict(team_id, email_to) do + update set role = ?, valid_until = ?, updated_at = now();") + +(defn- create-invitation + [{:keys [conn sprops team profile role email] :as cfg}] + (let [member (profile/retrieve-profile-data-by-email conn email) + token-exp (dt/in-future "168h") ;; 7 days + email (str/lower email) + itoken (tokens/generate sprops + {:iss :team-invitation + :exp token-exp + :profile-id (:id profile) + :role role + :team-id (:id team) + :member-email (:email member email) + :member-id (:id member)}) + ptoken (tokens/generate sprops + {:iss :profile-identity + :profile-id (:id profile) + :exp (dt/in-future {:days 30})})] + + (when (and member (not (eml/allow-send-emails? conn member))) + (ex/raise :type :validation + :code :member-is-muted + :email email + :hint "the profile has reported repeatedly as spam or has bounces")) + + ;; Secondly check if the invited member email is part of the global spam/bounce report. + (when (eml/has-bounce-reports? conn email) + (ex/raise :type :validation + :code :email-has-permanent-bounces + :email email + :hint "the email you invite has been repeatedly reported as spam or bounce")) + + (when (contains? cf/flags :log-invitation-tokens) + (l/trace :hint "invitation token" :token itoken)) + + ;; When we have email verification disabled and invitation user is + ;; already present in the database, we proceed to add it to the + ;; team as-is, without email roundtrip. + + ;; TODO: if member does not exists and email verification is + ;; disabled, we should proceed to create the profile (?) + (if (and (not (contains? cf/flags :email-verification)) + (some? member)) + (let [params (merge {:team-id (:id team) + :profile-id (:id member)} + (role->params role))] + + ;; Insert the invited member to the team + (db/insert! conn :team-profile-rel params {:on-conflict-do-nothing true}) + + ;; If profile is not yet verified, mark it as verified because + ;; accepting an invitation link serves as verification. + (when-not (:is-active member) + (db/update! conn :profile + {:is-active true} + {:id (:id member)}))) + (do + (db/exec-one! conn [sql:upsert-team-invitation + (:id team) (str/lower email) (name role) + token-exp (name role) token-exp]) + (eml/send! {::eml/conn conn + ::eml/factory eml/invite-to-team + :public-uri (:public-uri cfg) + :to email + :invited-by (:fullname profile) + :team (:name team) + :token itoken + :extra-data ptoken}))) + + itoken)) + +(s/def ::email ::us/email) +(s/def ::emails ::us/set-of-valid-emails) +(s/def ::create-team-invitations + (s/keys :req-un [::profile-id ::team-id ::role] + :opt-un [::email ::emails])) + +(sv/defmethod ::create-team-invitations + "A rpc call that allow to send a single or multiple invitations to + join the team." + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}] + (db/with-atomic [conn pool] + (let [perms (get-permissions conn profile-id team-id) + profile (db/get-by-id conn :profile profile-id) + team (db/get-by-id conn :team team-id) + emails (cond-> (or emails #{}) (string? email) (conj email))] + + (when-not (:is-admin perms) + (ex/raise :type :validation + :code :insufficient-permissions)) + + ;; First check if the current profile is allowed to send emails. + (when-not (eml/allow-send-emails? conn profile) + (ex/raise :type :validation + :code :profile-is-muted + :hint "looks like the profile has reported repeatedly as spam or has permanent bounces")) + + (let [invitations (->> emails + (map (fn [email] + (assoc cfg + :email email + :conn conn + :team team + :profile profile + :role role))) + (map create-invitation))] + (with-meta (vec invitations) + {::audit/props {:invitations (count invitations)}}))))) + + +;; --- Mutation: Create Team & Invite Members + +(s/def ::emails ::us/set-of-valid-emails) +(s/def ::create-team-and-invitations + (s/merge ::create-team + (s/keys :req-un [::emails ::role]))) + +(sv/defmethod ::create-team-and-invitations + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}] + (db/with-atomic [conn pool] + (let [team (create-team conn params) + profile (db/get-by-id conn :profile profile-id)] + + ;; Create invitations for all provided emails. + (doseq [email emails] + (create-invitation + (assoc cfg + :conn conn + :team team + :profile profile + :email email + :role role))) + + (-> team + (vary-meta assoc ::audit/props {:invitations (count emails)}) + (rph/with-defer + #(when-let [collector (::audit/collector cfg)] + (audit/submit! collector + {:type "command" + :name "create-team-invitations" + :profile-id profile-id + :props {:emails emails + :role role + :profile-id profile-id + :invitations (count emails)}}))))))) + +;; --- Mutation: Update invitation role + +(s/def ::update-team-invitation-role + (s/keys :req-un [::profile-id ::team-id ::email ::role])) + +(sv/defmethod ::update-team-invitation-role + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id team-id email role] :as params}] + (db/with-atomic [conn pool] + (let [perms (get-permissions conn profile-id team-id)] + + (when-not (:is-admin perms) + (ex/raise :type :validation + :code :insufficient-permissions)) + + (db/update! conn :team-invitation + {:role (name role) :updated-at (dt/now)} + {:team-id team-id :email-to (str/lower email)}) + nil))) + +;; --- Mutation: Delete invitation + +(s/def ::delete-team-invitation + (s/keys :req-un [::profile-id ::team-id ::email])) + +(sv/defmethod ::delete-team-invitation + {::doc/added "1.17"} + [{:keys [pool] :as cfg} {:keys [profile-id team-id email] :as params}] + (db/with-atomic [conn pool] + (let [perms (get-permissions conn profile-id team-id)] + + (when-not (:is-admin perms) + (ex/raise :type :validation + :code :insufficient-permissions)) + + (db/delete! conn :team-invitation + {:team-id team-id :email-to (str/lower email)}) + nil))) diff --git a/backend/src/app/rpc/commands/verify_token.clj b/backend/src/app/rpc/commands/verify_token.clj index 9b5df458f1..3242a82114 100644 --- a/backend/src/app/rpc/commands/verify_token.clj +++ b/backend/src/app/rpc/commands/verify_token.clj @@ -11,9 +11,9 @@ [app.db :as db] [app.http.session :as session] [app.loggers.audit :as audit] + [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] - [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] [app.tokens :as tokens] [app.tokens.spec.team-invitation :as-alias spec.team-invitation] diff --git a/backend/src/app/rpc/commands/webhooks.clj b/backend/src/app/rpc/commands/webhooks.clj index fdbc30851b..454dfee42b 100644 --- a/backend/src/app/rpc/commands/webhooks.clj +++ b/backend/src/app/rpc/commands/webhooks.clj @@ -12,8 +12,8 @@ [app.db :as db] [app.http.client :as http] [app.loggers.webhooks :as webhooks] + [app.rpc.commands.teams :refer [check-edition-permissions! check-read-permissions!]] [app.rpc.doc :as-alias doc] - [app.rpc.queries.teams :refer [check-edition-permissions! check-read-permissions!]] [app.util.services :as sv] [app.util.time :as dt] [app.worker :as-alias wrk] diff --git a/backend/src/app/rpc/mutations/fonts.clj b/backend/src/app/rpc/mutations/fonts.clj index b92a3fc864..716147a397 100644 --- a/backend/src/app/rpc/mutations/fonts.clj +++ b/backend/src/app/rpc/mutations/fonts.clj @@ -15,9 +15,9 @@ [app.loggers.webhooks :as-alias webhooks] [app.media :as media] [app.rpc.climit :as-alias climit] + [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] - [app.rpc.queries.teams :as teams] [app.storage :as sto] [app.util.services :as sv] [app.util.time :as dt] diff --git a/backend/src/app/rpc/mutations/media.clj b/backend/src/app/rpc/mutations/media.clj index fb02982dbb..dba890ed43 100644 --- a/backend/src/app/rpc/mutations/media.clj +++ b/backend/src/app/rpc/mutations/media.clj @@ -16,7 +16,7 @@ [app.http.client :as http] [app.media :as media] [app.rpc.climit :as climit] - [app.rpc.queries.teams :as teams] + [app.rpc.commands.teams :as teams] [app.storage :as sto] [app.storage.tmp :as tmp] [app.util.services :as sv] diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index c41a3501c7..e1f07f5985 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -17,10 +17,10 @@ [app.media :as media] [app.rpc :as-alias rpc] [app.rpc.climit :as-alias climit] - [app.rpc.commands.auth :as cmd.auth] + [app.rpc.commands.auth :as auth] + [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] - [app.rpc.mutations.teams :as teams] [app.rpc.queries.profile :as profile] [app.storage :as sto] [app.tokens :as tokens] @@ -111,7 +111,7 @@ (defn- validate-password! [conn {:keys [profile-id old-password] :as params}] (let [profile (db/get-by-id conn :profile profile-id)] - (when-not (:valid (cmd.auth/verify-password old-password (:password profile))) + (when-not (:valid (auth/verify-password old-password (:password profile))) (ex/raise :type :validation :code :old-password-not-match)) profile)) @@ -119,7 +119,7 @@ (defn update-profile-password! [conn {:keys [id password] :as profile}] (db/update! conn :profile - {:password (cmd.auth/derive-password password)} + {:password (auth/derive-password password)} {:id id})) ;; --- MUTATION: Update Photo @@ -182,7 +182,7 @@ (defn- change-email-immediately [{:keys [conn]} {:keys [profile email] :as params}] (when (not= email (:email profile)) - (cmd.auth/check-profile-existence! conn params)) + (auth/check-profile-existence! conn params)) (db/update! conn :profile {:email email} {:id (:id profile)}) @@ -201,7 +201,7 @@ :exp (dt/in-future {:days 30})})] (when (not= email (:email profile)) - (cmd.auth/check-profile-existence! conn params)) + (auth/check-profile-existence! conn params)) (when-not (eml/allow-send-emails? conn profile) (ex/raise :type :validation diff --git a/backend/src/app/rpc/mutations/projects.clj b/backend/src/app/rpc/mutations/projects.clj index 95c36d9572..ed7a073348 100644 --- a/backend/src/app/rpc/mutations/projects.clj +++ b/backend/src/app/rpc/mutations/projects.clj @@ -7,15 +7,13 @@ (ns app.rpc.mutations.projects (:require [app.common.spec :as us] - [app.common.uuid :as uuid] [app.db :as db] [app.loggers.audit :as-alias audit] [app.loggers.webhooks :as-alias webhooks] + [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] - [app.rpc.permissions :as perms] [app.rpc.queries.projects :as proj] - [app.rpc.queries.teams :as teams] [app.util.services :as sv] [app.util.time :as dt] [clojure.spec.alpha :as s])) @@ -28,9 +26,7 @@ ;; --- Mutation: Create Project -(declare create-project) -(declare create-project-role) -(declare create-team-project-profile) +(declare create-project-profile-state) (s/def ::team-id ::us/uuid) (s/def ::create-project @@ -43,33 +39,15 @@ [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] (db/with-atomic [conn pool] (teams/check-edition-permissions! conn profile-id team-id) - (let [project (create-project conn params) + (let [project (teams/create-project conn params) params (assoc params :project-id (:id project) :role :owner)] - (create-project-role conn params) - (create-team-project-profile conn params) + (teams/create-project-role conn params) + (create-project-profile-state conn params) (assoc project :is-pinned true)))) -(defn create-project - [conn {:keys [id team-id name is-default] :as params}] - (let [id (or id (uuid/next)) - is-default (if (boolean? is-default) is-default false)] - (db/insert! conn :project - {:id id - :name name - :team-id team-id - :is-default is-default}))) - -(defn create-project-role - [conn {:keys [project-id profile-id role]}] - (let [params {:project-id project-id - :profile-id profile-id}] - (->> (perms/assign-role-flags params role) - (db/insert! conn :project-profile-rel)))) - -;; TODO: pending to be refactored -(defn create-team-project-profile +(defn create-project-profile-state [conn {:keys [team-id project-id profile-id] :as params}] (db/insert! conn :team-project-profile-rel {:project-id project-id @@ -77,7 +55,6 @@ :team-id team-id :is-pinned true})) - ;; --- Mutation: Toggle Project Pin (def ^:private diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index 7da456c58a..d15a376df5 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -6,30 +6,19 @@ (ns app.rpc.mutations.teams (:require - [app.common.data :as d] [app.common.exceptions :as ex] - [app.common.logging :as l] [app.common.spec :as us] - [app.common.uuid :as uuid] - [app.config :as cf] [app.db :as db] [app.emails :as eml] [app.loggers.audit :as audit] [app.media :as media] - [app.rpc.climit :as climit] + [app.rpc.commands.teams :as cmd.teams] + [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] - [app.rpc.mutations.projects :as projects] - [app.rpc.permissions :as perms] - [app.rpc.queries.profile :as profile] - [app.rpc.queries.teams :as teams] - [app.storage :as sto] - [app.tokens :as tokens] [app.util.services :as sv] [app.util.time :as dt] [clojure.spec.alpha :as s] - [cuerdas.core :as str] - [promesa.core :as p] - [promesa.exec :as px])) + [cuerdas.core :as str])) ;; --- Helpers & Specs @@ -39,148 +28,54 @@ ;; --- Mutation: Create Team -(declare create-team) -(declare create-team-entry) -(declare create-team-role) -(declare create-team-default-project) - -(s/def ::create-team - (s/keys :req-un [::profile-id ::name] - :opt-un [::id])) +(s/def ::create-team ::cmd.teams/create-team) (sv/defmethod ::create-team - [{:keys [pool] :as cfg} params] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} params] (db/with-atomic [conn pool] - (create-team conn params))) - -(defn create-team - "This is a complete team creation process, it creates the team - object and all related objects (default role and default project)." - [conn params] - (let [team (create-team-entry conn params) - params (assoc params - :team-id (:id team) - :role :owner) - project (create-team-default-project conn params)] - (create-team-role conn params) - (assoc team :default-project-id (:id project)))) - -(defn- create-team-entry - [conn {:keys [id name is-default] :as params}] - (let [id (or id (uuid/next)) - is-default (if (boolean? is-default) is-default false)] - (db/insert! conn :team - {:id id - :name name - :is-default is-default}))) - -(defn- create-team-role - [conn {:keys [team-id profile-id role] :as params}] - (let [params {:team-id team-id - :profile-id profile-id}] - (->> (perms/assign-role-flags params role) - (db/insert! conn :team-profile-rel)))) - -(defn- create-team-default-project - [conn {:keys [team-id profile-id] :as params}] - (let [project {:id (uuid/next) - :team-id team-id - :name "Drafts" - :is-default true} - project (projects/create-project conn project)] - (projects/create-project-role conn {:project-id (:id project) - :profile-id profile-id - :role :owner}) - project)) + (cmd.teams/create-team conn params))) ;; --- Mutation: Update Team -(s/def ::update-team - (s/keys :req-un [::profile-id ::name ::id])) +(s/def ::update-team ::cmd.teams/update-team) (sv/defmethod ::update-team - [{:keys [pool] :as cfg} {:keys [id name profile-id] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [id name profile-id] :as params}] (db/with-atomic [conn pool] - (teams/check-edition-permissions! conn profile-id id) + (cmd.teams/check-edition-permissions! conn profile-id id) (db/update! conn :team {:name name} {:id id}) nil)) - ;; --- Mutation: Leave Team -(declare role->params) - -(s/def ::reassign-to ::us/uuid) -(s/def ::leave-team - (s/keys :req-un [::profile-id ::id] - :opt-un [::reassign-to])) +(s/def ::leave-team ::cmd.teams/leave-team) (sv/defmethod ::leave-team - [{:keys [pool] :as cfg} {:keys [id profile-id reassign-to]}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} params] (db/with-atomic [conn pool] - (let [perms (teams/get-permissions conn profile-id id) - members (teams/retrieve-team-members conn id)] - - (cond - ;; we can only proceed if there are more members in the team - ;; besides the current profile - (<= (count members) 1) - (ex/raise :type :validation - :code :no-enough-members-for-leave - :context {:members (count members)}) - - ;; if the `reassign-to` is filled and has a different value - ;; than the current profile-id, we proceed to reassing the - ;; owner role to profile identified by the `reassign-to`. - (and reassign-to (not= reassign-to profile-id)) - (let [member (d/seek #(= reassign-to (:id %)) members)] - (when-not member - (ex/raise :type :not-found :code :member-does-not-exist)) - - ;; unasign owner role to current profile - (db/update! conn :team-profile-rel - {:is-owner false} - {:team-id id - :profile-id profile-id}) - - ;; assign owner role to new profile - (db/update! conn :team-profile-rel - (role->params :owner) - {:team-id id :profile-id reassign-to})) - - ;; and finally, if all other conditions does not match and the - ;; current profile is owner, we dont allow it because there - ;; must always be an owner. - (:is-owner perms) - (ex/raise :type :validation - :code :owner-cant-leave-team - :hint "releasing owner before leave")) - - (db/delete! conn :team-profile-rel - {:profile-id profile-id - :team-id id}) - - nil))) + (cmd.teams/leave-team conn params))) ;; --- Mutation: Delete Team -(s/def ::delete-team - (s/keys :req-un [::profile-id ::id])) - -;; TODO: right now just don't allow delete default team, in future it -;; should raise a specific exception for signal that this action is -;; not allowed. +(s/def ::delete-team ::cmd.teams/delete-team) (sv/defmethod ::delete-team - [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] - (let [perms (teams/get-permissions conn profile-id id)] + (let [perms (cmd.teams/get-permissions conn profile-id id)] (when-not (:is-owner perms) (ex/raise :type :validation :code :only-owner-can-delete-team)) - (db/update! conn :team {:deleted-at (dt/now)} {:id id :is-default false}) @@ -189,89 +84,29 @@ ;; --- Mutation: Team Update Role -(declare retrieve-team-member) - -(s/def ::team-id ::us/uuid) -(s/def ::member-id ::us/uuid) -;; Temporarily disabled viewer role -;; https://tree.taiga.io/project/uxboxproject/issue/1083 -;; (s/def ::role #{:owner :admin :editor :viewer}) -(s/def ::role #{:owner :admin :editor}) - -(s/def ::update-team-member-role - (s/keys :req-un [::profile-id ::team-id ::member-id ::role])) +(s/def ::update-team-member-role ::cmd.teams/update-team-member-role) (sv/defmethod ::update-team-member-role - [{:keys [pool] :as cfg} {:keys [team-id profile-id member-id role] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} params] (db/with-atomic [conn pool] - (let [perms (teams/get-permissions conn profile-id team-id) - ;; We retrieve all team members instead of query the - ;; database for a single member. This is just for - ;; convenience, if this becomes a bottleneck or problematic, - ;; we will change it to more efficient fetch mechanisms. - members (teams/retrieve-team-members conn team-id) - member (d/seek #(= member-id (:id %)) members) - - is-owner? (:is-owner perms) - is-admin? (:is-admin perms)] - - ;; If no member is found, just 404 - (when-not member - (ex/raise :type :not-found - :code :member-does-not-exist)) - - ;; First check if we have permissions to change roles - (when-not (or is-owner? is-admin?) - (ex/raise :type :validation - :code :insufficient-permissions)) - - ;; Don't allow change role of owner member - (when (:is-owner member) - (ex/raise :type :validation - :code :cant-change-role-to-owner)) - - ;; Don't allow promote to owner to admin users. - (when (and (not is-owner?) (= role :owner)) - (ex/raise :type :validation - :code :cant-promote-to-owner)) - - (let [params (role->params role)] - ;; Only allow single owner on team - (when (= role :owner) - (db/update! conn :team-profile-rel - {:is-owner false} - {:team-id team-id - :profile-id profile-id})) - - (db/update! conn :team-profile-rel - params - {:team-id team-id - :profile-id member-id}) - nil)))) - -(defn role->params - [role] - (case role - :admin {:is-owner false :is-admin true :can-edit true} - :editor {:is-owner false :is-admin false :can-edit true} - :owner {:is-owner true :is-admin true :can-edit true} - :viewer {:is-owner false :is-admin false :can-edit false})) - + (cmd.teams/update-team-member-role conn params))) ;; --- Mutation: Delete Team Member -(s/def ::delete-team-member - (s/keys :req-un [::profile-id ::team-id ::member-id])) +(s/def ::delete-team-member ::cmd.teams/delete-team-member) (sv/defmethod ::delete-team-member - [{:keys [pool] :as cfg} {:keys [team-id profile-id member-id] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [team-id profile-id member-id] :as params}] (db/with-atomic [conn pool] - (let [perms (teams/get-permissions conn profile-id team-id)] + (let [perms (cmd.teams/get-permissions conn profile-id team-id)] (when-not (or (:is-owner perms) (:is-admin perms)) (ex/raise :type :validation :code :insufficient-permissions)) - (when (= member-id profile-id) (ex/raise :type :validation :code :cant-remove-yourself)) @@ -283,85 +118,27 @@ ;; --- Mutation: Update Team Photo -(declare ^:private upload-photo) -(declare ^:private update-team-photo) - -(s/def ::file ::media/upload) -(s/def ::update-team-photo - (s/keys :req-un [::profile-id ::team-id ::file])) +(s/def ::update-team-photo ::cmd.teams/update-team-photo) (sv/defmethod ::update-team-photo + {::doc/added "1.0" + ::doc/deprecated "1.17"} [cfg {:keys [file] :as params}] ;; Validate incoming mime type (media/validate-media-type! file #{"image/jpeg" "image/png" "image/webp"}) (let [cfg (update cfg :storage media/configure-assets-storage)] - (update-team-photo cfg params))) - -(defn update-team-photo - [{:keys [pool storage executor] :as cfg} {:keys [profile-id team-id] :as params}] - (p/let [team (px/with-dispatch executor - (teams/retrieve-team pool profile-id team-id)) - photo (upload-photo cfg params)] - - ;; Mark object as touched for make it ellegible for tentative - ;; garbage collection. - (when-let [id (:photo-id team)] - (sto/touch-object! storage id)) - - ;; Save new photo - (db/update! pool :team - {:photo-id (:id photo)} - {:id team-id}) - - (assoc team :photo-id (:id photo)))) - -(defn upload-photo - [{:keys [storage executor climit] :as cfg} {:keys [file]}] - (letfn [(get-info [content] - (climit/with-dispatch (:process-image climit) - (media/run {:cmd :info :input content}))) - - (generate-thumbnail [info] - (climit/with-dispatch (:process-image climit) - (media/run {:cmd :profile-thumbnail - :format :jpeg - :quality 85 - :width 256 - :height 256 - :input info}))) - - ;; Function responsible of calculating cryptographyc hash of - ;; the provided data. - (calculate-hash [data] - (px/with-dispatch executor - (sto/calculate-hash data)))] - - (p/let [info (get-info file) - thumb (generate-thumbnail info) - hash (calculate-hash (:data thumb)) - content (-> (sto/content (:data thumb) (:size thumb)) - (sto/wrap-with-hash hash))] - (sto/put-object! storage {::sto/content content - ::sto/deduplicate? true - :bucket "profile" - :content-type (:mtype thumb)})))) + (cmd.teams/update-team-photo cfg params))) ;; --- Mutation: Invite Member -(declare create-team-invitation) - -(s/def ::email ::us/email) -(s/def ::emails ::us/set-of-valid-emails) -(s/def ::invite-team-member - (s/keys :req-un [::profile-id ::team-id ::role] - :opt-un [::email ::emails])) +(s/def ::invite-team-member ::cmd.teams/create-team-invitations) (sv/defmethod ::invite-team-member - "A rpc call that allow to send a single or multiple invitations to - join the team." - [{:keys [pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id team-id email emails role] :as params}] (db/with-atomic [conn pool] - (let [perms (teams/get-permissions conn profile-id team-id) + (let [perms (cmd.teams/get-permissions conn profile-id team-id) profile (db/get-by-id conn :profile profile-id) team (db/get-by-id conn :team team-id) emails (cond-> (or emails #{}) (string? email) (conj email))] @@ -384,101 +161,25 @@ :team team :profile profile :role role))) - (map create-team-invitation))] + (map #'cmd.teams/create-invitation))] (with-meta (vec invitations) {::audit/props {:invitations (count invitations)}}))))) -(def sql:upsert-team-invitation - "insert into team_invitation(team_id, email_to, role, valid_until) - values (?, ?, ?, ?) - on conflict(team_id, email_to) do - update set role = ?, valid_until = ?, updated_at = now();") - -(defn- create-team-invitation - [{:keys [conn sprops team profile role email] :as cfg}] - (let [member (profile/retrieve-profile-data-by-email conn email) - token-exp (dt/in-future "168h") ;; 7 days - email (str/lower email) - itoken (tokens/generate sprops - {:iss :team-invitation - :exp token-exp - :profile-id (:id profile) - :role role - :team-id (:id team) - :member-email (:email member email) - :member-id (:id member)}) - ptoken (tokens/generate sprops - {:iss :profile-identity - :profile-id (:id profile) - :exp (dt/in-future {:days 30})})] - - (when (and member (not (eml/allow-send-emails? conn member))) - (ex/raise :type :validation - :code :member-is-muted - :email email - :hint "the profile has reported repeatedly as spam or has bounces")) - - ;; Secondly check if the invited member email is part of the global spam/bounce report. - (when (eml/has-bounce-reports? conn email) - (ex/raise :type :validation - :code :email-has-permanent-bounces - :email email - :hint "the email you invite has been repeatedly reported as spam or bounce")) - - (when (contains? cf/flags :log-invitation-tokens) - (l/trace :hint "invitation token" :token itoken)) - - ;; When we have email verification disabled and invitation user is - ;; already present in the database, we proceed to add it to the - ;; team as-is, without email roundtrip. - - ;; TODO: if member does not exists and email verification is - ;; disabled, we should proceed to create the profile (?) - (if (and (not (contains? cf/flags :email-verification)) - (some? member)) - (let [params (merge {:team-id (:id team) - :profile-id (:id member)} - (role->params role))] - - ;; Insert the invited member to the team - (db/insert! conn :team-profile-rel params {:on-conflict-do-nothing true}) - - ;; If profile is not yet verified, mark it as verified because - ;; accepting an invitation link serves as verification. - (when-not (:is-active member) - (db/update! conn :profile - {:is-active true} - {:id (:id member)}))) - (do - (db/exec-one! conn [sql:upsert-team-invitation - (:id team) (str/lower email) (name role) - token-exp (name role) token-exp]) - (eml/send! {::eml/conn conn - ::eml/factory eml/invite-to-team - :public-uri (:public-uri cfg) - :to email - :invited-by (:fullname profile) - :team (:name team) - :token itoken - :extra-data ptoken}))) - - itoken)) - ;; --- Mutation: Create Team & Invite Members -(s/def ::emails ::us/set-of-valid-emails) -(s/def ::create-team-and-invite-members - (s/and ::create-team (s/keys :req-un [::emails ::role]))) +(s/def ::create-team-and-invite-members ::cmd.teams/create-team-and-invitations) (sv/defmethod ::create-team-and-invite-members - [{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id emails role] :as params}] (db/with-atomic [conn pool] - (let [team (create-team conn params) + (let [team (cmd.teams/create-team conn params) profile (db/get-by-id conn :profile profile-id)] ;; Create invitations for all provided emails. (doseq [email emails] - (create-team-invitation + (#'cmd.teams/create-invitation (assoc cfg :conn conn :team team @@ -505,9 +206,11 @@ (s/keys :req-un [::profile-id ::team-id ::email ::role])) (sv/defmethod ::update-team-invitation-role - [{:keys [pool] :as cfg} {:keys [profile-id team-id email role] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id team-id email role] :as params}] (db/with-atomic [conn pool] - (let [perms (teams/get-permissions conn profile-id team-id)] + (let [perms (cmd.teams/get-permissions conn profile-id team-id)] (when-not (:is-admin perms) (ex/raise :type :validation @@ -520,13 +223,14 @@ ;; --- Mutation: Delete invitation -(s/def ::delete-team-invitation - (s/keys :req-un [::profile-id ::team-id ::email])) +(s/def ::delete-team-invitation ::cmd.teams/delete-team-invitation) (sv/defmethod ::delete-team-invitation - [{:keys [pool] :as cfg} {:keys [profile-id team-id email] :as params}] + {::doc/added "1.0" + ::doc/deprecated "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id team-id email] :as params}] (db/with-atomic [conn pool] - (let [perms (teams/get-permissions conn profile-id team-id)] + (let [perms (cmd.teams/get-permissions conn profile-id team-id)] (when-not (:is-admin perms) (ex/raise :type :validation diff --git a/backend/src/app/rpc/queries/comments.clj b/backend/src/app/rpc/queries/comments.clj index e9db1a6c8a..fbcb86f033 100644 --- a/backend/src/app/rpc/queries/comments.clj +++ b/backend/src/app/rpc/queries/comments.clj @@ -9,8 +9,8 @@ [app.db :as db] [app.rpc.commands.comments :as cmd.comments] [app.rpc.commands.files :as cmd.files] + [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] - [app.rpc.queries.teams :as teams] [app.util.services :as sv] [clojure.spec.alpha :as s])) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 0672e5f89a..398b1400ad 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -8,38 +8,38 @@ (:require [app.common.spec :as us] [app.db :as db] - [app.rpc.commands.files :as cmd.files] - [app.rpc.commands.search :as cmd.search] + [app.rpc.commands.files :as files] + [app.rpc.commands.search :as search] + [app.rpc.commands.teams :as teams] [app.rpc.doc :as-alias doc] [app.rpc.helpers :as rph] [app.rpc.queries.projects :as projects] - [app.rpc.queries.teams :as teams] [app.util.services :as sv] [clojure.spec.alpha :as s])) ;; --- Query: Project Files -(s/def ::project-files ::cmd.files/get-project-files) +(s/def ::project-files ::files/get-project-files) (sv/defmethod ::project-files - {::doc/added "1.1" + {::doc/added "1.0" ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] (with-open [conn (db/open pool)] (projects/check-read-permissions! conn profile-id project-id) - (cmd.files/get-project-files conn project-id))) + (files/get-project-files conn project-id))) ;; --- Query: File (By ID) (s/def ::components-v2 ::us/boolean) (s/def ::file - (s/and ::cmd.files/get-file + (s/and ::files/get-file (s/keys :opt-un [::components-v2]))) (defn get-file [conn id features] - (let [file (cmd.files/get-file conn id features) - thumbs (cmd.files/get-object-thumbnails conn id)] + (let [file (files/get-file conn id features) + thumbs (files/get-object-thumbnails conn id)] (assoc file :thumbnails thumbs))) (sv/defmethod ::file @@ -48,19 +48,19 @@ ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id id features components-v2] :as params}] (with-open [conn (db/open pool)] - (let [perms (cmd.files/get-permissions pool profile-id id) + (let [perms (files/get-permissions pool profile-id id) ;; BACKWARD COMPATIBILTY with the components-v2 parameter features (cond-> (or features #{}) components-v2 (conj "components/v2"))] - (cmd.files/check-read-permissions! perms) + (files/check-read-permissions! perms) (-> (get-file conn id features) (assoc :permissions perms))))) ;; --- QUERY: page (s/def ::page - (s/and ::cmd.files/get-page + (s/and ::files/get-page (s/keys :opt-un [::components-v2]))) (sv/defmethod ::page @@ -77,18 +77,18 @@ ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id features components-v2] :as params}] (with-open [conn (db/open pool)] - (cmd.files/check-read-permissions! conn profile-id file-id) + (files/check-read-permissions! conn profile-id file-id) (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter features (cond-> (or features #{}) components-v2 (conj "components/v2")) params (assoc params :features features)] - (cmd.files/get-page conn params)))) + (files/get-page conn params)))) ;; --- QUERY: file-data-for-thumbnail (s/def ::file-data-for-thumbnail - (s/and ::cmd.files/get-file-data-for-thumbnail + (s/and ::files/get-file-data-for-thumbnail (s/keys :opt-un [::components-v2]))) (sv/defmethod ::file-data-for-thumbnail @@ -98,18 +98,18 @@ ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id features components-v2] :as props}] (with-open [conn (db/open pool)] - (cmd.files/check-read-permissions! conn profile-id file-id) + (files/check-read-permissions! conn profile-id file-id) (let [;; BACKWARD COMPATIBILTY with the components-v2 parameter features (cond-> (or features #{}) components-v2 (conj "components/v2")) - file (cmd.files/get-file conn file-id features)] + file (files/get-file conn file-id features)] {:file-id file-id :revn (:revn file) - :page (cmd.files/get-file-data-for-thumbnail conn file)}))) + :page (files/get-file-data-for-thumbnail conn file)}))) ;; --- Query: Shared Library Files -(s/def ::team-shared-files ::cmd.files/get-team-shared-files) +(s/def ::team-shared-files ::files/get-team-shared-files) (sv/defmethod ::team-shared-files {::doc/added "1.3" @@ -117,37 +117,37 @@ [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] (with-open [conn (db/open pool)] (teams/check-read-permissions! conn profile-id team-id) - (cmd.files/get-team-shared-files conn params))) + (files/get-team-shared-files conn params))) ;; --- Query: File Libraries used by a File -(s/def ::file-libraries ::cmd.files/get-file-libraries) +(s/def ::file-libraries ::files/get-file-libraries) (sv/defmethod ::file-libraries {::doc/added "1.3" ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id features] :as params}] (with-open [conn (db/open pool)] - (cmd.files/check-read-permissions! conn profile-id file-id) - (cmd.files/get-file-libraries conn file-id features))) + (files/check-read-permissions! conn profile-id file-id) + (files/get-file-libraries conn file-id features))) ;; --- Query: Files that use this File library -(s/def ::library-using-files ::cmd.files/get-library-file-references) +(s/def ::library-using-files ::files/get-library-file-references) (sv/defmethod ::library-using-files {::doc/added "1.13" ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (with-open [conn (db/open pool)] - (cmd.files/check-read-permissions! conn profile-id file-id) - (cmd.files/get-library-file-references conn file-id))) + (files/check-read-permissions! conn profile-id file-id) + (files/get-library-file-references conn file-id))) ;; --- QUERY: team-recent-files -(s/def ::team-recent-files ::cmd.files/get-team-recent-files) +(s/def ::team-recent-files ::files/get-team-recent-files) (sv/defmethod ::team-recent-files {::doc/added "1.0" @@ -155,30 +155,30 @@ [{:keys [pool] :as cfg} {:keys [profile-id team-id]}] (with-open [conn (db/open pool)] (teams/check-read-permissions! conn profile-id team-id) - (cmd.files/get-team-recent-files conn team-id))) + (files/get-team-recent-files conn team-id))) ;; --- QUERY: get file thumbnail -(s/def ::file-thumbnail ::cmd.files/get-file-thumbnail) +(s/def ::file-thumbnail ::files/get-file-thumbnail) (sv/defmethod ::file-thumbnail {::doc/added "1.13" ::doc/deprecated "1.17"} [{:keys [pool]} {:keys [profile-id file-id revn]}] (with-open [conn (db/open pool)] - (cmd.files/check-read-permissions! conn profile-id file-id) - (-> (cmd.files/get-file-thumbnail conn file-id revn) - (rph/with-http-cache cmd.files/long-cache-duration)))) + (files/check-read-permissions! conn profile-id file-id) + (-> (files/get-file-thumbnail conn file-id revn) + (rph/with-http-cache files/long-cache-duration)))) ;; --- QUERY: search files -(s/def ::search-files ::cmd.search/search-files) +(s/def ::search-files ::search/search-files) (sv/defmethod ::search-files {::doc/added "1.0" ::doc/deprecated "1.17"} [{:keys [pool]} {:keys [search-term] :as params}] (when search-term - (cmd.search/search-files pool params))) + (search/search-files pool params))) diff --git a/backend/src/app/rpc/queries/fonts.clj b/backend/src/app/rpc/queries/fonts.clj index 077766cd33..ac592ed1af 100644 --- a/backend/src/app/rpc/queries/fonts.clj +++ b/backend/src/app/rpc/queries/fonts.clj @@ -9,13 +9,14 @@ [app.common.spec :as us] [app.db :as db] [app.rpc.commands.files :as files] + [app.rpc.commands.teams :as teams] [app.rpc.queries.projects :as projects] - [app.rpc.queries.teams :as teams] [app.util.services :as sv] [clojure.spec.alpha :as s])) ;; --- Query: Team Font Variants +;; FIXME: PLEASE RIGHT NOW ;; TODO: deprecated, should be removed on 1.7.x (s/def ::team-id ::us/uuid) diff --git a/backend/src/app/rpc/queries/projects.clj b/backend/src/app/rpc/queries/projects.clj index 2df15daa1d..64c4a9b423 100644 --- a/backend/src/app/rpc/queries/projects.clj +++ b/backend/src/app/rpc/queries/projects.clj @@ -8,8 +8,8 @@ (:require [app.common.spec :as us] [app.db :as db] + [app.rpc.commands.teams :as teams] [app.rpc.permissions :as perms] - [app.rpc.queries.teams :as teams] [app.util.services :as sv] [clojure.spec.alpha :as s])) diff --git a/backend/src/app/rpc/queries/teams.clj b/backend/src/app/rpc/queries/teams.clj index abcb543a66..10801413ec 100644 --- a/backend/src/app/rpc/queries/teams.clj +++ b/backend/src/app/rpc/queries/teams.clj @@ -6,244 +6,82 @@ (ns app.rpc.queries.teams (:require - [app.common.exceptions :as ex] - [app.common.spec :as us] [app.db :as db] - [app.rpc.permissions :as perms] - [app.rpc.queries.profile :as profile] + [app.rpc.commands.teams :as cmd.teams] + [app.rpc.doc :as-alias doc] [app.util.services :as sv] [clojure.spec.alpha :as s])) -;; --- Team Edition Permissions - -(def ^:private sql:team-permissions - "select tpr.is_owner, - tpr.is_admin, - tpr.can_edit - from team_profile_rel as tpr - join team as t on (t.id = tpr.team_id) - where tpr.profile_id = ? - and tpr.team_id = ? - and t.deleted_at is null") - -(defn get-permissions - [conn profile-id team-id] - (let [rows (db/exec! conn [sql:team-permissions profile-id team-id]) - is-owner (boolean (some :is-owner rows)) - is-admin (boolean (some :is-admin rows)) - can-edit (boolean (some :can-edit rows))] - (when (seq rows) - {:is-owner is-owner - :is-admin (or is-owner is-admin) - :can-edit (or is-owner is-admin can-edit) - :can-read true}))) - -(def has-edit-permissions? - (perms/make-edition-predicate-fn get-permissions)) - -(def has-read-permissions? - (perms/make-read-predicate-fn get-permissions)) - -(def check-edition-permissions! - (perms/make-check-fn has-edit-permissions?)) - -(def check-read-permissions! - (perms/make-check-fn has-read-permissions?)) - ;; --- Query: Teams -(declare retrieve-teams) - -(s/def ::profile-id ::us/uuid) -(s/def ::teams - (s/keys :req-un [::profile-id])) +(s/def ::teams ::cmd.teams/get-teams) (sv/defmethod ::teams + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id]}] (with-open [conn (db/open pool)] - (retrieve-teams conn profile-id))) - -(def sql:teams - "select t.*, - tp.is_owner, - tp.is_admin, - tp.can_edit, - (t.id = ?) as is_default - from team_profile_rel as tp - join team as t on (t.id = tp.team_id) - where t.deleted_at is null - and tp.profile_id = ? - order by tp.created_at asc") - -(defn process-permissions - [team] - (let [is-owner (:is-owner team) - is-admin (:is-admin team) - can-edit (:can-edit team) - permissions {:type :membership - :is-owner is-owner - :is-admin (or is-owner is-admin) - :can-edit (or is-owner is-admin can-edit)}] - (-> team - (dissoc :is-owner :is-admin :can-edit) - (assoc :permissions permissions)))) - -(defn retrieve-teams - [conn profile-id] - (let [defaults (profile/retrieve-additional-data conn profile-id)] - (->> (db/exec! conn [sql:teams (:default-team-id defaults) profile-id]) - (mapv process-permissions)))) + (cmd.teams/retrieve-teams conn profile-id))) ;; --- Query: Team (by ID) -(declare retrieve-team) - -(s/def ::id ::us/uuid) -(s/def ::team - (s/keys :req-un [::profile-id ::id])) +(s/def ::team ::cmd.teams/get-team) (sv/defmethod ::team + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id id]}] (with-open [conn (db/open pool)] - (retrieve-team conn profile-id id))) - -(defn retrieve-team - [conn profile-id team-id] - (let [defaults (profile/retrieve-additional-data conn profile-id) - sql (str "WITH teams AS (" sql:teams ") SELECT * FROM teams WHERE id=?") - result (db/exec-one! conn [sql (:default-team-id defaults) profile-id team-id])] - (when-not result - (ex/raise :type :not-found - :code :team-does-not-exist)) - (process-permissions result))) - + (cmd.teams/retrieve-team conn profile-id id))) ;; --- Query: Team Members -(declare retrieve-team-members) - -(s/def ::team-id ::us/uuid) -(s/def ::team-members - (s/keys :req-un [::profile-id ::team-id])) +(s/def ::team-members ::cmd.teams/get-team-members) (sv/defmethod ::team-members + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id team-id]}] (with-open [conn (db/open pool)] - (check-read-permissions! conn profile-id team-id) - (retrieve-team-members conn team-id))) - -(def sql:team-members - "select tp.*, - p.id, - p.email, - p.fullname as name, - p.fullname as fullname, - p.photo_id, - p.is_active - from team_profile_rel as tp - join profile as p on (p.id = tp.profile_id) - where tp.team_id = ?") - -(defn retrieve-team-members - [conn team-id] - (db/exec! conn [sql:team-members team-id])) - + (cmd.teams/check-read-permissions! conn profile-id team-id) + (cmd.teams/retrieve-team-members conn team-id))) ;; --- Query: Team Users - -(declare retrieve-users) -(declare retrieve-team-for-file) - -(s/def ::file-id ::us/uuid) -(s/def ::team-users - (s/and (s/keys :req-un [::profile-id] - :opt-un [::team-id ::file-id]) - #(or (:team-id %) (:file-id %)))) +(s/def ::team-users ::cmd.teams/get-team-users) (sv/defmethod ::team-users + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id team-id file-id]}] (with-open [conn (db/open pool)] (if team-id (do - (check-read-permissions! conn profile-id team-id) - (retrieve-users conn team-id)) - (let [{team-id :id} (retrieve-team-for-file conn file-id)] - (check-read-permissions! conn profile-id team-id) - (retrieve-users conn team-id))))) - -;; This is a similar query to team members but can contain more data -;; because some user can be explicitly added to project or file (not -;; implemented in UI) - -(def sql:team-users - "select pf.id, pf.fullname, pf.photo_id - from profile as pf - inner join team_profile_rel as tpr on (tpr.profile_id = pf.id) - where tpr.team_id = ? - union - select pf.id, pf.fullname, pf.photo_id - from profile as pf - inner join project_profile_rel as ppr on (ppr.profile_id = pf.id) - inner join project as p on (ppr.project_id = p.id) - where p.team_id = ? - union - select pf.id, pf.fullname, pf.photo_id - from profile as pf - inner join file_profile_rel as fpr on (fpr.profile_id = pf.id) - inner join file as f on (fpr.file_id = f.id) - inner join project as p on (f.project_id = p.id) - where p.team_id = ?") - -(def sql:team-by-file - "select p.team_id as id - from project as p - join file as f on (p.id = f.project_id) - where f.id = ?") - -(defn retrieve-users - [conn team-id] - (db/exec! conn [sql:team-users team-id team-id team-id])) - -(defn retrieve-team-for-file - [conn file-id] - (->> [sql:team-by-file file-id] - (db/exec-one! conn))) + (cmd.teams/check-read-permissions! conn profile-id team-id) + (cmd.teams/retrieve-users conn team-id)) + (let [{team-id :id} (cmd.teams/retrieve-team-for-file conn file-id)] + (cmd.teams/check-read-permissions! conn profile-id team-id) + (cmd.teams/retrieve-users conn team-id))))) ;; --- Query: Team Stats -(declare retrieve-team-stats) - -(s/def ::team-stats - (s/keys :req-un [::profile-id ::team-id])) +(s/def ::team-stats ::cmd.teams/get-team-stats) (sv/defmethod ::team-stats + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id team-id]}] (with-open [conn (db/open pool)] - (check-read-permissions! conn profile-id team-id) - (retrieve-team-stats conn team-id))) - -(def sql:team-stats - "select (select count(*) from project where team_id = ?) as projects, - (select count(*) from file as f join project as p on (p.id = f.project_id) where p.team_id = ?) as files") - -(defn retrieve-team-stats - [conn team-id] - (db/exec-one! conn [sql:team-stats team-id team-id])) - + (cmd.teams/check-read-permissions! conn profile-id team-id) + (cmd.teams/retrieve-team-stats conn team-id))) ;; --- Query: Team invitations -(s/def ::team-id ::us/uuid) -(s/def ::team-invitations - (s/keys :req-un [::profile-id ::team-id])) - -(def sql:team-invitations - "select email_to as email, role, (valid_until < now()) as expired - from team_invitation where team_id = ? order by valid_until desc") +(s/def ::team-invitations ::cmd.teams/get-team-invitations) (sv/defmethod ::team-invitations + {::doc/added "1.0" + ::doc/deprecated "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id team-id]}] (with-open [conn (db/open pool)] - (check-read-permissions! conn profile-id team-id) - (->> (db/exec! conn [sql:team-invitations team-id]) - (mapv #(update % :role keyword))))) + (cmd.teams/check-read-permissions! conn profile-id team-id) + (cmd.teams/get-team-invitations conn team-id))) diff --git a/backend/test/backend_tests/helpers.clj b/backend/test/backend_tests/helpers.clj index 41cf3e1cfa..7b2792ca41 100644 --- a/backend/test/backend_tests/helpers.clj +++ b/backend/test/backend_tests/helpers.clj @@ -17,14 +17,13 @@ [app.main :as main] [app.media] [app.migrations] - [app.rpc.helpers :as rph] [app.rpc.commands.auth :as cmd.auth] [app.rpc.commands.files :as files] [app.rpc.commands.files.create :as files.create] [app.rpc.commands.files.update :as files.update] + [app.rpc.commands.teams :as teams] + [app.rpc.helpers :as rph] [app.rpc.mutations.profile :as profile] - [app.rpc.mutations.projects :as projects] - [app.rpc.mutations.teams :as teams] [app.util.blob :as blob] [app.util.services :as sv] [app.util.time :as dt] @@ -172,7 +171,7 @@ (->> (merge {:id (mk-uuid "project" i) :name (str "project" i)} params) - (#'projects/create-project conn))))) + (#'teams/create-project conn))))) (defn create-file* ([i params] @@ -254,7 +253,7 @@ ([params] (create-project-role* *pool* params)) ([pool {:keys [project-id profile-id role] :or {role :owner}}] (with-open [conn (db/open pool)] - (#'projects/create-project-role conn {:project-id project-id + (#'teams/create-project-role conn {:project-id project-id :profile-id profile-id :role role})))) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 16feb70149..ca05fbfa26 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -110,7 +110,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (:current-team-id state)] - (->> (rp/query! :team-members {:team-id team-id}) + (->> (rp/cmd! :get-team-members {:team-id team-id}) (rx/map team-members-fetched)))))) ;; --- EVENT: fetch-team-stats @@ -128,7 +128,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (:current-team-id state)] - (->> (rp/query! :team-stats {:team-id team-id}) + (->> (rp/cmd! :get-team-stats {:team-id team-id}) (rx/map team-stats-fetched)))))) ;; --- EVENT: fetch-team-invitations @@ -146,7 +146,7 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (:current-team-id state)] - (->> (rp/query! :team-invitations {:team-id team-id}) + (->> (rp/cmd! :get-team-invitations {:team-id team-id}) (rx/map team-invitations-fetched)))))) ;; --- EVENT: fetch-team-webhooks @@ -384,14 +384,13 @@ (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] - (->> (rp/mutation! :create-team {:name name}) + (->> (rp/cmd! :create-team {:name name}) (rx/tap on-success) (rx/map team-created) (rx/catch on-error)))))) ;; --- EVENT: create-team-with-invitations - (defn create-team-with-invitations [{:keys [name emails role] :as params}] (us/assert! ::us/string name) @@ -404,7 +403,7 @@ params {:name name :emails #{emails} :role role}] - (->> (rp/mutation! :create-team-and-invite-members params) + (->> (rp/cmd! :create-team-and-invitations params) (rx/tap on-success) (rx/map team-created) (rx/catch on-error)))))) @@ -421,7 +420,7 @@ ptk/WatchEvent (watch [_ _ _] - (->> (rp/mutation! :update-team params) + (->> (rp/cmd! :update-team params) (rx/ignore))))) (defn update-team-photo @@ -440,7 +439,7 @@ (->> (rx/of file) (rx/map di/validate-file) (rx/map prepare) - (rx/mapcat #(rp/mutation :update-team-photo %)) + (rx/mapcat #(rp/cmd! :update-team-photo %)) (rx/do on-success) (rx/map du/fetch-teams) (rx/catch on-error)))))) @@ -454,7 +453,7 @@ (watch [_ state _] (let [team-id (:current-team-id state) params (assoc params :team-id team-id)] - (->> (rp/mutation! :update-team-member-role params) + (->> (rp/cmd! :update-team-member-role params) (rx/mapcat (fn [_] (rx/of (fetch-team-members) (du/fetch-teams))))))))) @@ -467,7 +466,7 @@ (watch [_ state _] (let [team-id (:current-team-id state) params (assoc params :team-id team-id)] - (->> (rp/mutation! :delete-team-member params) + (->> (rp/cmd! :delete-team-member params) (rx/mapcat (fn [_] (rx/of (fetch-team-members) (du/fetch-teams))))))))) @@ -487,7 +486,7 @@ params (cond-> {:id team-id} (uuid? reassign-to) (assoc :reassign-to reassign-to))] - (->> (rp/mutation! :leave-team params) + (->> (rp/cmd! :leave-team params) (rx/tap #(tm/schedule on-success)) (rx/catch on-error)))))) @@ -506,7 +505,7 @@ :or {on-success identity on-error rx/throw}} (meta params) params (dissoc params :resend?)] - (->> (rp/mutation! :invite-team-member params) + (->> (rp/cmd! :create-team-invitations params) (rx/tap on-success) (rx/catch on-error)))))) @@ -524,7 +523,7 @@ (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] - (->> (rp/mutation! :update-team-invitation-role params) + (->> (rp/cmd! :update-team-invitation-role params) (rx/tap on-success) (rx/catch on-error)))))) @@ -538,7 +537,7 @@ (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] - (->> (rp/mutation! :delete-team-invitation params) + (->> (rp/cmd! :delete-team-invitation params) (rx/tap on-success) (rx/catch on-error)))))) @@ -608,7 +607,7 @@ (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] - (->> (rp/mutation! :delete-team {:id id}) + (->> (rp/cmd! :delete-team {:id id}) (rx/tap on-success) (rx/catch on-error)))))) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 51f9626225..906ddfd7ac 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -87,7 +87,7 @@ (ptk/reify ::fetch-teams ptk/WatchEvent (watch [_ _ _] - (->> (rp/query! :teams) + (->> (rp/cmd! :get-teams) (rx/map teams-fetched))))) ;; --- EVENT: fetch-profile @@ -446,7 +446,7 @@ (ptk/reify ::fetch-team-users ptk/WatchEvent (watch [_ _ _] - (->> (rp/query! :team-users {:team-id team-id}) + (->> (rp/cmd! :get-team-users {:team-id team-id}) (rx/map #(partial fetched %))))))) (defn fetch-file-comments-users diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 74d1e1c1a6..e6517f329b 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -265,7 +265,7 @@ (->> (rx/zip (rp/cmd! :get-file {:id file-id :features features}) (rp/cmd! :get-file-object-thumbnails {:file-id file-id}) (rp/query! :project {:id project-id}) - (rp/query! :team-users {:file-id file-id}) + (rp/cmd! :get-team-users {:file-id file-id}) (rp/cmd! :get-profiles-for-file-comments {:file-id file-id :share-id share-id})) (rx/take 1) (rx/map (partial bundle-fetched features)) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index aea3ed1691..2fa651daff 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -17,6 +17,11 @@ (derive :get-file-libraries ::query) (derive :get-file-fragment ::query) (derive :search-files ::query) +(derive :get-teams ::query) +(derive :get-team-users ::query) +(derive :get-team-members ::query) +(derive :get-team-stats ::query) +(derive :get-team-invitations ::query) (defn handle-response [{:keys [status body] :as response}] From c820c49fc520955bd44030f4b7eca8faa94f84ec Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 11:17:47 +0100 Subject: [PATCH 355/682] :sparkles: Add generic (blocking) retry macro And use it on audit handling --- backend/src/app/loggers/audit.clj | 26 +++++++++++++++-------- backend/src/app/rpc/retry.clj | 20 +++++++++--------- backend/src/app/util/retry.clj | 34 +++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 19 deletions(-) create mode 100644 backend/src/app/util/retry.clj diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 3d1d172b67..0d692c2752 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -21,6 +21,7 @@ [app.main :as-alias main] [app.metrics :as mtx] [app.tokens :as tokens] + [app.util.retry :as rtry] [app.util.time :as dt] [app.worker :as wrk] [clojure.spec.alpha :as s] @@ -143,22 +144,29 @@ (defn- persist-event! [pool event] (us/verify! ::event event) - (let [now (dt/now) - params {:id (uuid/next) + (let [params {:id (uuid/next) :name (:name event) :type (:type event) :profile-id (:profile-id event) - :created-at now - :tracked-at now :ip-addr (:ip-addr event) :props (:props event)}] (when (contains? cf/flags :audit-log) - (db/insert! pool :audit-log - (-> params - (update :props db/tjson) - (update :ip-addr db/inet) - (assoc :source "backend")))) + + ;; NOTE: this operation may cause primary key conflicts on inserts + ;; because of the timestamp precission (two concurrent requests), in + ;; this case we just retry the operation. + (rtry/with-retry {::rtry/when rtry/conflict-exception? + ::rtry/max-retries 6 + ::rtry/label "persist-audit-log-event"} + (let [now (dt/now)] + (db/insert! pool :audit-log + (-> params + (update :props db/tjson) + (update :ip-addr db/inet) + (assoc :created-at now) + (assoc :tracked-at now) + (assoc :source "backend")))))) (when (and (contains? cf/flags :webhooks) (::webhooks/event? event)) diff --git a/backend/src/app/rpc/retry.clj b/backend/src/app/rpc/retry.clj index ffcb80106e..450ab4e9c6 100644 --- a/backend/src/app/rpc/retry.clj +++ b/backend/src/app/rpc/retry.clj @@ -5,23 +5,23 @@ ;; Copyright (c) KALEIDOS INC (ns app.rpc.retry - "A fault tolerance helpers. Allow retry some operations that we know - we can retry." + "A fault tolerance RPC middleware. Allow retry some operations that we + know we can retry." (:require [app.common.logging :as l] + [app.util.retry :refer [conflict-exception?]] [app.util.services :as sv] [promesa.core :as p])) (defn conflict-db-insert? "Check if exception matches a insertion conflict on postgresql." [e] - (and (instance? org.postgresql.util.PSQLException e) - (= "23505" (.getSQLState e)))) + (conflict-exception? e)) + +(def always-false (constantly false)) (defn wrap-retry - [_ f {:keys [::matches ::sv/name] - :or {matches (constantly false)} - :as mdata}] + [_ f {:keys [::matches ::sv/name] :or {matches always-false} :as mdata}] (when (::enabled mdata) (l/debug :hint "wrapping retry" :name name)) @@ -29,8 +29,8 @@ (if-let [max-retries (::max-retries mdata)] (fn [cfg params] (letfn [(run [retry] - (-> (f cfg params) - (p/catch (partial handle-error retry)))) + (->> (f cfg params) + (p/merr (partial handle-error retry)))) (handle-error [retry cause] (if (matches cause) @@ -40,6 +40,6 @@ (run current-retry) (throw cause))) (throw cause)))] - (run 0))) + (run 1))) f)) diff --git a/backend/src/app/util/retry.clj b/backend/src/app/util/retry.clj new file mode 100644 index 0000000000..666a09f478 --- /dev/null +++ b/backend/src/app/util/retry.clj @@ -0,0 +1,34 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; Copyright (c) KALEIDOS INC + +(ns app.util.retry + "A fault tolerance helpers. Allow retry some operations that we know + we can retry." + (:require + [app.common.logging :as l]) + (:import + org.postgresql.util.PSQLException)) + +(defn conflict-exception? + "Check if exception matches a insertion conflict on postgresql." + [e] + (and (instance? PSQLException e) + (= "23505" (.getSQLState ^PSQLException e)))) + +(defmacro with-retry + [{:keys [::when ::max-retries ::label] :or {max-retries 3}} & body] + `(loop [tnum# 1] + (let [result# (try + ~@body + (catch Throwable cause# + (if (and (~when cause#) (<= tnum# ~max-retries)) + ::retry + (throw cause#))))] + (if (= ::retry result#) + (do + (l/warn :hint "retrying operation" :label ~label) + (recur (inc tnum#))) + result#)))) From 653b6bdb4235233996f6521d91d06f932aafb4ff Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 11:20:46 +0100 Subject: [PATCH 356/682] :fire: Remove old deprecated method from fonts queries RPC --- backend/src/app/rpc/queries/fonts.clj | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/backend/src/app/rpc/queries/fonts.clj b/backend/src/app/rpc/queries/fonts.clj index ac592ed1af..12fa2d8de5 100644 --- a/backend/src/app/rpc/queries/fonts.clj +++ b/backend/src/app/rpc/queries/fonts.clj @@ -14,24 +14,6 @@ [app.util.services :as sv] [clojure.spec.alpha :as s])) -;; --- Query: Team Font Variants - -;; FIXME: PLEASE RIGHT NOW -;; TODO: deprecated, should be removed on 1.7.x - -(s/def ::team-id ::us/uuid) -(s/def ::profile-id ::us/uuid) -(s/def ::team-font-variants - (s/keys :req-un [::profile-id ::team-id])) - -(sv/defmethod ::team-font-variants - [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] - (with-open [conn (db/open pool)] - (teams/check-read-permissions! conn profile-id team-id) - (db/query conn :team-font-variant - {:team-id team-id - :deleted-at nil}))) - ;; --- Query: Font Variants (s/def ::file-id ::us/uuid) From c2ced974b1a784521cb7d4f13b2728eed697e2ea Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 11:24:19 +0100 Subject: [PATCH 357/682] :paperclip: Add missing doc/added metadata on fonts related RPC methods --- backend/src/app/rpc/queries/fonts.clj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend/src/app/rpc/queries/fonts.clj b/backend/src/app/rpc/queries/fonts.clj index 12fa2d8de5..e019b00fb4 100644 --- a/backend/src/app/rpc/queries/fonts.clj +++ b/backend/src/app/rpc/queries/fonts.clj @@ -10,6 +10,7 @@ [app.db :as db] [app.rpc.commands.files :as files] [app.rpc.commands.teams :as teams] + [app.rpc.doc :as-alias doc] [app.rpc.queries.projects :as projects] [app.util.services :as sv] [clojure.spec.alpha :as s])) @@ -30,6 +31,7 @@ (contains? o :project-id))))) (sv/defmethod ::font-variants + {::doc/added "1.7"} [{:keys [pool] :as cfg} {:keys [profile-id team-id file-id project-id] :as params}] (with-open [conn (db/open pool)] (cond From 7d2e3a086412d02ada502738d3bf16c8e29e445a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 11:34:01 +0100 Subject: [PATCH 358/682] :fire: Remove deprecated RPC methods --- backend/src/app/rpc.clj | 6 +- backend/src/app/rpc/mutations/comments.clj | 123 ------------------ backend/src/app/rpc/mutations/management.clj | 58 --------- .../src/app/rpc/mutations/verify_token.clj | 28 ---- backend/src/app/rpc/queries/comments.clj | 82 ------------ .../backend_tests/rpc_management_test.clj | 18 +-- backend/test/backend_tests/rpc_team_test.clj | 6 +- 7 files changed, 13 insertions(+), 308 deletions(-) delete mode 100644 backend/src/app/rpc/mutations/comments.clj delete mode 100644 backend/src/app/rpc/mutations/management.clj delete mode 100644 backend/src/app/rpc/mutations/verify_token.clj delete mode 100644 backend/src/app/rpc/queries/comments.clj diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 61c59a58a1..71ffd447c5 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -237,7 +237,6 @@ (->> (sv/scan-ns 'app.rpc.queries.projects 'app.rpc.queries.files 'app.rpc.queries.teams - 'app.rpc.queries.comments 'app.rpc.queries.profile 'app.rpc.queries.viewer 'app.rpc.queries.fonts) @@ -250,13 +249,10 @@ (->> (sv/scan-ns 'app.rpc.mutations.media 'app.rpc.mutations.profile 'app.rpc.mutations.files - 'app.rpc.mutations.comments 'app.rpc.mutations.projects 'app.rpc.mutations.teams - 'app.rpc.mutations.management 'app.rpc.mutations.fonts - 'app.rpc.mutations.share-link - 'app.rpc.mutations.verify-token) + 'app.rpc.mutations.share-link) (map (partial process-method cfg)) (into {})))) diff --git a/backend/src/app/rpc/mutations/comments.clj b/backend/src/app/rpc/mutations/comments.clj deleted file mode 100644 index 6b606ba359..0000000000 --- a/backend/src/app/rpc/mutations/comments.clj +++ /dev/null @@ -1,123 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.rpc.mutations.comments - (:require - [app.common.exceptions :as ex] - [app.common.spec :as us] - [app.db :as db] - [app.rpc.commands.comments :as cmd.comments] - [app.rpc.commands.files :as cmd.files] - [app.rpc.doc :as-alias doc] - [app.rpc.retry :as retry] - [app.util.services :as sv] - [clojure.spec.alpha :as s])) - -;; --- Mutation: Create Comment Thread - -(s/def ::create-comment-thread ::cmd.comments/create-comment-thread) - -(sv/defmethod ::create-comment-thread - {::retry/max-retries 3 - ::retry/matches retry/conflict-db-insert? - ::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] - (db/with-atomic [conn pool] - (cmd.files/check-comment-permissions! conn profile-id file-id share-id) - (cmd.comments/create-comment-thread conn params))) - -;; --- Mutation: Update Comment Thread Status - -(s/def ::id ::us/uuid) -(s/def ::share-id (s/nilable ::us/uuid)) - -(s/def ::update-comment-thread-status ::cmd.comments/update-comment-thread-status) - -(sv/defmethod ::update-comment-thread-status - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id id share-id] :as params}] - (db/with-atomic [conn pool] - (let [cthr (db/get-by-id conn :comment-thread id {:for-update true})] - (when-not cthr (ex/raise :type :not-found)) - (cmd.files/check-comment-permissions! conn profile-id (:file-id cthr) share-id) - (cmd.comments/upsert-comment-thread-status! conn profile-id (:id cthr))))) - - -;; --- Mutation: Update Comment Thread - -(s/def ::update-comment-thread ::cmd.comments/update-comment-thread) - -(sv/defmethod ::update-comment-thread - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id id is-resolved share-id] :as params}] - (db/with-atomic [conn pool] - (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] - (when-not thread - (ex/raise :type :not-found)) - - (cmd.files/check-comment-permissions! conn profile-id (:file-id thread) share-id) - (db/update! conn :comment-thread - {:is-resolved is-resolved} - {:id id}) - nil))) - - -;; --- Mutation: Add Comment - -(s/def ::add-comment ::cmd.comments/create-comment) - -(sv/defmethod ::add-comment - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} params] - (db/with-atomic [conn pool] - (cmd.comments/create-comment conn params))) - - -;; --- Mutation: Update Comment - -(s/def ::update-comment ::cmd.comments/update-comment) - -(sv/defmethod ::update-comment - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} params] - (db/with-atomic [conn pool] - (cmd.comments/update-comment conn params))) - - -;; --- Mutation: Delete Comment Thread - -(s/def ::delete-comment-thread ::cmd.comments/delete-comment-thread) - -(sv/defmethod ::delete-comment-thread - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] - (db/with-atomic [conn pool] - (let [thread (db/get-by-id conn :comment-thread id {:for-update true})] - (when-not (= (:owner-id thread) profile-id) - (ex/raise :type :validation :code :not-allowed)) - (db/delete! conn :comment-thread {:id id}) - nil))) - - -;; --- Mutation: Delete comment - -(s/def ::delete-comment ::cmd.comments/delete-comment) - -(sv/defmethod ::delete-comment - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] - (db/with-atomic [conn pool] - (let [comment (db/get-by-id conn :comment id {:for-update true})] - (when-not (= (:owner-id comment) profile-id) - (ex/raise :type :validation :code :not-allowed)) - (db/delete! conn :comment {:id id})))) diff --git a/backend/src/app/rpc/mutations/management.clj b/backend/src/app/rpc/mutations/management.clj deleted file mode 100644 index e29a5e98e1..0000000000 --- a/backend/src/app/rpc/mutations/management.clj +++ /dev/null @@ -1,58 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.rpc.mutations.management - "Move & Duplicate RPC methods for files and projects." - (:require - [app.db :as db] - [app.rpc.commands.management :as cmd.mgm] - [app.rpc.doc :as-alias doc] - [app.util.services :as sv] - [clojure.spec.alpha :as s])) - -;; --- MUTATION: Duplicate File - -(s/def ::duplicate-file ::cmd.mgm/duplicate-file) - -(sv/defmethod ::duplicate-file - {::doc/added "1.2" - ::doc/deprecated "1.16"} - [{:keys [pool] :as cfg} params] - (db/with-atomic [conn pool] - (cmd.mgm/duplicate-file conn params))) - -;; --- MUTATION: Duplicate Project - -(s/def ::duplicate-project ::cmd.mgm/duplicate-project) - -(sv/defmethod ::duplicate-project - {::doc/added "1.2" - ::doc/deprecated "1.16"} - [{:keys [pool] :as cfg} params] - (db/with-atomic [conn pool] - (cmd.mgm/duplicate-project conn params))) - -;; --- MUTATION: Move file - -(s/def ::move-files ::cmd.mgm/move-files) - -(sv/defmethod ::move-files - {::doc/added "1.2" - ::doc/deprecated "1.16"} - [{:keys [pool] :as cfg} params] - (db/with-atomic [conn pool] - (cmd.mgm/move-files conn params))) - -;; --- MUTATION: Move project - -(s/def ::move-project ::cmd.mgm/move-project) - -(sv/defmethod ::move-project - {::doc/added "1.2" - ::doc/deprecated "1.16"} - [{:keys [pool] :as cfg} params] - (db/with-atomic [conn pool] - (cmd.mgm/move-project conn params))) diff --git a/backend/src/app/rpc/mutations/verify_token.clj b/backend/src/app/rpc/mutations/verify_token.clj deleted file mode 100644 index a8551847bc..0000000000 --- a/backend/src/app/rpc/mutations/verify_token.clj +++ /dev/null @@ -1,28 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.rpc.mutations.verify-token - (:require - [app.db :as db] - [app.rpc.commands.verify-token :refer [process-token]] - [app.rpc.doc :as-alias doc] - [app.tokens :as tokens] - [app.util.services :as sv] - [clojure.spec.alpha :as s])) - -(s/def ::verify-token - (s/keys :req-un [::token] - :opt-un [::profile-id])) - -(sv/defmethod ::verify-token - {:auth false - ::doc/added "1.1" - ::doc/deprecated "1.15"} - [{:keys [pool sprops] :as cfg} {:keys [token] :as params}] - (db/with-atomic [conn pool] - (let [claims (tokens/verify sprops {:token token}) - cfg (assoc cfg :conn conn)] - (process-token cfg params claims)))) diff --git a/backend/src/app/rpc/queries/comments.clj b/backend/src/app/rpc/queries/comments.clj deleted file mode 100644 index fbcb86f033..0000000000 --- a/backend/src/app/rpc/queries/comments.clj +++ /dev/null @@ -1,82 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) KALEIDOS INC - -(ns app.rpc.queries.comments - (:require - [app.db :as db] - [app.rpc.commands.comments :as cmd.comments] - [app.rpc.commands.files :as cmd.files] - [app.rpc.commands.teams :as teams] - [app.rpc.doc :as-alias doc] - [app.util.services :as sv] - [clojure.spec.alpha :as s])) - -(defn decode-row - [{:keys [participants position] :as row}] - (cond-> row - (db/pgpoint? position) (assoc :position (db/decode-pgpoint position)) - (db/pgobject? participants) (assoc :participants (db/decode-transit-pgobject participants)))) - -;; --- QUERY: Comment Threads - -(s/def ::comment-threads ::cmd.comments/get-comment-threads) - -(sv/defmethod ::comment-threads - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} params] - (with-open [conn (db/open pool)] - (cmd.comments/retrieve-comment-threads conn params))) - -;; --- QUERY: Unread Comment Threads - -(s/def ::unread-comment-threads ::cmd.comments/get-unread-comment-threads) - -(sv/defmethod ::unread-comment-threads - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] - (with-open [conn (db/open pool)] - (teams/check-read-permissions! conn profile-id team-id) - (cmd.comments/retrieve-unread-comment-threads conn params))) - -;; --- QUERY: Single Comment Thread - -(s/def ::comment-thread ::cmd.comments/get-comment-thread) - -(sv/defmethod ::comment-thread - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id] :as params}] - (with-open [conn (db/open pool)] - (cmd.files/check-comment-permissions! conn profile-id file-id share-id) - (cmd.comments/get-comment-thread conn params))) - -;; --- QUERY: Comments - -(s/def ::comments ::cmd.comments/get-comments) - -(sv/defmethod ::comments - {::doc/added "1.0" - ::doc/deprecated "1.15"} - [{:keys [pool] :as cfg} {:keys [profile-id thread-id share-id] :as params}] - (with-open [conn (db/open pool)] - (let [thread (db/get-by-id conn :comment-thread thread-id)] - (cmd.files/check-comment-permissions! conn profile-id (:file-id thread) share-id)) - (cmd.comments/get-comments conn thread-id))) - - -;; --- QUERY: Get file comments users - -(s/def ::file-comments-users ::cmd.comments/get-profiles-for-file-comments) - -(sv/defmethod ::file-comments-users - {::doc/deprecated "1.15" - ::doc/added "1.13"} - [{:keys [pool] :as cfg} {:keys [profile-id file-id share-id]}] - (with-open [conn (db/open pool)] - (cmd.files/check-comment-permissions! conn profile-id file-id share-id) - (cmd.comments/get-file-comments-users conn file-id profile-id))) diff --git a/backend/test/backend_tests/rpc_management_test.clj b/backend/test/backend_tests/rpc_management_test.clj index c1f03838d9..5b4f26d058 100644 --- a/backend/test/backend_tests/rpc_management_test.clj +++ b/backend/test/backend_tests/rpc_management_test.clj @@ -53,7 +53,7 @@ :profile-id (:id profile) :file-id (:id file1) :name "file 1 (copy)"} - out (th/mutation! data)] + out (th/command! data)] ;; (th/print-result! out) @@ -125,7 +125,7 @@ :profile-id (:id profile) :file-id (:id file1) :name "file 1 (copy)"} - out (th/mutation! data)] + out (th/command! data)] ;; (th/print-result! out) @@ -187,7 +187,7 @@ :profile-id (:id profile) :project-id (:id project) :name "project 1 (copy)"} - out (th/mutation! data)] + out (th/command! data)] ;; Check that result is correct (t/is (nil? (:error out))) @@ -253,7 +253,7 @@ :profile-id (:id profile) :project-id (:id project) :name "project 1 (copy)"} - out (th/mutation! data)] + out (th/command! data)] ;; Check that result is correct (t/is (nil? (:error out))) @@ -317,7 +317,7 @@ :project-id (:id project1) :ids #{(:id file1)}} - out (th/mutation! data) + out (th/command! data) error (:error out)] (t/is (th/ex-info? error)) (t/is (th/ex-of-type? error :validation)) @@ -337,7 +337,7 @@ :project-id (:id project2) :ids #{(:id file1)}} - out (th/mutation! data)] + out (th/command! data)] (t/is (nil? (:error out))) (t/is (nil? (:result out))) @@ -419,7 +419,7 @@ :profile-id (:id profile) :project-id (:id project2) :ids #{(:id file1)}} - out (th/mutation! data)] + out (th/command! data)] (t/is (nil? (:error out))) (t/is (nil? (:result out))) @@ -492,7 +492,7 @@ :profile-id (:id profile) :project-id (:id project2) :ids #{(:id file2)}} - out (th/mutation! data)] + out (th/command! data)] (t/is (nil? (:error out))) (t/is (nil? (:result out))) @@ -578,7 +578,7 @@ :profile-id (:id profile) :project-id (:id project1) :team-id (:id team)} - out (th/mutation! data)] + out (th/command! data)] (t/is (nil? (:error out))) (t/is (nil? (:result out))) diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj index 302f80dc50..cababafae0 100644 --- a/backend/test/backend_tests/rpc_team_test.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -179,7 +179,7 @@ :valid-until (dt/in-future "48h")}) (let [data {::th/type :verify-token :token token} - out (th/mutation! data)] + out (th/command! data)] ;; (th/print-result! out) (t/is (th/success? out)) (let [result (:result out)] @@ -205,7 +205,7 @@ :valid-until (dt/in-future "48h")}) (let [data {::th/type :verify-token :token token :profile-id (:id profile2)} - out (th/mutation! data)] + out (th/command! data)] ;; (th/print-result! out) (t/is (th/success? out)) (let [result (:result out)] @@ -226,7 +226,7 @@ :valid-until (dt/in-future "48h")}) (let [data {::th/type :verify-token :token token :profile-id (:id profile1)} - out (th/mutation! data)] + out (th/command! data)] ;; (th/print-result! out) (t/is (not (th/success? out))) (let [edata (-> out :error ex-data)] From 842463ed1bb93534034e47770badc617449f930d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 15:29:43 +0100 Subject: [PATCH 359/682] :tada: Add the ability to copy team invitation link --- backend/src/app/main.clj | 2 +- backend/src/app/rpc/commands/teams.clj | 113 +++++++---- backend/src/app/rpc/commands/verify_token.clj | 2 +- backend/src/app/rpc/mutations/teams.clj | 35 ++-- backend/test/backend_tests/rpc_team_test.clj | 12 +- frontend/src/app/main/data/dashboard.cljs | 34 +++- frontend/src/app/main/ui/dashboard/team.cljs | 182 +++++++++++------- frontend/translations/en.po | 8 + frontend/translations/es.po | 9 + 9 files changed, 262 insertions(+), 135 deletions(-) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index b2145aeb46..c8d3a181a6 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -322,7 +322,7 @@ ::http.client/client (ig/ref ::http.client/client) ::db/pool (ig/ref ::db/pool) ::wrk/executor (ig/ref ::wrk/executor) - + ::props (ig/ref :app.setup/props) :pool (ig/ref ::db/pool) :session (ig/ref :app.http.session/manager) :sprops (ig/ref :app.setup/props) diff --git a/backend/src/app/rpc/commands/teams.clj b/backend/src/app/rpc/commands/teams.clj index ec88484e03..5abed23ebc 100644 --- a/backend/src/app/rpc/commands/teams.clj +++ b/backend/src/app/rpc/commands/teams.clj @@ -15,6 +15,7 @@ [app.db :as db] [app.emails :as eml] [app.loggers.audit :as audit] + [app.main :as-alias main] [app.media :as media] [app.rpc.climit :as climit] [app.rpc.doc :as-alias doc] @@ -260,7 +261,7 @@ (def sql:team-invitations "select email_to as email, role, (valid_until < now()) as expired - from team_invitation where team_id = ? order by valid_until desc") + from team_invitation where team_id = ? order by valid_until desc, created_at desc") (defn get-team-invitations [conn team-id] @@ -628,25 +629,37 @@ "insert into team_invitation(team_id, email_to, role, valid_until) values (?, ?, ?, ?) on conflict(team_id, email_to) do - update set role = ?, valid_until = ?, updated_at = now();") + update set role = ?, updated_at = now();") + +(defn- create-invitation-token + [cfg {:keys [expire profile-id team-id member-id member-email role]}] + (tokens/generate (::main/props cfg) + {:iss :team-invitation + :exp expire + :profile-id profile-id + :role role + :team-id team-id + :member-email member-email + :member-id member-id})) + +(defn- create-profile-identity-token + [cfg profile] + (tokens/generate (::main/props cfg) + {:iss :profile-identity + :profile-id (:id profile) + :exp (dt/in-future {:days 30})})) (defn- create-invitation - [{:keys [conn sprops team profile role email] :as cfg}] - (let [member (profile/retrieve-profile-data-by-email conn email) - token-exp (dt/in-future "168h") ;; 7 days - email (str/lower email) - itoken (tokens/generate sprops - {:iss :team-invitation - :exp token-exp - :profile-id (:id profile) - :role role - :team-id (:id team) - :member-email (:email member email) - :member-id (:id member)}) - ptoken (tokens/generate sprops - {:iss :profile-identity - :profile-id (:id profile) - :exp (dt/in-future {:days 30})})] + [{:keys [::conn] :as cfg} {:keys [team profile role email] :as params}] + (let [member (profile/retrieve-profile-data-by-email conn email) + expire (dt/in-future "168h") ;; 7 days + itoken (create-invitation-token cfg {:profile-id (:id profile) + :expire expire + :team-id (:id team) + :member-email (or (:email member) email) + :member-id (:id member) + :role role}) + ptoken (create-profile-identity-token cfg profile)] (when (and member (not (eml/allow-send-emails? conn member))) (ex/raise :type :validation @@ -687,11 +700,10 @@ {:id (:id member)}))) (do (db/exec-one! conn [sql:upsert-team-invitation - (:id team) (str/lower email) (name role) - token-exp (name role) token-exp]) + (:id team) (str/lower email) (name role) expire (name role)]) (eml/send! {::eml/conn conn ::eml/factory eml/invite-to-team - :public-uri (:public-uri cfg) + :public-uri (cf/get :public-uri) :to email :invited-by (:fullname profile) :team (:name team) @@ -727,15 +739,14 @@ :code :profile-is-muted :hint "looks like the profile has reported repeatedly as spam or has permanent bounces")) - (let [invitations (->> emails + (let [cfg (assoc cfg ::conn conn) + invitations (->> emails (map (fn [email] - (assoc cfg - :email email - :conn conn - :team team - :profile profile - :role role))) - (map create-invitation))] + {:email (str/lower email) + :team team + :profile profile + :role role})) + (map (partial create-invitation cfg)))] (with-meta (vec invitations) {::audit/props {:invitations (count invitations)}}))))) @@ -743,26 +754,26 @@ ;; --- Mutation: Create Team & Invite Members (s/def ::emails ::us/set-of-valid-emails) -(s/def ::create-team-and-invitations +(s/def ::create-team-with-invitations (s/merge ::create-team (s/keys :req-un [::emails ::role]))) -(sv/defmethod ::create-team-and-invitations +(sv/defmethod ::create-team-with-invitations {::doc/added "1.17"} [{:keys [pool] :as cfg} {:keys [profile-id emails role] :as params}] (db/with-atomic [conn pool] (let [team (create-team conn params) - profile (db/get-by-id conn :profile profile-id)] + profile (db/get-by-id conn :profile profile-id) + cfg (assoc cfg ::conn conn)] ;; Create invitations for all provided emails. - (doseq [email emails] - (create-invitation - (assoc cfg - :conn conn - :team team - :profile profile - :email email - :role role))) + (->> emails + (map (fn [email] + {:team team + :profile profile + :email (str/lower email) + :role role})) + (run! (partial create-invitation cfg))) (-> team (vary-meta assoc ::audit/props {:invitations (count emails)}) @@ -777,6 +788,28 @@ :profile-id profile-id :invitations (count emails)}}))))))) +;; --- Query: get-team-invitation-token + +(s/def ::get-team-invitation-token + (s/keys :req-un [::profile-id ::team-id ::email])) + +(sv/defmethod ::get-team-invitation-token + {::doc/added "1.17"} + [{:keys [::db/pool] :as cfg} {:keys [profile-id team-id email] :as params}] + (check-read-permissions! pool profile-id team-id) + (let [invit (-> (db/get pool :team-invitation + {:team-id team-id + :email-to (str/lower email)}) + (update :role keyword)) + member (profile/retrieve-profile-data-by-email pool (:email invit)) + token (create-invitation-token cfg {:team-id (:team-id invit) + :profile-id profile-id + :expire (:expire invit) + :role (:role invit) + :member-id (:id member) + :member-email (or (:email member) (:email-to invit))})] + {:token token})) + ;; --- Mutation: Update invitation role (s/def ::update-team-invitation-role diff --git a/backend/src/app/rpc/commands/verify_token.clj b/backend/src/app/rpc/commands/verify_token.clj index 3242a82114..66fce865d1 100644 --- a/backend/src/app/rpc/commands/verify_token.clj +++ b/backend/src/app/rpc/commands/verify_token.clj @@ -129,7 +129,7 @@ [{:keys [conn session] :as cfg} {:keys [profile-id token]} {:keys [member-id team-id member-email] :as claims}] - (us/assert ::team-invitation-claims claims) + (us/verify! ::team-invitation-claims claims) (let [invitation (db/get* conn :team-invitation {:team-id team-id :email-to member-email}) diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index d15a376df5..650ac18847 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -153,21 +153,20 @@ :code :profile-is-muted :hint "looks like the profile has reported repeatedly as spam or has permanent bounces")) - (let [invitations (->> emails + (let [cfg (assoc cfg ::cmd.teams/conn conn) + invitations (->> emails (map (fn [email] - (assoc cfg - :email email - :conn conn - :team team - :profile profile - :role role))) - (map #'cmd.teams/create-invitation))] + {:email (str/lower email) + :team team + :profile profile + :role role})) + (map (partial #'cmd.teams/create-invitation cfg)))] (with-meta (vec invitations) {::audit/props {:invitations (count invitations)}}))))) ;; --- Mutation: Create Team & Invite Members -(s/def ::create-team-and-invite-members ::cmd.teams/create-team-and-invitations) +(s/def ::create-team-and-invite-members ::cmd.teams/create-team-with-invitations) (sv/defmethod ::create-team-and-invite-members {::doc/added "1.0" @@ -175,17 +174,17 @@ [{:keys [::db/pool] :as cfg} {:keys [profile-id emails role] :as params}] (db/with-atomic [conn pool] (let [team (cmd.teams/create-team conn params) - profile (db/get-by-id conn :profile profile-id)] + profile (db/get-by-id conn :profile profile-id) + cfg (assoc cfg ::cmd.teams/conn conn)] ;; Create invitations for all provided emails. - (doseq [email emails] - (#'cmd.teams/create-invitation - (assoc cfg - :conn conn - :team team - :profile profile - :email email - :role role))) + (->> emails + (map (fn [email] + {:team team + :profile profile + :email (str/lower email) + :role role})) + (run! (partial #'cmd.teams/create-invitation cfg))) (-> team (vary-meta assoc ::audit/props {:invitations (count emails)}) diff --git a/backend/test/backend_tests/rpc_team_test.clj b/backend/test/backend_tests/rpc_team_test.clj index cababafae0..de66c24363 100644 --- a/backend/test/backend_tests/rpc_team_test.clj +++ b/backend/test/backend_tests/rpc_team_test.clj @@ -63,6 +63,16 @@ (t/is (th/success? out)) (t/is (= 1 (:call-count (deref mock))))) + ;; get invitation token + (let [params {::th/type :get-team-invitation-token + :profile-id (:id profile1) + :team-id (:id team) + :email "foo@bar.com"} + out (th/command! params)] + (t/is (th/success? out)) + (let [result (:result out)] + (contains? result :token))) + ;; invite user with bounce (th/reset-mock! mock) @@ -235,8 +245,6 @@ ))) - - (t/deftest invite-team-member-with-email-verification-disabled (with-mocks [mock {:target 'app.emails/send! :return nil}] (let [profile1 (th/create-profile* 1 {:is-active true}) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index ca05fbfa26..2aeb562fc5 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.spec :as us] [app.common.uuid :as uuid] + [app.config :as cf] [app.main.data.events :as ev] [app.main.data.fonts :as df] [app.main.data.media :as di] @@ -18,6 +19,7 @@ [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.timers :as tm] + [app.util.webapi :as wapi] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk])) @@ -403,7 +405,7 @@ params {:name name :emails #{emails} :role role}] - (->> (rp/cmd! :create-team-and-invitations params) + (->> (rp/cmd! :create-team-with-invitations params) (rx/tap on-success) (rx/map team-created) (rx/catch on-error)))))) @@ -509,6 +511,36 @@ (rx/tap on-success) (rx/catch on-error)))))) + +(defn copy-invitation-link + [{:keys [email team-id] :as params}] + (us/assert! ::us/email email) + (us/assert! ::us/uuid team-id) + + (ptk/reify ::copy-invitation-link + IDeref + (-deref [_] {:email email :team-id team-id}) + + + ptk/WatchEvent + (watch [_ state _] + (let [{:keys [on-success on-error] + :or {on-success identity + on-error rx/throw}} (meta params) + router (:router state)] + + (->> (rp/cmd! :get-team-invitation-token params) + (rx/map (fn [params] + (rt/resolve router :auth-verify-token {} params))) + (rx/map (fn [fragment] + (assoc @cf/public-uri :fragment fragment))) + (rx/tap (fn [uri] + (wapi/write-to-clipboard (str uri)))) + (rx/tap on-success) + (rx/ignore) + (rx/catch on-error)))))) + + (defn update-team-invitation-role [{:keys [email team-id role] :as params}] (us/assert! ::us/email email) diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 8997245e14..1c5a59f66e 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -448,81 +448,113 @@ :pending (= status :pending))} [:span.status-label (tr status-label)]])) -(mf/defc invitation-actions [{:keys [can-modify? delete resend] :as props}] - (let [show? (mf/use-state false)] - (when can-modify? - [:* - [:span.icon {:on-click #(reset! show? true)} [i/actions]] - [:& dropdown {:show @show? - :on-close #(reset! show? false)} - [:ul.dropdown.actions-dropdown - [:li {:on-click resend} (tr "labels.resend-invitation")] - [:li {:on-click delete} (tr "labels.delete-invitation")]]]]))) +(mf/defc invitation-actions + [{:keys [invitation team] :as props}] + (let [show? (mf/use-state false) + + team-id (:id team) + email (:email invitation) + role (:role invitation) + + on-resend-success + (mf/use-fn + (fn [] + (st/emit! (msg/success (tr "notifications.invitation-email-sent")) + (modal/hide)))) + + on-copy-success + (mf/use-fn + (fn [] + (st/emit! (msg/success (tr "notifications.invitation-link-copied")) + (modal/hide)))) + + on-error + (mf/use-fn + (mf/deps email) + (fn [{:keys [type code] :as error}] + (cond + (and (= :validation type) + (= :profile-is-muted code)) + (rx/of (msg/error (tr "errors.profile-is-muted"))) + + (and (= :validation type) + (= :member-is-muted code)) + (rx/of (msg/error (tr "errors.member-is-muted"))) + + (and (= :validation type) + (= :email-has-permanent-bounces code)) + (rx/of (msg/error (tr "errors.email-has-permanent-bounces" email))) + + :else + (rx/throw error)))) + + delete-fn + (mf/use-fn + (mf/deps email team-id) + (fn [] + (let [params {:email email :team-id team-id} + mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}] + (st/emit! (dd/delete-team-invitation (with-meta params mdata)))))) + + resend-fn + (mf/use-fn + (mf/deps email team-id) + (fn [] + (let [params (with-meta {:emails [email] + :team-id team-id + :resend? true + :role role} + {:on-success on-resend-success + :on-error on-error})] + (st/emit! + (-> (dd/invite-team-members params) + (with-meta {::ev/origin :team})))))) + + copy-fn + (mf/use-fn + (mf/deps email team-id) + (fn [] + (let [params (with-meta {:email email :team-id team-id} + {:on-success on-copy-success + :on-error on-error})] + (prn "KKK1") + (st/emit! + (-> (dd/copy-invitation-link params) + (with-meta {::ev/origin :team}))))))] + + + [:* + [:span.icon {:on-click #(reset! show? true)} [i/actions]] + [:& dropdown {:show @show? + :on-close #(reset! show? false)} + [:ul.dropdown.actions-dropdown + [:li {:on-click copy-fn} (tr "labels.copy-invitation-link")] + [:li {:on-click resend-fn} (tr "labels.resend-invitation")] + [:li {:on-click delete-fn} (tr "labels.delete-invitation")]]]])) (mf/defc invitation-row {::mf/wrap [mf/memo]} [{:keys [invitation can-invite? team] :as props}] - (let [expired? (:expired invitation) - email (:email invitation) - invitation-role (:role invitation) - status (if expired? - :expired - :pending) - - on-success - #(st/emit! (msg/success (tr "notifications.invitation-email-sent")) - (modal/hide) - (dd/fetch-team-invitations)) - - - on-error - (fn [email {:keys [type code] :as error}] - (cond - (and (= :validation type) - (= :profile-is-muted code)) - (msg/error (tr "errors.profile-is-muted")) - - (and (= :validation type) - (= :member-is-muted code)) - (msg/error (tr "errors.member-is-muted")) - - (and (= :validation type) - (= :email-has-permanent-bounces code)) - (msg/error (tr "errors.email-has-permanent-bounces" email)) - - :else - (msg/error (tr "errors.generic")))) + (let [expired? (:expired invitation) + email (:email invitation) + role (:role invitation) + status (if expired? :expired :pending) change-rol - (fn [role] - (let [params {:email email :team-id (:id team) :role role} - mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}] - (st/emit! (dd/update-team-invitation-role (with-meta params mdata))))) + (mf/use-fn + (mf/deps team email) + (fn [role] + (let [params {:email email :team-id (:id team) :role role} + mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}] + (st/emit! (dd/update-team-invitation-role (with-meta params mdata))))))] - delete-invitation - (fn [] - (let [params {:email email :team-id (:id team)} - mdata {:on-success #(st/emit! (dd/fetch-team-invitations))}] - (st/emit! (dd/delete-team-invitation (with-meta params mdata))))) - - resend-invitation - (fn [] - (let [params {:emails [email] - :team-id (:id team) - :resend? true - :role invitation-role} - mdata {:on-success on-success - :on-error (partial on-error email)}] - (st/emit! (-> (dd/invite-team-members (with-meta params mdata)) - (with-meta {::ev/origin :team})) - (dd/fetch-team-invitations))))] [:div.table-row [:div.table-field.mail email] [:div.table-field.roles [:& invitation-role-selector {:can-invite? can-invite? - :role invitation-role + :role role :status status :change-to-editor (partial change-rol :editor) :change-to-admin (partial change-rol :admin)}]] @@ -530,20 +562,22 @@ [:div.table-field.status [:& invitation-status-badge {:status status}]] [:div.table-field.actions - [:& invitation-actions - {:can-modify? can-invite? - :delete delete-invitation - :resend resend-invitation}]]])) + (when can-invite? + [:& invitation-actions + {:invitation invitation + :team team}])]])) -(mf/defc empty-invitation-table [can-invite?] +(mf/defc empty-invitation-table + [{:keys [can-invite?] :as props}] [:div.empty-invitations [:span (tr "labels.no-invitations")] - (when (:can-invite? can-invite?) [:span (tr "labels.no-invitations-hint")])]) + (when can-invite? + [:span (tr "labels.no-invitations-hint")])]) (mf/defc invitation-section [{:keys [team invitations] :as props}] - (let [owner? (get-in team [:permissions :is-owner]) - admin? (get-in team [:permissions :is-admin]) + (let [owner? (dm/get-in team [:permissions :is-owner]) + admin? (dm/get-in team [:permissions :is-admin]) can-invite? (or owner? admin?)] [:div.dashboard-table.invitations @@ -555,7 +589,11 @@ [:& empty-invitation-table {:can-invite? can-invite?}] [:div.table-rows (for [invitation invitations] - [:& invitation-row {:key (:email invitation) :invitation invitation :can-invite? can-invite? :team team}])])])) + [:& invitation-row + {:key (:email invitation) + :invitation invitation + :can-invite? can-invite? + :team team}])])])) (mf/defc team-invitations-page [{:keys [team] :as props}] @@ -568,7 +606,7 @@ (tr "dashboard.your-penpot") (:name team))))) - (mf/with-effect + (mf/with-effect [] (st/emit! (dd/fetch-team-invitations))) [:* diff --git a/frontend/translations/en.po b/frontend/translations/en.po index de91460a86..ba88e0d552 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1436,6 +1436,10 @@ msgstr "Rename team" msgid "labels.resend-invitation" msgstr "Resend invitation" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.copy-invitation-link" +msgstr "Copy invitation link" + #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Retry" @@ -1957,6 +1961,10 @@ msgstr "Update a component in a shared library" msgid "notifications.invitation-email-sent" msgstr "Invitation sent successfully" +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-link-copied" +msgstr "Invitation link copied" + #: src/app/main/ui/settings/delete_account.cljs msgid "notifications.profile-deletion-not-allowed" msgstr "You can't delete you profile. Reassign your teams before proceed." diff --git a/frontend/translations/es.po b/frontend/translations/es.po index f06309bb07..2c4076331b 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1606,6 +1606,10 @@ msgstr "Renombra el equipo" msgid "labels.resend-invitation" msgstr "Reenviar invitacion" +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.copy-invitation-link" +msgstr "Copiar link de invitación" + #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" msgstr "Reintentar" @@ -2172,6 +2176,11 @@ msgstr "Actualizar un componente en biblioteca" msgid "notifications.invitation-email-sent" msgstr "Invitación enviada con éxito" +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-link-copied" +msgstr "Enlace de invitacion copiado" + + #: src/app/main/ui/settings/delete_account.cljs msgid "notifications.profile-deletion-not-allowed" msgstr "No puedes borrar tu perfil. Reasigna tus equipos antes de seguir." From 096b5f096c6bee0e1debd7b7894960614450f32c Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Dec 2022 15:40:35 +0100 Subject: [PATCH 360/682] :paperclip: Add some cosmetic changes to kondo config --- .clj-kondo/config.edn | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index e655c6e4c9..5989d2c526 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -45,6 +45,15 @@ :redundant-do {:level :off} + :earmuffed-var-not-dynamic + {:level :off} + + :dynamic-var-not-earmuffed + {:level :off} + + :used-underscored-binding + {:level :warning} + :unused-binding {:exclude-destructured-as true :exclude-destructured-keys-in-fn-args false From a8f65ba69e52d1173aebc028ee7a97d0bde9213a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 14 Dec 2022 14:14:01 +0100 Subject: [PATCH 361/682] :lipstick: Fix linter issues --- .../src/app/main/ui/workspace/context_menu.cljs | 4 ++-- .../sidebar/options/menus/component.cljs | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 00d120793f..b2f89ff10e 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -432,7 +432,7 @@ do-update-component-in-bulk #(st/emit! (dwl/update-component-in-bulk component-shapes component-file)) do-restore-component #(st/emit! (dwl/restore-component component-file component-id)) - _do-update-remote-component + do-update-remote-component #(st/emit! (modal/show {:type :confirm :message "" @@ -516,7 +516,7 @@ [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides") :on-click do-reset-component}] [:& menu-entry {:title (tr "workspace.shape.menu.update-main") - :on-click _do-update-remote-component}] + :on-click do-update-remote-component}] [:& menu-entry {:title (tr "workspace.shape.menu.go-main") :on-click do-navigate-component-file}]])))]) [:& menu-separator]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs index 392b135eaa..d78a034f05 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/component.cljs @@ -57,7 +57,7 @@ do-detach-component #(st/emit! (dwl/detach-component id)) - _do-reset-component + do-reset-component #(st/emit! (dwl/reset-component id)) do-update-component @@ -66,7 +66,7 @@ do-restore-component #(st/emit! (dwl/restore-component library-id component-id)) - _do-update-remote-component + do-update-remote-component #(st/emit! (modal/show {:type :confirm :message "" @@ -100,27 +100,27 @@ ;; app/main/ui/workspace/context_menu.cljs [:& context-menu {:on-close on-menu-close :show (:menu-open @local) - :options + :options (if main-instance? [[(tr "workspace.shape.menu.show-in-assets") do-show-in-assets]] (if local-component? (if is-dangling? [[(tr "workspace.shape.menu.detach-instance") do-detach-component] - [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] + [(tr "workspace.shape.menu.reset-overrides") do-reset-component] (when components-v2 [(tr "workspace.shape.menu.restore-main") do-restore-component])] [[(tr "workspace.shape.menu.detach-instance") do-detach-component] - [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] + [(tr "workspace.shape.menu.reset-overrides") do-reset-component] [(tr "workspace.shape.menu.update-main") do-update-component] [(tr "workspace.shape.menu.show-main") do-show-component]]) (if is-dangling? [[(tr "workspace.shape.menu.detach-instance") do-detach-component] - [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] + [(tr "workspace.shape.menu.reset-overrides") do-reset-component] (when components-v2 [(tr "workspace.shape.menu.restore-main") do-restore-component])] [[(tr "workspace.shape.menu.detach-instance") do-detach-component] - [(tr "workspace.shape.menu.reset-overrides") _do-reset-component] - [(tr "workspace.shape.menu.update-main") _do-update-remote-component] + [(tr "workspace.shape.menu.reset-overrides") do-reset-component] + [(tr "workspace.shape.menu.update-main") do-update-remote-component] [(tr "workspace.shape.menu.go-main") do-navigate-component-file]])))}]]]]]))) From 6a7a25121e0c9e1d42dfce7805e260701baed07f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 14 Dec 2022 14:15:42 +0100 Subject: [PATCH 362/682] :sparkles: Improve default update-file webhook batch timeout --- backend/src/app/loggers/audit.clj | 1 - backend/src/app/rpc/commands/files/update.clj | 2 +- frontend/src/app/main/ui/workspace/context_menu.cljs | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 0d692c2752..853e9709c7 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -152,7 +152,6 @@ :props (:props event)}] (when (contains? cf/flags :audit-log) - ;; NOTE: this operation may cause primary key conflicts on inserts ;; because of the timestamp precission (two concurrent requests), in ;; this case we just retry the operation. diff --git a/backend/src/app/rpc/commands/files/update.clj b/backend/src/app/rpc/commands/files/update.clj index 520df28972..264d29615c 100644 --- a/backend/src/app/rpc/commands/files/update.clj +++ b/backend/src/app/rpc/commands/files/update.clj @@ -131,7 +131,7 @@ {::climit/queue :update-file ::climit/key-fn :id ::webhooks/event? true - ::webhooks/batch-timeout (dt/duration "2s") + ::webhooks/batch-timeout (dt/duration "2m") ::webhooks/batch-key webhook-batch-keyfn ::doc/added "1.17"} [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index b2f89ff10e..eda6e725bf 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -386,7 +386,7 @@ [:& menu-entry {:title (tr "workspace.shape.menu.add-flex") :shortcut (sc/get-tooltip :toogle-layout-flex) :on-click add-flex}]] - + is-flex-container? [:* [:& menu-separator] From 6ea0279c9eb72ded2146512e3cad22d35a88396e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 14 Dec 2022 16:21:23 +0100 Subject: [PATCH 363/682] :sparkles: Improve webhook URI validation --- backend/src/app/http/client.clj | 8 ++++++-- backend/src/app/loggers/webhooks.clj | 3 +++ backend/src/app/rpc/commands/webhooks.clj | 7 ++++--- common/src/app/common/spec.cljc | 2 +- .../resources/styles/main/partials/dashboard-team.scss | 8 +++++--- frontend/src/app/main/data/dashboard.cljs | 4 +++- frontend/src/app/main/ui/dashboard/team.cljs | 4 +++- frontend/translations/en.po | 9 ++++++--- frontend/translations/es.po | 3 +++ 9 files changed, 34 insertions(+), 14 deletions(-) diff --git a/backend/src/app/http/client.clj b/backend/src/app/http/client.clj index 9e0be572ae..542de4d188 100644 --- a/backend/src/app/http/client.clj +++ b/backend/src/app/http/client.clj @@ -11,7 +11,8 @@ [app.worker :as wrk] [clojure.spec.alpha :as s] [integrant.core :as ig] - [java-http-clj.core :as http]) + [java-http-clj.core :as http] + [promesa.core :as p]) (:import java.net.http.HttpClient)) @@ -34,7 +35,10 @@ (us/assert! ::client client) (if sync? (http/send req {:client client :as response-type}) - (http/send-async req {:client client :as response-type})))) + (try + (http/send-async req {:client client :as response-type}) + (catch Throwable cause + (p/rejected cause)))))) (defn req! "A convencience toplevel function for gradual migration to a new API diff --git a/backend/src/app/loggers/webhooks.clj b/backend/src/app/loggers/webhooks.clj index b05b815581..55a034baaa 100644 --- a/backend/src/app/loggers/webhooks.clj +++ b/backend/src/app/loggers/webhooks.clj @@ -169,6 +169,9 @@ (instance? java.net.ConnectException cause) "connection-error" + (instance? java.lang.IllegalArgumentException cause) + "invalid-uri" + (instance? java.net.http.HttpConnectTimeoutException cause) "timeout" )) diff --git a/backend/src/app/rpc/commands/webhooks.clj b/backend/src/app/rpc/commands/webhooks.clj index 454dfee42b..13f7578d48 100644 --- a/backend/src/app/rpc/commands/webhooks.clj +++ b/backend/src/app/rpc/commands/webhooks.clj @@ -74,7 +74,8 @@ (when (>= total max-hooks-for-team) (ex/raise :type :restriction :code :webhooks-quote-reached - :hint (str/ffmt "can't create more than % webhooks per team" max-hooks-for-team))))) + :hint (str/ffmt "can't create more than % webhooks per team" + max-hooks-for-team))))) (defn- insert-webhook! [{:keys [::db/pool]} {:keys [team-id uri mtype is-active] :as params}] @@ -99,8 +100,8 @@ {::doc/added "1.17"} [{:keys [::db/pool ::wrk/executor] :as cfg} {:keys [profile-id team-id] :as params}] (check-edition-permissions! pool profile-id team-id) - (->> (validate-quotes! cfg params) - (p/fmap executor (fn [_] (validate-webhook! cfg nil params))) + (validate-quotes! cfg params) + (->> (validate-webhook! cfg nil params) (p/fmap executor (fn [_] (insert-webhook! cfg params))))) (s/def ::update-webhook diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index d81e996c22..1dd0eff35e 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -135,7 +135,7 @@ (letfn [(conformer [s] (cond (u/uri? s) s - (string? s) (u/uri s) + (string? s) (u/uri (str/trim s)) :else ::s/invalid)) (unformer [v] (dm/str v))] diff --git a/frontend/resources/styles/main/partials/dashboard-team.scss b/frontend/resources/styles/main/partials/dashboard-team.scss index 88c626550e..0847c8988a 100644 --- a/frontend/resources/styles/main/partials/dashboard-team.scss +++ b/frontend/resources/styles/main/partials/dashboard-team.scss @@ -199,10 +199,12 @@ } } - &.uri, + &.uri { + flex-grow: 1; + } + &.active { - width: 48%; - min-width: 300px; + min-width: 100px; } &.last-delivery { diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 2aeb562fc5..8e84b6a21d 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -620,7 +620,9 @@ ptk/WatchEvent (watch [_ state _] (let [team-id (:current-team-id state) - params (assoc params :team-id team-id) + params (-> params + (assoc :team-id team-id) + (update :uri str)) {:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 1c5a59f66e..eada3b00a2 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -620,7 +620,7 @@ ;; WEBHOOKS SECTION ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(s/def ::uri ::us/not-empty-string) +(s/def ::uri ::us/uri) (s/def ::mtype ::us/not-empty-string) (s/def ::webhook-form (s/keys :req-un [::uri ::mtype])) @@ -657,6 +657,8 @@ (let [message (cond (= hint "unknown") (tr "errors.webhooks.unexpected") + (= hint "invalid-uri") + (tr "errors.webhooks.invalid-uri") (= hint "ssl-validation-error") (tr "errors.webhooks.ssl-validation") (= hint "timeout") diff --git a/frontend/translations/en.po b/frontend/translations/en.po index ba88e0d552..1c806d08c7 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -696,6 +696,9 @@ msgstr "Webhook updated successfully." msgid "dashboard.webhooks.create.success" msgstr "Webhook created successfully." +msgid "webhooks.last-delivery.success" +msgstr "Last delivery was successfull." + msgid "errors.webhooks.unexpected" msgstr "Unexpected error on validating" @@ -705,15 +708,15 @@ msgstr "Timeout" msgid "errors.webhooks.connection" msgstr "Connection error, url not reacheable" -msgid "webhooks.last-delivery.success" -msgstr "Last delivery was successfull." - msgid "errors.webhooks.last-delivery" msgstr "Last delivery was not successfull." msgid "errors.webhooks.ssl-validation" msgstr "Error on SSL validation." +msgid "errors.webhooks.invalid-uri" +msgstr "URL does not passes validation." + msgid "errors.webhooks.unexpected-status" msgstr "Unexpected status %s" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 2c4076331b..5cf167ec1f 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -761,6 +761,9 @@ msgstr "Error en la validación SSL." msgid "errors.webhooks.unexpected-status" msgstr "Estado inesperado %s" +msgid "errors.webhooks.invalid-uri" +msgstr "La URL no pasa la validacion." + #: src/app/main/ui/alert.cljs msgid "ds.alert-ok" msgstr "Ok" From 02f29ed4d0a2a7d1c94430de6c29c21c2c3521ad Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 15 Dec 2022 08:18:29 +0100 Subject: [PATCH 364/682] :tada: Add webhooks to the API doc --- .../app/templates/api-doc-entry.tmpl | 19 +++++++++++++------ backend/src/app/rpc/doc.clj | 3 +++ 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/backend/resources/app/templates/api-doc-entry.tmpl b/backend/resources/app/templates/api-doc-entry.tmpl index 97ce8a5077..43af67c244 100644 --- a/backend/resources/app/templates/api-doc-entry.tmpl +++ b/backend/resources/app/templates/api-doc-entry.tmpl @@ -6,14 +6,21 @@
{% if item.deprecated %} - Deprecated: - since v{{item.deprecated}}, + DEPRECATED + + {% endif %} + + {% if item.auth %} + + AUTH + + {% endif %} + + {% if item.webhook %} + + WEBHOOK {% endif %} - - Auth: - {% if item.auth %}YES{% else %}NO{% endif %} -