From 23076727c76d46ecef02e00cc682473cb56b446c Mon Sep 17 00:00:00 2001 From: Antonio Date: Thu, 20 May 2021 16:57:04 +0000 Subject: [PATCH 001/204] :globe_with_meridians: Add translations for: Catalan. Currently translated at 20.9% (139 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/ --- frontend/translations/ca.po | 283 ++++++++++++++++++++++++++---------- 1 file changed, 203 insertions(+), 80 deletions(-) diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 0944d45743..0d59c23df1 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -1,38 +1,43 @@ msgid "" msgstr "" +"PO-Revision-Date: 2021-05-23 21:33+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" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.7-dev\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" -msgstr "Ja tens un compte?" +msgstr "Ja teniu un compte?" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" msgstr "" -"Revisa el teu email i fes click al link per verificar i començar a " -"utilitzar Penpot." +"Reviseu el correu i cliqueu l'enllaç per verificar i començar a utilitzar " +"Penpot." #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" -msgstr "Confirmar contrasenya" +msgstr "Confirmeu la contrasenya" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.create-demo-account" -msgstr "Crea un compte de proba" +msgstr "Creeu un compte de prova" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.create-demo-profile" -msgstr "Vols probar-ho?" +msgstr "Ho voleu provar?" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" msgstr "" -"Aquest es un servei de PROBA. NO HO UTILITZIS per feina real, els projectes " -"seran esborrats periòdicament." +"Aquest és un servei de PROVA. NO L'UTILITZEU en feines reals, ja que els " +"projectes s'esborraran periòdicament." #: 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" @@ -40,7 +45,7 @@ msgstr "Correu electrònic" #: src/app/main/ui/auth/login.cljs msgid "auth.forgot-password" -msgstr "Has oblidat la contrasenya?" +msgstr "Heu oblidat la contrasenya?" #: src/app/main/ui/auth/register.cljs msgid "auth.fullname" @@ -48,43 +53,43 @@ msgstr "Nom complet" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.go-back-to-login" -msgstr "Tornar" +msgstr "Enrere" #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" -msgstr "Inicia sessió aquí" +msgstr "Inicieu la sessió aquí" #: src/app/main/ui/auth/login.cljs msgid "auth.login-submit" -msgstr "Accedir" +msgstr "Entreu" #: src/app/main/ui/auth/login.cljs msgid "auth.login-subtitle" -msgstr "Introdueix les teves dades aquí" +msgstr "Introduïu les vostres dades a continuació" #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" -msgstr "Encantats de tornar a veure't" +msgstr "Ens agrada tornar-vos a veure!" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" -msgstr "Accedir amb Github" +msgstr "Entreu amb GitHub" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.login-with-gitlab-submit" -msgstr "Accedir amb Gitlab" +msgstr "Entreu amb GitLab" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" -msgstr "Accedir amb LDAP" +msgstr "Identifiqueu-vos amb LDAP" #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" -msgstr "Introdueix la nova contrasenya" +msgstr "Escriviu la nova contrasenya" #: src/app/main/ui/auth/recovery.cljs msgid "auth.notifications.invalid-token-error" -msgstr "El codi de recuperació no és vàlid" +msgstr "El codi de recuperació no és vàlid." #: src/app/main/ui/auth/recovery.cljs msgid "auth.notifications.password-changed-succesfully" @@ -93,16 +98,15 @@ msgstr "La contrasenya s'ha canviat correctament" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.profile-not-verified" msgstr "" -"El perfil encara no s'ha verificat, si us plau verifica-ho abans de " -"continuar." +"El perfil encara no ha estat verificat. Verifiqueu-lo abans de continuar." #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.recovery-token-sent" -msgstr "Hem enviat un link de recuperació de contrasenya al teu email." +msgstr "S'ha enviat un enllaç de recuperació de contrasenya al vostre correu." #: src/app/main/ui/auth/verify_token.cljs msgid "auth.notifications.team-invitation-accepted" -msgstr "T'has unit al equip" +msgstr "Us heu unit a l'equip correctament" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.password" @@ -114,94 +118,94 @@ msgstr "Com a mínim 8 caràcters" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.recovery-request-submit" -msgstr "Recuperar contrasenya" +msgstr "Recupera la contrasenya" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.recovery-request-subtitle" -msgstr "T'enviarem un correu electrónic amb instruccions" +msgstr "Rebreu un correu electrònic amb instruccions" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.recovery-request-title" -msgstr "Has oblidat la teva contrasenya?" +msgstr "Heu oblidat la contrasenya?" #: src/app/main/ui/auth/recovery.cljs msgid "auth.recovery-submit" -msgstr "Canvia la teva contrasenya" +msgstr "Canvieu la contrasenya" #: src/app/main/ui/auth/login.cljs msgid "auth.register" -msgstr "Encara no tens compte?" +msgstr "No teniu un compte?" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs msgid "auth.register-submit" -msgstr "Crea un compte" +msgstr "Creeu un compte" #: src/app/main/ui/auth/register.cljs msgid "auth.register-subtitle" -msgstr "Es gratuit, es Open Source" +msgstr "És gratuït, és de codi obert" #: src/app/main/ui/auth/register.cljs msgid "auth.register-title" -msgstr "Crea un compte" +msgstr "Creeu un compte" #: src/app/main/ui/auth.cljs msgid "auth.sidebar-tagline" -msgstr "La solució de codi obert per disenyar i prototipar" +msgstr "La solució de codi obert per dissenyar i prototipar." #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" -msgstr "Em enviat un correu de verificació a" +msgstr "S'ha enviat un correu de verificació a" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.add-shared" -msgstr "Afegeix una Biblioteca Compartida" +msgstr "Afegeix una biblioteca compartida" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.change-email" -msgstr "Canviar correu" +msgstr "Canvia el correu" #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.create-new-team" -msgstr "+ Crear un nou equip" +msgstr "+ Crea un nou equip" #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.default-team-name" -msgstr "El teu Penpot" +msgstr "El vostre Penpot" #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.delete-team" -msgstr "Suprimir equip" +msgstr "Suprimeix l'equip" msgid "dashboard.draft-title" msgstr "Esborrany" #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.empty-files" -msgstr "Encara no hi ha cap arxiu aquí" +msgstr "Encara no teniu cap arxiu aquí" #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" -msgstr "Convidar a l'equip" +msgstr "Convida a l'equip" #: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.leave-team" -msgstr "Abandonar l'equip" +msgstr "Deixa l'equip" #: src/app/main/ui/dashboard/libraries.cljs msgid "dashboard.libraries-title" -msgstr "Biblioteques Compartides" +msgstr "Biblioteques compartides" #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.loading-files" -msgstr "carregan els teus fitxers" +msgstr "S'estan carregant els fitxers…" #: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs msgid "dashboard.new-file" -msgstr "+ Nou Arxiu" +msgstr "+ Fitxer nou" #: src/app/main/ui/dashboard/projects.cljs msgid "dashboard.new-project" -msgstr "+ Nou projecte" +msgstr "+ Projecte nou" #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" @@ -213,11 +217,11 @@ msgstr "Els projectes fixats apareixeran aquí" #: src/app/main/ui/auth/verify_token.cljs msgid "dashboard.notifications.email-changed-successfully" -msgstr "La teva adreça de correu s'ha actualizat" +msgstr "S'ha actualitzat l'adreça de correu" #: src/app/main/ui/auth/verify_token.cljs msgid "dashboard.notifications.email-verified-successfully" -msgstr "La teva adreça de correu ha sigut verificada" +msgstr "S'ha verificat l'adreça de correu" #: src/app/main/ui/settings/password.cljs msgid "dashboard.notifications.password-saved" @@ -241,11 +245,11 @@ msgstr "Promoure a propietari" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.remove-account" -msgstr "Vols esborrar el teu compte?" +msgstr "Voleu eliminar el vostre compte?" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.remove-shared" -msgstr "Elimina com Biblioteca Compartida" +msgstr "Elimina com a biblioteca compartida" #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.search-placeholder" @@ -253,7 +257,7 @@ msgstr "Cerca…" #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.searching-for" -msgstr "S'está cercant “%s“…" +msgstr "S'està cercant “%s“…" #: src/app/main/ui/settings/options.cljs msgid "dashboard.select-ui-language" @@ -265,11 +269,11 @@ msgstr "Selecciona un tema" #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.show-all-files" -msgstr "Veure tots els fitxers" +msgstr "Mostra tots els fitxers" #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.switch-team" -msgstr "Cambiar d'equip" +msgstr "Canvia d'equip" #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.team-info" @@ -285,47 +289,47 @@ msgstr "Projectes de l'equip" #: src/app/main/ui/settings/options.cljs msgid "dashboard.theme-change" -msgstr "Tema de l'interfície" +msgstr "Tema de la interfície" #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.title-search" -msgstr "Membres de l'equip" +msgstr "Resultats de la cerca" #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.type-something" -msgstr "Escriu per cercar resultats" +msgstr "Escriviu per cercar resultats" #: 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 "Actualitzar opcions" +msgstr "Actualitza la configuració" #: src/app/main/ui/settings.cljs msgid "dashboard.your-account-title" -msgstr "El teu compte" +msgstr "El vostre compte" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.your-email" -msgstr "Correu electrónic" +msgstr "Correu electrònic" #: src/app/main/ui/settings/profile.cljs msgid "dashboard.your-name" -msgstr "El teu nom" +msgstr "Nom" #: 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 "El teu Penpot" +msgstr "El vostre Penpot" #: src/app/main/ui/confirm.cljs msgid "ds.confirm-cancel" -msgstr "Cancel·lar" +msgstr "Cancel·la" #: src/app/main/ui/confirm.cljs msgid "ds.confirm-ok" -msgstr "Ok" +msgstr "D'acord" #: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs msgid "ds.confirm-title" -msgstr "Estàs segur?" +msgstr "N'esteu segur?" #: src/app/main/ui/dashboard/grid.cljs msgid "ds.updated-at" @@ -333,19 +337,19 @@ msgstr "Actualitzat: %s" #: src/app/main/data/workspace.cljs msgid "errors.clipboard-not-implemented" -msgstr "El teu navegador no pot realitzar aquesta operació" +msgstr "El vostre navegador no pot fer aquesta operació" #: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs msgid "errors.email-already-exists" -msgstr "El correu ja està en ús" +msgstr "Aquest correu ja està en ús" #: src/app/main/ui/auth/verify_token.cljs msgid "errors.email-already-validated" -msgstr "El correu ja està validat" +msgstr "Aquest correu ja està validat." #: 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 "El correu «%s» té molts informes de rebot permanents" +msgstr "El correu «%s» té molts informes de retorn permanents." #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-invalid-confirmation" @@ -357,31 +361,32 @@ msgstr "Alguna cosa ha anat malament" #: src/app/main/ui/auth/login.cljs msgid "errors.google-auth-not-enabled" -msgstr "L'autenticació amb google ha estat desactivada a aquest servidor" +msgstr "L'autenticació amb Google està desactivada en aquest servidor" msgid "errors.media-format-unsupported" -msgstr "El format d'imatge no està suportat (deu ser svg, jpg o png)," +msgstr "El format d'imatge no està suportat (ha de ser SVG, JPG o PNG)." #: src/app/main/data/workspace/persistence.cljs msgid "errors.media-too-large" -msgstr "La imatge es massa gran (ha de tenir menys de 5 mb)." +msgstr "La imatge és massa gran (ha de ser inferior a 5 MB)." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" -msgstr "Sembla que el contingut de la imatge no coincideix amb l'extensió del arxiu" +msgstr "" +"Sembla que el contingut de la imatge no coincideix amb l'extensió del fitxer." #: 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 "La imatge no sembla pas vàlida" +msgstr "Sembla que la imatge no és vàlida." #: src/app/main/ui/dashboard/team.cljs msgid "errors.member-is-muted" msgstr "" -"El perfil que estàs invitant té els emails mutejats (per informes de spam o " -"rebots alts" +"El perfil que estàs convidant té els missatges de correu silenciats (per " +"informes de correu brossa o de retorns alts)." msgid "errors.network" -msgstr "Impossible connectar amb el servidor principal" +msgstr "No es pot connectar amb el servidor principal." #: src/app/main/ui/settings/password.cljs msgid "errors.password-invalid-confirmation" @@ -389,15 +394,17 @@ msgstr "La contrasenya de confirmació ha de coincidir" #: src/app/main/ui/settings/password.cljs msgid "errors.password-too-short" -msgstr "La contrasenya ha de tenir 8 com a mínim 8 caràcters" +msgstr "La contrasenya ha de tenir 8 caràcters com a mínim" #: 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 "El teu perfil te els emails mutejats (per informes de spam o rebots alts)." +msgstr "" +"El teu perfil té els missatges de correu silenciats (per informes de correu " +"brossa o de retorn alts)." #: src/app/main/ui/auth/register.cljs msgid "errors.registration-disabled" -msgstr "El registre està desactivat actualment" +msgstr "El registre està desactivat" #: 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" @@ -479,4 +486,120 @@ msgstr "Projectes - %s - Penpot" #: src/app/main/ui/dashboard/search.cljs msgid "title.dashboard.search" -msgstr "Cerca - %s - Penpot" \ No newline at end of file +msgstr "Cerca - %s - Penpot" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.token-expired" +msgstr "El codi ha caducat" + +msgid "errors.terms-privacy-agreement-invalid" +msgstr "" +"Heu d'acceptar les nostres condicions del servei i la política de privacitat." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "L'autenticació LDAP està inhabilitada." + +#: src/app/main/ui/components/color_input.cljs +msgid "errors.invalid-color" +msgstr "El color no és vàlid" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "S'ha mogut el projecte" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "S'han mogut els fitxers" + +#: 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 "S'ha mogut el fitxer" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "S'ha eliminat el projecte" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "S'ha duplicat el fitxer" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "S'ha eliminat el projecte" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "S'ha eliminat el fitxer" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Fixa/Deixa de fixar" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "Obre el fitxer en una pestanya nova" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Projecte nou" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "Fitxer nou" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Mou a un altre equip" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Mou %s fitxers a" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Mou a" + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"Només podeu pujar tipus de lletra de la vostra propietat o dels que tingueu " +"una llicència que us permeti utilitzar-los al Penpot. Teniu més informació a " +"la secció de drets de contingut de les [Condicions del servei del " +"Penpot](https://penpot.app/terms.html). També podeu llegir sobre [" +"llicenciament de tipus de lletra](https://www.typography.com/faq)." + +#, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"Els tipus de lletra web que pengeu aquí s'afegiran a la llista de famílies " +"tipogràfiques disponibles a les propietats del text dels fitxers d'aquest " +"equip. Els tipus de lletra amb el mateix nom de família s'agruparan en **una " +"sola família tipogràfica**. Podeu pujar tipus de lletra amb aquests formats: " +"**TTF, OTF i WOFF** (només és necessari un)." + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Duplica %s fitxers" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplica" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(còpia)" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"En crear un nou compte, accepteu les nostres condicions del servei i la " +"política de privacitat." + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Entreu amb OpenID (SSO)" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Entreu amb Google" From deb0ee3d29cff65ce14558932d719459a9645640 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gizem=20Akg=C3=BCney?= Date: Sat, 22 May 2021 20:47:44 +0000 Subject: [PATCH 002/204] :globe_with_meridians: Add translations for: Turkish. Currently translated at 39.5% (262 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/ --- frontend/translations/tr.po | 145 ++++++++++++++++++++++++++++++++++-- 1 file changed, 139 insertions(+), 6 deletions(-) diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index a947e22230..ccf5c31cb4 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-05-17 21:32+0000\n" +"PO-Revision-Date: 2021-05-23 21:33+0000\n" "Last-Translator: Gizem Akgüney \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" @@ -263,7 +263,7 @@ msgstr "Yeni Proje" #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" -msgstr "%s için hiç sonuç bulunamadı" +msgstr "\"%s\" için sonuç bulunamadı" #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.no-projects-placeholder" @@ -870,7 +870,7 @@ msgstr "Gönder" #: src/app/main/ui/settings/feedback.cljs msgid "labels.sending" -msgstr "Gönderiliyor..." +msgstr "Gönderiliyor…" #: src/app/main/ui/static.cljs msgid "labels.service-unavailable.desc-message" @@ -920,4 +920,137 @@ msgstr "Fontu sil" #: src/app/main/ui/dashboard/fonts.cljs msgid "title.dashboard.fonts" -msgstr "Fontlar - %s - Penpot" \ No newline at end of file +msgstr "Fontlar - %s - Penpot" + +msgid "labels.manage-fonts" +msgstr "Fontları yönet" + +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.main-message" +msgstr "İç Hata" + +msgid "labels.font-providers" +msgstr "Font sağlayıcısı" + +msgid "labels.custom-fonts" +msgstr "Özel Fontlar" + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.main-message" +msgstr "Hatalı Ağ Geçidi" + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.desc-message" +msgstr "" +"Görünüşe göre biraz beklemen ve yeniden denemen gerekiyor; sunucularımızda " +"küçük bir bakım yapıyoruz." + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Kod" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Altı Çizili" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Üstü Çizili" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Satır Yüksekliği" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Harf Aralığı" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Font Stili" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Font Boyutu" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-family" +msgstr "Font Ailesi" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografi" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Genişlik" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Düz" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Hiçbiri" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Karışık" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Noktalı" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Merkezi" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Dışarıda" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "İçinde" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Çerçeve" + +#: 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.offset-x" +msgstr "X" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Gölge" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Genişlik" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Üst" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Rotasyon" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Sol" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Yükseklik" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" +"Lütfen bir sorun, fikir ya da kuşkunuzu açıklayarak e-postanızın nedenini " +"belirtin. Ekibimizin bir üyesi en kısa sürede yanıt verecektir." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle2" +msgstr "" +"Soru sorabilir ve soruları cevaplayabilir, açık uçlu tartışmalar yapabilir " +"ve projeyi etkileyen kararları takip edebilirsin." From 3094fe2855cb3e13ef47782a6c844ce17488c1e5 Mon Sep 17 00:00:00 2001 From: Amine Gdoura Date: Fri, 21 May 2021 23:54:35 +0000 Subject: [PATCH 003/204] :globe_with_meridians: Add translations for: Arabic. Currently translated at 11.3% (75 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 119 ++++++++++++++++++++++++++++++++++-- 1 file changed, 115 insertions(+), 4 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 86bac1358e..f0de33d7c5 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-05-17 21:32+0000\n" +"PO-Revision-Date: 2021-05-23 21:33+0000\n" "Last-Translator: Amine Gdoura \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" @@ -229,4 +229,115 @@ msgid "title.dashboard.fonts" msgstr "الخطوط -٪ s - Penpot" msgid "workspace.viewport.click-to-close-path" -msgstr "انقر لإغلاق المسار" \ No newline at end of file +msgstr "انقر لإغلاق المسار" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "تم حفظ كلمة المرور بنجاح!" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "تم التحقق من عنوان بريدك الإلكتروني بنجاح" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "تم تحديث عنوان بريدك الإلكتروني بنجاح" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "ستظهر المشاريع المثبتة هنا" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "لم يتم العثور على مطابقات ل \"٪s\"" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "مشروع جديد" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ مشروع جديد" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "ملف جديد" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ ملف جديد" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "الانتقال إلى فريق آخر" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "أنقل ٪s الملفات إلى" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "الانتقال إلى" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "تحميل ملفاتك …" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "المكتبات المشتركة" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "ترك الفريق" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "قم بدعوة فريق" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "لا يزال لديك 0 ملفات هنا" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "تكرير ٪s الملفات" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "تكرير" + +msgid "dashboard.draft-title" +msgstr "مسودة" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "حذف الفريق" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Penpot الخاص بك" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ إنشاء فريق جديد" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(نسخة)" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "تغيير البريد الإلكتروني" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "أضف كمكتبة مشتركة" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "تم إرسال رابط استعادة كلمة المرور إلى صندوق البريد الخاص بك." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "لم يتم التعرف على الحساب الشخصي ، يرجى التحقق قبل المتابعة." From 944e7c6e3de5221d745b8deaf587c356f1448c31 Mon Sep 17 00:00:00 2001 From: luthfi azhari Date: Fri, 21 May 2021 08:25:15 +0000 Subject: [PATCH 004/204] :globe_with_meridians: Add translations for: Indonesian. Currently translated at 7.2% (48 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/id/ --- frontend/translations/id.po | 208 +++++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 2 deletions(-) diff --git a/frontend/translations/id.po b/frontend/translations/id.po index b1fc23594e..5210b494db 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -1,6 +1,210 @@ msgid "" msgstr "" -"X-Generator: Weblate\n" +"PO-Revision-Date: 2021-05-23 21:33+0000\n" +"Last-Translator: luthfi azhari \n" +"Language-Team: Indonesian \n" +"Language: id\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=1; plural=0;\n" +"X-Generator: Weblate 4.7-dev\n" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Anda belum memiliki file disini" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Duplikasi % file" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplikat" + +msgid "dashboard.draft-title" +msgstr "Konsep" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Hapus tim" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Penpot anda" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Buat tim baru" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(salin)" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Ubah email" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Tambahkan sebagai pustaka bersama" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "Kami mengirim verifikasi ke surel anda" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Buat akun baru" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "Ini gratis, Open Source" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Buat akun baru" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Tidak ada akun?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Ubah kata sandi anda" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Lupa kata sandi?" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Kami akan mengirimi anda surel dengan intruksi" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Pemulihan Kata sandi" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Paling tidak 8 karakter" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.check-your-email" +msgstr "" +"Cek surel anda dan klik pada tautan tersebut untuk verifikasi dan mulai " +"gunakan Penpot." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.confirm-password" +msgstr "Konfirmasi kata sandi" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Ketika membuat akun baru, anda menyetujui persyaratan layanan dan kebijakan " +"privasi kami." + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "Solusi open-source untuk desain dan pembuatan prototype." + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.password" +msgstr "Kata sandi" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Berhasil bergabung dengan tim" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Link pemulihan kata sandi berhasil dikirim ke kotak masuk anda." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "" +"Akun belum terverifikasi, harap verifikasi profile anda sebelum melanjutkan." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-succesfully" +msgstr "Kata sandi berhasil diubah" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "Token pemulihan tidak valid." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Ketikkan kata sandi baru" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Masuk dengan OpenID (SSO)" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "Masuk dengan LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Masuk dengan Google" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "Masuk dengan Gitlab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Masuk dengan Github" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Senang bertemu denganmu lagi!" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-subtitle" +msgstr "Masukkan detail anda di bawah ini" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.demo-warning" +msgstr "" +"Ini layanan DEMO, JANGAN GUNAKAN untuk pekerjaan nyata, project ini akan di " +"hapus secara berkala." + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-submit" +msgstr "Masuk" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.login-here" +msgstr "Masuk disini" + +#: 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.fullname" +msgstr "Nama Lengkap" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.forgot-password" +msgstr "Lupa kata sandi?" + +#: 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 "Surel" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-profile" +msgstr "Ingin mencobanya?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Buat akun demo" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.already-have-account" +msgstr "Sudah memiliki akun?" From 6a68e9c118deb042b586d92d59b80089d9e6a98b Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 21 May 2021 14:51:02 +0200 Subject: [PATCH 005/204] :recycle: Refactor embed resouces --- frontend/src/app/main/exports.cljs | 61 ++++---- frontend/src/app/main/fonts.cljs | 62 +++++++- frontend/src/app/main/ui/context.cljs | 1 - frontend/src/app/main/ui/hooks.cljs | 25 +++ frontend/src/app/main/ui/render.cljs | 4 +- frontend/src/app/main/ui/shapes/embed.cljs | 39 +++++ .../src/app/main/ui/shapes/fill_image.cljs | 11 +- frontend/src/app/main/ui/shapes/frame.cljs | 8 +- frontend/src/app/main/ui/shapes/image.cljs | 35 +---- .../src/app/main/ui/shapes/text/embed.cljs | 144 ------------------ .../app/main/ui/shapes/text/fontfaces.cljs | 79 ++++++++++ .../app/main/ui/workspace/shapes/frame.cljs | 42 +---- .../src/app/main/ui/workspace/viewport.cljs | 5 +- frontend/src/app/util/cache.cljs | 26 ++++ frontend/src/app/util/http.cljs | 30 +++- 15 files changed, 312 insertions(+), 260 deletions(-) create mode 100644 frontend/src/app/main/ui/shapes/embed.cljs delete mode 100644 frontend/src/app/main/ui/shapes/text/embed.cljs create mode 100644 frontend/src/app/main/ui/shapes/text/fontfaces.cljs create mode 100644 frontend/src/app/util/cache.cljs diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index aa5e08c213..1f70e5e48d 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -26,7 +26,8 @@ [app.main.ui.shapes.text :as text] [app.main.ui.shapes.group :as group] [app.main.ui.shapes.svg-raw :as svg-raw] - [app.main.ui.shapes.shape :refer [shape-container]])) + [app.main.ui.shapes.shape :refer [shape-container]] + [app.main.ui.shapes.embed :as embed])) (def ^:private default-color "#E8E9EA") ;; $color-canvas @@ -43,8 +44,9 @@ [{:keys [objects] :as data} vport] (let [shapes (cp/select-toplevel-shapes objects {:include-frames? true}) to-finite (fn [val fallback] (if (not (mth/finite? val)) fallback val)) - rect (->> (gsh/selection-rect shapes) - (gal/adjust-to-viewport vport))] + rect (cond->> (gsh/selection-rect shapes) + (some? vport) + (gal/adjust-to-viewport vport))] (-> rect (update :x to-finite 0) (update :y to-finite 0) @@ -121,13 +123,14 @@ (mf/defc page-svg {::mf/wrap [mf/memo]} - [{:keys [data width height thumbnails?] :as props}] + [{:keys [data width height thumbnails? embed?] :as props}] (let [objects (:objects data) root (get objects uuid/zero) shapes (->> (:shapes root) (map #(get objects %))) - vport {:width width :height height} + vport (when (and (some? width) (some? height)) + {:width width :height height}) dim (calculate-dimensions data vport) vbox (get-viewbox dim) background-color (get-in data [:options :background] default-color) @@ -140,29 +143,31 @@ (mf/use-memo (mf/deps objects) #(shape-wrapper-factory objects))] - [:svg {:view-box vbox - :version "1.1" - :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns "http://www.w3.org/2000/svg"} - [:& background {:vbox dim :color background-color}] - (for [item shapes] - (let [frame? (= (:type item) :frame)] - (cond - (and frame? thumbnails? (some? (:thumbnail item))) - [:image {:xlinkHref (:thumbnail item) - :x (:x item) - :y (:y item) - :width (:width item) - :height (:height item) - ;; DEBUG - ;; :style {:filter "sepia(1)"} - }] - frame? - [:& frame-wrapper {:shape item - :key (:id item)}] - :else - [:& shape-wrapper {:shape item - :key (:id item)}])))])) + [:& (mf/provider embed/context) {:value embed?} + [:svg {:view-box vbox + :version "1.1" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns "http://www.w3.org/2000/svg" + :xmlns:penpot "https://penpot.app/xmlns"} + [:& background {:vbox dim :color background-color}] + (for [item shapes] + (let [frame? (= (:type item) :frame)] + (cond + (and frame? thumbnails? (some? (:thumbnail item))) + [:image {:xlinkHref (:thumbnail item) + :x (:x item) + :y (:y item) + :width (:width item) + :height (:height item) + ;; DEBUG + ;; :style {:filter "sepia(1)"} + }] + frame? + [:& frame-wrapper {:shape item + :key (:id item)}] + :else + [:& shape-wrapper {:shape item + :key (:id item)}])))]])) (mf/defc frame-svg {::mf/wrap [mf/memo]} diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index 8ca0ef906d..3c4799f5be 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -8,17 +8,19 @@ "Fonts management and loading logic." (:require-macros [app.main.fonts :refer [preload-gfonts]]) (:require - [app.config :as cf] [app.common.data :as d] + [app.common.text :as txt] + [app.config :as cf] [app.util.dom :as dom] + [app.util.http :as http] + [app.util.logging :as log] [app.util.object :as obj] [app.util.timers :as ts] - [app.util.logging :as log] - [lambdaisland.uri :as u] - [goog.events :as gev] [beicon.core :as rx] [clojure.set :as set] [cuerdas.core :as str] + [goog.events :as gev] + [lambdaisland.uri :as u] [okulary.core :as l] [promesa.core :as p])) @@ -216,3 +218,55 @@ (or (d/seek #(or (= (:id %) "regular") (= (:name %) "regular")) variants) (first variants))) + +;; Font embedding functions + +;; Template for a CSS font face + +(def font-face-template " +/* latin */ +@font-face { + font-family: '%(family)s'; + font-style: %(style)s; + font-weight: %(weight)s; + font-display: block; + src: url(/fonts/%(family)s-%(suffix)s.woff) format('woff'); +} +") + +(defn get-content-fonts + "Extracts the fonts used by the content of a text shape" + [{font-id :font-id children :children :as content}] + (let [current-font + (if (some? font-id) + #{(select-keys content [:font-id :font-variant-id])} + #{(select-keys txt/default-text-attrs [:font-id :font-variant-id])}) + children-font (->> children (mapv get-content-fonts))] + (reduce set/union (conj children-font current-font)))) + + +(defn fetch-font-css + "Given a font and the variant-id, retrieves the fontface CSS" + [{:keys [font-id font-variant-id] + :or {font-variant-id "regular"}}] + + (let [{:keys [backend family variants]} (get @fontsdb font-id)] + (if (= :google backend) + (-> (generate-gfonts-url + {:family family + :variants [{:id font-variant-id}]}) + (http/fetch-text)) + + (let [{:keys [weight style suffix] :as variant} + (d/seek #(= (:id %) font-variant-id) variants) + font-data {:family family + :style style + :suffix (or suffix font-variant-id) + :weight weight}] + (rx/of (str/fmt font-face-template font-data)))))) + +(defn extract-fontface-urls + "Parses the CSS and retrieves the font urls" + [^string css] + (->> (re-seq #"url\(([^)]+)\)" css) + (mapv second))) diff --git a/frontend/src/app/main/ui/context.cljs b/frontend/src/app/main/ui/context.cljs index fe18ce753d..631b4b6f58 100644 --- a/frontend/src/app/main/ui/context.cljs +++ b/frontend/src/app/main/ui/context.cljs @@ -8,7 +8,6 @@ (:require [rumext.alpha :as mf])) -(def embed-ctx (mf/create-context false)) (def render-ctx (mf/create-context nil)) (def def-ctx (mf/create-context false)) diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index 5b0b0645e8..789b5ab8f3 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -225,3 +225,28 @@ (fn [] (mf/set-ref-val! ref value))) (mf/ref-val ref))) + +(defn use-equal-memo + [val] + (let [ref (mf/use-ref nil)] + (when-not (= (mf/ref-val ref) val) + (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))) diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index 1060de4d39..f7de7b8628 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -14,7 +14,7 @@ [app.common.uuid :as uuid] [app.main.exports :as exports] [app.main.repo :as repo] - [app.main.ui.context :as muc] + [app.main.ui.shapes.embed :as embed] [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.shape :refer [shape-container]] [beicon.core :as rx] @@ -71,7 +71,7 @@ #(exports/shape-wrapper-factory objects)) ] - [:& (mf/provider muc/embed-ctx) {:value true} + [:& (mf/provider embed/context) {:value true} [:svg {:id "screenshot" :view-box vbox :width width diff --git a/frontend/src/app/main/ui/shapes/embed.cljs b/frontend/src/app/main/ui/shapes/embed.cljs new file mode 100644 index 0000000000..fd2de767a3 --- /dev/null +++ b/frontend/src/app/main/ui/shapes/embed.cljs @@ -0,0 +1,39 @@ +;; 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.main.ui.shapes.embed + (:require + [app.main.ui.hooks :as hooks] + [app.util.http :as http] + [beicon.core :as rx] + [rumext.alpha :as mf])) + +(def context (mf/create-context false)) + +(defn use-data-uris [urls] + (let [embed? (mf/use-ctx context) + urls (hooks/use-equal-memo urls) + uri-data (mf/use-ref {}) + state (mf/use-state 0)] + + (hooks/use-effect-ssr + (mf/deps embed? urls) + (fn [] + (let [sub (when embed? + (->> (rx/from urls) + (rx/merge-map http/fetch-data-uri) + (rx/reduce conj {}) + (rx/subs (fn [data] + (when-not (= data (mf/ref-val uri-data)) + (mf/set-ref-val! uri-data data) + (reset! state inc))))))] + #(when sub + (rx/dispose! sub))))) + + ;; Use ref so if the urls are cached will return inmediately instead of the + ;; next render + (when embed? + (mf/ref-val uri-data)))) diff --git a/frontend/src/app/main/ui/shapes/fill_image.cljs b/frontend/src/app/main/ui/shapes/fill_image.cljs index 17d13291bb..8fb66a3ef9 100644 --- a/frontend/src/app/main/ui/shapes/fill_image.cljs +++ b/frontend/src/app/main/ui/shapes/fill_image.cljs @@ -8,10 +8,9 @@ (:require [app.common.geom.shapes :as gsh] [app.config :as cfg] + [app.main.ui.shapes.embed :as embed] [app.util.object :as obj] - [rumext.alpha :as mf] - [app.common.geom.point :as gpt] - [app.main.ui.shapes.image :as image])) + [rumext.alpha :as mf])) (mf/defc fill-image-pattern {::mf/wrap-props false} @@ -22,8 +21,8 @@ (when (contains? shape :fill-image) (let [{:keys [x y width height]} (:selrect shape) fill-image-id (str "fill-image-" render-id) - media (:fill-image shape) - uri (image/use-image-uri media) + uri (cfg/resolve-file-media (:fill-image shape)) + embed (embed/use-data-uris [uri]) transform (gsh/transform-matrix shape)] [:pattern {:id fill-image-id @@ -33,6 +32,6 @@ :height height :width width :patternTransform transform} - [:image {:xlinkHref uri + [:image {:xlinkHref (get embed uri uri) :width width :height height}]])))) diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 966619c67c..4146200273 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -7,13 +7,16 @@ (ns app.main.ui.shapes.frame (:require [app.common.data :as d] - [app.common.geom.shapes :as geom] [app.main.ui.shapes.attrs :as attrs] + [app.main.ui.shapes.text.fontfaces :as ff] [app.util.object :as obj] [rumext.alpha :as mf])) (def frame-default-props {:fill-color "#ffffff"}) +(defn is-text? [{type :type}] + (= :text type)) + (defn frame-shape [shape-wrapper] (mf/fnc frame-shape @@ -23,6 +26,8 @@ shape (unchecked-get props "shape") {:keys [id width height]} shape + text-childs (->> childs (filterv is-text?)) + props (-> (merge frame-default-props shape) (attrs/extract-style-attrs) (obj/merge! @@ -32,6 +37,7 @@ :height height :className "frame-background"}))] [:* + [:& ff/fontfaces-style {:shapes text-childs}] [:> :rect props] (for [[i item] (d/enumerate childs)] [:& shape-wrapper {:frame shape diff --git a/frontend/src/app/main/ui/shapes/image.cljs b/frontend/src/app/main/ui/shapes/image.cljs index 45ceb303b7..a886049f1a 100644 --- a/frontend/src/app/main/ui/shapes/image.cljs +++ b/frontend/src/app/main/ui/shapes/image.cljs @@ -7,35 +7,13 @@ (ns app.main.ui.shapes.image (:require [app.common.geom.shapes :as geom] - [app.config :as cfg] - [app.main.ui.context :as muc] [app.main.ui.shapes.attrs :as attrs] + [app.main.ui.shapes.embed :as se] [app.util.dom :as dom] - [app.util.http :as http] [app.util.object :as obj] - [app.util.webapi :as wapi] - [beicon.core :as rx] - [rumext.alpha :as mf])) - -(defn use-image-uri - [media] - (let [uri (mf/use-memo (mf/deps (:id media)) - #(cfg/resolve-file-media media)) - embed-resources? (mf/use-ctx muc/embed-ctx) - data-uri (mf/use-state (when (not embed-resources?) uri))] - - (mf/use-effect - (mf/deps uri) - (fn [] - (if embed-resources? - (->> (http/send! {:method :get - :uri uri - :response-type :blob}) - (rx/map :body) - (rx/mapcat wapi/read-file-as-data-url) - (rx/subs #(reset! data-uri %)))))) - - (or @data-uri uri))) + [rumext.alpha :as mf] + [app.config :as cfg] + [app.main.ui.shapes.embed :as embed])) (mf/defc image-shape {::mf/wrap-props false} @@ -43,7 +21,8 @@ (let [shape (unchecked-get props "shape") {:keys [id x y width height rotation metadata]} shape - uri (use-image-uri metadata)] + uri (cfg/resolve-file-media metadata) + embed (embed/use-data-uris [uri])] (let [transform (geom/transform-matrix shape) props (-> (attrs/extract-style-attrs shape) @@ -60,5 +39,5 @@ [:> "image" (obj/merge! props - #js {:xlinkHref uri + #js {:xlinkHref (get embed uri uri) :onDragStart on-drag-start})]))) diff --git a/frontend/src/app/main/ui/shapes/text/embed.cljs b/frontend/src/app/main/ui/shapes/text/embed.cljs deleted file mode 100644 index 61ef3fd4ea..0000000000 --- a/frontend/src/app/main/ui/shapes/text/embed.cljs +++ /dev/null @@ -1,144 +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.main.ui.shapes.text.embed - (:refer-clojure :exclude [memoize]) - (:require - [app.common.data :as d] - [app.common.text :as txt] - [app.main.fonts :as fonts] - [app.util.http :as http] - [app.util.time :as dt] - [app.util.webapi :as wapi] - [app.util.object :as obj] - [clojure.set :as set] - [cuerdas.core :as str] - [promesa.core :as p] - [beicon.core :as rx] - [rumext.alpha :as mf])) - - -(defonce cache (atom {})) - -(defn with-cache - [{:keys [key max-age]} observable] - (let [entry (get @cache key) - age (when entry - (dt/diff (dt/now) - (:created-at entry)))] - (if (and (some? entry) - (< age max-age)) - (rx/of (:data entry)) - (->> observable - (rx/tap (fn [data] - (let [entry {:created-at (dt/now) :data data}] - (swap! cache assoc key entry)))))))) - -(def font-face-template " -/* latin */ -@font-face { - font-family: '%(family)s'; - font-style: %(style)s; - font-weight: %(weight)s; - font-display: block; - src: url(/fonts/%(family)s-%(suffix)s.woff) format('woff'); -} -") - -;; -- Embed fonts into styles - -(defn get-node-fonts - [node] - (let [current-font (if (not (nil? (:font-id node))) - #{(select-keys node [:font-id :font-variant-id])} - #{(select-keys txt/default-text-attrs [:font-id :font-variant-id])}) - children-font (map get-node-fonts (:children node))] - (reduce set/union (conj children-font current-font)))) - -(defn get-font-css - "Given a font and the variant-id, retrieves the style CSS for it." - [{:keys [id backend family variants] :as font} font-variant-id] - (if (= :google backend) - (let [uri (fonts/generate-gfonts-url {:family family :variants [{:id font-variant-id}]})] - (->> (http/send! {:method :get - :mode :cors - :omit-default-headers true - :uri uri - :response-type :text}) - (rx/map :body) - (http/as-promise))) - (let [{:keys [name weight style suffix] :as variant} (d/seek #(= (:id %) font-variant-id) variants) - result (str/fmt font-face-template {:family family - :style style - :suffix (or suffix font-variant-id) - :weight weight})] - (p/resolved result)))) - -(defn- to-promise - [observable] - (p/create (fn [resolve reject] - (->> (rx/take 1 observable) - (rx/subs resolve reject))))) - -(defn fetch-font-data - "Parses the CSS and retrieves the font data as DataURI." - [^string css] - (let [uris (->> (re-seq #"url\(([^)]+)\)" css) - (mapv second))] - (with-cache {:key uris :max-age (dt/duration {:hours 4})} - (->> (rx/from (seq uris)) - (rx/mapcat (fn [uri] - (->> (http/send! {:method :get :uri uri :response-type :blob :omit-default-headers true}) - (rx/map :body) - (rx/mapcat wapi/read-file-as-data-url) - (rx/map #(vector uri %))))) - (rx/reduce conj []))))) - -(defn get-font-data - "Parses the CSS and retrieves the font data as DataURI." - [^string css] - (->> (fetch-font-data css) - (http/as-promise))) - -(defn embed-font - "Given a font-id and font-variant-id, retrieves the CSS for it and - convert all external urls to embedded data URI's." - [{:keys [font-id font-variant-id] :or {font-variant-id "regular"}}] - (let [{:keys [backend family] :as font} (get @fonts/fontsdb font-id)] - (p/let [css (get-font-css font font-variant-id) - url-to-data (get-font-data css) - replace-text (fn [text [url data]] (str/replace text url data))] - (reduce replace-text css url-to-data)))) - -;; NOTE: we can't move this to generic hooks namespace because that -;; namespace imports some code incompatible with webworkers and this -;; font embbeding should be able run on browser and webworker -;; contexts. -(defn- memoize - [val] - (let [ref (mf/use-ref #js {})] - (when-not (= (mf/ref-val ref) val) - (mf/set-ref-val! ref val)) - (mf/ref-val ref))) - -(mf/defc embed-fontfaces-style - {::mf/wrap-props false - ::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]} - [props] - (let [shapes (obj/get props "shapes") - node {:children (->> shapes (map :content))} - fonts (-> node get-node-fonts memoize) - style (mf/use-state nil)] - - (mf/use-effect - (mf/deps fonts) - (fn [] - (-> (p/all (map embed-font fonts)) - (p/then (fn [result] - (reset! style (str/join "\n" result))))))) - - (when (some? @style) - [:style @style]))) diff --git a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs new file mode 100644 index 0000000000..e00a6919c6 --- /dev/null +++ b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs @@ -0,0 +1,79 @@ +;; 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.main.ui.shapes.text.fontfaces + (:require + [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] + [clojure.set :as set] + [cuerdas.core :as str] + [rumext.alpha :as mf])) + +(defn replace-embeds + "Replace into the font-faces of a CSS the URL's that are present in `embed-data` by its + data-uri" + [css urls embed-data] + (letfn [(replace-url [css url] + (str/replace css url (get embed-data url url)))] + (->> urls + (reduce replace-url css)))) + +(defn use-fonts-css + "Hook that retrieves the CSS of the fonts passed as parameter" + [fonts] + (let [fonts-css-ref (mf/use-ref "") + redraw (mf/use-state 0)] + + (hooks/use-effect-ssr + (mf/deps fonts) + (fn [] + (let [sub + (->> (rx/from fonts) + (rx/merge-map fonts/fetch-font-css) + (rx/reduce conj []) + (rx/subs + (fn [result] + (let [css (str/join "\n" result)] + (when-not (= (mf/ref-val fonts-css-ref) css) + (mf/set-ref-val! fonts-css-ref css) + (reset! redraw inc))))))] + #(rx/dispose! sub)))) + + (mf/ref-val fonts-css-ref))) + +(mf/defc fontfaces-style + {::mf/wrap-props false + ::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]} + [props] + (let [shapes (obj/get props "shapes") + + content (->> shapes (mapv :content)) + + ;; Retrieve the fonts ids used by the text shapes + fonts (->> content + (mapv fonts/get-content-fonts) + (reduce set/union #{}) + (hooks/use-equal-memo)) + + ;; Fetch its CSS fontfaces + fonts-css (use-fonts-css fonts) + + ;; Extract from the CSS the URL's to embed + fonts-urls (mf/use-memo + (mf/deps fonts-css) + #(fonts/extract-fontface-urls fonts-css)) + + ;; Calculate the data-uris for these fonts + fonts-embed (embed/use-data-uris fonts-urls) + + ;; Creates a style tag by replacing the urls with the data uri + style (replace-embeds fonts-css fonts-urls fonts-embed)] + + (when (and (some? style) (not (empty? style))) + [:style style]))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 8875ba6e5d..592bb5d451 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -6,45 +6,15 @@ (ns app.main.ui.workspace.shapes.frame (:require - [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.main.data.workspace :as dw] - [app.main.data.workspace.changes :as dch] [app.main.refs :as refs] - [app.main.store :as st] - [app.main.ui.context :as muc] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.shape :refer [shape-container]] - [app.main.ui.shapes.text.embed :as ste] - [app.util.dom :as dom] - [app.util.keyboard :as kbd] [app.util.object :as obj] [app.util.timers :as ts] [beicon.core :as rx] - [okulary.core :as l] [rumext.alpha :as mf])) -(def obs-config - #js {:attributes true - :childList true - :subtree true - :characterData true}) - -(defn make-is-moving-ref - [id] - (let [check-moving (fn [local] - (and (= :move (:transform local)) - (contains? (:selected local) id)))] - (l/derived check-moving refs/workspace-local))) - -(defn check-props - ([props] (check-props props =)) - ([props eqfn?] - (fn [np op] - (every? #(eqfn? (unchecked-get np %) - (unchecked-get op %)) - props)))) - (defn check-frame-props "Checks for changes in the props of a frame" [new-props old-props] @@ -107,14 +77,9 @@ thumbnail? (unchecked-get props "thumbnail?") edition (mf/deref refs/selected-edition) - embed-fonts? (mf/use-ctx muc/embed-ctx) shape (gsh/transform-shape shape) children (mapv #(get objects %) (:shapes shape)) - text-childs (->> objects - vals - (filterv #(and (= :text (:type %)) - (= (:id shape) (:frame-id %))))) ds-modifier (get-in shape [:modifiers :displacement]) @@ -131,12 +96,7 @@ [:g.frame-wrapper {:display (when (:hidden shape) "none")} (when-not show-thumbnail? - [:> shape-container {:shape shape - :ref on-dom} - - (when embed-fonts? - [:& ste/embed-fontfaces-style {:shapes text-childs}]) - + [:> shape-container {:shape shape :ref on-dom} [:& frame-shape {:shape shape :childs children}]]) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a210dad733..8ead981ca2 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -7,12 +7,13 @@ (ns app.main.ui.workspace.viewport (:require [app.common.data :as d] - [app.common.pages :as cp] [app.common.geom.shapes :as gsh] + [app.common.pages :as cp] [app.main.refs :as refs] [app.main.ui.context :as ctx] [app.main.ui.context :as muc] [app.main.ui.measurements :as msr] + [app.main.ui.shapes.embed :as embed] [app.main.ui.workspace.shapes :as shapes] [app.main.ui.workspace.shapes.text.editor :as editor] [app.main.ui.workspace.viewport.actions :as actions] @@ -187,7 +188,7 @@ :style {:background-color (get options :background "#E8E9EA") :pointer-events "none"}} - [:& (mf/provider muc/embed-ctx) {:value true} + [:& (mf/provider embed/context) {:value true} ;; Render root shape [:& shapes/root-shape {:key page-id :objects objects diff --git a/frontend/src/app/util/cache.cljs b/frontend/src/app/util/cache.cljs new file mode 100644 index 0000000000..53fa610ccb --- /dev/null +++ b/frontend/src/app/util/cache.cljs @@ -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) UXBOX Labs SL + +(ns app.util.cache + (:require + [app.util.time :as dt] + [beicon.core :as rx])) + +(defonce cache (atom {})) + +(defn with-cache + [{:keys [key max-age]} observable] + (let [entry (get @cache key) + age (when entry + (dt/diff (dt/now) + (:created-at entry)))] + (if (and (some? entry) (< age max-age)) + (rx/of (:data entry)) + (->> observable + (rx/tap + (fn [data] + (let [entry {:created-at (dt/now) :data data}] + (swap! cache assoc key entry)))))))) diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index a5c4920b0c..cba4052375 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -10,9 +10,12 @@ [app.common.data :as d] [app.common.uri :as u] [app.config :as cfg] + [app.util.cache :as c] [app.util.globals :as globals] [app.util.object :as obj] + [app.util.time :as dt] [app.util.transit :as t] + [app.util.webapi :as wapi] [beicon.core :as rx] [cuerdas.core :as str] [promesa.core :as p])) @@ -152,6 +155,27 @@ (defn as-promise [observable] - (p/create (fn [resolve reject] - (->> (rx/take 1 observable) - (rx/subs resolve reject))))) + (p/create + (fn [resolve reject] + (->> (rx/take 1 observable) + (rx/subs resolve reject))))) + +(defn fetch-data-uri [uri] + (c/with-cache {:key uri :max-age (dt/duration {:hours 4})} + (->> (send! {:method :get + :uri uri + :response-type :blob + :omit-default-headers true}) + (rx/map :body) + (rx/mapcat wapi/read-file-as-data-url) + (rx/map #(hash-map uri %))))) + +(defn fetch-text [url] + (c/with-cache {:key url :max-age (dt/duration {:hours 4})} + (->> (send! + {:method :get + :mode :cors + :omit-default-headers true + :uri url + :response-type :text}) + (rx/map :body)))) From 7d80a5a7f73bcfd5be1a9f03daf5c38d1a3778f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yannik=20R=C3=B6del?= Date: Mon, 24 May 2021 12:09:30 +0000 Subject: [PATCH 006/204] :globe_with_meridians: Add translations for: German. Currently translated at 91.9% (609 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/de/ --- frontend/translations/de.po | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index c5179aefad..c707cf139b 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-05-13 08:44+0000\n" -"Last-Translator: Andrey Antukh \n" -"Language-Team: German " -"\n" +"PO-Revision-Date: 2021-05-25 12:31+0000\n" +"Last-Translator: Yannik Rödel \n" +"Language-Team: German \n" "Language: de\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" @@ -2409,4 +2409,31 @@ 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" + +#, markdown +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)." + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "%s Dateien duplizieren" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Wenn Sie ein neues Konto erstellen, stimmen Sie unseren Nutzungsbedingungen " +"und Datenschutzrichtlinien zu." + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Einloggen mit OpenID (SSO)" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Einloggen mit Google" From 6e18bc9e046591cf9679fec3c5f77818c9fe1f63 Mon Sep 17 00:00:00 2001 From: Eranot Date: Wed, 26 May 2021 09:14:37 +0000 Subject: [PATCH 007/204] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 38.0% (252 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 107 +++++++++++++++++++++++++++++++-- 1 file changed, 103 insertions(+), 4 deletions(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index a00125a3d2..bbd103dcc6 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,9 +1,9 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-05-17 21:32+0000\n" +"PO-Revision-Date: 2021-05-26 09:23+0000\n" "Last-Translator: Eranot \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" @@ -925,4 +925,103 @@ msgstr "Provedores de fonte - %s - Penpot" #: src/app/main/ui/dashboard/fonts.cljs msgid "title.dashboard.fonts" -msgstr "Fontes - %s - Penpot" \ No newline at end of file +msgstr "Fontes - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Configurações - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Membros - %s - Penpot" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Perfil - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Senha - Penpot" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Configurações - Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Dê sua opinião - Penpot" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot - Liberdade de design para equipes" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Bibliotecas Compartilhadas - %s - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Pesquisar - %s - Penpot" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Projetos - %s - Penpot" + +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - 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 "Misto" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Ir para a página de login" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "E-mail de verificação enviado para %s. Verifique seu e-mail!" + +#: 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/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "" +"Você não pode deletar seu perfil. Designe um novo proprietário para suas " +"equipes antes de continuar." + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Convite enviado com sucesso" + +#: 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" + +#: 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." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Depois de removida como Biblioteca Compartilhada, os Componentes deste " +"arquivo deixarão de estar disponível para serem usados com o resto de seus " +"arquivos." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "Você é o proprietário de %s." + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Excluir membro da equipe" + +#: 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?" From 641e4080bc639d8d07cd2d2adb4aa51f4745e635 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 25 May 2021 15:55:03 +0200 Subject: [PATCH 008/204] :sparkles: Changed transparent for none --- .../app/main/ui/handoff/selection_feedback.cljs | 2 +- frontend/src/app/main/ui/measurements.cljs | 2 +- frontend/src/app/main/ui/shapes/attrs.cljs | 2 +- frontend/src/app/main/ui/shapes/path.cljs | 4 ++-- .../main/ui/workspace/shapes/bounding_box.cljs | 2 +- .../app/main/ui/workspace/shapes/path/editor.cljs | 11 +++++++---- .../app/main/ui/workspace/viewport/drawarea.cljs | 2 +- .../main/ui/workspace/viewport/frame_grid.cljs | 2 +- .../main/ui/workspace/viewport/interactions.cljs | 8 +++++--- .../app/main/ui/workspace/viewport/outline.cljs | 2 +- .../app/main/ui/workspace/viewport/selection.cljs | 15 ++++++++------- 11 files changed, 29 insertions(+), 23 deletions(-) diff --git a/frontend/src/app/main/ui/handoff/selection_feedback.cljs b/frontend/src/app/main/ui/handoff/selection_feedback.cljs index 890accbe28..ccfd218243 100644 --- a/frontend/src/app/main/ui/handoff/selection_feedback.cljs +++ b/frontend/src/app/main/ui/handoff/selection_feedback.cljs @@ -75,7 +75,7 @@ :y y :width width :height height - :style {:fill "transparent" + :style {:fill "none" :stroke select-color :stroke-width selection-rect-width}}]])) diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs index 75dce1ac17..3cdb0faff7 100644 --- a/frontend/src/app/main/ui/measurements.cljs +++ b/frontend/src/app/main/ui/measurements.cljs @@ -177,7 +177,7 @@ :y y :width width :height height - :style {:fill "transparent" + :style {:fill "none" :stroke hover-color :stroke-width selection-rect-width}}]])) diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index bbd09a2621..83f1debbe5 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -89,7 +89,7 @@ ;; we setup the default fill as transparent (instead of black) (and (not (contains? shape :svg-attrs)) (not (#{ :svg-raw :group } (:type shape)))) - {:fill "transparent"} + {:fill "none"} :else {}) diff --git a/frontend/src/app/main/ui/shapes/path.cljs b/frontend/src/app/main/ui/shapes/path.cljs index 08b716e152..256730eb22 100644 --- a/frontend/src/app/main/ui/shapes/path.cljs +++ b/frontend/src/app/main/ui/shapes/path.cljs @@ -28,8 +28,8 @@ #js {:d pdata}))] (if background? [:g - [:path {:stroke "transparent" - :fill "transparent" + [:path {:stroke "none" + :fill "none" :stroke-width "20px" :d pdata}] [:& shape-custom-stroke {:shape shape diff --git a/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs b/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs index 62746cd99b..83e58e5c70 100644 --- a/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs @@ -42,7 +42,7 @@ :height height :transform (or transform "none") :style {:stroke color - :fill "transparent" + :fill "none" :stroke-width "1px" :pointer-events "none"}}]) 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 88cc101144..5d050a58cd 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -86,10 +86,11 @@ :on-mouse-down on-mouse-down :on-mouse-enter on-enter :on-mouse-leave on-leave + :pointer-events (when-not preview? "visible") :style {:cursor (cond (= edit-mode :draw) cur/pen-node (= edit-mode :move) cur/pointer-node) - :fill "transparent"}}]])) + :fill "none"}}]])) (mf/defc path-handler [{:keys [index prefix point handler zoom selected? hover? edit-mode snap-angle?]}] (when (and point handler) @@ -111,7 +112,7 @@ (= edit-mode :move) (st/emit! (drp/start-move-handler index prefix))))] - [:g.handler {:pointer-events (when (= edit-mode :draw))} + [:g.handler {:pointer-events (if (= edit-mode :draw) "none" "visible")} [:line {:x1 (:x point) :y1 (:y point) @@ -147,12 +148,12 @@ :on-mouse-enter on-enter :on-mouse-leave on-leave :style {:cursor (when (= edit-mode :move) cur/pointer-move) - :fill "transparent"}}]]))) + :fill "none"}}]]))) (mf/defc path-preview [{:keys [zoom command from]}] [:g.preview {:style {:pointer-events "none"}} (when (not= :move-to (:command command)) - [:path {:style {:fill "transparent" + [:path {:style {:fill "none" :stroke pc/black-color :stroke-width (/ 1 zoom) :stroke-dasharray (/ 4 zoom)} @@ -274,6 +275,7 @@ [:g.drag-handler {:pointer-events "none"} [:& path-handler {:point last-p :handler drag-handler + :edit-mode edit-mode :zoom zoom}]]) (when @hover-point @@ -325,6 +327,7 @@ (when prev-handler [:g.prev-handler {:pointer-events "none"} [:& path-handler {:point last-p + :edit-mode edit-mode :handler prev-handler :zoom zoom}]]) diff --git a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs index ebf106595a..20d6e49600 100644 --- a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs @@ -45,6 +45,6 @@ :width width :height height :style {:stroke "#1FDEA7" - :fill "transparent" + :fill "none" :stroke-width (/ 1 zoom)}}]))) 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 e10f485a99..d9b147bbc3 100644 --- a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs @@ -55,7 +55,7 @@ :opacity color-opacity} #js {:stroke color-value :strokeOpacity color-opacity - :fill "transparent"})] + :fill "none"})] [:g.grid (for [{:keys [x y width height]} (gg/grid-areas frame grid)] (do diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs index 6c894fab61..4e126fe878 100644 --- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -109,7 +109,7 @@ "translate(" (* zoom x) ", " (* zoom y) ")")}] (when arrow-dir [:path {:stroke "#31EFB8" - :fill "transparent" + :fill "none" :stroke-width 2 :d arrow-pdata :transform (str @@ -134,14 +134,16 @@ (if-not selected? [:path {:stroke "#B1B2B5" - :fill "transparent" + :fill "none" + :pointer-events "visible" :stroke-width (/ 2 zoom) :d pdata :on-mouse-down #(on-mouse-down % orig-shape selected)}] [:g {:on-mouse-down #(on-mouse-down % orig-shape selected)} [:path {:stroke "#31EFB8" - :fill "transparent" + :fill "none" + :pointer-events "visible" :stroke-width (/ 2 zoom) :d pdata}] [:& interaction-marker {:x orig-x diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs index 6c87337768..a024a7d3a9 100644 --- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs @@ -36,7 +36,7 @@ :path "path" "rect") - common {:fill "transparent" + common {:fill "none" :stroke color :strokeWidth (/ 1 zoom) :pointerEvents "none" diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 85b91a8ffb..3b18129437 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -53,7 +53,7 @@ :on-mouse-down on-move-selected :style {:stroke color :stroke-width (/ selection-rect-width zoom) - :fill "transparent"}}]))) + :fill "none"}}]))) (defn- handlers-for-selection [{:keys [x y width height]} {:keys [type]} zoom] (let [zoom-width (* width zoom) @@ -142,7 +142,7 @@ :y y :width size :height size - :fill (if (debug? :rotation-handler) "blue" "transparent") + :fill (if (debug? :rotation-handler) "blue" "none") :transform transform :on-mouse-down on-rotate}])) @@ -174,7 +174,7 @@ :width resize-point-circle-radius :height resize-point-circle-radius :transform (when rotation (str/fmt "rotate(%s, %s, %s)" rotation cx' cy')) - :style {:fill (if (debug? :resize-handler) "red" "transparent") + :style {:fill (if (debug? :resize-handler) "red" "none") :cursor cursor} :on-mouse-down #(on-resize {:x cx' :y cy'} %)}]) @@ -187,7 +187,7 @@ :r (/ resize-point-circle-radius zoom) :cx cx' :cy cy' - :style {:fill (if (debug? :resize-handler) "red" "transparent") + :style {:fill (if (debug? :resize-handler) "red" "none") :cursor cursor}}]) )])) @@ -214,7 +214,7 @@ :transform (gmt/multiply transform (gmt/rotate-matrix angle (gpt/point x y))) :on-mouse-down #(on-resize res-point %) - :style {:fill (if (debug? :resize-handler) "yellow" "transparent") + :style {:fill (if (debug? :resize-handler) "yellow" "none") :cursor (if (#{:left :right} position) (cur/resize-ew rotation) (cur/resize-ns rotation)) }}])) @@ -245,7 +245,7 @@ transform (geom/transform-matrix shape {:no-flip true})] (when (not (#{:move :rotate} current-transform)) - [:g.controls {:pointer-events (when disable-handlers "none")} + [:g.controls {:pointer-events (if disable-handlers "none" "visible")} ;; Selection rect [:& selection-rect {:rect selrect @@ -282,10 +282,11 @@ :transform (geom/transform-matrix shape) :width width :height height + :pointer-events "visible" :style {:stroke color :stroke-width "0.5" :stroke-opacity "1" - :fill "transparent"}}]])) + :fill "none"}}]])) (mf/defc multiple-selection-handlers [{:keys [shapes selected zoom color disable-handlers on-move-selected] :as props}] From 68d287ed821c9ea2977349f641a2d7b44ab33409 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 25 May 2021 15:56:51 +0200 Subject: [PATCH 009/204] :recycle: Refactor trigger download --- frontend/src/app/main/ui/handoff/exports.cljs | 2 +- .../sidebar/options/menus/exports.cljs | 17 +-------- frontend/src/app/util/dom.cljs | 37 +++++++++++++++---- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/frontend/src/app/main/ui/handoff/exports.cljs b/frontend/src/app/main/ui/handoff/exports.cljs index 6462a81014..a8a36b2bf2 100644 --- a/frontend/src/app/main/ui/handoff/exports.cljs +++ b/frontend/src/app/main/ui/handoff/exports.cljs @@ -34,7 +34,7 @@ (fn [{:keys [status body] :as response}] (js/console.log status body) (if (= status 200) - (we/trigger-download (:name shape) body) + (dom/trigger-download (:name shape) body) (st/emit! (dm/error (t locale "errors.unexpected-error"))))) (constantly nil) (fn [] 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 65f05ff673..dd62aef455 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 @@ -29,21 +29,6 @@ :name (:name shape) :exports exports})) -(defn- trigger-download - [filename blob] - (let [link (dom/create-element "a") - uri (dom/create-uri blob) - extension (dom/mtype->extension (.-type ^js blob)) - filename (if extension - (str filename "." extension) - filename)] - (obj/set! link "href" uri) - (obj/set! link "download" filename) - (obj/set! (.-style ^js link) "display" "none") - (.appendChild (.-body ^js js/document) link) - (.click link) - (.remove link))) - (mf/defc exports-menu [{:keys [shape page-id file-id] :as props}] (let [locale (mf/deref i18n/locale) @@ -64,7 +49,7 @@ (->> (request-export (assoc shape :page-id page-id :file-id file-id) exports) (rx/subs (fn [body] - (trigger-download filename body)) + (dom/trigger-download filename body)) (fn [error] (swap! loading? not) (st/emit! (dm/error (tr "errors.unexpected-error")))) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 7601435ca6..5864e285cd 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -54,6 +54,10 @@ [id] (dom/getElement id)) +(defn get-elements-by-tag + [node tag] + (.getElementsByTagName node tag)) + (defn stop-propagation [e] (when e @@ -279,13 +283,14 @@ (defn mtype->extension [mtype] ;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types (case mtype - "image/apng" "apng" - "image/avif" "avif" - "image/gif" "gif" - "image/jpeg" "jpg" - "image/png" "png" - "image/svg+xml" "svg" - "image/webp" "webp" + "image/apng" "apng" + "image/avif" "avif" + "image/gif" "gif" + "image/jpeg" "jpg" + "image/png" "png" + "image/svg+xml" "svg" + "image/webp" "webp" + "application/zip" "zip" nil)) (defn set-attribute [^js node ^string attr value] @@ -311,3 +316,21 @@ (>= (.-left rect) 0) (<= (.-bottom rect) height) (<= (.-right rect) width)))) + +(defn trigger-download-uri + [filename mtype uri] + (let [link (create-element "a") + extension (mtype->extension mtype) + filename (if extension + (str filename "." extension) + filename)] + (obj/set! link "href" uri) + (obj/set! link "download" filename) + (obj/set! (.-style ^js link) "display" "none") + (.appendChild (.-body ^js js/document) link) + (.click link) + (.remove link))) + +(defn trigger-download + [filename blob] + (trigger-download-uri filename (.-type ^js blob) (dom/create-uri blob))) From aaef0777b03c33e862d10c18825699839e56737d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 25 May 2021 15:57:20 +0200 Subject: [PATCH 010/204] :arrow_up: Add jszip dependency --- frontend/package.json | 1 + frontend/yarn.lock | 29 ++++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/frontend/package.json b/frontend/package.json index 9e583d58a6..8e710283dd 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -40,6 +40,7 @@ "draft-js": "^0.11.7", "highlight.js": "^10.6.0", "js-beautify": "^1.13.5", + "jszip": "^3.6.0", "luxon": "^1.26.0", "mousetrap": "^1.6.5", "opentype.js": "^1.3.3", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index caa556a85b..592ff54cad 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2375,6 +2375,11 @@ ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== +immediate@~3.0.5: + version "3.0.6" + resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" + integrity sha1-nbHb0Pr43m++D13V5Wu2BigN5ps= + immutable@~3.7.4: version "3.7.6" resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b" @@ -2838,6 +2843,16 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +jszip@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/jszip/-/jszip-3.6.0.tgz#839b72812e3f97819cc13ac4134ffced95dd6af9" + integrity sha512-jgnQoG9LKnWO3mnVNBnfhkh0QknICd1FGSrXcgrl67zioyJ4wgx25o9ZqwNtrROSflGBCGYnJfjrIyRIby1OoQ== + dependencies: + lie "~3.3.0" + pako "~1.0.2" + readable-stream "~2.3.6" + set-immediate-shim "~1.0.1" + just-debounce@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.1.0.tgz#2f81a3ad4121a76bc7cb45dbf704c0d76a8e5ddf" @@ -2920,6 +2935,13 @@ lead@^1.0.0: dependencies: flush-write-stream "^1.0.2" +lie@~3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/lie/-/lie-3.3.0.tgz#dcf82dee545f46074daf200c7c1c5a08e0f40f6a" + integrity sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ== + dependencies: + immediate "~3.0.5" + liftoff@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-3.1.0.tgz#c9ba6081f908670607ee79062d700df062c52ed3" @@ -3762,7 +3784,7 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@~1.0.5: +pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== @@ -4521,6 +4543,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-immediate-shim@~1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E= + set-value@^2.0.0, set-value@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" From b648fb7446e4c842c6e2897b7586aed3f0ef8dee Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 May 2021 14:14:13 +0200 Subject: [PATCH 011/204] :sparkles: Zip utils --- frontend/src/app/util/zip.cljs | 41 ++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/util/zip.cljs b/frontend/src/app/util/zip.cljs index 6cc7a6fd82..071a7c456c 100644 --- a/frontend/src/app/util/zip.cljs +++ b/frontend/src/app/util/zip.cljs @@ -6,14 +6,47 @@ (ns app.util.zip "Helpers for make zip file (using jszip)." - (:require [vendor.jszip] - [beicon.core :as rx])) + (:require + ["jszip" :as zip] + [app.common.data :as d] + [beicon.core :as rx] + [promesa.core :as p])) -(defn build +(defn compress-files [files] (letfn [(attach-file [zobj [name content]] (.file zobj name content))] - (let [zobj (js/JSZip.)] + (let [zobj (zip.)] (run! (partial attach-file zobj) files) (->> (.generateAsync zobj #js {:type "blob"}) (rx/from))))) + +(defn extract-files + "Creates a stream that will emit values for every file in the zip" + [file] + (rx/create + (fn [subs] + (let [process-entry + (fn [path entry] + (if (.-dir entry) + (rx/push! subs {:dir path}) + (p/then + (.async entry "text") + (fn [content] + (rx/push! subs + {:path path + :content content})))))] + + (p/let [response (js/fetch file) + data (.blob response) + content (zip/loadAsync data)] + + (let [promises (atom [])] + (.forEach content + (fn [path entry] + (let [current (process-entry path entry)] + (swap! promises conj current)))) + + (p/then (p/all @promises) + #(rx/end! subs)))) + nil)))) From 1a7007140583f4da28111873b1dabc5968f5bfca Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 May 2021 14:31:10 +0200 Subject: [PATCH 012/204] :sparkles: Adds support to rx streams on workers framework --- frontend/src/app/main/worker.cljs | 4 ++ frontend/src/app/util/worker.cljs | 35 ++++++++++---- frontend/src/app/worker.cljs | 61 ++++++++++++++----------- frontend/src/app/worker/thumbnails.cljs | 27 +++++------ 4 files changed, 76 insertions(+), 51 deletions(-) diff --git a/frontend/src/app/main/worker.cljs b/frontend/src/app/main/worker.cljs index a0985d97ff..de564536dc 100644 --- a/frontend/src/app/main/worker.cljs +++ b/frontend/src/app/main/worker.cljs @@ -26,3 +26,7 @@ (defn ask-buffered! [message] (uw/ask-buffered! instance message)) + +(defn ask-many! + [message] + (uw/ask-many! instance message)) diff --git a/frontend/src/app/util/worker.cljs b/frontend/src/app/util/worker.cljs index 4c07a7f083..8e9943e4fd 100644 --- a/frontend/src/app/util/worker.cljs +++ b/frontend/src/app/util/worker.cljs @@ -14,14 +14,25 @@ (declare handle-response) (defrecord Worker [instance stream]) -(defn- send-message! [worker {sender-id :sender-id :as message}] - (let [data (t/encode message) - instance (:instance worker)] - (.postMessage instance data) - (->> (:stream worker) - (rx/filter #(= (:reply-to %) sender-id)) - (rx/take 1) - (rx/map handle-response)))) +(defn- send-message! + ([worker message] + (send-message! worker message nil)) + + ([worker {sender-id :sender-id :as message} {:keys [many?] :or {many? false}}] + (let [take-messages + (fn [ob] + (if many? + (rx/take-while #(not (:completed %)) ob) + (rx/take 1 ob))) + + data (t/encode message) + instance (:instance worker)] + + (.postMessage instance data) + (->> (:stream worker) + (rx/filter #(= (:reply-to %) sender-id)) + (take-messages) + (rx/map handle-response))))) (defn ask! [worker message] @@ -30,6 +41,14 @@ {:sender-id (uuid/next) :payload message})) +(defn ask-many! + [worker message] + (send-message! + worker + {:sender-id (uuid/next) + :payload message} + {:many? true})) + (defn ask-buffered! [worker message] (send-message! diff --git a/frontend/src/app/worker.cljs b/frontend/src/app/worker.cljs index 0c21935324..38b68a57d5 100644 --- a/frontend/src/app/worker.cljs +++ b/frontend/src/app/worker.cljs @@ -43,35 +43,42 @@ "Process the message and returns to the client" [{:keys [sender-id payload] :as message}] (us/assert ::message message) - (try - (let [result (impl/handler payload)] - (cond - (p/promise? result) - (p/handle result - (fn [msg] - (.postMessage js/self (t/encode - {:reply-to sender-id - :payload msg}))) - (fn [err] - (.postMessage js/self (t/encode - {:reply-to sender-id - :error {:data (ex-data err) - :message (ex-message err)}})))) + (letfn [(post [msg] + (let [msg (-> msg (assoc :reply-to sender-id) (t/encode))] + (.postMessage js/self msg))) - (or (rx/observable? result) - (rx/subject? result)) - (throw (ex-info "not implemented" {})) + (reply [result] + (post {:payload result})) - :else - (.postMessage js/self (t/encode - {:reply-to sender-id - :payload result})))) - (catch :default e - (.error js/console "error" e) - (let [message {:reply-to sender-id - :error {:data (ex-data e) - :message (ex-message e)}}] - (.postMessage js/self (t/encode message)))))) + (reply-error [err] + (.error js/console "error" err) + (post {:error {:data (ex-data err) + :message (ex-message err)}})) + + (reply-completed + ([] (reply-completed nil)) + ([msg] (post {:payload msg + :completed true})))] + + (try + (let [result (impl/handler payload) + promise? (p/promise? result) + stream? (or (rx/observable? result) (rx/subject? result))] + + (cond + promise? + (-> result + (p/then reply-completed) + (p/catch reply-error)) + + stream? + (rx/subscribe result reply reply-error reply-completed) + + :else + (reply result))) + + (catch :default err + (reply-error err))))) (defn- drop-message "Sends to the client a notifiction that its messages have been dropped" diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index ddf65c6b1a..bf010d56a9 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -31,17 +31,12 @@ (defn- request-page [file-id page-id] (let [uri "/api/rpc/query/page"] - (p/create - (fn [resolve reject] - (->> (http/send! {:uri uri - :query {:file-id file-id :id page-id :strip-thumbnails true} - :method :get}) - (rx/map http/conditional-decode-transit) - (rx/mapcat handle-response) - (rx/subs (fn [body] - (resolve body)) - (fn [error] - (reject error)))))))) + (->> (http/send! + {:uri uri + :query {:file-id file-id :id page-id :strip-thumbnails true} + :method :get}) + (rx/map http/conditional-decode-transit) + (rx/mapcat handle-response)))) (defonce cache (atom {})) @@ -57,8 +52,8 @@ (defmethod impl/handler :thumbnails/generate [{:keys [file-id page-id] :as message}] - (p/then - (request-page file-id page-id) - (fn [data] - {:svg (render-page data #{file-id page-id}) - :fonts @fonts/loaded}))) + (->> (request-page file-id page-id) + (rx/map + (fn [data] + {:svg (render-page data #{file-id page-id}) + :fonts @fonts/loaded})))) From 9cda3615235a0eda3465b38e6dc18cf060466504 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 27 May 2021 14:31:47 +0200 Subject: [PATCH 013/204] :sparkles: Removed unnecessary background box --- frontend/src/app/main/exports.cljs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index 1f70e5e48d..1496caaa20 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -148,8 +148,10 @@ :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" :xmlns "http://www.w3.org/2000/svg" - :xmlns:penpot "https://penpot.app/xmlns"} - [:& background {:vbox dim :color background-color}] + :xmlns:penpot "https://penpot.app/xmlns" + :style {:width "100%" + :height "100%" + :background background-color}} (for [item shapes] (let [frame? (= (:type item) :frame)] (cond From d102144746a59517769d2b8f644c5d978233e8f2 Mon Sep 17 00:00:00 2001 From: Michael G Date: Sun, 30 May 2021 18:56:35 +0200 Subject: [PATCH 014/204] :book: Fix typos and rephrase some comments Minor typos and the names of official services corrected in comments. --- docker/images/config.env | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/docker/images/config.env b/docker/images/config.env index 79f1472eef..396ce8cf94 100644 --- a/docker/images/config.env +++ b/docker/images/config.env @@ -1,8 +1,8 @@ -# Should be set to the public domain when penpot is going to be +# Should be set to the public domain where penpot is going to be # served. PENPOT_PUBLIC_URI=http://localhost:9001 -# Standard database connection parametes (only postgresql is +# Standard database connection parameters (only postgresql is # supported): PENPOT_DATABASE_URI=postgresql://penpot-postgres/penpot PENPOT_DATABASE_USERNAME=penpot @@ -11,22 +11,22 @@ PENPOT_DATABASE_PASSWORD=penpot # Redis is used for the websockets notifications. PENPOT_REDIS_URI=redis://penpot-redis/0 -# By default files upload by user are stored in local filesystem. But -# it can be configured to store in AWS S3 or completelly in de the +# By default, files uploaded by users are stored in local filesystem. But +# it can be configured to store in AWS S3 or completely in de the # database. Storing in the database makes the backups more easy but # will make access to media less performant. PENPOT_STORAGE_BACKEND=fs PENPOT_STORAGE_FS_DIRECTORY=/opt/data/assets -# Telemetry. When enabled, a periodical process will send annonymous +# Telemetry. When enabled, a periodical process will send anonymous # data about this instance. Telemetry data will enable us to learn on -# how the application is used based on real scenarios. If you want to +# how the application is used, based on real scenarios. If you want to # help us, please leave it enabled. PENPOT_TELEMETRY_ENABLED=true -# Email sending configuration. By default emails are printed in -# console, but for production usage is recommeded to setup a real SMTP -# provider. Emails are used for confirm user registration. +# Email sending configuration. By default, emails are printed in the +# console, but for production usage is recommended to setup a real SMTP +# provider. Emails are used to confirm user registrations. PENPOT_SMTP_ENABLED=false PENPOT_SMTP_DEFAULT_FROM=no-reply@example.com PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com @@ -40,7 +40,7 @@ PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com # Enable or disable external user registration process. PENPOT_REGISTRATION_ENABLED=true -# Comma separated list of allowed domains to register. Empty for allow +# Comma separated list of allowed domains to register. Empty to allow # all. # PENPOT_REGISTRATION_DOMAIN_WHITELIST="" @@ -55,11 +55,11 @@ PENPOT_ALLOW_DEMO_USERS=true # PENPOT_GOOGLE_CLIENT_ID= # PENPOT_GOOGLE_CLIENT_SECRET= -# Github +# GitHub # PENPOT_GITHUB_CLIENT_ID= # PENPOT_GITHUB_CLIENT_SECRET= -# Gitlab +# GitLab # PENPOT_GITLAB_BASE_URI=https://gitlab.com # PENPOT_GITLAB_CLIENT_ID= # PENPOT_GITLAB_CLIENT_SECRET= From 9d54f71dbb3c67a48ddadb3c35ef492d55a8fdcb Mon Sep 17 00:00:00 2001 From: Michael G Date: Sun, 30 May 2021 19:02:26 +0200 Subject: [PATCH 015/204] :books: Align comments to 80 characters I did not find any style recommendation that states an exact line length. Assuming a common value of 80, this leads to less lines being split. --- docker/images/config.env | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/docker/images/config.env b/docker/images/config.env index 396ce8cf94..6d1f76bd27 100644 --- a/docker/images/config.env +++ b/docker/images/config.env @@ -1,9 +1,7 @@ -# Should be set to the public domain where penpot is going to be -# served. +# Should be set to the public domain where penpot is going to be served. PENPOT_PUBLIC_URI=http://localhost:9001 -# Standard database connection parameters (only postgresql is -# supported): +# Standard database connection parameters (only postgresql is supported): PENPOT_DATABASE_URI=postgresql://penpot-postgres/penpot PENPOT_DATABASE_USERNAME=penpot PENPOT_DATABASE_PASSWORD=penpot @@ -11,22 +9,22 @@ PENPOT_DATABASE_PASSWORD=penpot # Redis is used for the websockets notifications. PENPOT_REDIS_URI=redis://penpot-redis/0 -# By default, files uploaded by users are stored in local filesystem. But -# it can be configured to store in AWS S3 or completely in de the -# database. Storing in the database makes the backups more easy but -# will make access to media less performant. +# By default, files uploaded by users are stored in local filesystem. But it +# can be configured to store in AWS S3 or completely in de the database. +# Storing in the database makes the backups more easy but will make access to +# media less performant. PENPOT_STORAGE_BACKEND=fs PENPOT_STORAGE_FS_DIRECTORY=/opt/data/assets -# Telemetry. When enabled, a periodical process will send anonymous -# data about this instance. Telemetry data will enable us to learn on -# how the application is used, based on real scenarios. If you want to -# help us, please leave it enabled. +# Telemetry. When enabled, a periodical process will send anonymous data about +# this instance. Telemetry data will enable us to learn on how the application +# is used, based on real scenarios. If you want to help us, please leave it +# enabled. PENPOT_TELEMETRY_ENABLED=true -# Email sending configuration. By default, emails are printed in the -# console, but for production usage is recommended to setup a real SMTP -# provider. Emails are used to confirm user registrations. +# Email sending configuration. By default, emails are printed in the console, +# but for production usage is recommended to setup a real SMTP provider. Emails +# are used to confirm user registrations. PENPOT_SMTP_ENABLED=false PENPOT_SMTP_DEFAULT_FROM=no-reply@example.com PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com @@ -40,13 +38,12 @@ PENPOT_SMTP_DEFAULT_REPLY_TO=no-reply@example.com # Enable or disable external user registration process. PENPOT_REGISTRATION_ENABLED=true -# Comma separated list of allowed domains to register. Empty to allow -# all. +# Comma separated list of allowed domains to register. Empty to allow all. # PENPOT_REGISTRATION_DOMAIN_WHITELIST="" # Penpot comes with the facility to create quick demo users that are -# automatically deleted after some time. This settings enables or -# disables the creation of demo users. +# automatically deleted after some time. This settings enables or disables the +# creation of demo users. PENPOT_ALLOW_DEMO_USERS=true ## Authentication providers @@ -82,4 +79,3 @@ PENPOT_ALLOW_DEMO_USERS=true # PENPOT_LDAP_ATTRS_FULLNAME=cn # PENPOT_LDAP_ATTRS_PHOTO=jpegPhoto # PENPOT_LOGIN_WITH_LDAP=true - From 548664f6ce811db3becf76ad3979cf664ed4c5c3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 28 May 2021 13:50:42 +0200 Subject: [PATCH 016/204] :recycle: Internal directory refactor. Make common as first-class module. --- .circleci/config.yml | 40 +- .gitignore | 2 + backend/deps.edn | 61 +- backend/dev/user.clj | 2 +- backend/src/app/notifications.clj | 2 +- backend/src/app/util/time.clj | 44 +- backend/src/app/worker.clj | 2 +- .../app/bounce_handling_test.clj} | 14 +- .../app/emails_test.clj} | 4 +- .../app/services_files_test.clj} | 6 +- .../app/services_fonts_test.clj} | 8 +- .../app/services_management_test.clj} | 4 +- .../app/services_media_test.clj} | 6 +- .../app/services_profile_test.clj} | 16 +- .../app/services_projects_test.clj} | 10 +- .../app/services_teams_test.clj} | 8 +- .../app/services_viewer_test.clj} | 8 +- .../app/storage_test.clj} | 8 +- .../_files => test/app/test_files}/font-1.otf | Bin .../_files => test/app/test_files}/font-1.ttf | Bin .../app/test_files}/font-1.woff | Bin .../_files => test/app/test_files}/font-2.otf | Bin .../app/test_files}/font-2.woff | Bin .../_files => test/app/test_files}/sample.jpg | Bin .../app/test_files}/sample1.svg | 0 .../app/test_files}/sample2.svg | 0 .../helpers.clj => test/app/test_helpers.clj} | 2 +- backend/tests.edn | 4 +- common/deps.edn | 76 ++ common/dev/user.clj | 51 ++ common/package.json | 13 + common/shadow-cljs.edn | 17 + common/{ => src}/app/common/attrs.cljc | 0 common/{ => src}/app/common/data.cljc | 0 .../{ => src}/app/common/data/undo_stack.cljc | 0 common/{ => src}/app/common/exceptions.cljc | 0 common/{ => src}/app/common/geom/align.cljc | 0 common/{ => src}/app/common/geom/matrix.cljc | 0 common/{ => src}/app/common/geom/point.cljc | 0 .../app/common/geom/proportions.cljc | 0 common/{ => src}/app/common/geom/shapes.cljc | 0 .../app/common/geom/shapes/common.cljc | 0 .../app/common/geom/shapes/intersect.cljc | 0 .../app/common/geom/shapes/path.cljc | 0 .../app/common/geom/shapes/rect.cljc | 0 .../app/common/geom/shapes/transforms.cljc | 0 common/{ => src}/app/common/math.cljc | 0 common/{ => src}/app/common/media.cljc | 0 common/{ => src}/app/common/pages.cljc | 0 .../{ => src}/app/common/pages/changes.cljc | 0 common/{ => src}/app/common/pages/common.cljc | 0 .../{ => src}/app/common/pages/helpers.cljc | 0 .../{ => src}/app/common/pages/indices.cljc | 0 common/{ => src}/app/common/pages/init.cljc | 0 .../app/common/pages/migrations.cljc | 0 common/{ => src}/app/common/pages/spec.cljc | 0 common/{ => src}/app/common/spec.cljc | 0 common/{ => src}/app/common/text.cljc | 12 +- common/src/app/common/transit.cljc | 216 +++++ common/{ => src}/app/common/uri.cljc | 0 common/{ => src}/app/common/uuid.cljc | 7 +- common/{ => src}/app/common/uuid_impl.js | 6 + common/{ => src}/app/common/version.cljc | 1 - .../test/app/common/geom_shapes_test.cljc | 2 +- .../test/app/common/geom_test.cljc | 2 +- .../app/common/pages_migrations_test.cljc | 15 +- .../test/app/common/pages_test.cljc | 7 +- common/test/app/common/setup_test.cljc | 10 + .../test/app/common/text_test.cljc | 6 +- common/yarn.lock | 31 + docker/devenv/files/start-tmux.sh | 2 +- frontend/deps.edn | 27 +- frontend/package.json | 5 +- frontend/shadow-cljs.edn | 33 +- frontend/src/app/util/perf.cljs | 2 +- frontend/test.cljs | 0 .../app/components_basic_test.cljs} | 8 +- .../app/components_sync_test.cljs} | 20 +- frontend/test/app/init_test.cljs | 9 + .../app/shapes_test.cljs} | 27 +- .../app/test_helpers/events.cljs | 21 +- .../app/test_helpers/libraries.cljs | 25 +- .../app/test_helpers/pages.cljs | 0 .../app/util/range_tree_test.cljs} | 13 +- .../app/util/simple_math_test.cljs} | 11 +- frontend/yarn.lock | 783 +++--------------- 86 files changed, 801 insertions(+), 908 deletions(-) rename backend/{tests/app/tests/test_bounces_handling.clj => test/app/bounce_handling_test.clj} (99%) rename backend/{tests/app/tests/test_emails.clj => test/app/emails_test.clj} (92%) rename backend/{tests/app/tests/test_services_files.clj => test/app/services_files_test.clj} (98%) rename backend/{tests/app/tests/test_services_fonts.clj => test/app/services_fonts_test.clj} (93%) rename backend/{tests/app/tests/test_services_management.clj => test/app/services_management_test.clj} (99%) rename backend/{tests/app/tests/test_services_media.clj => test/app/services_media_test.clj} (96%) rename backend/{tests/app/tests/test_services_profile.clj => test/app/services_profile_test.clj} (98%) rename backend/{tests/app/tests/test_services_projects.clj => test/app/services_projects_test.clj} (97%) rename backend/{tests/app/tests/test_services_teams.clj => test/app/services_teams_test.clj} (95%) rename backend/{tests/app/tests/test_services_viewer.clj => test/app/services_viewer_test.clj} (97%) rename backend/{tests/app/tests/test_storage.clj => test/app/storage_test.clj} (98%) rename backend/{tests/app/tests/_files => test/app/test_files}/font-1.otf (100%) rename backend/{tests/app/tests/_files => test/app/test_files}/font-1.ttf (100%) rename backend/{tests/app/tests/_files => test/app/test_files}/font-1.woff (100%) rename backend/{tests/app/tests/_files => test/app/test_files}/font-2.otf (100%) rename backend/{tests/app/tests/_files => test/app/test_files}/font-2.woff (100%) rename backend/{tests/app/tests/_files => test/app/test_files}/sample.jpg (100%) rename backend/{tests/app/tests/_files => test/app/test_files}/sample1.svg (100%) rename backend/{tests/app/tests/_files => test/app/test_files}/sample2.svg (100%) rename backend/{tests/app/tests/helpers.clj => test/app/test_helpers.clj} (99%) create mode 100644 common/deps.edn create mode 100644 common/dev/user.clj create mode 100644 common/package.json create mode 100644 common/shadow-cljs.edn rename common/{ => src}/app/common/attrs.cljc (100%) rename common/{ => src}/app/common/data.cljc (100%) rename common/{ => src}/app/common/data/undo_stack.cljc (100%) rename common/{ => src}/app/common/exceptions.cljc (100%) rename common/{ => src}/app/common/geom/align.cljc (100%) rename common/{ => src}/app/common/geom/matrix.cljc (100%) rename common/{ => src}/app/common/geom/point.cljc (100%) rename common/{ => src}/app/common/geom/proportions.cljc (100%) rename common/{ => src}/app/common/geom/shapes.cljc (100%) rename common/{ => src}/app/common/geom/shapes/common.cljc (100%) rename common/{ => src}/app/common/geom/shapes/intersect.cljc (100%) rename common/{ => src}/app/common/geom/shapes/path.cljc (100%) rename common/{ => src}/app/common/geom/shapes/rect.cljc (100%) rename common/{ => src}/app/common/geom/shapes/transforms.cljc (100%) rename common/{ => src}/app/common/math.cljc (100%) rename common/{ => src}/app/common/media.cljc (100%) rename common/{ => src}/app/common/pages.cljc (100%) rename common/{ => src}/app/common/pages/changes.cljc (100%) rename common/{ => src}/app/common/pages/common.cljc (100%) rename common/{ => src}/app/common/pages/helpers.cljc (100%) rename common/{ => src}/app/common/pages/indices.cljc (100%) rename common/{ => src}/app/common/pages/init.cljc (100%) rename common/{ => src}/app/common/pages/migrations.cljc (100%) rename common/{ => src}/app/common/pages/spec.cljc (100%) rename common/{ => src}/app/common/spec.cljc (100%) rename common/{ => src}/app/common/text.cljc (96%) create mode 100644 common/src/app/common/transit.cljc rename common/{ => src}/app/common/uri.cljc (100%) rename common/{ => src}/app/common/uuid.cljc (87%) rename common/{ => src}/app/common/uuid_impl.js (94%) rename common/{ => src}/app/common/version.cljc (97%) rename backend/tests/app/tests/test_common_geom_shapes.clj => common/test/app/common/geom_shapes_test.cljc (99%) rename backend/tests/app/tests/test_common_geom.clj => common/test/app/common/geom_test.cljc (98%) rename backend/tests/app/tests/test_common_pages_migrations.clj => common/test/app/common/pages_migrations_test.cljc (92%) rename backend/tests/app/tests/test_common_pages.clj => common/test/app/common/pages_test.cljc (99%) create mode 100644 common/test/app/common/setup_test.cljc rename frontend/tests/app/test_draft_conversion.cljs => common/test/app/common/text_test.cljc (89%) create mode 100644 common/yarn.lock delete mode 100644 frontend/test.cljs rename frontend/{tests/app/test_components_basic.cljs => test/app/components_basic_test.cljs} (98%) rename frontend/{tests/app/test_components_sync.cljs => test/app/components_sync_test.cljs} (97%) create mode 100644 frontend/test/app/init_test.cljs rename frontend/{tests/app/test_shapes.cljs => test/app/shapes_test.cljs} (72%) rename frontend/{tests => test}/app/test_helpers/events.cljs (66%) rename frontend/{tests => test}/app/test_helpers/libraries.cljs (87%) rename frontend/{tests => test}/app/test_helpers/pages.cljs (100%) rename frontend/{tests/app/test_util_range_tree.cljs => test/app/util/range_tree_test.cljs} (96%) rename frontend/{tests/app/test_util_simple_math.cljs => test/app/util/simple_math_test.cljs} (92%) diff --git a/.circleci/config.yml b/.circleci/config.yml index 0fc2473ce9..fc6f86d2e6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,7 +9,7 @@ jobs: # CircleCI maintains a library of pre-built images # documented at https://circleci.com/docs/2.0/circleci-images/ # - image: circleci/postgres:9.4 - - image: circleci/postgres:13.1-ram + - image: circleci/postgres:13.3-ram environment: POSTGRES_USER: penpot_test POSTGRES_PASSWORD: penpot_test @@ -29,21 +29,21 @@ jobs: # Download and cache dependencies - restore_cache: keys: - - v1-dependencies-{{ checksum "backend/deps.edn" }}-{{ checksum "frontend/deps.edn"}} + - v1-dependencies-{{ checksum "backend/deps.edn" }}-{{ checksum "frontend/deps.edn"}}-{{ checksum "common/deps.edn"}} # fallback to using the latest cache if no exact match is found - v1-dependencies- - # run lint + # run backend lint - run: - working_directory: "./backend" name: backend lint + working_directory: "./backend" command: "clj-kondo --lint src/" - # run test + # run backend test - run: - working_directory: "./backend" name: backend test - command: "clojure -M:dev:tests" + working_directory: "./backend" + command: "clojure -X:dev:test" environment: PENPOT_TEST_DATABASE_URI: "postgresql://localhost/penpot_test" PENPOT_TEST_DATABASE_USERNAME: penpot_test @@ -51,11 +51,31 @@ jobs: PENPOT_TEST_REDIS_URI: "redis://localhost/1" - run: - working_directory: "./frontend" name: frontend tests + working_directory: "./frontend" command: | yarn install - npx shadow-cljs compile tests + clojure -M:dev:shadow-cljs compile test + node target/tests.js + + environment: + JAVA_HOME: /usr/lib/jvm/openjdk16 + PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin:/usr/lib/jvm/openjdk16/bin + + # - run: + # name: common lint + # working_directory: "./common" + # command: "clj-kondo --lint src/" + + - run: + working_directory: "./common" + name: common tests + command: | + yarn install + clojure -M:dev:shadow-cljs compile test + node target/tests.js + clojure -X:dev:test + environment: JAVA_HOME: /usr/lib/jvm/openjdk16 PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin:/usr/lib/jvm/openjdk16/bin @@ -63,5 +83,5 @@ jobs: - save_cache: paths: - ~/.m2 - key: v1-dependencies-{{ checksum "backend/deps.edn" }}-{{ checksum "frontend/deps.edn"}} + key: v1-dependencies-{{ checksum "backend/deps.edn" }}-{{ checksum "frontend/deps.edn"}}-{{ checksum "common/deps.edn"}} diff --git a/.gitignore b/.gitignore index 4695ea70a2..d1cc5a7af2 100644 --- a/.gitignore +++ b/.gitignore @@ -30,6 +30,8 @@ node_modules /exporter/target /exporter/.shadow-cljs /docker/images/bundle* +/common/.shadow-cljs +/common/target /.clj-kondo/.cache /bundle* /media diff --git a/backend/deps.edn b/backend/deps.edn index 8032825c08..6a3ae16699 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -1,22 +1,14 @@ -{:mvn/repos - {"central" {:url "https://repo1.maven.org/maven2/"} - "clojars" {:url "https://clojars.org/repo"} - "jcenter" {:url "https://jcenter.bintray.com/"}} +{ + ;; :mvn/repos + ;; {"central" {:url "https://repo1.maven.org/maven2/"} + ;; "clojars" {:url "https://clojars.org/repo"} + ;; "jcenter" {:url "https://jcenter.bintray.com/"} + ;; } :deps - {org.clojure/clojure {:mvn/version "1.10.3"} - org.clojure/data.json {:mvn/version "2.2.3"} - org.clojure/core.async {:mvn/version "1.3.618"} - org.clojure/tools.cli {:mvn/version "1.0.206"} - org.clojure/clojurescript {:mvn/version "1.10.844"} + {penpot/common + {:local/root "../common"} ;; Logging - org.clojure/tools.logging {:mvn/version "1.1.0"} - org.apache.logging.log4j/log4j-api {:mvn/version "2.14.1"} - org.apache.logging.log4j/log4j-core {:mvn/version "2.14.1"} - org.apache.logging.log4j/log4j-web {:mvn/version "2.14.1"} - org.apache.logging.log4j/log4j-jul {:mvn/version "2.14.1"} - org.apache.logging.log4j/log4j-slf4j18-impl {:mvn/version "2.14.1"} - org.slf4j/slf4j-api {:mvn/version "2.0.0-alpha1"} org.zeromq/jeromq {:mvn/version "0.5.2"} com.taoensso/nippy {:mvn/version "3.1.1"} @@ -32,49 +24,31 @@ org.eclipse.jetty/jetty-servlet]} io.prometheus/simpleclient_httpserver {:mvn/version "0.9.0"} - selmer/selmer {:mvn/version "1.12.40"} - expound/expound {:mvn/version "0.8.9"} - com.cognitect/transit-clj {:mvn/version "1.0.324"} - io.lettuce/lettuce-core {:mvn/version "6.1.2.RELEASE"} java-http-clj/java-http-clj {:mvn/version "0.4.2"} info.sunng/ring-jetty9-adapter {:mvn/version "0.15.1"} com.github.seancorfield/next.jdbc {:mvn/version "1.2.659"} metosin/reitit-ring {:mvn/version "0.5.13"} - metosin/jsonista {:mvn/version "0.3.3"} - org.postgresql/postgresql {:mvn/version "42.2.20"} com.zaxxer/HikariCP {:mvn/version "4.0.3"} funcool/datoteka {:mvn/version "2.0.0"} - funcool/promesa {:mvn/version "6.0.1"} - funcool/cuerdas {:mvn/version "2021.05.09-0"} buddy/buddy-core {:mvn/version "1.10.1"} buddy/buddy-hashers {:mvn/version "1.8.1"} buddy/buddy-sign {:mvn/version "3.4.1"} - lambdaisland/uri {:mvn/version "1.4.54" - :exclusions [org.clojure/data.json]} - - frankiesardo/linked {:mvn/version "1.3.0"} - danlentz/clj-uuid {:mvn/version "0.1.9"} org.jsoup/jsoup {:mvn/version "1.13.1"} org.im4java/im4java {:mvn/version "1.4.0"} org.lz4/lz4-java {:mvn/version "1.7.1"} - commons-io/commons-io {:mvn/version "2.8.0"} - com.sun.mail/jakarta.mail {:mvn/version "2.0.1"} org.clojars.pntblnk/clj-ldap {:mvn/version "0.0.17"} integrant/integrant {:mvn/version "0.8.0"} - software.amazon.awssdk/s3 {:mvn/version "2.16.62"} + software.amazon.awssdk/s3 {:mvn/version "2.16.62"}} - ;; exception printing - io.aviso/pretty {:mvn/version "0.1.37"} - environ/environ {:mvn/version "1.2.0"}} - :paths ["src" "resources" "../common" "common"] + :paths ["src" "resources"] :aliases {:dev {:extra-deps @@ -82,19 +56,24 @@ org.clojure/tools.namespace {:mvn/version "RELEASE"} org.clojure/test.check {:mvn/version "RELEASE"} - fipp/fipp {:mvn/version "0.6.23"} - criterium/criterium {:mvn/version "0.4.6"} - mockery/mockery {:mvn/version "0.1.4"}} - :extra-paths ["tests" "dev"]} + criterium/criterium {:mvn/version "RELEASE"} + mockery/mockery {:mvn/version "RELEASE"}} + :extra-paths ["test" "dev"]} :fn-fixtures {:exec-fn app.cli.fixtures/run :args {}} - :tests + :kaocha {:extra-deps {lambdaisland/kaocha {:mvn/version "1.0.829"}} :main-opts ["-m" "kaocha.runner"]} + :test + {:extra-deps {io.github.cognitect-labs/test-runner + {:git/url "https://github.com/cognitect-labs/test-runner.git" + :sha "705ad25bbf0228b1c38d0244a36001c2987d7337"}} + :exec-fn cognitect.test-runner.api/test} + :outdated {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}} :main-opts ["-m" "antq.core"]} diff --git a/backend/dev/user.clj b/backend/dev/user.clj index fc3fe94bd8..4f47934a8b 100644 --- a/backend/dev/user.clj +++ b/backend/dev/user.clj @@ -50,7 +50,7 @@ ;; --- Development Stuff (defn- run-tests - ([] (run-tests #"^app.tests.*")) + ([] (run-tests #"^app.*-test$")) ([o] (repl/refresh) (cond diff --git a/backend/src/app/notifications.clj b/backend/src/app/notifications.clj index 75b7de0c62..f938352e93 100644 --- a/backend/src/app/notifications.clj +++ b/backend/src/app/notifications.clj @@ -163,7 +163,7 @@ ;; when connection is closed (mtx-aconn :dec) - (mtx-sessions :observe (/ (inst-ms (dt/duration-between created-at (dt/now))) 1000.0)) + (mtx-sessions :observe (/ (inst-ms (dt/diff created-at (dt/now))) 1000.0)) ;; close subscription (a/close! sub-ch)))) diff --git a/backend/src/app/util/time.clj b/backend/src/app/util/time.clj index c2839f483d..9d5d3d4d75 100644 --- a/backend/src/app/util/time.clj +++ b/backend/src/app/util/time.clj @@ -10,13 +10,14 @@ [clojure.spec.alpha :as s] [cuerdas.core :as str]) (:import - java.time.Instant java.time.Duration - java.util.Date - java.time.ZonedDateTime + java.time.Instant + java.time.OffsetDateTime java.time.ZoneId + java.time.ZonedDateTime java.time.format.DateTimeFormatter java.time.temporal.TemporalAmount + java.util.Date org.apache.logging.log4j.core.util.CronExpression)) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -54,28 +55,37 @@ (obj->duration ms-or-obj))) (defn duration-between + {:deprecated true} [t1 t2] (Duration/between t1 t2)) -(letfn [(conformer [v] - (cond - (duration? v) v +(defn diff + [t1 t2] + (Duration/between t1 t2)) - (string? v) - (try - (duration v) - (catch java.time.format.DateTimeParseException _e - ::s/invalid)) +(s/def ::duration + (s/conformer + (fn [v] + (cond + (duration? v) v - :else - ::s/invalid)) - (unformer [v] - (subs (str v) 2))] - (s/def ::duration (s/conformer conformer unformer))) + (string? v) + (try + (duration v) + (catch java.time.format.DateTimeParseException _e + ::s/invalid)) + + :else + ::s/invalid)) + (fn [v] + (subs (str v) 2)))) (extend-protocol clojure.core/Inst java.time.Duration - (inst-ms* [v] (.toMillis ^Duration v))) + (inst-ms* [v] (.toMillis ^Duration v)) + + OffsetDateTime + (inst-ms* [v] (.toEpochMilli (.toInstant ^OffsetDateTime v)))) (defmethod print-method Duration [mv ^java.io.Writer writer] diff --git a/backend/src/app/worker.clj b/backend/src/app/worker.clj index e33a114648..b6752c8964 100644 --- a/backend/src/app/worker.clj +++ b/backend/src/app/worker.clj @@ -442,7 +442,7 @@ (s/assert dt/cron? cron) (let [now (dt/now) next (dt/next-valid-instant-from cron now)] - (inst-ms (dt/duration-between now next)))) + (inst-ms (dt/diff now next)))) (defn- schedule-task [{:keys [scheduler] :as cfg} {:keys [cron] :as task}] diff --git a/backend/tests/app/tests/test_bounces_handling.clj b/backend/test/app/bounce_handling_test.clj similarity index 99% rename from backend/tests/app/tests/test_bounces_handling.clj rename to backend/test/app/bounce_handling_test.clj index 57e838af53..3d423f73f3 100644 --- a/backend/tests/app/tests/test_bounces_handling.clj +++ b/backend/test/app/bounce_handling_test.clj @@ -4,16 +4,16 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-bounces-handling +(ns app.bounce-handling-test (:require - [clojure.pprint :refer [pprint]] - [app.http.awsns :as awsns] - [app.emails :as emails] - [app.tests.helpers :as th] [app.db :as db] + [app.emails :as emails] + [app.http.awsns :as awsns] + [app.test-helpers :as th] [app.util.time :as dt] - [mockery.core :refer [with-mocks]] - [clojure.test :as t])) + [clojure.pprint :refer [pprint]] + [clojure.test :as t] + [mockery.core :refer [with-mocks]])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) diff --git a/backend/tests/app/tests/test_emails.clj b/backend/test/app/emails_test.clj similarity index 92% rename from backend/tests/app/tests/test_emails.clj rename to backend/test/app/emails_test.clj index 7cc62966ef..4e3e5bed98 100644 --- a/backend/tests/app/tests/test_emails.clj +++ b/backend/test/app/emails_test.clj @@ -4,13 +4,13 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-emails +(ns app.emails-test (:require [clojure.test :as t] [promesa.core :as p] [app.db :as db] [app.emails :as emails] - [app.tests.helpers :as th])) + [app.test-helpers :as th])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) diff --git a/backend/tests/app/tests/test_services_files.clj b/backend/test/app/services_files_test.clj similarity index 98% rename from backend/tests/app/tests/test_services_files.clj rename to backend/test/app/services_files_test.clj index 248dc86f29..b9a1c44264 100644 --- a/backend/tests/app/tests/test_services_files.clj +++ b/backend/test/app/services_files_test.clj @@ -4,13 +4,13 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-services-files +(ns app.services-files-test (:require [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] [app.storage :as sto] - [app.tests.helpers :as th] + [app.test-helpers :as th] [clojure.test :as t] [datoteka.core :as fs])) @@ -134,7 +134,7 @@ (t/deftest file-media-gc-task (letfn [(create-file-media-object [{:keys [profile-id file-id]}] (let [mfile {:filename "sample.jpg" - :tempfile (th/tempfile "app/tests/_files/sample.jpg") + :tempfile (th/tempfile "app/test_files/sample.jpg") :content-type "image/jpeg" :size 312043} params {::th/type :upload-file-media-object diff --git a/backend/tests/app/tests/test_services_fonts.clj b/backend/test/app/services_fonts_test.clj similarity index 93% rename from backend/tests/app/tests/test_services_fonts.clj rename to backend/test/app/services_fonts_test.clj index 86836b71e4..71f217b6aa 100644 --- a/backend/tests/app/tests/test_services_fonts.clj +++ b/backend/test/app/services_fonts_test.clj @@ -4,13 +4,13 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-services-fonts +(ns app.services-fonts-test (:require [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] [app.storage :as sto] - [app.tests.helpers :as th] + [app.test-helpers :as th] [clojure.java.io :as io] [clojure.test :as t] [datoteka.core :as fs])) @@ -24,7 +24,7 @@ proj-id (:default-project-id prof) font-id (uuid/custom 10 1) - ttfdata (-> (io/resource "app/tests/_files/font-1.ttf") + ttfdata (-> (io/resource "app/test_files/font-1.ttf") (fs/slurp-bytes)) params {::th/type :create-font-variant @@ -59,7 +59,7 @@ proj-id (:default-project-id prof) font-id (uuid/custom 10 1) - data (-> (io/resource "app/tests/_files/font-1.woff") + data (-> (io/resource "app/test_files/font-1.woff") (fs/slurp-bytes)) params {::th/type :create-font-variant diff --git a/backend/tests/app/tests/test_services_management.clj b/backend/test/app/services_management_test.clj similarity index 99% rename from backend/tests/app/tests/test_services_management.clj rename to backend/test/app/services_management_test.clj index f662b43810..7e75d434a5 100644 --- a/backend/tests/app/tests/test_services_management.clj +++ b/backend/test/app/services_management_test.clj @@ -4,13 +4,13 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-services-management +(ns app.services-management-test (:require [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] [app.storage :as sto] - [app.tests.helpers :as th] + [app.test-helpers :as th] [clojure.test :as t] [buddy.core.bytes :as b] [datoteka.core :as fs])) diff --git a/backend/tests/app/tests/test_services_media.clj b/backend/test/app/services_media_test.clj similarity index 96% rename from backend/tests/app/tests/test_services_media.clj rename to backend/test/app/services_media_test.clj index 112eed7795..a0e9c9780f 100644 --- a/backend/tests/app/tests/test_services_media.clj +++ b/backend/test/app/services_media_test.clj @@ -4,12 +4,12 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-services-media +(ns app.services-media-test (:require [app.common.uuid :as uuid] [app.db :as db] [app.storage :as sto] - [app.tests.helpers :as th] + [app.test-helpers :as th] [clojure.test :as t] [datoteka.core :as fs])) @@ -57,7 +57,7 @@ :project-id (:default-project-id prof) :is-shared false}) mfile {:filename "sample.jpg" - :tempfile (th/tempfile "app/tests/_files/sample.jpg") + :tempfile (th/tempfile "app/test_files/sample.jpg") :content-type "image/jpeg" :size 312043} diff --git a/backend/tests/app/tests/test_services_profile.clj b/backend/test/app/services_profile_test.clj similarity index 98% rename from backend/tests/app/tests/test_services_profile.clj rename to backend/test/app/services_profile_test.clj index c3daf5c143..3fc1ab3a61 100644 --- a/backend/tests/app/tests/test_services_profile.clj +++ b/backend/test/app/services_profile_test.clj @@ -4,16 +4,16 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-services-profile +(ns app.services-profile-test (:require - [clojure.test :as t] - [clojure.java.io :as io] - [mockery.core :refer [with-mocks]] - [cuerdas.core :as str] - [datoteka.core :as fs] [app.db :as db] [app.rpc.mutations.profile :as profile] - [app.tests.helpers :as th])) + [app.test-helpers :as th] + [clojure.java.io :as io] + [clojure.test :as t] + [cuerdas.core :as str] + [datoteka.core :as fs] + [mockery.core :refer [with-mocks]])) ;; TODO: profile deletion with teams ;; TODO: profile deletion with owner teams @@ -108,7 +108,7 @@ :profile-id (:id profile) :file {:filename "sample.jpg" :size 123123 - :tempfile "tests/app/tests/_files/sample.jpg" + :tempfile (th/tempfile "app/test_files/sample.jpg") :content-type "image/jpeg"}} out (th/mutation! data)] diff --git a/backend/tests/app/tests/test_services_projects.clj b/backend/test/app/services_projects_test.clj similarity index 97% rename from backend/tests/app/tests/test_services_projects.clj rename to backend/test/app/services_projects_test.clj index 5c791fe74e..6137bb0ff6 100644 --- a/backend/tests/app/tests/test_services_projects.clj +++ b/backend/test/app/services_projects_test.clj @@ -4,14 +4,14 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-services-projects +(ns app.services-projects-test (:require - [clojure.test :as t] - [promesa.core :as p] + [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] - [app.tests.helpers :as th] - [app.common.uuid :as uuid])) + [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/tests/app/tests/test_services_teams.clj b/backend/test/app/services_teams_test.clj similarity index 95% rename from backend/tests/app/tests/test_services_teams.clj rename to backend/test/app/services_teams_test.clj index c64f7922d2..144d652f8c 100644 --- a/backend/tests/app/tests/test_services_teams.clj +++ b/backend/test/app/services_teams_test.clj @@ -4,16 +4,16 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-services-teams +(ns app.services-teams-test (:require [app.common.uuid :as uuid] [app.db :as db] [app.http :as http] [app.storage :as sto] - [app.tests.helpers :as th] - [mockery.core :refer [with-mocks]] + [app.test-helpers :as th] [clojure.test :as t] - [datoteka.core :as fs])) + [datoteka.core :as fs] + [mockery.core :refer [with-mocks]])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) diff --git a/backend/tests/app/tests/test_services_viewer.clj b/backend/test/app/services_viewer_test.clj similarity index 97% rename from backend/tests/app/tests/test_services_viewer.clj rename to backend/test/app/services_viewer_test.clj index ce638a19eb..d3176f81ac 100644 --- a/backend/tests/app/tests/test_services_viewer.clj +++ b/backend/test/app/services_viewer_test.clj @@ -4,13 +4,13 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-services-viewer +(ns app.services-viewer-test (:require - [clojure.test :as t] - [datoteka.core :as fs] [app.common.uuid :as uuid] [app.db :as db] - [app.tests.helpers :as th])) + [app.test-helpers :as th] + [clojure.test :as t] + [datoteka.core :as fs])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) diff --git a/backend/tests/app/tests/test_storage.clj b/backend/test/app/storage_test.clj similarity index 98% rename from backend/tests/app/tests/test_storage.clj rename to backend/test/app/storage_test.clj index 7c0f49e8fb..d35dae02fe 100644 --- a/backend/tests/app/tests/test_storage.clj +++ b/backend/test/app/storage_test.clj @@ -4,12 +4,12 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-storage +(ns app.storage-test (:require [app.common.exceptions :as ex] [app.db :as db] [app.storage :as sto] - [app.tests.helpers :as th] + [app.test-helpers :as th] [app.util.time :as dt] [clojure.java.io :as io] [clojure.test :as t] @@ -106,7 +106,7 @@ :project-id (:default-project-id prof) :is-shared false}) mfile {:filename "sample.jpg" - :tempfile (th/tempfile "app/tests/_files/sample.jpg") + :tempfile (th/tempfile "app/test_files/sample.jpg") :content-type "image/jpeg" :size 312043} @@ -167,7 +167,7 @@ :project-id (:default-project-id prof) :is-shared false}) mfile {:filename "sample.jpg" - :tempfile (th/tempfile "app/tests/_files/sample.jpg") + :tempfile (th/tempfile "app/test_files/sample.jpg") :content-type "image/jpeg" :size 312043} diff --git a/backend/tests/app/tests/_files/font-1.otf b/backend/test/app/test_files/font-1.otf similarity index 100% rename from backend/tests/app/tests/_files/font-1.otf rename to backend/test/app/test_files/font-1.otf diff --git a/backend/tests/app/tests/_files/font-1.ttf b/backend/test/app/test_files/font-1.ttf similarity index 100% rename from backend/tests/app/tests/_files/font-1.ttf rename to backend/test/app/test_files/font-1.ttf diff --git a/backend/tests/app/tests/_files/font-1.woff b/backend/test/app/test_files/font-1.woff similarity index 100% rename from backend/tests/app/tests/_files/font-1.woff rename to backend/test/app/test_files/font-1.woff diff --git a/backend/tests/app/tests/_files/font-2.otf b/backend/test/app/test_files/font-2.otf similarity index 100% rename from backend/tests/app/tests/_files/font-2.otf rename to backend/test/app/test_files/font-2.otf diff --git a/backend/tests/app/tests/_files/font-2.woff b/backend/test/app/test_files/font-2.woff similarity index 100% rename from backend/tests/app/tests/_files/font-2.woff rename to backend/test/app/test_files/font-2.woff diff --git a/backend/tests/app/tests/_files/sample.jpg b/backend/test/app/test_files/sample.jpg similarity index 100% rename from backend/tests/app/tests/_files/sample.jpg rename to backend/test/app/test_files/sample.jpg diff --git a/backend/tests/app/tests/_files/sample1.svg b/backend/test/app/test_files/sample1.svg similarity index 100% rename from backend/tests/app/tests/_files/sample1.svg rename to backend/test/app/test_files/sample1.svg diff --git a/backend/tests/app/tests/_files/sample2.svg b/backend/test/app/test_files/sample2.svg similarity index 100% rename from backend/tests/app/tests/_files/sample2.svg rename to backend/test/app/test_files/sample2.svg diff --git a/backend/tests/app/tests/helpers.clj b/backend/test/app/test_helpers.clj similarity index 99% rename from backend/tests/app/tests/helpers.clj rename to backend/test/app/test_helpers.clj index bf6de1ff11..c7541a4a03 100644 --- a/backend/tests/app/tests/helpers.clj +++ b/backend/test/app/test_helpers.clj @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.helpers +(ns app.test-helpers (:require [app.common.data :as d] [app.common.pages :as cp] diff --git a/backend/tests.edn b/backend/tests.edn index 5b277459dd..6d31eb3c65 100644 --- a/backend/tests.edn +++ b/backend/tests.edn @@ -1,5 +1,5 @@ #kaocha/v1 {:tests [{:id :unit - :test-paths ["tests" "src"] - :ns-patterns ["test-.*"]}]} + :test-paths ["test" "src"] + :ns-patterns [".*-test$"]}]} diff --git a/common/deps.edn b/common/deps.edn new file mode 100644 index 0000000000..aa42cd43ec --- /dev/null +++ b/common/deps.edn @@ -0,0 +1,76 @@ +{ + ;; :mvn/repos + ;; {"central" {:url "https://repo1.maven.org/maven2/"} + ;; "clojars" {:url "https://clojars.org/repo"} + ;; "jcenter" {:url "https://jcenter.bintray.com/"}} + :deps + {org.clojure/clojure {:mvn/version "1.10.3"} + org.clojure/data.json {:mvn/version "2.3.1"} + org.clojure/core.async {:mvn/version "1.3.618"} + org.clojure/tools.cli {:mvn/version "1.0.206"} + org.clojure/clojurescript {:mvn/version "1.10.866"} + metosin/jsonista {:mvn/version "0.3.3"} + + ;; Logging + org.clojure/tools.logging {:mvn/version "1.1.0"} + org.apache.logging.log4j/log4j-api {:mvn/version "2.14.1"} + org.apache.logging.log4j/log4j-core {:mvn/version "2.14.1"} + org.apache.logging.log4j/log4j-web {:mvn/version "2.14.1"} + org.apache.logging.log4j/log4j-jul {:mvn/version "2.14.1"} + org.apache.logging.log4j/log4j-slf4j18-impl {:mvn/version "2.14.1"} + org.slf4j/slf4j-api {:mvn/version "2.0.0-alpha1"} + + selmer/selmer {:mvn/version "1.12.40"} + expound/expound {:mvn/version "0.8.9"} + com.cognitect/transit-clj {:mvn/version "1.0.324"} + java-http-clj/java-http-clj {:mvn/version "0.4.2"} + + funcool/promesa {:mvn/version "6.0.1"} + funcool/cuerdas {:mvn/version "2021.05.29-0"} + + lambdaisland/uri {:mvn/version "1.4.54" + :exclusions [org.clojure/data.json]} + + frankiesardo/linked {:mvn/version "1.3.0"} + danlentz/clj-uuid {:mvn/version "0.1.9"} + commons-io/commons-io {:mvn/version "2.8.0"} + com.sun.mail/jakarta.mail {:mvn/version "2.0.1"} + + ;; exception printing + io.aviso/pretty {:mvn/version "0.1.37"} + environ/environ {:mvn/version "1.2.0"}} + :paths ["src"] + :aliases + {:dev + {:extra-deps + {org.clojure/tools.namespace {:mvn/version "RELEASE"} + org.clojure/test.check {:mvn/version "RELEASE"} + org.clojure/tools.deps.alpha {:mvn/version "RELEASE"} + thheller/shadow-cljs {:mvn/version "2.14.1"} + criterium/criterium {:mvn/version "RELEASE"} + mockery/mockery {:mvn/version "RELEASE"}} + :extra-paths ["test" "dev"]} + + :repl + {:extra-deps + {com.bhauman/rebel-readline {:mvn/version "RELEASE"}} + :main-opts ["-m" "rebel-readline.main"]} + + :kaocha + {:extra-deps {lambdaisland/kaocha {:mvn/version "RELEASE"}} + :main-opts ["-m" "kaocha.runner"]} + + :test + {:extra-paths ["test"] + :extra-deps {io.github.cognitect-labs/test-runner + {:git/url "https://github.com/cognitect-labs/test-runner.git" + :sha "705ad25bbf0228b1c38d0244a36001c2987d7337"}} + :exec-fn cognitect.test-runner.api/test} + + :shadow-cljs + {:main-opts ["-m" "shadow.cljs.devtools.cli"]} + + :outdated + {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"}} + :main-opts ["-m" "antq.core"]}}} + diff --git a/common/dev/user.clj b/common/dev/user.clj new file mode 100644 index 0000000000..10336b4f31 --- /dev/null +++ b/common/dev/user.clj @@ -0,0 +1,51 @@ +;; 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 user + (:require + [clojure.java.io :as io] + [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.tools.namespace.repl :as repl] + [clojure.walk :refer [macroexpand-all]] + [criterium.core :refer [quick-bench bench with-progress-reporting]])) + +;; --- Benchmarking Tools + +(defmacro run-quick-bench + [& exprs] + `(with-progress-reporting (quick-bench (do ~@exprs) :verbose))) + +(defmacro run-quick-bench' + [& exprs] + `(quick-bench (do ~@exprs))) + +(defmacro run-bench + [& exprs] + `(with-progress-reporting (bench (do ~@exprs) :verbose))) + +(defmacro run-bench' + [& exprs] + `(bench (do ~@exprs))) + +;; --- Development Stuff + +(defn- run-tests + ([] (run-tests #"^app.common.tests.*")) + ([o] + (repl/refresh) + (cond + (instance? java.util.regex.Pattern o) + (test/run-all-tests o) + + (symbol? o) + (if-let [sns (namespace o)] + (do (require (symbol sns)) + (test/test-vars [(resolve o)])) + (test/test-ns o))))) diff --git a/common/package.json b/common/package.json new file mode 100644 index 0000000000..4344351821 --- /dev/null +++ b/common/package.json @@ -0,0 +1,13 @@ +{ + "name": "penpot-common", + "version": "1.0.0", + "main": "index.js", + "license": "MPL-2.0", + "dependencies": { + "luxon": "^1.27.0" + }, + "devDependencies": { + "source-map-support": "^0.5.19", + "ws": "^7.4.6" + } +} diff --git a/common/shadow-cljs.edn b/common/shadow-cljs.edn new file mode 100644 index 0000000000..e6bcd7175c --- /dev/null +++ b/common/shadow-cljs.edn @@ -0,0 +1,17 @@ +{:deps {:aliases [:dev]} + ;; :http {:port 3448} + ;; :nrepl {:port 3447} + :jvm-opts ["-Xmx700m" "-Xms100m" "-XX:+UseSerialGC" "-XX:-OmitStackTraceInFastThrow"] + + :builds + {:test + {:target :node-test + :output-to "target/tests.js" + :ns-regexp "^app.common.*-test$" + ;; :autorun true + + :compiler-options + {:output-feature-set :es-next + :output-wrapper false + :warnings {:fn-deprecated false}}}}} + diff --git a/common/app/common/attrs.cljc b/common/src/app/common/attrs.cljc similarity index 100% rename from common/app/common/attrs.cljc rename to common/src/app/common/attrs.cljc diff --git a/common/app/common/data.cljc b/common/src/app/common/data.cljc similarity index 100% rename from common/app/common/data.cljc rename to common/src/app/common/data.cljc diff --git a/common/app/common/data/undo_stack.cljc b/common/src/app/common/data/undo_stack.cljc similarity index 100% rename from common/app/common/data/undo_stack.cljc rename to common/src/app/common/data/undo_stack.cljc diff --git a/common/app/common/exceptions.cljc b/common/src/app/common/exceptions.cljc similarity index 100% rename from common/app/common/exceptions.cljc rename to common/src/app/common/exceptions.cljc diff --git a/common/app/common/geom/align.cljc b/common/src/app/common/geom/align.cljc similarity index 100% rename from common/app/common/geom/align.cljc rename to common/src/app/common/geom/align.cljc diff --git a/common/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc similarity index 100% rename from common/app/common/geom/matrix.cljc rename to common/src/app/common/geom/matrix.cljc diff --git a/common/app/common/geom/point.cljc b/common/src/app/common/geom/point.cljc similarity index 100% rename from common/app/common/geom/point.cljc rename to common/src/app/common/geom/point.cljc diff --git a/common/app/common/geom/proportions.cljc b/common/src/app/common/geom/proportions.cljc similarity index 100% rename from common/app/common/geom/proportions.cljc rename to common/src/app/common/geom/proportions.cljc diff --git a/common/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc similarity index 100% rename from common/app/common/geom/shapes.cljc rename to common/src/app/common/geom/shapes.cljc diff --git a/common/app/common/geom/shapes/common.cljc b/common/src/app/common/geom/shapes/common.cljc similarity index 100% rename from common/app/common/geom/shapes/common.cljc rename to common/src/app/common/geom/shapes/common.cljc diff --git a/common/app/common/geom/shapes/intersect.cljc b/common/src/app/common/geom/shapes/intersect.cljc similarity index 100% rename from common/app/common/geom/shapes/intersect.cljc rename to common/src/app/common/geom/shapes/intersect.cljc diff --git a/common/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc similarity index 100% rename from common/app/common/geom/shapes/path.cljc rename to common/src/app/common/geom/shapes/path.cljc diff --git a/common/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc similarity index 100% rename from common/app/common/geom/shapes/rect.cljc rename to common/src/app/common/geom/shapes/rect.cljc diff --git a/common/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc similarity index 100% rename from common/app/common/geom/shapes/transforms.cljc rename to common/src/app/common/geom/shapes/transforms.cljc diff --git a/common/app/common/math.cljc b/common/src/app/common/math.cljc similarity index 100% rename from common/app/common/math.cljc rename to common/src/app/common/math.cljc diff --git a/common/app/common/media.cljc b/common/src/app/common/media.cljc similarity index 100% rename from common/app/common/media.cljc rename to common/src/app/common/media.cljc diff --git a/common/app/common/pages.cljc b/common/src/app/common/pages.cljc similarity index 100% rename from common/app/common/pages.cljc rename to common/src/app/common/pages.cljc diff --git a/common/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc similarity index 100% rename from common/app/common/pages/changes.cljc rename to common/src/app/common/pages/changes.cljc diff --git a/common/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc similarity index 100% rename from common/app/common/pages/common.cljc rename to common/src/app/common/pages/common.cljc diff --git a/common/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc similarity index 100% rename from common/app/common/pages/helpers.cljc rename to common/src/app/common/pages/helpers.cljc diff --git a/common/app/common/pages/indices.cljc b/common/src/app/common/pages/indices.cljc similarity index 100% rename from common/app/common/pages/indices.cljc rename to common/src/app/common/pages/indices.cljc diff --git a/common/app/common/pages/init.cljc b/common/src/app/common/pages/init.cljc similarity index 100% rename from common/app/common/pages/init.cljc rename to common/src/app/common/pages/init.cljc diff --git a/common/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc similarity index 100% rename from common/app/common/pages/migrations.cljc rename to common/src/app/common/pages/migrations.cljc diff --git a/common/app/common/pages/spec.cljc b/common/src/app/common/pages/spec.cljc similarity index 100% rename from common/app/common/pages/spec.cljc rename to common/src/app/common/pages/spec.cljc diff --git a/common/app/common/spec.cljc b/common/src/app/common/spec.cljc similarity index 100% rename from common/app/common/spec.cljc rename to common/src/app/common/spec.cljc diff --git a/common/app/common/text.cljc b/common/src/app/common/text.cljc similarity index 96% rename from common/app/common/text.cljc rename to common/src/app/common/text.cljc index a3d1e97fde..6c92810a7c 100644 --- a/common/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -9,7 +9,7 @@ [app.common.attrs :as attrs] [app.common.uuid :as uuid] [app.common.data :as d] - [app.util.transit :as t] + [app.common.transit :as t] [clojure.walk :as walk] [cuerdas.core :as str])) @@ -82,13 +82,11 @@ (defn encode-style-value [v] - #?(:cljs (t/encode v) - :clj (t/encode-str v))) + (t/encode-str v)) (defn decode-style-value [v] - #?(:cljs (t/decode v) - :clj (t/decode-str v))) + (t/decode-str v)) (defn encode-style [key val] @@ -235,9 +233,9 @@ (remove empty?) (mapcat vec) (distinct)) - proc #(process-attr children %1 %2)] + f #(process-attr children %1 %2)] (persistent! - (transduce xform proc (transient []) children)))) + (transduce xform (completing f) (transient []) children)))) (build-block [{:keys [key children] :as paragraph}] {:key key diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc new file mode 100644 index 0000000000..365d1913e7 --- /dev/null +++ b/common/src/app/common/transit.cljc @@ -0,0 +1,216 @@ +;; 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.transit + (:require + [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] + [cognitect.transit :as t] + [linked.core :as lk] + [linked.set :as lks] + #?(:cljs ["luxon" :as lxn])) + #?(:clj + (: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.Duration + java.time.OffsetDateTime + linked.set.LinkedSet))) + +;; --- MISC + +#?(:clj + (defn str->bytes + ([^String s] + (str->bytes s "UTF-8")) + ([^String s, ^String encoding] + (.getBytes s encoding)))) + +#?(:clj + (defn- bytes->str + ([^bytes data] + (bytes->str data "UTF-8")) + ([^bytes data, ^String encoding] + (String. data encoding)))) + +#?(:clj + (def ^:private file-write-handler + (t/write-handler + (constantly "file") + (fn [v] (str v))))) + +#?(:cljs + (def bigint-read-handler + (t/read-handler + (fn [value] + (js/parseInt value 10))))) + +#?(:cljs + (def uuid-read-handler + (t/read-handler uuid))) + +;; --- 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) %))) + +;; --- 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))))) + +;; --- 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 + #?@(:cljs ["n" bigint-read-handler + "u" uuid-read-handler]) + }) + +(def +write-handlers+ + #?(:clj + {Matrix matrix-write-handler + Point point-write-handler + Instant instant-write-handler + LinkedSet ordered-set-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} + )) + +;; --- Low-Level Api + +#?(:clj + (defn reader + ([istream] + (reader istream nil)) + ([istream {:keys [type] :or {type :json}}] + (t/reader istream type {:handlers +read-handlers+})))) + +#?(:clj + (defn writer + ([ostream] + (writer ostream nil)) + ([ostream {:keys [type] :or {type :json}}] + (t/writer ostream type {:handlers +write-handlers+})))) +#?(:clj + (defn read! + [reader] + (t/read reader))) + +#?(:clj + (defn write! + [writer data] + (t/write writer data))) + + +;; --- High-Level Api + +#?(:clj + (defn encode + ([data] (encode data nil)) + ([data opts] + (with-open [out (ByteArrayOutputStream.)] + (t/write (writer out opts) data) + (.toByteArray out))))) + +#?(:clj + (defn decode + ([data] (decode data nil)) + ([data opts] + (with-open [input (ByteArrayInputStream. ^bytes data)] + (t/read (reader input opts)))))) + +(defn encode-str + ([data] (encode-str data nil)) + ([data opts] + #?(:cljs + (let [t (:type opts :json) + w (t/writer t {:handlers +write-handlers+})] + (t/write w data)) + :clj + (->> (encode data opts) + (bytes->str))))) + +(defn decode-str + ([data] (decode-str data nil)) + ([data opts] + #?(:cljs + (let [t (:type opts :json) + r (t/reader t {:handlers +read-handlers+})] + (t/read r data)) + :clj + (-> (str->bytes data) + (decode opts))))) + +(defn transit? + "Checks if a string can be decoded with transit" + [v] + (try + (-> v decode-str nil? not) + (catch #?(:cljs js/SyntaxError :clj Exception) e + false))) diff --git a/common/app/common/uri.cljc b/common/src/app/common/uri.cljc similarity index 100% rename from common/app/common/uri.cljc rename to common/src/app/common/uri.cljc diff --git a/common/app/common/uuid.cljc b/common/src/app/common/uuid.cljc similarity index 87% rename from common/app/common/uuid.cljc rename to common/src/app/common/uuid.cljc index 0cc0106dfd..f7530aae37 100644 --- a/common/app/common/uuid.cljc +++ b/common/src/app/common/uuid.cljc @@ -42,7 +42,6 @@ #?(:clj (UUID/fromString s) :cljs (c/uuid s))) -#?(:clj - (defn custom - ([a] (UUID. 0 a)) - ([b a] (UUID. b a)))) +(defn custom + ([a] #?(:clj (UUID. 0 a) :cljs (c/uuid (impl/custom 0 a)))) + ([b a] #?(:clj (UUID. b a) :cljs (c/uuid (impl/custom b a))))) diff --git a/common/app/common/uuid_impl.js b/common/src/app/common/uuid_impl.js similarity index 94% rename from common/app/common/uuid_impl.js rename to common/src/app/common/uuid_impl.js index 791dd58eab..2c2a9f45bd 100644 --- a/common/app/common/uuid_impl.js +++ b/common/src/app/common/uuid_impl.js @@ -189,4 +189,10 @@ goog.scope(function() { self.v1 = v1; self.v4 = v4; + + self.custom = function formatAsUUID(mostSigBits, leastSigBits) { + const most = mostSigBits.toString("16").padStart(16, "0"); + const least = leastSigBits.toString("16").padStart(16, "0"); + return `${most.substring(0, 8)}-${most.substring(8, 12)}-${most.substring(12)}-${least.substring(0, 4)}-${least.substring(4)}`; + } }); diff --git a/common/app/common/version.cljc b/common/src/app/common/version.cljc similarity index 97% rename from common/app/common/version.cljc rename to common/src/app/common/version.cljc index aafad2d384..cb79885cfc 100644 --- a/common/app/common/version.cljc +++ b/common/src/app/common/version.cljc @@ -7,7 +7,6 @@ (ns app.common.version "A version parsing helper." (:require - [app.common.data :as d] [cuerdas.core :as str])) (def version-re #"^(([A-Za-z]+)\-?)?((\d+)\.(\d+)\.(\d+))(\-?((alpha|prealpha|beta|rc|dev)(\d+)?))?(\-?(\d+))?(\-?g(\w+))$") diff --git a/backend/tests/app/tests/test_common_geom_shapes.clj b/common/test/app/common/geom_shapes_test.cljc similarity index 99% rename from backend/tests/app/tests/test_common_geom_shapes.clj rename to common/test/app/common/geom_shapes_test.cljc index 8019da9940..2b6a6fc57c 100644 --- a/backend/tests/app/tests/test_common_geom_shapes.clj +++ b/common/test/app/common/geom_shapes_test.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-common-geom-shapes +(ns app.common.geom-shapes-test (:require [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] diff --git a/backend/tests/app/tests/test_common_geom.clj b/common/test/app/common/geom_test.cljc similarity index 98% rename from backend/tests/app/tests/test_common_geom.clj rename to common/test/app/common/geom_test.cljc index 4656f716c3..fce01a0613 100644 --- a/backend/tests/app/tests/test_common_geom.clj +++ b/common/test/app/common/geom_test.cljc @@ -4,7 +4,7 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-common-geom +(ns app.common.geom-test (:require [clojure.test :as t] [app.common.geom.point :as gpt] diff --git a/backend/tests/app/tests/test_common_pages_migrations.clj b/common/test/app/common/pages_migrations_test.cljc similarity index 92% rename from backend/tests/app/tests/test_common_pages_migrations.clj rename to common/test/app/common/pages_migrations_test.cljc index 4e8adebd68..ab697886eb 100644 --- a/backend/tests/app/tests/test_common_pages_migrations.clj +++ b/common/test/app/common/pages_migrations_test.cljc @@ -4,17 +4,14 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-common-pages-migrations +(ns app.common.pages-migrations-test (:require [clojure.test :as t] [clojure.pprint :refer [pprint]] - [promesa.core :as p] - [mockery.core :refer [with-mock]] [app.common.data :as d] [app.common.pages :as cp] [app.common.pages.migrations :as cpm] - [app.common.uuid :as uuid] - [app.tests.helpers :as th])) + [app.common.uuid :as uuid])) (t/deftest test-migration-8-1 (let [page-id (uuid/custom 0 0) @@ -43,8 +40,8 @@ res (cpm/migrate-data data)] - (pprint data) - (pprint res) + ;; (pprint data) + ;; (pprint res) (t/is (= (dissoc data :version) (dissoc res :version))))) @@ -86,8 +83,8 @@ res (cpm/migrate-data data)] - (pprint res) - (pprint expct) + ;; (pprint res) + ;; (pprint expct) (t/is (= (dissoc expct :version) (dissoc res :version))) diff --git a/backend/tests/app/tests/test_common_pages.clj b/common/test/app/common/pages_test.cljc similarity index 99% rename from backend/tests/app/tests/test_common_pages.clj rename to common/test/app/common/pages_test.cljc index 80e3ea7e2f..abfa5f8f31 100644 --- a/backend/tests/app/tests/test_common_pages.clj +++ b/common/test/app/common/pages_test.cljc @@ -4,15 +4,12 @@ ;; ;; Copyright (c) UXBOX Labs SL -(ns app.tests.test-common-pages +(ns app.common.pages-test (:require [clojure.test :as t] [clojure.pprint :refer [pprint]] - [promesa.core :as p] - [mockery.core :refer [with-mock]] [app.common.pages :as cp] - [app.common.uuid :as uuid] - [app.tests.helpers :as th])) + [app.common.uuid :as uuid])) (t/deftest process-change-set-option (let [file-id (uuid/custom 2 2) diff --git a/common/test/app/common/setup_test.cljc b/common/test/app/common/setup_test.cljc new file mode 100644 index 0000000000..5b11a5af28 --- /dev/null +++ b/common/test/app/common/setup_test.cljc @@ -0,0 +1,10 @@ +(ns app.common.setup-test + (:require + [clojure.test :as t])) + +#?(:cljs + (defmethod t/report [:cljs.test/default :end-run-tests] + [m] + (if (t/successful? m) + (set! (.-exitCode js/process) 0) + (set! (.-exitCode js/process) 1)))) diff --git a/frontend/tests/app/test_draft_conversion.cljs b/common/test/app/common/text_test.cljc similarity index 89% rename from frontend/tests/app/test_draft_conversion.cljs rename to common/test/app/common/text_test.cljc index 72c143e6a0..9cd52a2531 100644 --- a/frontend/tests/app/test_draft_conversion.cljs +++ b/common/test/app/common/text_test.cljc @@ -1,9 +1,9 @@ -(ns app.test-draft-conversion +(ns app.common.text-test (:require [app.common.data :as d] [app.common.text :as txt] - [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]])) + [clojure.test :as t :include-macros true] + [clojure.pprint :refer [pprint]])) (t/deftest test-basic-conversion-roundtrip (let [text "qwqw 🠒" diff --git a/common/yarn.lock b/common/yarn.lock new file mode 100644 index 0000000000..425c4fc04e --- /dev/null +++ b/common/yarn.lock @@ -0,0 +1,31 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +buffer-from@^1.0.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== + +luxon@^1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.27.0.tgz#ae10c69113d85dab8f15f5e8390d0cbeddf4f00f" + integrity sha512-VKsFsPggTA0DvnxtJdiExAucKdAnwbCCNlMM5ENvHlxubqWd0xhZcdb4XgZ7QFNhaRhilXCFxHuoObP5BNA4PA== + +source-map-support@^0.5.19: + version "0.5.19" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" + integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +ws@^7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== diff --git a/docker/devenv/files/start-tmux.sh b/docker/devenv/files/start-tmux.sh index 1770934fdf..e21875389e 100755 --- a/docker/devenv/files/start-tmux.sh +++ b/docker/devenv/files/start-tmux.sh @@ -21,7 +21,7 @@ tmux -2 new-session -d -s penpot tmux new-window -t penpot:1 -n 'shadow watch' tmux select-window -t penpot:1 tmux send-keys -t penpot 'cd penpot/frontend' enter C-l -tmux send-keys -t penpot 'npx shadow-cljs watch main' enter +tmux send-keys -t penpot 'clojure -M:dev:shadow-cljs watch main' enter tmux new-window -t penpot:2 -n 'exporter' tmux select-window -t penpot:2 diff --git a/frontend/deps.edn b/frontend/deps.edn index 4a3337c6a8..8d24465069 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -1,9 +1,32 @@ -{:paths ["src" "vendor" "resources" "../common"] - :deps {} +{:paths ["src" "vendor" "resources" "test"] + :deps + {penpot/common + {:local/root "../common"} + + binaryage/devtools {:mvn/version "RELEASE"} + metosin/reitit-core {:mvn/version "0.5.13"} + + funcool/beicon {:mvn/version "2021.04.29-0"} + funcool/okulary {:mvn/version "2020.04.14-0"} + funcool/potok {:mvn/version "4.0.0"} + funcool/rumext {:mvn/version "2021.05.12-1"} + funcool/tubax {:mvn/version "2021.05.20-0"} + + instaparse/instaparse {:mvn/version "1.4.10"} + } + :aliases {:outdated {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"} org.slf4j/slf4j-nop {:mvn/version "RELEASE"}} :main-opts ["-m" "antq.core"]} + + :dev + {:extra-deps + {thheller/shadow-cljs {:mvn/version "2.14.1"}}} + + :shadow-cljs + {:main-opts ["-m" "shadow.cljs.devtools.cli"]} + }} diff --git a/frontend/package.json b/frontend/package.json index 8e710283dd..b16eb66cd6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,8 +32,7 @@ "postcss": "^8.2.15", "postcss-clean": "^1.2.2", "rimraf": "^3.0.0", - "sass": "^1.32.8", - "shadow-cljs": "2.12.6" + "sass": "^1.32.8" }, "dependencies": { "date-fns": "^2.21.3", @@ -48,7 +47,7 @@ "react": "~17.0.1", "react-dom": "~17.0.1", "react-virtualized": "^9.22.3", - "rxjs": "~7.0.1", + "rxjs": "~7.1.0", "sax": "^1.2.4", "source-map-support": "^0.5.16", "tdigest": "^0.1.1", diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index d4b2b0a2e6..82c7116a65 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -1,32 +1,9 @@ -{:http {:port 3448} +{:deps {:aliases [:dev]} + :http {:port 3448} :nrepl {:port 3447} :jvm-opts ["-Xmx700m" "-Xms100m" "-XX:+UseSerialGC" "-XX:-OmitStackTraceInFastThrow"] :dev-http {8888 "classpath:public"} - :source-paths ["src", "vendor", "resources", "../common", "tests", "dev"] - :dependencies - [[binaryage/devtools "RELEASE"] - - [environ/environ "1.2.0"] - [metosin/reitit-core "0.5.13"] - [expound/expound "0.8.9"] - - [danlentz/clj-uuid "0.1.9"] - [frankiesardo/linked "1.3.0"] - - [funcool/beicon "2021.04.29-0"] - [funcool/cuerdas "2021.05.09-0"] - [funcool/okulary "2020.04.14-0"] - [funcool/potok "4.0.0"] - [funcool/promesa "6.0.0"] - [funcool/rumext "2021.05.12-1"] - [funcool/tubax "2021.05.20-0"] - - [lambdaisland/uri "1.4.54" - :exclusions [org.clojure/data.json]] - - [instaparse/instaparse "1.4.10"]] - :builds {:main {:target :browser @@ -58,11 +35,11 @@ :anon-fn-naming-policy :off :source-map-detail-level :all}}} - :tests + :test {:target :node-test :output-to "target/tests.js" - :ns-regexp "^app.test-" - :autorun true + :ns-regexp "^app.*-test$" + ;; :autorun true :compiler-options {:output-feature-set :es8 diff --git a/frontend/src/app/util/perf.cljs b/frontend/src/app/util/perf.cljs index 03935f3016..e23401201b 100644 --- a/frontend/src/app/util/perf.cljs +++ b/frontend/src/app/util/perf.cljs @@ -39,7 +39,7 @@ this))) (defn tdigest-summary - [td] + [^js td] (str "samples=" (unchecked-get td "n") "\n" "Q50=" (.percentile td 0.50) "\n" "Q75=" (.percentile td 0.75) "\n" diff --git a/frontend/test.cljs b/frontend/test.cljs deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frontend/tests/app/test_components_basic.cljs b/frontend/test/app/components_basic_test.cljs similarity index 98% rename from frontend/tests/app/test_components_basic.cljs rename to frontend/test/app/components_basic_test.cljs index 2c6eba8886..702b36cadd 100644 --- a/frontend/tests/app/test_components_basic.cljs +++ b/frontend/test/app/components_basic_test.cljs @@ -1,4 +1,4 @@ -(ns app.test-components-basic +(ns app.components-basic-test (:require [app.common.data :as d] [app.common.geom.point :as gpt] @@ -29,7 +29,7 @@ (->> state (the/do-update (dw/select-shape (thp/id :shape1))) - (the/do-watch-update dwl/add-component) + (the/do-watch-update dwl/add-component) (rx/do (fn [new-state] (let [shape1 (thp/get-shape new-state :shape1) @@ -73,7 +73,7 @@ (the/do-update (dw/select-shapes (lks/set (thp/id :shape1) (thp/id :shape2)))) - (the/do-watch-update dwl/add-component) + (the/do-watch-update dwl/add-component) (rx/do (fn [new-state] (let [shape1 (thp/get-shape new-state :shape1) @@ -125,7 +125,7 @@ (->> state (the/do-update (dw/select-shape (thp/id :group1))) - (the/do-watch-update dwl/add-component) + (the/do-watch-update dwl/add-component) (rx/do (fn [new-state] (let [[[group shape1 shape2] diff --git a/frontend/tests/app/test_components_sync.cljs b/frontend/test/app/components_sync_test.cljs similarity index 97% rename from frontend/tests/app/test_components_sync.cljs rename to frontend/test/app/components_sync_test.cljs index e93ab55ed2..048cea7a1e 100644 --- a/frontend/tests/app/test_components_sync.cljs +++ b/frontend/test/app/components_sync_test.cljs @@ -1,18 +1,18 @@ -(ns app.test-components-sync +(ns app.components-sync-test (:require - [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]] - [beicon.core :as rx] - [linked.core :as lks] - [app.test-helpers.events :as the] - [app.test-helpers.pages :as thp] - [app.test-helpers.libraries :as thl] - [app.common.geom.point :as gpt] [app.common.data :as d] + [app.common.geom.point :as gpt] [app.common.pages.helpers :as cph] [app.main.data.workspace.changes :as dwc] [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.libraries-helpers :as dwlh])) + [app.main.data.workspace.libraries-helpers :as dwlh] + [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] + [linked.core :as lks])) (t/use-fixtures :each {:before thp/reset-idmap!}) diff --git a/frontend/test/app/init_test.cljs b/frontend/test/app/init_test.cljs new file mode 100644 index 0000000000..0e36cdcd04 --- /dev/null +++ b/frontend/test/app/init_test.cljs @@ -0,0 +1,9 @@ +(ns app.init-test + (:require + [cljs.test :as t :include-macros true])) + +(defmethod t/report [:cljs.test/default :end-run-tests] + [m] + (if (t/successful? m) + (set! (.-exitCode js/process) 0) + (set! (.-exitCode js/process) 1))) diff --git a/frontend/tests/app/test_shapes.cljs b/frontend/test/app/shapes_test.cljs similarity index 72% rename from frontend/tests/app/test_shapes.cljs rename to frontend/test/app/shapes_test.cljs index 2b473c02a7..45bf8afb75 100644 --- a/frontend/tests/app/test_shapes.cljs +++ b/frontend/test/app/shapes_test.cljs @@ -1,16 +1,17 @@ -(ns app.test-shapes - (:require [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]] - [clojure.stacktrace :as stk] - [beicon.core :as rx] - [linked.core :as lks] - [app.test-helpers.events :as the] - [app.test-helpers.pages :as thp] - [app.test-helpers.libraries :as thl] - [app.common.geom.point :as gpt] - [app.common.data :as d] - [app.common.pages.helpers :as cph] - [app.main.data.workspace.libraries :as dwl])) +(ns app.shapes-test + (:require + [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])) (t/use-fixtures :each {:before thp/reset-idmap!}) diff --git a/frontend/tests/app/test_helpers/events.cljs b/frontend/test/app/test_helpers/events.cljs similarity index 66% rename from frontend/tests/app/test_helpers/events.cljs rename to frontend/test/app/test_helpers/events.cljs index 106510ab3f..85c6488ad0 100644 --- a/frontend/tests/app/test_helpers/events.cljs +++ b/frontend/test/app/test_helpers/events.cljs @@ -1,14 +1,15 @@ (ns app.test-helpers.events - (:require [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]] - [beicon.core :as rx] - [potok.core :as ptk] - [app.common.uuid :as uuid] - [app.common.geom.point :as gpt] - [app.common.geom.shapes :as gsh] - [app.common.pages :as cp] - [app.common.pages.helpers :as cph] - [app.main.data.workspace :as dw])) + (:require + [cljs.test :as t :include-macros true] + [cljs.pprint :refer [pprint]] + [beicon.core :as rx] + [potok.core :as ptk] + [app.common.uuid :as uuid] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.pages :as cp] + [app.common.pages.helpers :as cph] + [app.main.data.workspace :as dw])) ;; ---- Helpers to manage global events diff --git a/frontend/tests/app/test_helpers/libraries.cljs b/frontend/test/app/test_helpers/libraries.cljs similarity index 87% rename from frontend/tests/app/test_helpers/libraries.cljs rename to frontend/test/app/test_helpers/libraries.cljs index aa62894459..b93f81f2a5 100644 --- a/frontend/tests/app/test_helpers/libraries.cljs +++ b/frontend/test/app/test_helpers/libraries.cljs @@ -1,16 +1,17 @@ (ns app.test-helpers.libraries - (:require [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]] - [beicon.core :as rx] - [potok.core :as ptk] - [app.common.uuid :as uuid] - [app.common.geom.point :as gpt] - [app.common.geom.shapes :as gsh] - [app.common.pages :as cp] - [app.common.pages.helpers :as cph] - [app.main.data.workspace :as dw] - [app.main.data.workspace.libraries-helpers :as dwlh] - [app.test-helpers.pages :as thp])) + (:require + [cljs.test :as t :include-macros true] + [cljs.pprint :refer [pprint]] + [beicon.core :as rx] + [potok.core :as ptk] + [app.common.uuid :as uuid] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.pages :as cp] + [app.common.pages.helpers :as cph] + [app.main.data.workspace :as dw] + [app.main.data.workspace.libraries-helpers :as dwlh] + [app.test-helpers.pages :as thp])) ;; ---- Helpers to manage libraries and synchronization diff --git a/frontend/tests/app/test_helpers/pages.cljs b/frontend/test/app/test_helpers/pages.cljs similarity index 100% rename from frontend/tests/app/test_helpers/pages.cljs rename to frontend/test/app/test_helpers/pages.cljs diff --git a/frontend/tests/app/test_util_range_tree.cljs b/frontend/test/app/util/range_tree_test.cljs similarity index 96% rename from frontend/tests/app/test_util_range_tree.cljs rename to frontend/test/app/util/range_tree_test.cljs index 507a8386b7..d97a46c79b 100644 --- a/frontend/tests/app/test_util_range_tree.cljs +++ b/frontend/test/app/util/range_tree_test.cljs @@ -1,8 +1,9 @@ -(ns app.test-util-range-tree - (:require [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]] - [app.common.geom.point :as gpt] - [app.util.range-tree :as rt])) +(ns app.util.range-tree-test + (:require + [cljs.test :as t :include-macros true] + [cljs.pprint :refer [pprint]] + [app.common.geom.point :as gpt] + [app.util.range-tree :as rt])) (defn check-max-height [tree num-nodes]) (defn check-sorted [tree]) @@ -109,7 +110,7 @@ (rt/insert 50 :e) (rt/update 50 :d :xx))] (t/is (= (rt/get tree 50) [:b :xx :e])))) - + (t/testing "Try to update non-existing element" (let [tree (-> (rt/make-tree) (rt/insert 100 :a) diff --git a/frontend/tests/app/test_util_simple_math.cljs b/frontend/test/app/util/simple_math_test.cljs similarity index 92% rename from frontend/tests/app/test_util_simple_math.cljs rename to frontend/test/app/util/simple_math_test.cljs index bc6d48c74e..4b220a9e17 100644 --- a/frontend/tests/app/test_util_simple_math.cljs +++ b/frontend/test/app/util/simple_math_test.cljs @@ -1,8 +1,9 @@ -(ns app.test-util-simple-math - (:require [cljs.test :as t :include-macros true] - [cljs.pprint :refer [pprint]] - [app.common.math :as cm] - [app.util.simple-math :as sm])) +(ns app.util.simple-math-test + (:require + [cljs.test :as t :include-macros true] + [cljs.pprint :refer [pprint]] + [app.common.math :as cm] + [app.util.simple-math :as sm])) (t/deftest test-parser-inst (t/testing "Evaluate an empty string" diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 592ff54cad..34b578a2ff 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -252,16 +252,6 @@ asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= -asn1.js@^5.2.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" - integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== - dependencies: - bn.js "^4.0.0" - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - safer-buffer "^2.1.0" - asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -274,14 +264,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assert@^1.1.1: - version "1.5.0" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" - integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== - dependencies: - object-assign "^4.1.1" - util "0.10.3" - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -307,11 +289,6 @@ async-foreach@^0.1.3: resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - async-settle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" @@ -342,14 +319,14 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@^10.2.4: - version "10.2.5" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.5.tgz#096a0337dbc96c0873526d7fef5de4428d05382d" - integrity sha512-7H4AJZXvSsn62SqZyJCP+1AWwOuoYpUfK6ot9vm0e87XD6mT8lDywc9D9OTJPMULyGcvmIxzTAMeG2Cc+YX+fA== + version "10.2.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.6.tgz#aadd9ec34e1c98d403e01950038049f0eb252949" + integrity sha512-8lChSmdU6dCNMCQopIf4Pe5kipkAGj/fvTMslCsih0uHpOrXOPUEVOmYMMqmw3cekQkSD7EhIeuYl5y0BLdKqg== dependencies: - browserslist "^4.16.3" - caniuse-lite "^1.0.30001196" + browserslist "^4.16.6" + caniuse-lite "^1.0.30001230" colorette "^1.2.2" - fraction.js "^4.0.13" + fraction.js "^4.1.1" normalize-range "^0.1.2" postcss-value-parser "^4.1.0" @@ -383,11 +360,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base64-js@^1.0.2: - version "1.5.1" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" - integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== - base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -437,16 +409,6 @@ block-stream@*: dependencies: inherits "~2.0.0" -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: - version "4.12.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" - integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== - -bn.js@^5.0.0, bn.js@^5.1.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" - integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== - boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -483,78 +445,12 @@ braces@~3.0.2: dependencies: fill-range "^7.0.1" -brorand@^1.0.1, brorand@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" - integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= - browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== -browserify-aes@^1.0.0, browserify-aes@^1.0.4: - version "1.2.0" - resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" - integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== - dependencies: - buffer-xor "^1.0.3" - cipher-base "^1.0.0" - create-hash "^1.1.0" - evp_bytestokey "^1.0.3" - inherits "^2.0.1" - safe-buffer "^5.0.1" - -browserify-cipher@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" - integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== - dependencies: - browserify-aes "^1.0.4" - browserify-des "^1.0.0" - evp_bytestokey "^1.0.0" - -browserify-des@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" - integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== - dependencies: - cipher-base "^1.0.1" - des.js "^1.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - -browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" - integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== - dependencies: - bn.js "^5.0.0" - randombytes "^2.0.1" - -browserify-sign@^4.0.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" - integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== - dependencies: - bn.js "^5.1.1" - browserify-rsa "^4.0.1" - create-hash "^1.2.0" - create-hmac "^1.1.7" - elliptic "^6.5.3" - inherits "^2.0.4" - parse-asn1 "^5.1.5" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -browserify-zlib@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" - integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== - dependencies: - pako "~1.0.5" - -browserslist@^4.16.3: +browserslist@^4.16.6: version "4.16.6" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" integrity sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ== @@ -580,25 +476,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== -buffer-xor@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= - -buffer@^4.3.0: - version "4.9.2" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" - integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - isarray "^1.0.0" - -builtin-status-codes@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" - integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= - bytes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -669,10 +546,10 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001196, caniuse-lite@^1.0.30001219: - version "1.0.30001221" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001221.tgz#b916721ddf59066cfbe96c5c9a77cf7ae5c52e65" - integrity sha512-b9TOZfND3uGSLjMOrLh8XxSQ41x8mX+9MLJYDM4AAHLfaZHttrLNPrScWjVnBITRZbY5sPpCt7X85n7VSLZ+/g== +caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001230: + version "1.0.30001230" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71" + integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ== caseless@~0.12.0: version "0.12.0" @@ -733,14 +610,6 @@ chokidar@^2.0.0: optionalDependencies: fsevents "^1.2.7" -cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" - integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -953,21 +822,11 @@ config-chain@^1.1.12: ini "^1.3.4" proto-list "~1.2.1" -console-browserify@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" - integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== - console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -constants-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" - integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= - content-type@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -994,14 +853,14 @@ copy-props@^2.0.1: is-plain-object "^5.0.0" core-js-pure@^3.0.0: - version "3.11.2" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.11.2.tgz#10e3b35788c00f431bc0d601d7551475ec3e792c" - integrity sha512-DQxdEKm+zFsnON7ZGOgUAQXBt1UJJ01tOzN/HgQ7cNf0oEHW1tcBLfCQQd1q6otdLu5gAdvKYxKHAoXGwE/kiQ== + version "3.13.0" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.13.0.tgz#9d267fb47d1d7046cfbc05e7b67bb235b6735355" + integrity sha512-7VTvXbsMxROvzPAVczLgfizR8CyYnvWPrb1eGrtlZAJfjQWEHLofVfCKljLHdpazTfpaziRORwUH/kfGDKvpdA== core-js@^3.6.4: - version "3.11.2" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.11.2.tgz#af087a43373fc6e72942917c4a4c3de43ed574d6" - integrity sha512-3tfrrO1JpJSYGKnd9LKTBPqgUES/UYiCzMKeqwR1+jF16q4kD1BY2NvqkfuzXwQ6+CIWm55V9cjD7PQd+hijdw== + version "3.13.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.13.0.tgz#58ca436bf01d6903aee3d364089868d0d89fe58d" + integrity sha512-iWDbiyha1M5vFwPFmQnvRv+tJzGbFAm6XimJUT0NgHYW3xZEs1SkCAcasWSVFxpI2Xb/V1DDJckq3v90+bQnog== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" @@ -1018,37 +877,6 @@ cosmiconfig@^5.0.0: js-yaml "^3.13.1" parse-json "^4.0.0" -create-ecdh@^4.0.0: - version "4.0.4" - resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" - integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== - dependencies: - bn.js "^4.1.0" - elliptic "^6.5.3" - -create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" - integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== - dependencies: - cipher-base "^1.0.1" - inherits "^2.0.1" - md5.js "^1.3.4" - ripemd160 "^2.0.1" - sha.js "^2.4.0" - -create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: - version "1.1.7" - resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" - integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== - dependencies: - cipher-base "^1.0.3" - create-hash "^1.1.0" - inherits "^2.0.1" - ripemd160 "^2.0.0" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - cross-fetch@^3.0.4: version "3.1.4" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" @@ -1075,23 +903,6 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -crypto-browserify@^3.11.0: - version "3.12.0" - resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" - integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== - dependencies: - browserify-cipher "^1.0.0" - browserify-sign "^4.0.0" - create-ecdh "^4.0.0" - create-hash "^1.1.0" - create-hmac "^1.1.0" - diffie-hellman "^5.0.0" - inherits "^2.0.1" - pbkdf2 "^3.0.3" - public-encrypt "^4.0.0" - randombytes "^2.0.0" - randomfill "^1.0.3" - css-select-base-adapter@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" @@ -1187,9 +998,9 @@ dashdash@^1.12.0: assert-plus "^1.0.0" date-fns@^2.21.3: - version "2.21.3" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.21.3.tgz#8f5f6889d7a96bbcc1f0ea50239b397a83357f9b" - integrity sha512-HeYdzCaFflc1i4tGbj7JKMjM4cKGYoyxwcIIkHzNgCkX8xXDNJDZXgDDVchIWpN4eQc3lH37WarduXFZJOtxfw== + version "2.22.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.0.tgz#e25d79dda0639ae9e840d02ab34446613037c392" + integrity sha512-1TMrlJRPYjeR6KS9TgnJz4DX1rHW5NkfgIHpe9NWL6TGTzd6qo8mLo6ibt3p1wvXAu/DOal1Yce5YloFGeexBA== dateformat@^3.0.3: version "3.0.3" @@ -1287,14 +1098,6 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -des.js@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" - integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== - dependencies: - inherits "^2.0.1" - minimalistic-assert "^1.0.0" - detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" @@ -1310,15 +1113,6 @@ diff@3.5.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== -diffie-hellman@^5.0.0: - version "5.0.3" - resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" - integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== - dependencies: - bn.js "^4.1.0" - miller-rabin "^4.0.0" - randombytes "^2.0.0" - dom-helpers@^5.1.3: version "5.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" @@ -1335,11 +1129,6 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" -domain-browser@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== - domelementtype@1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" @@ -1404,22 +1193,9 @@ editorconfig@^0.15.3: sigmund "^1.0.1" electron-to-chromium@^1.3.723: - version "1.3.726" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.726.tgz#6d3c577e5f5a48904ba891464740896c05e3bdb1" - integrity sha512-dw7WmrSu/JwtACiBzth8cuKf62NKL1xVJuNvyOg0jvruN/n4NLtGYoTzciQquCPNaS2eR+BT5GrxHbslfc/w1w== - -elliptic@^6.5.3: - version "6.5.4" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" - integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== - dependencies: - bn.js "^4.11.9" - brorand "^1.1.0" - hash.js "^1.0.0" - hmac-drbg "^1.0.1" - inherits "^2.0.4" - minimalistic-assert "^1.0.1" - minimalistic-crypto-utils "^1.0.1" + version "1.3.741" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.741.tgz#dc1024b19b31e27fb2c8c0a1f120cb05fc6ca695" + integrity sha512-4i3T0cwnHo1O4Mnp9JniEco8bZiXoqbm3PhW5hv7uu8YLg35iajYrRnNyKFaN8/8SSTskU2hYqVTeYVPceSpUA== emoji-regex@^7.0.1: version "7.0.3" @@ -1457,10 +1233,10 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.17.2, es-abstract@^1.18.0-next.2: - version "1.18.0" - resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.0.tgz#ab80b359eecb7ede4c298000390bc5ac3ec7b5a4" - integrity sha512-LJzK7MrQa8TS0ja2w3YNLzUgJCGPdPOV1yVvezjNnS89D+VR08+Szt2mz3YB2Dck/+w5tfIq/RoUAFqJJGM2yw== +es-abstract@^1.17.2, es-abstract@^1.18.0-next.2, es-abstract@^1.18.2: + version "1.18.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.18.3.tgz#25c4c3380a27aa203c44b2b685bba94da31b63e0" + integrity sha512-nQIr12dxV7SSxE6r6f1l3DtAeEYdsGpps13dR0TwJg1S8gyp4ZPgy3FZcHBgbiQqnoqSTb+oC+kO4UQ0C/J8vw== dependencies: call-bind "^1.0.2" es-to-primitive "^1.2.1" @@ -1470,14 +1246,14 @@ es-abstract@^1.17.2, es-abstract@^1.18.0-next.2: has-symbols "^1.0.2" is-callable "^1.2.3" is-negative-zero "^2.0.1" - is-regex "^1.1.2" - is-string "^1.0.5" - object-inspect "^1.9.0" + is-regex "^1.1.3" + is-string "^1.0.6" + object-inspect "^1.10.3" object-keys "^1.1.1" object.assign "^4.1.2" string.prototype.trimend "^1.0.4" string.prototype.trimstart "^1.0.4" - unbox-primitive "^1.0.0" + unbox-primitive "^1.0.1" es-to-primitive@^1.2.1: version "1.2.1" @@ -1557,19 +1333,6 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" -events@^3.0.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" - integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== - -evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" - integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== - dependencies: - md5.js "^1.3.4" - safe-buffer "^5.1.1" - execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -1837,10 +1600,10 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -fraction.js@^4.0.13: - version "4.0.13" - resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.0.13.tgz#3c1c315fa16b35c85fffa95725a36fa729c69dfe" - integrity sha512-E1fz2Xs9ltlUp+qbiyx9wmt2n9dRzPsS11Jtdb8D2o+cC7wr9xkkKsVKJuBX0ST+LVS+LhLO+SbLJNtfWcJvXA== +fraction.js@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.1.1.tgz#ac4e520473dae67012d618aab91eda09bcb400ff" + integrity sha512-MHOhvvxHTfRFpF1geTK9czMIZ6xclsEor2wkIGYYq+PxcQqT7vStJqjhe6S1TenZrMZzo+wlqOufBDVepUEgPg== fragment-cache@^0.2.1: version "0.2.1" @@ -2030,9 +1793,9 @@ glob@7.1.2: path-is-absolute "^1.0.0" glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -2293,23 +2056,6 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" -hash-base@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" - integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== - dependencies: - inherits "^2.0.4" - readable-stream "^3.6.0" - safe-buffer "^5.2.0" - -hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.7" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" - integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.1" - hasha@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/hasha/-/hasha-2.2.0.tgz#78d7cbfc1e6d66303fe79837365984517b2f6ee1" @@ -2328,15 +2074,6 @@ highlight.js@^10.6.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg== -hmac-drbg@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" - integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= - dependencies: - hash.js "^1.0.3" - minimalistic-assert "^1.0.0" - minimalistic-crypto-utils "^1.0.1" - homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" @@ -2358,23 +2095,13 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" -https-browserify@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= - iconv-lite@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" - integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + version "0.6.3" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" + integrity sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw== dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -ieee754@^1.1.4: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" - integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== - immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -2427,21 +2154,11 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== -inherits@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" - integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= - -inherits@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= - ini@^1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" @@ -2495,9 +2212,9 @@ is-arrayish@^0.3.1: integrity sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ== is-bigint@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.1.tgz#6923051dfcbc764278540b9ce0e6b3213aa5ebc2" - integrity sha512-J0ELF4yHFxHy0cmSxZuheDOz2luOdVvqjwmEcj8H/L1JHeuEDSDbeRP+Dk9kFVk5RTFzbucJ2Kb9F7ixY2QaCg== + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.2.tgz#ffb381442503235ad245ea89e45b3dbff040ee5a" + integrity sha512-0JV5+SOCQkIdzjBK9buARcV804Ddu7A0Qet6sHi3FimE9ne6m4BGQZfRn+NZiXbBk4F4XmHfDZIipLj9pX8dSA== is-binary-path@^1.0.0: version "1.0.1" @@ -2514,11 +2231,11 @@ is-binary-path@~2.1.0: binary-extensions "^2.0.0" is-boolean-object@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.0.tgz#e2aaad3a3a8fca34c28f6eee135b156ed2587ff0" - integrity sha512-a7Uprx8UtD+HWdyYwnD1+ExtTgqQtD2k/1yJgtXP6wnMm8byhkoTZRl+95LLThpzNZJ5aEvi46cdH+ayMFRwmA== + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.1.tgz#3c0878f035cb821228d350d2e1e36719716a3de8" + integrity sha512-bXdQWkECBUIAcCkeH1unwJLIpZYaa5VvuygSyS/c2lf719mTKZDU5UdDRlpd01UjADgmW8RfqaP+mRaVPdr/Ng== dependencies: - call-bind "^1.0.0" + call-bind "^1.0.2" is-buffer@^1.1.5: version "1.1.6" @@ -2531,9 +2248,9 @@ is-callable@^1.1.4, is-callable@^1.2.3: integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== is-core-module@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.3.0.tgz#d341652e3408bca69c4671b79a0954a3d349f887" - integrity sha512-xSphU2KG9867tsYdLD4RWQ1VqdFl4HTO9Thf3I/3dLEfr0dbPTWKsuCKrgqMljg4nPE+Gq0VCnzT3gr0CyBmsw== + version "2.4.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" + integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== dependencies: has "^1.0.3" @@ -2552,9 +2269,9 @@ is-data-descriptor@^1.0.0: kind-of "^6.0.0" is-date-object@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" - integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.4.tgz#550cfcc03afada05eea3dd30981c7b09551f73e5" + integrity sha512-/b4ZVsG7Z5XVtIxs/h9W8nvfLgSAyKYdtGWQLbqy6jA1icmgjf8WCoTKgeS4wy5tYaPePouzFMANbnj94c2Z+A== is-descriptor@^0.1.0: version "0.1.6" @@ -2638,9 +2355,9 @@ is-negative-zero@^2.0.1: integrity sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w== is-number-object@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" - integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== + version "1.0.5" + resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.5.tgz#6edfaeed7950cff19afedce9fbfca9ee6dd289eb" + integrity sha512-RU0lI/n95pMoUKu9v1BZP5MBcZuNSVJkMkAG2dJqC4z2GlkGUNeH68SuHuBKBD/XFe+LHZ+f9BKkLET60Niedw== is-number@^3.0.0: version "3.0.0" @@ -2676,13 +2393,13 @@ is-promise@^2.2.2: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== -is-regex@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.2.tgz#81c8ebde4db142f2cf1c53fc86d6a45788266251" - integrity sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg== +is-regex@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.3.tgz#d029f9aff6448b93ebbe3f33dac71511fdcbef9f" + integrity sha512-qSVXFz28HM7y+IWX6vLCsexdlvzT1PJNFSBuaQLQ5o0IEw8UDYW6/2+eCMVyIsbM8CNLX2a/QWmSpyxYEHY7CQ== dependencies: call-bind "^1.0.2" - has-symbols "^1.0.1" + has-symbols "^1.0.2" is-relative@^1.0.0: version "1.0.0" @@ -2701,17 +2418,17 @@ is-stream@^2.0.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.0.tgz#bde9c32680d6fae04129d6ac9d921ce7815f78e3" integrity sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw== -is-string@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" - integrity sha512-buY6VNRjhQMiF1qWDouloZlQbRhDPCebwxSjxMjxgemYT46YMd2NR0/H+fBhEfWX4A/w9TBJ+ol+okqJKFE6vQ== +is-string@^1.0.5, is-string@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.6.tgz#3fe5d5992fb0d93404f32584d4b0179a71b54a5f" + integrity sha512-2gdzbKUuqtQ3lYNrUTQYoClPhm7oQu4UdpSZMp1/DGgkHBT8E2Z1l0yMdb6D4zNAxwDiMv8MdulKROJGNl0Q0w== is-symbol@^1.0.2, is-symbol@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" - integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c" + integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg== dependencies: - has-symbols "^1.0.1" + has-symbols "^1.0.2" is-typedarray@~1.0.0: version "1.0.0" @@ -2740,7 +2457,7 @@ is-windows@^1.0.1, is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -3127,9 +2844,9 @@ lru-queue@^0.1.0: es5-ext "~0.10.2" luxon@^1.26.0: - version "1.26.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.26.0.tgz#d3692361fda51473948252061d0f8561df02b578" - integrity sha512-+V5QIQ5f6CDXQpWNICELwjwuHdqeJM1UenlZWx5ujcRMc9venvluCjFb4t5NYLhb6IhkbMVOxzVuOqkgMxee2A== + version "1.27.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.27.0.tgz#ae10c69113d85dab8f15f5e8390d0cbeddf4f00f" + integrity sha512-VKsFsPggTA0DvnxtJdiExAucKdAnwbCCNlMM5ENvHlxubqWd0xhZcdb4XgZ7QFNhaRhilXCFxHuoObP5BNA4PA== make-iterator@^1.0.0: version "1.0.1" @@ -3168,9 +2885,9 @@ map-visit@^1.0.0: object-visit "^1.0.0" marked@^2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.3.tgz#3551c4958c4da36897bda2a16812ef1399c8d6b0" - integrity sha512-5otztIIcJfPc2qGTN8cVtOJEjNJZ0jwa46INMagrYfk0EvqtRuEHLsEe0LrFS0/q+ZRKT0+kXK7P2T1AN5lWRA== + version "2.0.6" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.6.tgz#c3ab1403b9b70f26acd92590fc272eb1718e778c" + integrity sha512-S2mYj0FzTQa0dLddssqwRVW4EOJOVJ355Xm2Vcbm+LU7GQRGWvwbO5K87OaPSOux2AwTSgtPPaXmc8sDPrhn2A== matchdep@^2.0.0: version "2.0.0" @@ -3182,15 +2899,6 @@ matchdep@^2.0.0: resolve "^1.4.0" stack-trace "0.0.10" -md5.js@^1.3.4: - version "1.3.5" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" - integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - safe-buffer "^5.1.2" - mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -3259,14 +2967,6 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" -miller-rabin@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" - integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== - dependencies: - bn.js "^4.0.0" - brorand "^1.0.1" - mime-db@1.47.0: version "1.47.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" @@ -3284,16 +2984,6 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" - integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== - -minimalistic-crypto-utils@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" - integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= - minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -3450,39 +3140,10 @@ node-gyp@^3.8.0: tar "^2.0.0" which "1" -node-libs-browser@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" - integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== - dependencies: - assert "^1.1.1" - browserify-zlib "^0.2.0" - buffer "^4.3.0" - console-browserify "^1.1.0" - constants-browserify "^1.0.0" - crypto-browserify "^3.11.0" - domain-browser "^1.1.1" - events "^3.0.0" - https-browserify "^1.0.0" - os-browserify "^0.3.0" - path-browserify "0.0.1" - process "^0.11.10" - punycode "^1.2.4" - querystring-es3 "^0.2.0" - readable-stream "^2.3.3" - stream-browserify "^2.0.1" - stream-http "^2.7.2" - string_decoder "^1.0.0" - timers-browserify "^2.0.4" - tty-browserify "0.0.0" - url "^0.11.0" - util "^0.11.0" - vm-browserify "^1.0.1" - node-releases@^1.1.71: - version "1.1.71" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.71.tgz#cb1334b179896b1c89ecfdd4b725fb7bbdfc7dbb" - integrity sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg== + version "1.1.72" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" + integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== node-sass@^4.8.3: version "4.14.1" @@ -3603,10 +3264,10 @@ object-copy@^0.1.0: define-property "^0.2.5" kind-of "^3.0.3" -object-inspect@^1.9.0: - version "1.10.2" - resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.2.tgz#b6385a3e2b7cae0b5eafcf90cddf85d128767f30" - integrity sha512-gz58rdPpadwztRrPjZE9DZLOABUpTGdcANUgOwBFO1C+HZZhePoP83M65WGDmbpwFYJSWqavbl4SgDn4k8RYTA== +object-inspect@^1.10.3: + version "1.10.3" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.10.3.tgz#c2aa7d2d09f50c99375704f7a0adf24c5782d369" + integrity sha512-e5mCJlSH7poANfC8z8S9s9S2IN5/4Zb3aZ33f5s8YqoazCFzNLloLU8r5VCG+G7WoqLvAAZoVMcy3tp/3X0Plw== object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" @@ -3673,14 +3334,13 @@ object.reduce@^1.0.0: make-iterator "^1.0.0" object.values@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" - integrity sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw== + version "1.1.4" + resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.4.tgz#0d273762833e816b693a637d30073e7051535b30" + integrity sha512-TnGo7j4XSnKQoK3MfvkzqKCi0nVe/D9I9IjwTNYdb/fxYHpjrluHVOgw0AF6jrRFGMPHdfuidR09tIDiIvnaSg== dependencies: call-bind "^1.0.2" define-properties "^1.1.3" - es-abstract "^1.18.0-next.2" - has "^1.0.3" + es-abstract "^1.18.2" once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: version "1.4.0" @@ -3711,11 +3371,6 @@ ordered-read-streams@^1.0.0: dependencies: readable-stream "^2.0.1" -os-browserify@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" - integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= - os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -3784,22 +3439,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@~1.0.2, pako@~1.0.5: +pako@~1.0.2: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== -parse-asn1@^5.0.0, parse-asn1@^5.1.5: - version "5.1.6" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" - integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== - dependencies: - asn1.js "^5.2.0" - browserify-aes "^1.0.0" - evp_bytestokey "^1.0.0" - pbkdf2 "^3.0.3" - safe-buffer "^5.1.1" - parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -3839,11 +3483,6 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= -path-browserify@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" - integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== - path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" @@ -3872,9 +3511,9 @@ path-key@^2.0.0, path-key@^2.0.1: integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-root-regex@^0.1.0: version "0.1.2" @@ -3897,17 +3536,6 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -pbkdf2@^3.0.3: - version "3.1.2" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" - integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== - dependencies: - create-hash "^1.1.2" - create-hmac "^1.1.4" - ripemd160 "^2.0.1" - safe-buffer "^5.0.1" - sha.js "^2.4.8" - pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -3934,9 +3562,9 @@ phantomjs-prebuilt@^2.1.16: which "^1.2.10" picomatch@^2.0.4, picomatch@^2.2.1: - version "2.2.3" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.3.tgz#465547f359ccc206d3c48e46a1bcb89bf7ee619d" - integrity sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg== + version "2.3.0" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" + integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw== pify@^2.0.0: version "2.3.0" @@ -4010,13 +3638,13 @@ postcss@^7.0.16: supports-color "^6.1.0" postcss@^8.2.15: - version "8.2.15" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.15.tgz#9e66ccf07292817d226fc315cbbf9bc148fbca65" - integrity sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q== + version "8.3.0" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f" + integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ== dependencies: colorette "^1.2.2" nanoid "^3.1.23" - source-map "^0.6.1" + source-map-js "^0.6.2" pretty-hrtime@^1.0.0: version "1.0.3" @@ -4033,11 +3661,6 @@ process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -process@^0.11.10: - version "0.11.10" - resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= - progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -4074,18 +3697,6 @@ psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== -public-encrypt@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" - integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== - dependencies: - bn.js "^4.1.0" - browserify-rsa "^4.0.0" - create-hash "^1.1.0" - parse-asn1 "^5.0.0" - randombytes "^2.0.1" - safe-buffer "^5.1.2" - pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -4111,16 +3722,6 @@ pumpify@^1.3.5: inherits "^2.0.3" pump "^2.0.0" -punycode@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= - -punycode@^1.2.4: - version "1.4.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" - integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= - punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -4136,36 +3737,11 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -querystring-es3@^0.2.0: - version "0.2.1" - resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" - integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= - -querystring@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" - integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= - -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.1.0" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" - integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== - dependencies: - safe-buffer "^5.1.0" - randomcolor@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/randomcolor/-/randomcolor-0.6.2.tgz#7a57362ae1a1278439aeed2c15e5deb8ea33f56d" integrity sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A== -randomfill@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" - integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== - dependencies: - randombytes "^2.0.5" - safe-buffer "^5.1.0" - react-dom@~17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -4260,11 +3836,6 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" -readline-sync@^1.4.7: - version "1.4.10" - resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" - integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== - rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -4446,22 +4017,14 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" -ripemd160@^2.0.0, ripemd160@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" - integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== - dependencies: - hash-base "^3.0.0" - inherits "^2.0.1" - -rxjs@~7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.0.1.tgz#5f41c4f991cea550471fc5d215727390103702c7" - integrity sha512-wViQ4Vgps1xJwqWIBooMNN44usCSthL7wCUl4qWqrVjhGfWyVyXcxlYzfDKkJKACQvZMTOft/jJ3RkbwK1j9QQ== +rxjs@~7.1.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.1.0.tgz#94202d27b19305ef7b1a4f330277b2065df7039e" + integrity sha512-gCFO5iHIbRPwznl6hAYuwNFld8W4S2shtSJIqG27ReWXo9IWrCyEICxUA+6vJHwSR/OakoenC4QsDxq50tzYmw== dependencies: tslib "~2.1.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4494,9 +4057,9 @@ sass-graph@2.2.5: yargs "^13.3.2" sass@^1.32.8: - version "1.32.12" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.32.12.tgz#a2a47ad0f1c168222db5206444a30c12457abb9f" - integrity sha512-zmXn03k3hN0KaiVTjohgkg98C3UowhL1/VSGdj4/VAAiMKGQOE80PFPxFP2Kyq0OUskPKcY5lImkhBKEHlypJA== + version "1.34.0" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.34.0.tgz#e46d5932d8b0ecc4feb846d861f26a578f7f7172" + integrity sha512-rHEN0BscqjUYuomUEaqq3BMgsXqQfkcMVR7UhscsAVub0/spUrZGBMxQXFS2kfiDsPLZw5yuU9iJEFNC2x38Qw== dependencies: chokidar ">=3.0.0 <4.0.0" @@ -4558,36 +4121,11 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.4, setimmediate@^1.0.5: +setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= -sha.js@^2.4.0, sha.js@^2.4.8: - version "2.4.11" - resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" - integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== - dependencies: - inherits "^2.0.1" - safe-buffer "^5.0.1" - -shadow-cljs-jar@1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" - integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== - -shadow-cljs@2.12.6: - version "2.12.6" - resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.12.6.tgz#039539fdc35a19c2f2cd15792ae17e7928f97428" - integrity sha512-dNw989EFQki/59kD8Cd8b6HIpBTqPj9ksWIvSg6hI1bgezZHT0oHfJH5UIbXPD+dnVLvbOnDnfOMWYH6ozalcA== - dependencies: - node-libs-browser "^2.2.1" - readline-sync "^1.4.7" - shadow-cljs-jar "1.3.2" - source-map-support "^0.4.15" - which "^1.3.1" - ws "^3.0.0" - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -4691,6 +4229,11 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" +source-map-js@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-0.6.2.tgz#0bb5de631b41cfbda6cfba8bd05a80efdfd2385e" + integrity sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug== + source-map-resolve@^0.5.0: version "0.5.3" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" @@ -4710,13 +4253,6 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" -source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" - integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== - dependencies: - source-map "^0.5.6" - source-map-support@^0.5.16: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -4774,9 +4310,9 @@ spdx-expression-parse@^3.0.0: spdx-license-ids "^3.0.0" spdx-license-ids@^3.0.0: - version "3.0.7" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz#e9c18a410e5ed7e12442a549fbd8afa767038d65" - integrity sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ== + version "3.0.9" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.9.tgz#8a595135def9592bda69709474f1cbeea7c2467f" + integrity sha512-Ki212dKK4ogX+xDo4CtOZBVIwhsKBEfsEEcwmJfLQzirgc2jIWdzg40Unxz/HzEUqM1WFzVlQSMF9kZZ2HboLQ== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -4830,30 +4366,11 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" -stream-browserify@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" - integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== - dependencies: - inherits "~2.0.1" - readable-stream "^2.0.2" - stream-exhaust@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== -stream-http@^2.7.2: - version "2.8.3" - resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" - integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== - dependencies: - builtin-status-codes "^3.0.0" - inherits "^2.0.1" - readable-stream "^2.3.6" - to-arraybuffer "^1.0.0" - xtend "^4.0.0" - stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" @@ -4913,7 +4430,7 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string_decoder@^1.0.0, string_decoder@^1.1.1: +string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -5107,13 +4624,6 @@ time-stamp@^1.0.0: resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= -timers-browserify@^2.0.4: - version "2.0.12" - resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" - integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== - dependencies: - setimmediate "^1.0.4" - timers-ext@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" @@ -5135,11 +4645,6 @@ to-absolute-glob@^2.0.0: is-absolute "^1.0.0" is-negated-glob "^1.0.0" -to-arraybuffer@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" - integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= - to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -5209,11 +4714,6 @@ tslib@~2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== -tty-browserify@0.0.0: - version "0.0.0" - resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" - integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -5246,12 +4746,7 @@ ua-parser-js@^0.7.18, ua-parser-js@^0.7.28: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== - -unbox-primitive@^1.0.0: +unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" integrity sha512-tZU/3NqK3dA5gpE1KtyiJUrEB0lxnGkMFHptJ7q6ewdZ8s12QrODwNbhIJStmJkd1QDXa1NRA8aF2A1zk/Ypyw== @@ -5335,14 +4830,6 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= -url@^0.11.0: - version "0.11.0" - resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" - integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= - dependencies: - punycode "1.3.2" - querystring "0.2.0" - use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -5363,20 +4850,6 @@ util.promisify@~1.0.0: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.0" -util@0.10.3: - version "0.10.3" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" - integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= - dependencies: - inherits "2.0.1" - -util@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" - integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== - dependencies: - inherits "2.0.3" - uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -5466,11 +4939,6 @@ vinyl@^2.0.0, vinyl@^2.2.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vm-browserify@^1.0.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" - integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -5492,7 +4960,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@1, which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.1: +which@1, which@^1.2.10, which@^1.2.14, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -5551,15 +5019,6 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^3.0.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== - dependencies: - async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" - xmldom@0.1.27: version "0.1.27" resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" @@ -5577,7 +5036,7 @@ xregexp@^5.0.1: dependencies: "@babel/runtime-corejs3" "^7.12.1" -xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: +xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From 2ec769981ac0ab86f7c3fdc1e1a2d6cd711f3768 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 31 May 2021 09:06:14 +0200 Subject: [PATCH 017/204] :sparkles: Resolve almost all linter issues on common module. --- .clj-kondo/config.edn | 5 +++ .clj-kondo/hooks/export.clj | 11 ++++++ common/src/app/common/data.cljc | 9 ++--- common/src/app/common/data/undo_stack.cljc | 4 +-- common/src/app/common/geom/align.cljc | 4 +-- common/src/app/common/geom/matrix.cljc | 6 ++-- common/src/app/common/geom/shapes.cljc | 3 +- .../src/app/common/geom/shapes/intersect.cljc | 36 +++++++++---------- common/src/app/common/geom/shapes/path.cljc | 10 +++--- .../app/common/geom/shapes/transforms.cljc | 31 ++++++---------- common/src/app/common/pages/changes.cljc | 11 +++--- common/src/app/common/pages/helpers.cljc | 19 +++++----- common/src/app/common/pages/indices.cljc | 4 +-- common/src/app/common/pages/init.cljc | 4 +-- common/src/app/common/pages/migrations.cljc | 10 +++--- common/src/app/common/spec.cljc | 4 +-- common/src/app/common/text.cljc | 2 -- common/src/app/common/transit.cljc | 2 +- 18 files changed, 86 insertions(+), 89 deletions(-) create mode 100644 .clj-kondo/hooks/export.clj diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index becc057527..7f0a4e788c 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -1,7 +1,12 @@ {:lint-as {potok.core/reify clojure.core/reify promesa.core/let clojure.core/let rumext.alpha/defc clojure.core/defn + app.common.data/export clojure.core/def app.db/with-atomic clojure.core/with-open} + + :hooks + {:analyze-call {app.common.data/export hooks.export/export}} + :output {:exclude-files ["data_readers.clj"]} diff --git a/.clj-kondo/hooks/export.clj b/.clj-kondo/hooks/export.clj new file mode 100644 index 0000000000..bac6996ca6 --- /dev/null +++ b/.clj-kondo/hooks/export.clj @@ -0,0 +1,11 @@ +(ns hooks.export + (:require [clj-kondo.hooks-api :as api])) + +(defn export + [{:keys [:node]}] + (let [[_ sname] (:children node) + result (api/list-node + [(api/token-node (symbol "def")) + (api/token-node (symbol (name (:value sname)))) + sname])] + {:node result})) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 649e290977..580184cdfc 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -10,14 +10,15 @@ #?(:cljs (:require-macros [app.common.data])) (:require - [linked.set :as lks] [app.common.math :as mth] [clojure.set :as set] #?(:clj [cljs.analyzer.api :as aapi]) #?(:cljs [cljs.reader :as r] :clj [clojure.edn :as r]) #?(:cljs [cljs.core :as core] - :clj [clojure.core :as core])) + :clj [clojure.core :as core]) + [linked.set :as lks]) + #?(:clj (:import linked.set.LinkedSet))) @@ -482,8 +483,8 @@ " [m1 m2] - (let [m1ks (keys m1) - m2ks (keys m2) + (let [m1ks (set (keys m1)) + m2ks (set (keys m2)) keys (set/union m1ks m2ks) diff-attr diff --git a/common/src/app/common/data/undo_stack.cljc b/common/src/app/common/data/undo_stack.cljc index 57f71d1286..09694aa01d 100644 --- a/common/src/app/common/data/undo_stack.cljc +++ b/common/src/app/common/data/undo_stack.cljc @@ -46,7 +46,7 @@ (assoc-in stack [:items index] value)) (defn undo - [{index :index items :items :as stack}] + [stack] (update stack :index dec)) (defn redo @@ -56,5 +56,5 @@ (update :index inc))) (defn size - [{index :index items :items :as stack}] + [{index :index :as stack}] (inc index)) diff --git a/common/src/app/common/geom/align.cljc b/common/src/app/common/geom/align.cljc index 4a14e4b1e4..45d8b43210 100644 --- a/common/src/app/common/geom/align.cljc +++ b/common/src/app/common/geom/align.cljc @@ -6,9 +6,9 @@ (ns app.common.geom.align (:require - [clojure.spec.alpha :as s] + [app.common.data :as d] [app.common.geom.shapes :as gsh] - [app.common.data :as d])) + [clojure.spec.alpha :as s])) ;; --- Alignment diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index 04aa8651d8..1c0a834825 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -8,10 +8,8 @@ (:require #?(:cljs [cljs.pprint :as pp] :clj [clojure.pprint :as pp]) - [cuerdas.core :as str] - [app.common.data :as d] - [app.common.math :as mth] - [app.common.geom.point :as gpt])) + [app.common.geom.point :as gpt] + [app.common.math :as mth])) ;; --- Matrix Impl diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index db0d51e296..96e4891576 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -7,14 +7,13 @@ (ns app.common.geom.shapes (:require [app.common.data :as d] - [app.common.math :as mth] [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 gin] [app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.rect :as gpr] [app.common.geom.shapes.transforms :as gtr] - [app.common.geom.shapes.intersect :as gin] [app.common.spec :as us])) diff --git a/common/src/app/common/geom/shapes/intersect.cljc b/common/src/app/common/geom/shapes/intersect.cljc index 2b55cb3392..e4dc2cc562 100644 --- a/common/src/app/common/geom/shapes/intersect.cljc +++ b/common/src/app/common/geom/shapes/intersect.cljc @@ -6,9 +6,8 @@ (ns app.common.geom.shapes.intersect (:require - [app.common.data :as d] - [app.common.geom.point :as gpt] [app.common.geom.matrix :as gmt] + [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as gpp] [app.common.geom.shapes.rect :as gpr] [app.common.math :as mth])) @@ -113,11 +112,10 @@ ;; Even-odd algorithm ;; Cast a ray from the point in any direction and count the intersections ;; if it's odd the point is inside the polygon - (let [] - (->> lines - (filter #(intersect-ray? p %)) - (count) - (odd?)))) + (->> lines + (filter #(intersect-ray? p %)) + (count) + (odd?))) (defn- next-windup "Calculates the next windup number for the nonzero algorithm" @@ -173,7 +171,7 @@ (defn overlaps-path? "Checks if the given rect overlaps with the path in any point" [shape rect] - + (let [;; If paths are too complex the intersection is too expensive ;; we fallback to check its bounding box otherwise the performance penalty ;; is too big @@ -186,7 +184,7 @@ (points->lines (:points shape)) (gpp/path->lines shape)) start-point (-> shape :content (first) :params (gpt/point))] - + (or (is-point-inside-nonzero? (first rect-points) path-lines) (is-point-inside-nonzero? start-point rect-lines) (intersects-lines? rect-lines path-lines)))) @@ -197,14 +195,14 @@ (let [center (gpt/point cx cy) transform (gmt/transform-in center transform) - {px :x py :y} (gpt/transform point transform)] - ;; Ellipse inequality formula - ;; https://en.wikipedia.org/wiki/Ellipse#Shifted_ellipse - (let [v (+ (/ (mth/sq (- px cx)) - (mth/sq rx)) - (/ (mth/sq (- py cy)) - (mth/sq ry)))] - (<= v 1)))) + {px :x py :y} (gpt/transform point transform) + ;; Ellipse inequality formula + ;; https://en.wikipedia.org/wiki/Ellipse#Shifted_ellipse + v (+ (/ (mth/sq (- px cx)) + (mth/sq rx)) + (/ (mth/sq (- py cy)) + (mth/sq ry)))] + (<= v 1))) (defn intersects-line-ellipse? "Checks wether a single line intersects with the given ellipse" @@ -272,13 +270,13 @@ center (gpt/point (+ x (/ width 2)) (+ y (/ height 2))) - + ellipse-data {:cx (:x center) :cy (:y center) :rx (/ width 2) :ry (/ height 2) :transform (:transform-inverse shape)}] - + (or (is-point-inside-evenodd? center rect-lines) (is-point-inside-ellipse? (first rect-points) ellipse-data) (intersects-lines-ellipse? rect-lines ellipse-data)))) diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc index 0339cd652e..ae2d1d6f3a 100644 --- a/common/src/app/common/geom/shapes/path.cljc +++ b/common/src/app/common/geom/shapes/path.cljc @@ -6,10 +6,10 @@ (ns app.common.geom.shapes.path (:require + [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.rect :as gpr] - [app.common.math :as mth] - [app.common.data :as d])) + [app.common.math :as mth])) (defn content->points [content] (->> content @@ -79,7 +79,7 @@ ;; When the term a is close to zero we have a linear equation [(/ (- c) b)] - ;; If a is not close to zero return the two roots for a cuadratic + ;; If a is not close to zero return the two roots for a cuadratic (not (mth/almost-zero? a)) [(/ (+ (- b) sqrt-b2-4ac) (* 2 a)) @@ -267,7 +267,7 @@ (and (< (d ht) (d t1)) (< (d ht) (d t2))) [ht1 ht2] - + (< (d t1) (d t2)) [t1 ht] @@ -324,7 +324,7 @@ (if (and (some? acc) (or (not cur) (<= min-dist cur-dist))) [min-p min-dist] [cur-p cur-dist]))] - + (->> (:content shape) (d/with-prev) (map point+distance) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 0c7b8183d8..3af771a220 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -7,13 +7,13 @@ (ns app.common.geom.shapes.transforms (:require [app.common.attrs :as attrs] + [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.path :as gpa] [app.common.geom.shapes.rect :as gpr] [app.common.math :as mth] - [app.common.data :as d] [app.common.text :as txt])) ;; --- Relative Movement @@ -58,12 +58,6 @@ dy (- (d/check-num y) (-> shape :selrect :y))] (move shape (gpt/point dx dy)))) - -(defn- modif-rotation [shape] - (let [cur-rotation (d/check-num (:rotation shape)) - delta-angle (d/check-num (get-in shape [:modifiers :rotation]))] - (mod (+ cur-rotation delta-angle) 360))) - (defn transform-matrix "Returns a transformation matrix without changing the shape properties. The result should be used in a `transform` attribute in svg" @@ -86,14 +80,13 @@ (gpt/point 0 0))] (inverse-transform-matrix shape shape-center))) ([{:keys [flip-x flip-y] :as shape} center] - (let [] - (-> (gmt/matrix) - (gmt/translate center) - (cond-> - flip-x (gmt/scale (gpt/point -1 1)) - flip-y (gmt/scale (gpt/point 1 -1))) - (gmt/multiply (:transform-inverse shape (gmt/matrix))) - (gmt/translate (gpt/negate center)))))) + (-> (gmt/matrix) + (gmt/translate center) + (cond-> + flip-x (gmt/scale (gpt/point -1 1)) + flip-y (gmt/scale (gpt/point 1 -1))) + (gmt/multiply (:transform-inverse shape (gmt/matrix))) + (gmt/translate (gpt/negate center))))) (defn transform-point-center "Transform a point around the shape center" @@ -333,8 +326,9 @@ (dissoc :modifiers)))) shape))) +;; TODO: looks like orig-shape is useless argument (defn apply-text-resize - [shape orig-shape modifiers] + [shape _orig-shape modifiers] (if (and (= (:type shape) :text) (:resize-scale-text modifiers)) (let [merge-attrs (fn [attrs] @@ -376,7 +370,7 @@ :y (- (:y new-selrect 0) (:y selrect 0)) :width (- (:width new-selrect 1) (:width selrect 1)) :height (- (:height new-selrect 1) (:height selrect 1))}] - + (cond-> group (and (some? svg-viewbox) (some? selrect) (some? new-selrect)) (update :svg-viewbox @@ -388,9 +382,6 @@ (defn update-group-selrect [group children] (let [shape-center (gco/center-shape group) - transform (:transform group (gmt/matrix)) - transform-inverse (:transform-inverse group (gmt/matrix)) - ;; Points for every shape inside the group points (->> children (mapcat :points)) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 5d2624574c..cc346aac93 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -9,12 +9,11 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.common.geom.shapes :as gsh] - [app.common.pages.helpers :as cph] - [app.common.pages.spec :as ps] - [app.common.spec :as us] [app.common.pages.common :refer [component-sync-attrs]] + [app.common.pages.helpers :as cph] [app.common.pages.init :as init] - [app.common.pages.spec :as spec])) + [app.common.pages.spec :as spec] + [app.common.spec :as us])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Page Transformation Changes @@ -33,7 +32,7 @@ (when verify? (us/assert ::spec/changes items)) - (let [pages (into #{} (map :page-id) items) + (let [pages (into #{} (map :page-id) items) result (->> items (reduce #(or (process-change %1 %2) %1) data))] @@ -42,7 +41,7 @@ (doseq [page-id pages] (let [page (get-in result [:pages-index page-id])] (doseq [[id shape] (:objects page)] - (if-not (= shape (get-in data [:pages-index page-id :objects id])) + (when-not (= shape (get-in data [:pages-index page-id :objects id])) ;; If object has change verify is correct (us/verify ::spec/shape shape)))))) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 9eb194c271..ae9b83da48 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -10,7 +10,6 @@ [app.common.geom.shapes :as gsh] [app.common.spec :as us] [app.common.uuid :as uuid] - [clojure.set :as set] [cuerdas.core :as str])) (defn walk-pages @@ -119,14 +118,14 @@ (conj! pending current) (first children) (rest children)) - [result pending]))] + [result pending])) - ;; If we have still pending, advance the iterator - (let [length (count pending)] - (if (pos? length) - (let [next (get pending (dec length))] - (recur result (pop! pending) next)) - (persistent! result)))))) + ;; If we have still pending, advance the iterator + length (count pending)] + (if (pos? length) + (let [next (get pending (dec length))] + (recur result (pop! pending) next)) + (persistent! result))))) (defn get-children-objects "Retrieve all children objects recursively for a given object" @@ -403,14 +402,14 @@ [objects shape-id] (let [shape (get objects shape-id) parent (get objects (:parent-id shape)) - [parent-idx _] (d/seek (fn [[idx child-id]] (= child-id shape-id)) + [parent-idx _] (d/seek (fn [[_idx child-id]] (= child-id shape-id)) (d/enumerate (:shapes parent)))] parent-idx)) (defn split-path - [path] "Decompose a string in the form 'one / two / three' into an array of strings, normalizing spaces." + [path] (->> (str/split path "/") (map str/trim) (remove str/empty?))) diff --git a/common/src/app/common/pages/indices.cljc b/common/src/app/common/pages/indices.cljc index fa39937992..c1681fef19 100644 --- a/common/src/app/common/pages/indices.cljc +++ b/common/src/app/common/pages/indices.cljc @@ -7,7 +7,6 @@ (ns app.common.pages.indices (:require [app.common.data :as d] - [app.common.geom.shapes :as gsh] [app.common.pages.helpers :as helpers] [app.common.uuid :as uuid] [clojure.set :as set])) @@ -100,7 +99,8 @@ "Retrieves the mask information for an object" [objects parents-index] (let [retrieve-masks - (fn [id parents] + (fn [_ parents] + ;; TODO: use transducers? (->> parents (map #(get objects %)) (filter #(:masked-group? %)) diff --git a/common/src/app/common/pages/init.cljc b/common/src/app/common/pages/init.cljc index 5ca2b85f12..08d222f34c 100644 --- a/common/src/app/common/pages/init.cljc +++ b/common/src/app/common/pages/init.cljc @@ -7,9 +7,9 @@ (ns app.common.pages.init (:require [app.common.data :as d] - [app.common.uuid :as uuid] [app.common.exceptions :as ex] - [app.common.pages.common :refer [file-version default-color]])) + [app.common.pages.common :refer [file-version default-color]] + [app.common.uuid :as uuid])) (def root uuid/zero) diff --git a/common/src/app/common/pages/migrations.cljc b/common/src/app/common/pages/migrations.cljc index 8e93da8043..7a12167807 100644 --- a/common/src/app/common/pages/migrations.cljc +++ b/common/src/app/common/pages/migrations.cljc @@ -6,13 +6,13 @@ (ns app.common.pages.migrations (:require - [app.common.pages :as cp] + [app.common.data :as d] + [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.path :as gsp] - [app.common.geom.matrix :as gmt] [app.common.math :as mth] - [app.common.uuid :as uuid] - [app.common.data :as d])) + [app.common.pages :as cp] + [app.common.uuid :as uuid])) ;; TODO: revisit this and rename to file-migrations @@ -94,7 +94,7 @@ (= :curve (:type object)) (assoc :type :path) - (or (#{:curve :path} (:type object))) + (#{:curve :path} (:type object)) (migrate-path) (= :frame (:type object)) diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 61c651136c..ea08717e5a 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -11,11 +11,9 @@ (:require #?(:clj [clojure.spec.alpha :as s] :cljs [cljs.spec.alpha :as s]) - - [expound.alpha :as expound] - [app.common.uuid :as uuid] [app.common.exceptions :as ex] [app.common.geom.point :as gpt] + [app.common.uuid :as uuid] [cuerdas.core :as str])) (s/check-asserts true) diff --git a/common/src/app/common/text.cljc b/common/src/app/common/text.cljc index 6c92810a7c..9f184e7e9d 100644 --- a/common/src/app/common/text.cljc +++ b/common/src/app/common/text.cljc @@ -6,8 +6,6 @@ (ns app.common.text (:require - [app.common.attrs :as attrs] - [app.common.uuid :as uuid] [app.common.data :as d] [app.common.transit :as t] [clojure.walk :as walk] diff --git a/common/src/app/common/transit.cljc b/common/src/app/common/transit.cljc index 365d1913e7..7f1eddda54 100644 --- a/common/src/app/common/transit.cljc +++ b/common/src/app/common/transit.cljc @@ -212,5 +212,5 @@ [v] (try (-> v decode-str nil? not) - (catch #?(:cljs js/SyntaxError :clj Exception) e + (catch #?(:cljs js/SyntaxError :clj Exception) _e false))) From 068c94da4e2f257c3f6bf6827ce810f1ee9bbf69 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 31 May 2021 09:20:44 +0200 Subject: [PATCH 018/204] :recycle: Replace frontend transit ns usage with common transit. --- frontend/src/app/main/data/comments.cljs | 1 - frontend/src/app/main/data/workspace.cljs | 8 +- .../main/data/workspace/notifications.cljs | 4 +- .../app/main/data/workspace/persistence.cljs | 1 - frontend/src/app/main/repo.cljs | 1 - frontend/src/app/main/ui/hooks.cljs | 1 - frontend/src/app/util/dom/dnd.cljs | 8 +- frontend/src/app/util/http.cljs | 6 +- frontend/src/app/util/i18n.cljs | 3 +- frontend/src/app/util/storage.cljs | 15 +-- frontend/src/app/util/text_editor.cljs | 1 - frontend/src/app/util/theme.cljs | 1 - frontend/src/app/util/transit.cljs | 127 ------------------ frontend/src/app/util/webapi.cljs | 1 - frontend/src/app/util/websockets.cljs | 4 +- frontend/src/app/util/worker.cljs | 10 +- frontend/src/app/worker.cljs | 24 ++-- frontend/src/app/worker/impl.cljs | 6 +- 18 files changed, 43 insertions(+), 179 deletions(-) delete mode 100644 frontend/src/app/util/transit.cljs diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index aeda98fdcc..034921fda3 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -24,7 +24,6 @@ [app.main.worker :as uw] [app.util.router :as rt] [app.util.timers :as ts] - [app.util.transit :as t] [app.util.webapi :as wapi] [beicon.core :as rx] [cljs.spec.alpha :as s] diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 39f452e49f..f7017f541a 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -16,6 +16,7 @@ [app.common.pages :as cp] [app.common.pages.helpers :as cph] [app.common.spec :as us] + [app.common.transit :as t] [app.common.uuid :as uuid] [app.config :as cfg] [app.main.data.messages :as dm] @@ -39,7 +40,6 @@ [app.util.i18n :as i18n] [app.util.logging :as log] [app.util.router :as rt] - [app.util.transit :as t] [app.util.webapi :as wapi] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -1400,7 +1400,7 @@ (rx/merge-map (partial prepare-object objects selected)) (rx/reduce collect-data initial) (rx/mapcat (partial sort-selected state)) - (rx/map t/encode) + (rx/map t/encode-str) (rx/map wapi/write-to-clipboard) (rx/catch on-copy-error) (rx/ignore))))))) @@ -1420,7 +1420,7 @@ paste-transit-str (->> clipboard-str (rx/filter t/transit?) - (rx/map t/decode) + (rx/map t/decode-str) (rx/filter #(= :copied-shapes (:type %))) (rx/map #(select-keys % [:selected :objects])) (rx/map paste-shape)) @@ -1459,7 +1459,7 @@ image-data (wapi/extract-images paste-data) text-data (wapi/extract-text paste-data) decoded-data (and (t/transit? text-data) - (t/decode text-data)) + (t/decode-str text-data)) edit-id (get-in state [:workspace-local :edition]) is-editing-text? (and edit-id (= :text (get-in objects [edit-id :type])))] diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index 95afceb6d1..49c7d13877 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -10,6 +10,7 @@ [app.common.geom.point :as gpt] [app.common.pages :as cp] [app.common.spec :as us] + [app.common.transit :as t] [app.common.uri :as u] [app.config :as cf] [app.main.data.workspace.changes :as dch] @@ -21,7 +22,6 @@ [app.util.avatars :as avatars] [app.util.i18n :as i18n :refer [tr]] [app.util.time :as dt] - [app.util.transit :as t] [app.util.websockets :as ws] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -74,7 +74,7 @@ ;; Process all incoming messages. (->> (ws/-stream wsession) (rx/filter ws/message?) - (rx/map (comp t/decode :payload)) + (rx/map (comp t/decode-str :payload)) (rx/filter #(s/valid? ::message %)) (rx/map process-message)) diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 3228e0e5ed..2f668f7e15 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -32,7 +32,6 @@ [app.util.object :as obj] [app.util.router :as rt] [app.util.time :as dt] - [app.util.transit :as t] [app.util.uri :as uu] [beicon.core :as rx] [cljs.spec.alpha :as s] diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index 36d935ffb5..bb3531d5c9 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -11,7 +11,6 @@ [app.config :as cfg] [app.util.http :as http] [app.util.time :as dt] - [app.util.transit :as t] [beicon.core :as rx] [cuerdas.core :as str])) diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index 789b5ab8f3..830906bc17 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -15,7 +15,6 @@ [app.util.logging :as log] [app.util.object :as obj] [app.util.timers :as ts] - [app.util.transit :as t] [app.util.webapi :as wapi] [beicon.core :as rx] [cljs.spec.alpha :as s] diff --git a/frontend/src/app/util/dom/dnd.cljs b/frontend/src/app/util/dom/dnd.cljs index c6cf9c6a28..3f681b99b4 100644 --- a/frontend/src/app/util/dom/dnd.cljs +++ b/frontend/src/app/util/dom/dnd.cljs @@ -7,9 +7,9 @@ (ns app.util.dom.dnd "Drag & Drop interop helpers." (:require - [cuerdas.core :as str] + [app.common.transit :as t] [app.util.data :refer (read-string)] - [app.util.transit :as t])) + [cuerdas.core :as str])) ;; This is the official documentation for the dnd API: ;; https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API @@ -59,7 +59,7 @@ (let [dt (.-dataTransfer e)] (if (or (str/starts-with? data-type "application") (str/starts-with? data-type "penpot")) - (.setData dt data-type (t/encode data)) + (.setData dt data-type (t/encode-str data)) (.setData dt data-type data)) e))) @@ -112,7 +112,7 @@ (let [dt (.-dataTransfer e)] (if (or (str/starts-with? data-type "penpot") (= data-type "application/json")) - (t/decode (.getData dt data-type)) + (t/decode-str (.getData dt data-type)) (.getData dt data-type))))) (defn get-files diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index cba4052375..b1213bd1c0 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -8,13 +8,13 @@ "A http client with rx streams interface." (:require [app.common.data :as d] + [app.common.transit :as t] [app.common.uri :as u] [app.config :as cfg] [app.util.cache :as c] [app.util.globals :as globals] [app.util.object :as obj] [app.util.time :as dt] - [app.util.transit :as t] [app.util.webapi :as wapi] [beicon.core :as rx] [cuerdas.core :as str] @@ -129,7 +129,7 @@ (defn transit-data [data] (reify IBodyData - (-get-body-data [_] (t/encode data)) + (-get-body-data [_] (t/encode-str data)) (-update-headers [_ headers] (assoc headers "content-type" "application/transit+json")))) @@ -138,7 +138,7 @@ (let [contentype (get headers "content-type")] (if (and (str/starts-with? contentype "application/transit+json") (pos? (count body))) - (assoc response :body (t/decode body)) + (assoc response :body (t/decode-str body)) response))) (defn success? diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 6bc479f804..870467abdd 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -9,9 +9,8 @@ (:require [app.config :as cfg] [app.util.globals :as globals] - [app.util.storage :refer [storage]] [app.util.object :as obj] - [app.util.transit :as t] + [app.util.storage :refer [storage]] [beicon.core :as rx] [cuerdas.core :as str] [goog.object :as gobj] diff --git a/frontend/src/app/util/storage.cljs b/frontend/src/app/util/storage.cljs index aecd1f03a4..2f2e5e9cba 100644 --- a/frontend/src/app/util/storage.cljs +++ b/frontend/src/app/util/storage.cljs @@ -6,10 +6,10 @@ (ns app.util.storage (:require - [app.util.transit :as t] - [app.util.timers :as tm] + [app.common.exceptions :as ex] + [app.common.transit :as t] [app.util.globals :as g] - [app.common.exceptions :as ex])) + [app.util.timers :as tm])) (defn- ^boolean is-worker? [] @@ -18,8 +18,7 @@ (defn- decode [v] - (ex/ignoring (t/decode v))) - + (ex/ignoring (t/decode-str v))) (defn- persist [storage prev curr] @@ -29,8 +28,8 @@ (when (not= curr* prev*) (tm/schedule-on-idle #(if (some? curr*) - (.setItem ^js storage (t/encode key) (t/encode curr*)) - (.removeItem ^js storage (t/encode key))))))) + (.setItem ^js storage (t/encode-str key) (t/encode-str curr*)) + (.removeItem ^js storage (t/encode-str key))))))) (into #{} (concat (keys curr) (keys prev))))) @@ -43,7 +42,7 @@ (let [key (.key ^js storage index) val (.getItem ^js storage key)] (try - (assoc res (t/decode key) (t/decode val)) + (assoc res (t/decode-str key) (t/decode-str val)) (catch :default e res)))) {} diff --git a/frontend/src/app/util/text_editor.cljs b/frontend/src/app/util/text_editor.cljs index 869e929c12..ccd7fd8dcc 100644 --- a/frontend/src/app/util/text_editor.cljs +++ b/frontend/src/app/util/text_editor.cljs @@ -15,7 +15,6 @@ [app.common.uuid :as uuid] [app.util.array :as arr] [app.util.object :as obj] - [app.util.transit :as t] [clojure.walk :as walk] [cuerdas.core :as str])) diff --git a/frontend/src/app/util/theme.cljs b/frontend/src/app/util/theme.cljs index deed7644ac..ae446f5bf6 100644 --- a/frontend/src/app/util/theme.cljs +++ b/frontend/src/app/util/theme.cljs @@ -14,7 +14,6 @@ [goog.object :as gobj] [app.config :as cfg] [app.util.dom :as dom] - [app.util.transit :as t] [app.util.storage :refer [storage]])) (defonce theme (get @storage ::theme cfg/default-theme)) diff --git a/frontend/src/app/util/transit.cljs b/frontend/src/app/util/transit.cljs deleted file mode 100644 index 24d3851769..0000000000 --- a/frontend/src/app/util/transit.cljs +++ /dev/null @@ -1,127 +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.util.transit - "A lightweight abstraction for transit serialization." - (:require - [cognitect.transit :as t] - [linked.core :as lk] - [linked.set :as lks] - [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.common.geom.matrix :as gmt] - [app.util.time :as dt])) - -(deftype Blob [content] - IDeref - (-deref [_] content)) - -(defn blob? - [v] - (instance? Blob v)) - -(def blob-write-handler - (t/write-handler - (constantly "jsonblob") - (fn [v] (js/JSON.stringify @v)))) - -(def blob-read-handler - (t/read-handler - (fn [value] - (->Blob (js/JSON.parse value))))) - -;; --- Transit adapters - -(def bigint-read-handler - (t/read-handler - (fn [value] - (js/parseInt value 10)))) - -(def point-write-handler - (t/write-handler - (constantly "point") - (fn [v] (into {} v)))) - -(def point-read-handler - (t/read-handler - (fn [value] - (gpt/map->Point value)))) - -(def matrix-write-handler - (t/write-handler - (constantly "matrix") - (fn [v] (into {} v)))) - -(def matrix-read-handler - (t/read-handler - (fn [value] - (gmt/map->Matrix value)))) - -(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) %))) - -(def date-read-handler - (t/read-handler (fn [value] (-> value (js/parseInt 10) (dt/datetime))))) - -(def duration-read-handler - (t/read-handler (fn [value] (dt/duration value)))) - -(def date-write-handler - (t/write-handler - (constantly "m") - (fn [v] (str (inst-ms v))))) - -(def duration-write-handler - (t/write-handler - (constantly "duration") - (fn [v] (inst-ms v)))) - -;; --- Transit Handlers - -(def ^:privare +read-handlers+ - {"u" uuid - "n" bigint-read-handler - "ordered-set" ordered-set-read-handler - "jsonblob" blob-read-handler - "matrix" matrix-read-handler - "m" date-read-handler - "duration" duration-read-handler - "point" point-read-handler}) - -(def ^:privare +write-handlers+ - {gmt/Matrix matrix-write-handler - Blob blob-write-handler - dt/DateTime date-write-handler - dt/Duration duration-write-handler - lks/LinkedSet ordered-set-write-handler - gpt/Point point-write-handler}) - -;; --- Public Api - -(defn decode - [data] - (let [r (t/reader :json {:handlers +read-handlers+})] - (t/read r data))) - -(defn encode - [data] - (try - (let [w (t/writer :json-verbose {:handlers +write-handlers+})] - (t/write w data)) - (catch :default e - (throw e)))) - -(defn transit? - "Checks if a string can be decoded with transit" - [str] - (try - (-> str decode nil? not) - (catch js/SyntaxError e false))) diff --git a/frontend/src/app/util/webapi.cljs b/frontend/src/app/util/webapi.cljs index 0da4d753a4..ef77aaafc2 100644 --- a/frontend/src/app/util/webapi.cljs +++ b/frontend/src/app/util/webapi.cljs @@ -10,7 +10,6 @@ [app.common.data :as d] [app.common.exceptions :as ex] [app.util.object :as obj] - [app.util.transit :as t] [beicon.core :as rx] [cuerdas.core :as str] [promesa.core :as p])) diff --git a/frontend/src/app/util/websockets.cljs b/frontend/src/app/util/websockets.cljs index 61e06baba3..9690110200 100644 --- a/frontend/src/app/util/websockets.cljs +++ b/frontend/src/app/util/websockets.cljs @@ -7,9 +7,9 @@ (ns app.util.websockets "A interface to webworkers exposed functionality." (:require + [app.common.transit :as t] [app.common.uri :as u] [app.config :as cfg] - [app.util.transit :as t] [beicon.core :as rx] [goog.events :as ev] [potok.core :as ptk]) @@ -55,4 +55,4 @@ (defn send! [ws msg] - (-send ws (t/encode msg))) + (-send ws (t/encode-str msg))) diff --git a/frontend/src/app/util/worker.cljs b/frontend/src/app/util/worker.cljs index 8e9943e4fd..19d4b3df4e 100644 --- a/frontend/src/app/util/worker.cljs +++ b/frontend/src/app/util/worker.cljs @@ -7,9 +7,9 @@ (ns app.util.worker "A lightweight layer on top of webworkers api." (:require - [beicon.core :as rx] + [app.common.transit :as t] [app.common.uuid :as uuid] - [app.util.transit :as t])) + [beicon.core :as rx])) (declare handle-response) (defrecord Worker [instance stream]) @@ -25,7 +25,7 @@ (rx/take-while #(not (:completed %)) ob) (rx/take 1 ob))) - data (t/encode message) + data (t/encode-str message) instance (:instance worker)] (.postMessage instance data) @@ -67,7 +67,7 @@ handle-message (fn [event] (let [data (.-data event) - data (t/decode data)] + data (t/decode-str data)] (if (:error data) (on-error (:error data)) (rx/push! bus data)))) @@ -75,7 +75,7 @@ handle-error (fn [error] (on-error worker (.-data error)))] - + (.addEventListener instance "message" handle-message) (.addEventListener instance "error" handle-error) diff --git a/frontend/src/app/worker.cljs b/frontend/src/app/worker.cljs index 38b68a57d5..05d9ffc51b 100644 --- a/frontend/src/app/worker.cljs +++ b/frontend/src/app/worker.cljs @@ -6,20 +6,20 @@ (ns app.worker (:require - [cljs.spec.alpha :as s] - [promesa.core :as p] - [beicon.core :as rx] - [cuerdas.core :as str] [app.common.exceptions :as ex] [app.common.spec :as us] + [app.common.transit :as t] [app.common.uuid :as uuid] + [app.util.object :as obj] + [app.util.worker :as w] [app.worker.impl :as impl] [app.worker.selection] - [app.worker.thumbnails] [app.worker.snaps] - [app.util.object :as obj] - [app.util.transit :as t] - [app.util.worker :as w])) + [app.worker.thumbnails] + [beicon.core :as rx] + [cljs.spec.alpha :as s] + [cuerdas.core :as str] + [promesa.core :as p])) ;; --- Messages Handling @@ -44,7 +44,7 @@ [{:keys [sender-id payload] :as message}] (us/assert ::message message) (letfn [(post [msg] - (let [msg (-> msg (assoc :reply-to sender-id) (t/encode))] + (let [msg (-> msg (assoc :reply-to sender-id) (t/encode-str))] (.postMessage js/self msg))) (reply [result] @@ -84,8 +84,8 @@ "Sends to the client a notifiction that its messages have been dropped" [{:keys [sender-id payload] :as message}] (us/assert ::message message) - (.postMessage js/self (t/encode {:reply-to sender-id - :dropped true}))) + (.postMessage js/self (t/encode-str {:reply-to sender-id + :dropped true}))) (defn subscribe-buffer-messages "Creates a subscription to process the buffer messages" @@ -143,7 +143,7 @@ [event] (when (nil? (.-source event)) (let [message (.-data event) - message (t/decode message)] + message (t/decode-str message)] (if (:buffer? message) (rx/push! buffer message) (handle-message message))))) diff --git a/frontend/src/app/worker/impl.cljs b/frontend/src/app/worker/impl.cljs index 77032133a6..cfa69ac234 100644 --- a/frontend/src/app/worker/impl.cljs +++ b/frontend/src/app/worker/impl.cljs @@ -6,9 +6,9 @@ (ns app.worker.impl (:require - [okulary.core :as l] - [app.util.transit :as t] - [app.common.pages.changes :as ch])) + [app.common.pages.changes :as ch] + [app.common.transit :as t] + [okulary.core :as l])) (enable-console-print!) From ee1738c9d4643ef6320decbe64efee621c93f5cb Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 31 May 2021 09:28:20 +0200 Subject: [PATCH 019/204] :recycle: Replace backend transit ns with common transit. --- backend/src/app/db.clj | 4 ++-- backend/src/app/http/middleware.clj | 2 +- backend/src/app/loggers/audit.clj | 2 +- backend/src/app/notifications.clj | 2 +- backend/src/app/tokens.clj | 2 +- backend/src/app/util/blob.clj | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index b9886ec7e4..6527fa2a8d 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -10,13 +10,13 @@ [app.common.exceptions :as ex] [app.common.geom.point :as gpt] [app.common.spec :as us] + [app.common.transit :as t] [app.db.sql :as sql] [app.metrics :as mtx] [app.util.json :as json] [app.util.logging :as l] [app.util.migrations :as mg] [app.util.time :as dt] - [app.util.transit :as t] [clojure.java.io :as io] [clojure.spec.alpha :as s] [integrant.core :as ig] @@ -344,7 +344,7 @@ [data] (doto (org.postgresql.util.PGobject.) (.setType "jsonb") - (.setValue (t/encode-verbose-str data)))) + (.setValue (t/encode-str data {:type :json-verbose})))) (defn json "Encode as plain json." diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index 16d7429edf..91c275cf02 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -6,10 +6,10 @@ (ns app.http.middleware (:require + [app.common.transit :as t] [app.metrics :as mtx] [app.util.json :as json] [app.util.logging :as l] - [app.util.transit :as t] [buddy.core.codecs :as bc] [buddy.core.hash :as bh] [clojure.java.io :as io] diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index bbffcf7811..31e920b1b0 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -9,6 +9,7 @@ (:require [app.common.exceptions :as ex] [app.common.spec :as us] + [app.common.transit :as t] [app.common.uuid :as uuid] [app.config :as cf] [app.db :as db] @@ -16,7 +17,6 @@ [app.util.http :as http] [app.util.logging :as l] [app.util.time :as dt] - [app.util.transit :as t] [app.worker :as wrk] [clojure.core.async :as a] [clojure.spec.alpha :as s] diff --git a/backend/src/app/notifications.clj b/backend/src/app/notifications.clj index f938352e93..80afa560f8 100644 --- a/backend/src/app/notifications.clj +++ b/backend/src/app/notifications.clj @@ -8,12 +8,12 @@ "A websocket based notifications mechanism." (:require [app.common.spec :as us] + [app.common.transit :as t] [app.db :as db] [app.metrics :as mtx] [app.util.async :as aa] [app.util.logging :as l] [app.util.time :as dt] - [app.util.transit :as t] [app.worker :as wrk] [clojure.core.async :as a] [clojure.spec.alpha :as s] diff --git a/backend/src/app/tokens.clj b/backend/src/app/tokens.clj index bfa682ea6c..5c96c19802 100644 --- a/backend/src/app/tokens.clj +++ b/backend/src/app/tokens.clj @@ -9,8 +9,8 @@ (:require [app.common.exceptions :as ex] [app.common.spec :as us] + [app.common.transit :as t] [app.util.time :as dt] - [app.util.transit :as t] [buddy.core.kdf :as bk] [buddy.sign.jwe :as jwe] [clojure.spec.alpha :as s] diff --git a/backend/src/app/util/blob.clj b/backend/src/app/util/blob.clj index d70d12ab0b..3c740516bb 100644 --- a/backend/src/app/util/blob.clj +++ b/backend/src/app/util/blob.clj @@ -8,8 +8,8 @@ "A generic blob storage encoding. Mainly used for page data, page options and txlog payload storage." (:require + [app.common.transit :as t] [app.config :as cf] - [app.util.transit :as t] [taoensso.nippy :as n]) (:import java.io.ByteArrayInputStream From 6ed182002b3f5431ed93b2fce46a21bc3f65827b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 31 May 2021 09:29:02 +0200 Subject: [PATCH 020/204] :arrow_up: Update lambdaisland/uri dependency. --- common/deps.edn | 2 +- common/src/app/common/uri.cljc | 5 +---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/common/deps.edn b/common/deps.edn index aa42cd43ec..9955b4cc85 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -28,7 +28,7 @@ funcool/promesa {:mvn/version "6.0.1"} funcool/cuerdas {:mvn/version "2021.05.29-0"} - lambdaisland/uri {:mvn/version "1.4.54" + lambdaisland/uri {:mvn/version "1.4.70" :exclusions [org.clojure/data.json]} frankiesardo/linked {:mvn/version "1.3.0"} diff --git a/common/src/app/common/uri.cljc b/common/src/app/common/uri.cljc index d381ee7b0a..dbe03bf79e 100644 --- a/common/src/app/common/uri.cljc +++ b/common/src/app/common/uri.cljc @@ -15,10 +15,7 @@ (d/export u/join) (d/export u/query-encode) (d/export un/percent-encode) - -(defn uri? - [o] - (instance? lambdaisland.uri.URI o)) +(d/export u/uri?) (defn query-string->map [s] From 3c7dda02c658312c5a02c1a1912939b2e3312705 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 31 May 2021 11:55:13 +0200 Subject: [PATCH 021/204] :ambulance: Add tempory shadow-cljs npm dependency. --- frontend/package.json | 3 +- frontend/yarn.lock | 561 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 555 insertions(+), 9 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index b16eb66cd6..9dd8342283 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -32,7 +32,8 @@ "postcss": "^8.2.15", "postcss-clean": "^1.2.2", "rimraf": "^3.0.0", - "sass": "^1.32.8" + "sass": "^1.32.8", + "shadow-cljs": "^2.14.2" }, "dependencies": { "date-fns": "^2.21.3", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 34b578a2ff..561957a9d3 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -252,6 +252,16 @@ asap@~2.0.3: resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" integrity sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY= +asn1.js@^5.2.0: + version "5.4.1" + resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-5.4.1.tgz#11a980b84ebb91781ce35b0fdc2ee294e3783f07" + integrity sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA== + dependencies: + bn.js "^4.0.0" + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + safer-buffer "^2.1.0" + asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" @@ -264,6 +274,14 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +assert@^1.1.1: + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== + dependencies: + object-assign "^4.1.1" + util "0.10.3" + assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -289,6 +307,11 @@ async-foreach@^0.1.3: resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= +async-limiter@~1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" + integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== + async-settle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" @@ -360,6 +383,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base64-js@^1.0.2: + version "1.5.1" + resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" + integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== + base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" @@ -409,6 +437,16 @@ block-stream@*: dependencies: inherits "~2.0.0" +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: + version "4.12.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" + integrity sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA== + +bn.js@^5.0.0, bn.js@^5.1.1: + version "5.2.0" + resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.0.tgz#358860674396c6997771a9d051fcc1b57d4ae002" + integrity sha512-D7iWRBvnZE8ecXiLj/9wbxH7Tk79fAh8IHaTNq1RWRixsS02W+5qS+iE9yq6RYl0asXx5tw0bLhmT5pIfbSquw== + boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -445,11 +483,77 @@ braces@~3.0.2: dependencies: fill-range "^7.0.1" +brorand@^1.0.1, brorand@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= + browser-stdout@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== +browserify-aes@^1.0.0, browserify-aes@^1.0.4: + version "1.2.0" + resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== + dependencies: + buffer-xor "^1.0.3" + cipher-base "^1.0.0" + create-hash "^1.1.0" + evp_bytestokey "^1.0.3" + inherits "^2.0.1" + safe-buffer "^5.0.1" + +browserify-cipher@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== + dependencies: + browserify-aes "^1.0.4" + browserify-des "^1.0.0" + evp_bytestokey "^1.0.0" + +browserify-des@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== + dependencies: + cipher-base "^1.0.1" + des.js "^1.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + +browserify-rsa@^4.0.0, browserify-rsa@^4.0.1: + version "4.1.0" + resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.1.0.tgz#b2fd06b5b75ae297f7ce2dc651f918f5be158c8d" + integrity sha512-AdEER0Hkspgno2aR97SAf6vi0y0k8NuOpGnVH3O99rcA5Q6sh8QxcngtHuJ6uXwnfAXNM4Gn1Gb7/MV1+Ymbog== + dependencies: + bn.js "^5.0.0" + randombytes "^2.0.1" + +browserify-sign@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.2.1.tgz#eaf4add46dd54be3bb3b36c0cf15abbeba7956c3" + integrity sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg== + dependencies: + bn.js "^5.1.1" + browserify-rsa "^4.0.1" + create-hash "^1.2.0" + create-hmac "^1.1.7" + elliptic "^6.5.3" + inherits "^2.0.4" + parse-asn1 "^5.1.5" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +browserify-zlib@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== + dependencies: + pako "~1.0.5" + browserslist@^4.16.6: version "4.16.6" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.6.tgz#d7901277a5a88e554ed305b183ec9b0c08f66fa2" @@ -476,6 +580,25 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== +buffer-xor@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= + +buffer@^4.3.0: + version "4.9.2" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.2.tgz#230ead344002988644841ab0244af8c44bbe3ef8" + integrity sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg== + dependencies: + base64-js "^1.0.2" + ieee754 "^1.1.4" + isarray "^1.0.0" + +builtin-status-codes@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= + bytes@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -610,6 +733,14 @@ chokidar@^2.0.0: optionalDependencies: fsevents "^1.2.7" +cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" @@ -822,11 +953,21 @@ config-chain@^1.1.12: ini "^1.3.4" proto-list "~1.2.1" +console-browserify@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" + integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== + console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= +constants-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= + content-type@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" @@ -877,6 +1018,37 @@ cosmiconfig@^5.0.0: js-yaml "^3.13.1" parse-json "^4.0.0" +create-ecdh@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" + integrity sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A== + dependencies: + bn.js "^4.1.0" + elliptic "^6.5.3" + +create-hash@^1.1.0, create-hash@^1.1.2, create-hash@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== + dependencies: + cipher-base "^1.0.1" + inherits "^2.0.1" + md5.js "^1.3.4" + ripemd160 "^2.0.1" + sha.js "^2.4.0" + +create-hmac@^1.1.0, create-hmac@^1.1.4, create-hmac@^1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== + dependencies: + cipher-base "^1.0.3" + create-hash "^1.1.0" + inherits "^2.0.1" + ripemd160 "^2.0.0" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + cross-fetch@^3.0.4: version "3.1.4" resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.1.4.tgz#9723f3a3a247bf8b89039f3a380a9244e8fa2f39" @@ -903,6 +1075,23 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" +crypto-browserify@^3.11.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== + dependencies: + browserify-cipher "^1.0.0" + browserify-sign "^4.0.0" + create-ecdh "^4.0.0" + create-hash "^1.1.0" + create-hmac "^1.1.0" + diffie-hellman "^5.0.0" + inherits "^2.0.1" + pbkdf2 "^3.0.3" + public-encrypt "^4.0.0" + randombytes "^2.0.0" + randomfill "^1.0.3" + css-select-base-adapter@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz#3b2ff4972cc362ab88561507a95408a1432135d7" @@ -1098,6 +1287,14 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= +des.js@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" + integrity sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA== + dependencies: + inherits "^2.0.1" + minimalistic-assert "^1.0.0" + detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" @@ -1113,6 +1310,15 @@ diff@3.5.0: resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diffie-hellman@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== + dependencies: + bn.js "^4.1.0" + miller-rabin "^4.0.0" + randombytes "^2.0.0" + dom-helpers@^5.1.3: version "5.2.1" resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" @@ -1129,6 +1335,11 @@ dom-serializer@0: domelementtype "^2.0.1" entities "^2.0.0" +domain-browser@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== + domelementtype@1: version "1.3.1" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" @@ -1197,6 +1408,19 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.741.tgz#dc1024b19b31e27fb2c8c0a1f120cb05fc6ca695" integrity sha512-4i3T0cwnHo1O4Mnp9JniEco8bZiXoqbm3PhW5hv7uu8YLg35iajYrRnNyKFaN8/8SSTskU2hYqVTeYVPceSpUA== +elliptic@^6.5.3: + version "6.5.4" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" + integrity sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ== + dependencies: + bn.js "^4.11.9" + brorand "^1.1.0" + hash.js "^1.0.0" + hmac-drbg "^1.0.1" + inherits "^2.0.4" + minimalistic-assert "^1.0.1" + minimalistic-crypto-utils "^1.0.1" + emoji-regex@^7.0.1: version "7.0.3" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" @@ -1333,6 +1557,19 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" +events@^3.0.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== + dependencies: + md5.js "^1.3.4" + safe-buffer "^5.1.1" + execa@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" @@ -2056,6 +2293,23 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hash-base@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33" + integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA== + dependencies: + inherits "^2.0.4" + readable-stream "^3.6.0" + safe-buffer "^5.2.0" + +hash.js@^1.0.0, hash.js@^1.0.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== + dependencies: + inherits "^2.0.3" + minimalistic-assert "^1.0.1" + hasha@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/hasha/-/hasha-2.2.0.tgz#78d7cbfc1e6d66303fe79837365984517b2f6ee1" @@ -2074,6 +2328,15 @@ highlight.js@^10.6.0: resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg== +hmac-drbg@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= + dependencies: + hash.js "^1.0.3" + minimalistic-assert "^1.0.0" + minimalistic-crypto-utils "^1.0.1" + homedir-polyfill@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8" @@ -2095,6 +2358,11 @@ http-signature@~1.2.0: jsprim "^1.2.2" sshpk "^1.7.0" +https-browserify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= + iconv-lite@^0.6.2: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -2102,6 +2370,11 @@ iconv-lite@^0.6.2: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" +ieee754@^1.1.4: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" + integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -2154,11 +2427,21 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + +inherits@2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + ini@^1.3.4: version "1.3.8" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c" @@ -2457,7 +2740,7 @@ is-windows@^1.0.1, is-windows@^1.0.2: resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== -isarray@1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -2899,6 +3182,15 @@ matchdep@^2.0.0: resolve "^1.4.0" stack-trace "0.0.10" +md5.js@^1.3.4: + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + safe-buffer "^5.1.2" + mdn-data@2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" @@ -2967,6 +3259,14 @@ micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: snapdragon "^0.8.1" to-regex "^3.0.2" +miller-rabin@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== + dependencies: + bn.js "^4.0.0" + brorand "^1.0.1" + mime-db@1.47.0: version "1.47.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" @@ -2984,6 +3284,16 @@ mimic-fn@^2.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== + +minimalistic-crypto-utils@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= + minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" @@ -3140,6 +3450,35 @@ node-gyp@^3.8.0: tar "^2.0.0" which "1" +node-libs-browser@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" + integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== + dependencies: + assert "^1.1.1" + browserify-zlib "^0.2.0" + buffer "^4.3.0" + console-browserify "^1.1.0" + constants-browserify "^1.0.0" + crypto-browserify "^3.11.0" + domain-browser "^1.1.1" + events "^3.0.0" + https-browserify "^1.0.0" + os-browserify "^0.3.0" + path-browserify "0.0.1" + process "^0.11.10" + punycode "^1.2.4" + querystring-es3 "^0.2.0" + readable-stream "^2.3.3" + stream-browserify "^2.0.1" + stream-http "^2.7.2" + string_decoder "^1.0.0" + timers-browserify "^2.0.4" + tty-browserify "0.0.0" + url "^0.11.0" + util "^0.11.0" + vm-browserify "^1.0.1" + node-releases@^1.1.71: version "1.1.72" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" @@ -3371,6 +3710,11 @@ ordered-read-streams@^1.0.0: dependencies: readable-stream "^2.0.1" +os-browserify@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= + os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -3439,11 +3783,22 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -pako@~1.0.2: +pako@~1.0.2, pako@~1.0.5: version "1.0.11" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.11.tgz#6c9599d340d54dfd3946380252a35705a6b992bf" integrity sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw== +parse-asn1@^5.0.0, parse-asn1@^5.1.5: + version "5.1.6" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.6.tgz#385080a3ec13cb62a62d39409cb3e88844cdaed4" + integrity sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw== + dependencies: + asn1.js "^5.2.0" + browserify-aes "^1.0.0" + evp_bytestokey "^1.0.0" + pbkdf2 "^3.0.3" + safe-buffer "^5.1.1" + parse-filepath@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" @@ -3483,6 +3838,11 @@ pascalcase@^0.1.1: resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= +path-browserify@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a" + integrity sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ== + path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" @@ -3536,6 +3896,17 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +pbkdf2@^3.0.3: + version "3.1.2" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.1.2.tgz#dd822aa0887580e52f1a039dc3eda108efae3075" + integrity sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA== + dependencies: + create-hash "^1.1.2" + create-hmac "^1.1.4" + ripemd160 "^2.0.1" + safe-buffer "^5.0.1" + sha.js "^2.4.8" + pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" @@ -3661,6 +4032,11 @@ process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== +process@^0.11.10: + version "0.11.10" + resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= + progress@^1.1.8: version "1.1.8" resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" @@ -3697,6 +4073,18 @@ psl@^1.1.28: resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== +public-encrypt@^4.0.0: + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== + dependencies: + bn.js "^4.1.0" + browserify-rsa "^4.0.0" + create-hash "^1.1.0" + parse-asn1 "^5.0.0" + randombytes "^2.0.1" + safe-buffer "^5.1.2" + pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" @@ -3722,6 +4110,16 @@ pumpify@^1.3.5: inherits "^2.0.3" pump "^2.0.0" +punycode@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= + +punycode@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= + punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" @@ -3737,11 +4135,36 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +querystring-es3@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= + +querystring@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= + +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + randomcolor@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/randomcolor/-/randomcolor-0.6.2.tgz#7a57362ae1a1278439aeed2c15e5deb8ea33f56d" integrity sha512-Mn6TbyYpFgwFuQ8KJKqf3bqqY9O1y37/0jgSK/61PUxV4QfIMv0+K2ioq8DfOjkBslcjwSzRfIDEXfzA9aCx7A== +randomfill@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== + dependencies: + randombytes "^2.0.5" + safe-buffer "^5.1.0" + react-dom@~17.0.1: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" @@ -3836,6 +4259,11 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" +readline-sync@^1.4.7: + version "1.4.10" + resolved "https://registry.yarnpkg.com/readline-sync/-/readline-sync-1.4.10.tgz#41df7fbb4b6312d673011594145705bf56d8873b" + integrity sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw== + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -4017,6 +4445,14 @@ rimraf@^3.0.0: dependencies: glob "^7.1.3" +ripemd160@^2.0.0, ripemd160@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== + dependencies: + hash-base "^3.0.0" + inherits "^2.0.1" + rxjs@~7.1.0: version "7.1.0" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.1.0.tgz#94202d27b19305ef7b1a4f330277b2065df7039e" @@ -4024,7 +4460,7 @@ rxjs@~7.1.0: dependencies: tslib "~2.1.0" -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@^5.2.1, safe-buffer@~5.2.0: +safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@^5.2.1, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -4121,11 +4557,36 @@ set-value@^2.0.0, set-value@^2.0.1: is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@^1.0.5: +setimmediate@^1.0.4, setimmediate@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= +sha.js@^2.4.0, sha.js@^2.4.8: + version "2.4.11" + resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== + dependencies: + inherits "^2.0.1" + safe-buffer "^5.0.1" + +shadow-cljs-jar@1.3.2: + version "1.3.2" + resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" + integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== + +shadow-cljs@^2.14.2: + version "2.14.2" + resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.14.2.tgz#dba651ea124028064aea6fa9a390f257cb6eede4" + integrity sha512-ficaYfBAATzJ6OGt/GbIl393+cqLchzNkdTrM2PY4ttbsAOyBfWd39t+PZcYpCqemXjkgfBdZt9DJda7WaHJGA== + dependencies: + node-libs-browser "^2.2.1" + readline-sync "^1.4.7" + shadow-cljs-jar "1.3.2" + source-map-support "^0.4.15" + which "^1.3.1" + ws "^3.0.0" + shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" @@ -4253,6 +4714,13 @@ source-map-resolve@^0.6.0: atob "^2.1.2" decode-uri-component "^0.2.0" +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== + dependencies: + source-map "^0.5.6" + source-map-support@^0.5.16: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -4366,11 +4834,30 @@ stdout-stream@^1.4.0: dependencies: readable-stream "^2.0.1" +stream-browserify@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== + dependencies: + inherits "~2.0.1" + readable-stream "^2.0.2" + stream-exhaust@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== +stream-http@^2.7.2: + version "2.8.3" + resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== + dependencies: + builtin-status-codes "^3.0.0" + inherits "^2.0.1" + readable-stream "^2.3.6" + to-arraybuffer "^1.0.0" + xtend "^4.0.0" + stream-shift@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.1.tgz#d7088281559ab2778424279b0877da3c392d5a3d" @@ -4430,7 +4917,7 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string_decoder@^1.1.1: +string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA== @@ -4624,6 +5111,13 @@ time-stamp@^1.0.0: resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= +timers-browserify@^2.0.4: + version "2.0.12" + resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.12.tgz#44a45c11fbf407f34f97bccd1577c652361b00ee" + integrity sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ== + dependencies: + setimmediate "^1.0.4" + timers-ext@^0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/timers-ext/-/timers-ext-0.1.7.tgz#6f57ad8578e07a3fb9f91d9387d65647555e25c6" @@ -4645,6 +5139,11 @@ to-absolute-glob@^2.0.0: is-absolute "^1.0.0" is-negated-glob "^1.0.0" +to-arraybuffer@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -4714,6 +5213,11 @@ tslib@~2.1.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== +tty-browserify@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= + tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -4746,6 +5250,11 @@ ua-parser-js@^0.7.18, ua-parser-js@^0.7.28: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== +ultron@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" + integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -4830,6 +5339,14 @@ urix@^0.1.0: resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= +url@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= + dependencies: + punycode "1.3.2" + querystring "0.2.0" + use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" @@ -4850,6 +5367,20 @@ util.promisify@~1.0.0: has-symbols "^1.0.1" object.getownpropertydescriptors "^2.1.0" +util@0.10.3: + version "0.10.3" + resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= + dependencies: + inherits "2.0.1" + +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== + dependencies: + inherits "2.0.3" + uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" @@ -4939,6 +5470,11 @@ vinyl@^2.0.0, vinyl@^2.2.0: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" +vm-browserify@^1.0.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" + integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -4960,7 +5496,7 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@1, which@^1.2.10, which@^1.2.14, which@^1.2.9: +which@1, which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== @@ -5019,6 +5555,15 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +ws@^3.0.0: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" + integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== + dependencies: + async-limiter "~1.0.0" + safe-buffer "~5.1.0" + ultron "~1.1.0" + xmldom@0.1.27: version "0.1.27" resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9" @@ -5036,7 +5581,7 @@ xregexp@^5.0.1: dependencies: "@babel/runtime-corejs3" "^7.12.1" -xtend@~4.0.0, xtend@~4.0.1: +xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== From b2fef7b7a8c72c3d8d9c95471d8e3d09e8ad3d6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 12 May 2021 14:00:30 +0200 Subject: [PATCH 022/204] :tada: Add many functions to assets panel and big refactor --- CHANGES.md | 5 + common/src/app/common/data.cljc | 3 +- common/src/app/common/pages.cljc | 2 + common/src/app/common/pages/helpers.cljc | 14 +- .../app/main/data/workspace/libraries.cljs | 7 + .../app/main/ui/components/context_menu.cljs | 4 +- .../app/main/ui/workspace/sidebar/assets.cljs | 1301 +++++++++++------ frontend/translations/en.po | 8 + frontend/translations/es.po | 8 + 9 files changed, 906 insertions(+), 446 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ac75ab0096..7c6eab5460 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,11 @@ ### :sparkles: New features +- Allow nested asset groups [Taiga #1716](https://tree.taiga.io/project/penpot/us/1716) +- Allow to ungroup assets [Taiga #1719](https://tree.taiga.io/project/penpot/us/1719) +- Allow to rename assets groups [Taiga #1721](https://tree.taiga.io/project/penpot/us/1721) +- Memorize collapse state of assets in panel [Taiga #1718](https://tree.taiga.io/project/penpot/us/1718) + ### :bug: Bugs fixed ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 580184cdfc..e034f00e8d 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -479,8 +479,7 @@ :var [2 nil]} :d [nil 10] } - If both maps are identical the result will be an empty map - " + If both maps are identical the result will be an empty map." [m1 m2] (let [m1ks (set (keys m1)) diff --git a/common/src/app/common/pages.cljc b/common/src/app/common/pages.cljc index 4ef70babaa..32bd260845 100644 --- a/common/src/app/common/pages.cljc +++ b/common/src/app/common/pages.cljc @@ -60,6 +60,8 @@ (d/export helpers/get-base-shape) (d/export helpers/is-parent?) (d/export helpers/get-index-in-parent) +(d/export helpers/split-path) +(d/export helpers/join-path) (d/export helpers/parse-path-name) (d/export helpers/merge-path-item) (d/export helpers/compact-path) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index ae9b83da48..9ad6aa04e9 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -408,11 +408,17 @@ (defn split-path "Decompose a string in the form 'one / two / three' into - an array of strings, normalizing spaces." + a vector of strings, normalizing spaces." [path] (->> (str/split path "/") (map str/trim) - (remove str/empty?))) + (remove str/empty?) + vec)) + +(defn join-path + "Regenerate a path as a string, from a vector." + [path-vec] + (str/join " / " path-vec)) (defn parse-path-name "Parse a string in the form 'group / subgroup / name'. @@ -427,7 +433,9 @@ "Put the item at the end of the path." [path name] (if-not (empty? path) - (str path " / " name) + (if-not (empty? name) + (str path " / " name) + path) name)) (defn compact-path diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 7609759fa2..088b2f368d 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -71,6 +71,13 @@ (update [_ state] (assoc-in state [:workspace-local :assets-files-open file-id box] open?)))) +(defn set-assets-group-open + [file-id box path open?] + (ptk/reify ::set-assets-group-open + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :assets-files-open file-id :groups box path] open?)))) + (defn default-color-name [color] (or (:color color) (case (get-in color [:gradient :type]) diff --git a/frontend/src/app/main/ui/components/context_menu.cljs b/frontend/src/app/main/ui/components/context_menu.cljs index 075d99544b..16b1ccf4c1 100644 --- a/frontend/src/app/main/ui/components/context_menu.cljs +++ b/frontend/src/app/main/ui/components/context_menu.cljs @@ -105,7 +105,9 @@ {:class (dom/classnames :is-selected (and selected (= option-name selected))) :key option-name} (if-not sub-options - [:a.context-menu-action {:on-click option-handler} + [:a.context-menu-action {:on-click #(do (dom/stop-propagation %) + (on-close) + (option-handler %))} option-name] [:a.context-menu-action.submenu {:data-no-close true diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index de210b4224..228f7dc124 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -47,52 +47,108 @@ [okulary.core :as l] [rumext.alpha :as mf])) +; TODO: refactor to remove duplicate code and less parameter passing. +; - Move all state to [:workspace-local :assets-bar file-id :open-boxes {} +; :open-groups {} +; :reverse-sort? +; :listing-thumbs? +; :selected-assets {}] +; - Move selection code to independent functions that receive the state as a parameter. +; +; TODO: change update operations to admit multiple ids, thus avoiding the need of +; emitting many events and opening an undo transaction. Also move the logic +; of grouping, deleting, etc. to events in the data module, since now the +; selection info is in the global state. + ;; ---- Group assets management ---- +(defn group-assets + "Convert a list of assets in a nested structure like this: + + {'': [{assetA} {assetB}] + 'group1': {'': [{asset1A} {asset1B}] + 'subgroup11': {'': [{asset11A} {asset11B} {asset11C}]} + 'subgroup12': {'': [{asset12A}]}} + 'group2': {'subgroup21': {'': [{asset21A}}}} + " + [assets] + (when-not (empty? assets) + (reduce (fn [groups asset] + (let [path-vector (cp/split-path (or (:path asset) ""))] + (update-in groups (conj path-vector "") + (fn [group] + (if-not group + [asset] + (conj group asset)))))) + {} + assets))) + +(defn add-group + [asset group-name] + (-> (:path asset) + (cp/merge-path-item group-name) + (cp/merge-path-item (:name asset)))) + +(defn rename-group + [asset path last-path] + (-> (:path asset) + (str/slice 0 (count path)) + (cp/split-path) + butlast + (vec) + (conj last-path) + (cp/join-path) + (str (str/slice (:path asset) (count path))) + (cp/merge-path-item (:name asset)))) + +(defn ungroup + [asset path] + (-> (:path asset) + (str/slice 0 (count path)) + (cp/split-path) + butlast + (cp/join-path) + (str (str/slice (:path asset) (count path))) + (cp/merge-path-item (:name asset)))) + (s/def ::asset-name ::us/not-empty-string) -(s/def ::create-group-form +(s/def ::name-group-form (s/keys :req-un [::asset-name])) -(defn group-assets - [assets] - (reduce (fn [groups asset] - (update groups (or (:path asset) "") - #(conj (or % []) asset))) - (sorted-map) - assets)) - -(def empty-folded-groups #{}) - -(defn toggle-folded-group - [folded-groups path] - (if (contains? folded-groups path) - (disj folded-groups path) - (conj folded-groups path))) - -(mf/defc create-group-dialog +(mf/defc name-group-dialog {::mf/register modal/components - ::mf/register-as :create-group-dialog} - [{:keys [create] :as ctx}] - (let [form (fm/use-form :spec ::create-group-form - :initial {}) + ::mf/register-as :name-group-dialog} + [{:keys [path last-path accept] :as ctx + :or {path "" last-path ""}}] + (let [initial (mf/use-memo + (mf/deps last-path) + (constantly {:asset-name last-path})) + form (fm/use-form :spec ::name-group-form + :initial initial) - close #(modal/hide!) + create? (empty? path) + + on-close #(modal/hide!) on-accept (mf/use-callback (mf/deps form) (fn [event] (let [asset-name (get-in @form [:clean-data :asset-name])] - (create asset-name) + (if create? + (accept asset-name) + (accept path asset-name)) (modal/hide!))))] [:div.modal-overlay [:div.modal-container.confirm-dialog [:div.modal-header [:div.modal-header-title - [:h2 (tr "workspace.assets.create-group")]] + [:h2 (if create? + (tr "workspace.assets.create-group") + (tr "workspace.assets.rename-group"))]] [:div.modal-close-button - {:on-click close} i/close]] + {:on-click on-close} i/close]] [:div.modal-content.generic-form [:& fm/form {:form form} @@ -106,27 +162,192 @@ [:input.cancel-button {:type "button" :value (tr "labels.cancel") - :on-click close}] + :on-click on-close}] [:input.accept-button.primary {:type "button" :class (when-not (:valid @form) "btn-disabled") :disabled (not (:valid @form)) - :value (tr "labels.create") + :value (if create? (tr "labels.create") (tr "labels.rename")) :on-click on-accept}]]]]])) +;; ---- Common blocks ---- + +(def auto-pos-menu-state {:open? false + :top nil + :left nil}) + +(defn- open-auto-pos-menu + [state event] + (let [pos (dom/get-client-position event) + top (:y pos) + left (+ (:x pos) 10)] + (dom/prevent-default event) + (assoc state + :open? true + :top top + :left left))) + +(defn- close-auto-pos-menu + [state] + (assoc state :open? false)) + +(mf/defc auto-pos-menu + [{:keys [options state on-close]}] + [:& context-menu + {:selectable false + :show (:open? state) + :on-close on-close + :top (:top state) + :left (:left state) + :options options}]) + +(mf/defc asset-section + [{:keys [children file-id title box assets-count open?]}] + (let [children (if (array? children) children [children]) + get-role #(.. % -props -role) + title-buttons (filter #(= (get-role %) :title-button) children) + content (filter #(= (get-role %) :content) children)] + [:div.asset-section + [:div.asset-title {:class (when (not open?) "closed")} + [:span {:on-click (st/emitf (dwl/set-assets-box-open file-id box (not open?)))} + i/arrow-slide title] + [:span.num-assets (str "\u00A0(") assets-count ")"] ;; Unicode 00A0 is non-breaking space + title-buttons] + (when open? + content)])) + +(mf/defc asset-section-block + [{:keys [children role]}] + [:* children]) + +(mf/defc asset-group-title + [{:keys [file-id box path group-open? on-rename on-ungroup]}] + (when-not (empty? path) + (let [[other-path last-path truncated] (cp/compact-path path 35) + menu-state (mf/use-state auto-pos-menu-state) + + on-fold-group + (mf/use-callback + (mf/deps group-open?) + (fn [event] + (dom/stop-propagation event) + (st/emit! (dwl/set-assets-group-open file-id + box + path + (not group-open?))))) + on-context-menu + (mf/use-callback + (fn [event] + (swap! menu-state #(open-auto-pos-menu % event)))) + + on-close-menu + (mf/use-callback + (fn [] + (swap! menu-state close-auto-pos-menu)))] + + [:div.group-title {:class (when-not group-open? "closed") + :on-click on-fold-group + :on-context-menu on-context-menu} + [:span i/arrow-slide] + (when-not (empty? other-path) + [:span.dim {:title (when truncated path)} + other-path "\u00A0/\u00A0"]) + [:span {:title (when truncated path)} + last-path] + [:& auto-pos-menu + {:on-close on-close-menu + :state @menu-state + :options [[(tr "workspace.assets.rename") #(on-rename % path last-path)] + [(tr "workspace.assets.ungroup") #(on-ungroup path)]]}]]))) + ;; ---- Components box ---- +(mf/defc components-item + [{:keys [component renaming listing-thumbs? selected-components + on-asset-click on-context-menu on-drag-start do-rename cancel-rename]}] + [:div {:key (:id component) + :class-name (dom/classnames + :selected (contains? selected-components (:id component)) + :grid-cell @listing-thumbs? + :enum-item (not @listing-thumbs?)) + :draggable true + :on-click #(on-asset-click % (:id component) nil) + :on-context-menu (on-context-menu (:id component)) + :on-drag-start (partial on-drag-start component)} + [:& exports/component-svg {:group (get-in component [:objects (:id component)]) + :objects (:objects component)}] + (let [renaming? (= renaming (:id component))] + [:& editable-label + {:class-name (dom/classnames + :cell-name @listing-thumbs? + :item-name (not @listing-thumbs?) + :editing renaming?) + :value (cp/merge-path-item (:path component) (:name component)) + :tooltip (cp/merge-path-item (:path component) (:name component)) + :display-value (if @listing-thumbs? + (:name component) + (cp/compact-name (:path component) + (:name component))) + :editing? renaming? + :disable-dbl-click? true + :on-change do-rename + :on-cancel cancel-rename}])]) + +(mf/defc components-group + [{:keys [file-id prefix groups open-groups renaming listing-thumbs? selected-components on-asset-click + on-drag-start do-rename cancel-rename on-rename-group on-ungroup on-context-menu]}] + (let [group-open? (get open-groups prefix true)] + + [:* + [:& asset-group-title {:file-id file-id + :box :components + :path prefix + :group-open? group-open? + :on-rename on-rename-group + :on-ungroup on-ungroup}] + (when group-open? + [:* + (let [components (get groups "" [])] + [:div {:class-name (dom/classnames + :asset-grid @listing-thumbs? + :big @listing-thumbs? + :asset-enum (not @listing-thumbs?))} + (for [component components] + [:& components-item {:component component + :renaming renaming + :listing-thumbs? listing-thumbs? + :selected-components selected-components + :on-asset-click on-asset-click + :on-context-menu on-context-menu + :on-drag-start on-drag-start + :do-rename do-rename + :cancel-rename cancel-rename}])]) + (for [[path-item content] groups] + (when-not (empty? path-item) + [:& components-group {:file-id file-id + :prefix (cp/merge-path-item prefix path-item) + :groups content + :open-groups open-groups + :renaming renaming + :listing-thumbs? listing-thumbs? + :selected-components selected-components + :on-asset-click on-asset-click + :on-drag-start on-drag-start + :do-rename do-rename + :cancel-rename cancel-rename + :on-rename-group on-rename-group + :on-ungroup on-ungroup + :on-context-menu on-context-menu}]))])])) + (mf/defc components-box - [{:keys [file-id local? components listing-thumbs? open? selected-assets + [{:keys [file-id local? components listing-thumbs? open? open-groups selected-assets on-asset-click on-assets-delete on-clear-selection] :as props}] - (let [state (mf/use-state {:menu-open false - :renaming nil - :top nil - :left nil - :component-id nil - :folded-groups empty-folded-groups}) + (let [state (mf/use-state {:renaming nil + :component-id nil}) + + menu-state (mf/use-state auto-pos-menu-state) selected-components (:components selected-assets) multi-components? (> (count selected-components) 1) @@ -134,28 +355,27 @@ (not (empty? (:colors selected-assets))) (not (empty? (:typographies selected-assets)))) - groups (group-assets components) - folded-groups (:folded-groups @state) + groups (group-assets components) on-duplicate (mf/use-callback - (mf/deps @state) - (fn [] - (if (empty? selected-components) - (st/emit! (dwl/duplicate-component {:id (:component-id @state)})) - (do - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! (map #(dwl/duplicate-component {:id %}) selected-components)) - (st/emit! (dwu/commit-undo-transaction)))))) + (mf/deps @state) + (fn [] + (if (empty? selected-components) + (st/emit! (dwl/duplicate-component {:id (:component-id @state)})) + (do + (st/emit! (dwu/start-undo-transaction)) + (apply st/emit! (map #(dwl/duplicate-component {:id %}) selected-components)) + (st/emit! (dwu/commit-undo-transaction)))))) on-delete (mf/use-callback - (mf/deps @state file-id multi-components? multi-assets?) - (fn [] - (if (or multi-components? multi-assets?) - (on-assets-delete) - (st/emit! (dwl/delete-component {:id (:component-id @state)}))) - (st/emit! (dwl/sync-file file-id file-id)))) + (mf/deps @state file-id multi-components? multi-assets?) + (fn [] + (if (or multi-components? multi-assets?) + (on-assets-delete) + (st/emit! (dwl/delete-component {:id (:component-id @state)}))) + (st/emit! (dwl/sync-file file-id file-id)))) on-rename (mf/use-callback @@ -181,47 +401,75 @@ (fn [component-id] (fn [event] (when local? - (let [pos (dom/get-client-position event) - top (:y pos) - left (- (:x pos) 20)] - (dom/prevent-default event) - (when-not (contains? selected-components component-id) - (on-clear-selection)) - (swap! state assoc :menu-open true - :top top - :left left - :component-id component-id)))))) + (when-not (contains? selected-components component-id) + (on-clear-selection)) + (swap! state assoc :component-id component-id) + (swap! menu-state #(open-auto-pos-menu % event)))))) + + on-close-menu + (mf/use-callback + (fn [] + (swap! menu-state close-auto-pos-menu))) create-group (mf/use-callback (mf/deps components selected-components on-clear-selection) - (fn [name] + (fn [group-name] (on-clear-selection) (st/emit! (dwu/start-undo-transaction)) (apply st/emit! (->> components - (filter #(contains? selected-components (:id %))) + (filter #(if multi-components? + (contains? selected-components (:id %)) + (= (:component-id @state) (:id %)))) (map #(dwl/rename-component (:id %) - (str name " / " - (cp/merge-path-item (:path %) (:name %))))))) + (add-group % group-name))))) (st/emit! (dwu/commit-undo-transaction)))) - on-fold-group + rename-group (mf/use-callback - (mf/deps groups folded-groups) - (fn [path] - (fn [event] - (dom/stop-propagation event) - (swap! state update :folded-groups - toggle-folded-group path)))) + (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)))) on-group (mf/use-callback (mf/deps components selected-components) (fn [event] (dom/stop-propagation event) - (modal/show! :create-group-dialog {:create create-group}))) + (modal/show! :name-group-dialog {:accept create-group}))) + + on-rename-group + (mf/use-callback + (mf/deps components) + (fn [event path last-path] + (dom/stop-propagation event) + (modal/show! :name-group-dialog {:path path + :last-path last-path + :accept rename-group}))) + + on-ungroup + (mf/use-callback + (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)))) on-drag-start (mf/use-callback @@ -230,89 +478,127 @@ :component component}) (dnd/set-allowed-effect! event "move")))] - [:div.asset-section - [:div.asset-title {:class (when (not open?) "closed")} - [:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :components (not open?)))} - i/arrow-slide (tr "workspace.assets.components")] - [:span (str "\u00A0(") (count components) ")"]] ;; Unicode 00A0 is non-breaking space - (when open? - (for [group groups] - (let [path (first group) - components (second group) - group-open? (not (contains? folded-groups path))] - [:* - (when-not (empty? path) - (let [[other-path last-path truncated] (cp/compact-path path 35)] - [:div.group-title {:class (when-not group-open? "closed") - :on-click (on-fold-group path)} - [:span i/arrow-slide] - (when-not (empty? other-path) - [:span.dim {:title (when truncated path)} - other-path "\u00A0/\u00A0"]) - [:span {:title (when truncated path)} - last-path]])) - (when group-open? - [:div {:class-name (dom/classnames - :asset-grid @listing-thumbs? - :big @listing-thumbs? - :asset-enum (not @listing-thumbs?))} - (for [component components] - (let [renaming? (= (:renaming @state)(:id component))] - [:div {:key (:id component) - :class-name (dom/classnames - :selected (contains? selected-components (:id component)) - :grid-cell @listing-thumbs? - :enum-item (not @listing-thumbs?)) - :draggable true - :on-click #(on-asset-click % (:id component) groups nil) - :on-context-menu (on-context-menu (:id component)) - :on-drag-start (partial on-drag-start component)} - [:& exports/component-svg {:group (get-in component [:objects (:id component)]) - :objects (:objects component)}] - [:& editable-label - {:class-name (dom/classnames - :cell-name @listing-thumbs? - :item-name (not @listing-thumbs?) - :editing renaming?) - :value (cp/merge-path-item (:path component) (:name component)) - :tooltip (cp/merge-path-item (:path component) (:name component)) - :display-value (if @listing-thumbs? - (:name component) - (cp/compact-name (:path component) - (:name component))) - :editing? renaming? - :disable-dbl-click? true - :on-change do-rename - :on-cancel cancel-rename}]]))])]))) - - (when local? - [:& context-menu - {:selectable false - :show (:menu-open @state) - :on-close #(swap! state assoc :menu-open false) - :top (:top @state) - :left (:left @state) - :options [(when-not (or multi-components? multi-assets?) - [(tr "workspace.assets.rename") on-rename]) - (when-not multi-assets? - [(tr "workspace.assets.duplicate") on-duplicate]) - [(tr "workspace.assets.delete") on-delete] - (when-not multi-assets? - [(tr "workspace.assets.group") on-group])]}])])) + [:& asset-section {:file-id file-id + :title (tr "workspace.assets.components") + :box :components + :assets-count (count components) + :open? open?} + [:& asset-section-block {:role :content} + [:& components-group {:file-id file-id + :prefix "" + :groups groups + :open-groups open-groups + :renaming (:renaming @state) + :listing-thumbs? listing-thumbs? + :selected-components selected-components + :on-asset-click (partial on-asset-click groups) + :on-drag-start on-drag-start + :do-rename do-rename + :cancel-rename cancel-rename + :on-rename-group on-rename-group + :on-ungroup on-ungroup + :on-context-menu on-context-menu}] + (when local? + [:& auto-pos-menu + {:on-close on-close-menu + :state @menu-state + :options [(when-not (or multi-components? multi-assets?) + [(tr "workspace.assets.rename") on-rename]) + (when-not multi-assets? + [(tr "workspace.assets.duplicate") on-duplicate]) + [(tr "workspace.assets.delete") on-delete] + (when-not multi-assets? + [(tr "workspace.assets.group") on-group])]}])]])) ;; ---- Graphics box ---- +(mf/defc graphics-item + [{:keys [object renaming listing-thumbs? selected-objects + on-asset-click on-context-menu on-drag-start do-rename cancel-rename]}] + [:div {:key (:id object) + :class-name (dom/classnames + :selected (contains? selected-objects (:id object)) + :grid-cell @listing-thumbs? + :enum-item (not @listing-thumbs?)) + :draggable true + :on-click #(on-asset-click % (:id object) nil) + :on-context-menu (on-context-menu (:id object)) + :on-drag-start (partial on-drag-start object)} + [:img {:src (cfg/resolve-file-media object true) + :draggable false}] ;; Also need to add css pointer-events: none + + (let [renaming? (= renaming (:id object))] + [:& editable-label + {:class-name (dom/classnames + :cell-name @listing-thumbs? + :item-name (not @listing-thumbs?) + :editing renaming?) + :value (cp/merge-path-item (:path object) (:name object)) + :tooltip (cp/merge-path-item (:path object) (:name object)) + :display-value (if @listing-thumbs? + (:name object) + (cp/compact-name (:path object) + (:name object))) + :editing? renaming? + :disable-dbl-click? true + :on-change do-rename + :on-cancel cancel-rename}])]) + +(mf/defc graphics-group + [{:keys [file-id prefix groups open-groups renaming listing-thumbs? selected-objects on-asset-click + on-drag-start do-rename cancel-rename on-rename-group on-ungroup + on-context-menu]}] + (let [group-open? (get open-groups prefix true)] + + [:* + [:& asset-group-title {:file-id file-id + :box :graphics + :path prefix + :group-open? group-open? + :on-rename on-rename-group + :on-ungroup on-ungroup}] + (when group-open? + [:* + (let [objects (get groups "" [])] + [:div {:class-name (dom/classnames + :asset-grid @listing-thumbs? + :asset-enum (not @listing-thumbs?))} + (for [object objects] + [:& graphics-item {:object object + :renaming renaming + :listing-thumbs? listing-thumbs? + :selected-objects selected-objects + :on-asset-click on-asset-click + :on-context-menu on-context-menu + :on-drag-start on-drag-start + :do-rename do-rename + :cancel-rename cancel-rename}])]) + (for [[path-item content] groups] + (when-not (empty? path-item) + [:& graphics-group {:file-id file-id + :prefix (cp/merge-path-item prefix path-item) + :groups content + :open-groups open-groups + :renaming renaming + :listing-thumbs? listing-thumbs? + :selected-objects selected-objects + :on-asset-click on-asset-click + :on-drag-start on-drag-start + :do-rename do-rename + :cancel-rename cancel-rename + :on-rename-group on-rename-group + :on-ungroup on-ungroup + :on-context-menu on-context-menu}]))])])) + (mf/defc graphics-box - [{:keys [file-id local? objects listing-thumbs? open? selected-assets + [{:keys [file-id local? objects listing-thumbs? open? 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 {:menu-open false - :renaming nil - :top nil - :left nil - :object-id nil - :folded-groups empty-folded-groups}) + state (mf/use-state {:renaming nil + :object-id nil}) + + menu-state (mf/use-state auto-pos-menu-state) selected-objects (:graphics selected-assets) multi-objects? (> (count selected-objects) 1) @@ -320,8 +606,7 @@ (not (empty? (:colors selected-assets))) (not (empty? (:typographies selected-assets)))) - groups (group-assets objects) - folded-groups (:folded-groups @state) + groups (group-assets objects) add-graphic (mf/use-callback @@ -369,47 +654,74 @@ (fn [object-id] (fn [event] (when local? - (let [pos (dom/get-client-position event) - top (:y pos) - left (- (:x pos) 20)] - (dom/prevent-default event) - (when-not (contains? selected-objects object-id) - (on-clear-selection)) - (swap! state assoc :menu-open true - :top top - :left left - :object-id object-id)))))) + (when-not (contains? selected-objects object-id) + (on-clear-selection)) + (swap! state assoc :object-id object-id) + (swap! menu-state #(open-auto-pos-menu % event)))))) + + on-close-menu + (mf/use-callback + (fn [] + (swap! menu-state close-auto-pos-menu))) create-group (mf/use-callback (mf/deps objects selected-objects on-clear-selection) - (fn [name] + (fn [group-name] (on-clear-selection) (st/emit! (dwu/start-undo-transaction)) (apply st/emit! (->> objects - (filter #(contains? selected-objects (:id %))) + (filter #(if multi-objects? + (contains? selected-objects (:id %)) + (= (:object-id @state) (:id %)))) (map #(dwl/rename-media (:id %) - (str name " / " - (cp/merge-path-item (:path %) (:name %))))))) + (add-group % group-name))))) (st/emit! (dwu/commit-undo-transaction)))) - on-fold-group + rename-group (mf/use-callback - (mf/deps groups folded-groups) - (fn [path] - (fn [event] - (dom/stop-propagation event) - (swap! state update :folded-groups - toggle-folded-group path)))) + (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)))) on-group (mf/use-callback (mf/deps objects selected-objects) (fn [event] (dom/stop-propagation event) - (modal/show! :create-group-dialog {:create create-group}))) + (modal/show! :name-group-dialog {:accept create-group}))) + + on-rename-group + (mf/use-callback + (mf/deps objects) + (fn [event path last-path] + (dom/stop-propagation event) + (modal/show! :name-group-dialog {:path path + :last-path last-path + :accept rename-group}))) + on-ungroup + (mf/use-callback + (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)))) on-drag-start (mf/use-callback @@ -419,80 +731,44 @@ (dnd/set-data! event "text/asset-type" mtype) (dnd/set-allowed-effect! event "move")))] - [:div.asset-section - [:div.asset-title {:class (when (not open?) "closed")} - [:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :graphics (not open?)))} - i/arrow-slide (tr "workspace.assets.graphics")] - [:span.num-assets (str "\u00A0(") (count objects) ")"] ;; Unicode 00A0 is non-breaking space + [:& asset-section {:file-id file-id + :title (tr "workspace.assets.graphics") + :box :graphics + :assets-count (count objects) + :open? open?} (when local? - [:div.assets-button {:on-click add-graphic} - i/plus - [:& file-uploader {:accept cm/str-image-types - :multi true - :input-ref input-ref - :on-selected on-file-selected}]])] - (when open? - (for [group groups] - (let [path (first group) - objects (second group) - group-open? (not (contains? folded-groups path))] - [:* - (when-not (empty? path) - (let [[other-path last-path truncated] (cp/compact-path path 35)] - [:div.group-title {:class (when-not group-open? "closed") - :on-click (on-fold-group path)} - [:span i/arrow-slide] - (when-not (empty? other-path) - [:span.dim {:title (when truncated path)} - other-path "\u00A0/\u00A0"]) - [:span {:title (when truncated path)} - last-path]])) - (when group-open? - [:div {:class-name (dom/classnames - :asset-grid @listing-thumbs? - :asset-enum (not @listing-thumbs?))} - (for [object objects] - [:div {:key (:id object) - :class-name (dom/classnames - :selected (contains? selected-objects (:id object)) - :grid-cell @listing-thumbs? - :enum-item (not @listing-thumbs?)) - :draggable true - :on-click #(on-asset-click % (:id object) groups nil) - :on-context-menu (on-context-menu (:id object)) - :on-drag-start (partial on-drag-start object)} - [:img {:src (cfg/resolve-file-media object true) - :draggable false}] ;; Also need to add css pointer-events: none + [:& asset-section-block {:role :title-button} + [:div.assets-button {:on-click add-graphic} + i/plus + [:& file-uploader {:accept cm/str-image-types + :multi true + :input-ref input-ref + :on-selected on-file-selected}]]]) - (let [renaming? (= (:renaming @state) (:id object))] - [:& editable-label - {:class-name (dom/classnames - :cell-name @listing-thumbs? - :item-name (not @listing-thumbs?) - :editing renaming?) - :value (cp/merge-path-item (:path object) (:name object)) - :tooltip (cp/merge-path-item (:path object) (:name object)) - :display-value (if @listing-thumbs? - (:name object) - (cp/compact-name (:path object) - (:name object))) - :editing? renaming? - :disable-dbl-click? true - :on-change do-rename - :on-cancel cancel-rename}])])])]))) - - (when local? - [:& context-menu - {:selectable false - :show (:menu-open @state) - :on-close #(swap! state assoc :menu-open false) - :top (:top @state) - :left (:left @state) - :options [(when-not (or multi-objects? multi-assets?) - [(tr "workspace.assets.rename") on-rename]) - [(tr "workspace.assets.delete") on-delete] - (when-not multi-assets? - [(tr "workspace.assets.group") on-group])]}])])) + [:& asset-section-block {:role :content} + [:& graphics-group {:file-id file-id + :prefix "" + :groups groups + :open-groups open-groups + :renaming (:renaming @state) + :listing-thumbs? listing-thumbs? + :selected-objects selected-objects + :on-asset-click (partial on-asset-click groups) + :on-drag-start on-drag-start + :do-rename do-rename + :cancel-rename cancel-rename + :on-rename-group on-rename-group + :on-ungroup on-ungroup + :on-context-menu on-context-menu}] + (when local? + [:& auto-pos-menu + {:on-close on-close-menu + :state @menu-state + :options [(when-not (or multi-objects? multi-assets?) + [(tr "workspace.assets.rename") on-rename]) + [(tr "workspace.assets.delete") on-delete] + (when-not multi-assets? + [(tr "workspace.assets.group") on-group])]}])]])) ;; ---- Colors box ---- @@ -504,10 +780,9 @@ (let [rename? (= (:color-for-rename @refs/workspace-local) (:id color)) id (:id color) input-ref (mf/use-ref) - state (mf/use-state {:menu-open false - :top nil - :left nil - :editing rename?}) + state (mf/use-state {:editing rename?}) + + menu-state (mf/use-state auto-pos-menu-state) default-name (cond (:gradient color) (bc/gradient-type->string (get-in color [:gradient :type])) @@ -574,16 +849,14 @@ (mf/deps color selected-colors on-clear-selection) (fn [event] (when local? - (let [pos (dom/get-client-position event) - top (:y pos) - left (+ 10 (:x pos))] - (dom/prevent-default event) - (when-not (contains? selected-colors (:id color)) - (on-clear-selection)) - (swap! state assoc - :menu-open true - :top top - :left left)))))] + (when-not (contains? selected-colors (:id color)) + (on-clear-selection)) + (swap! menu-state #(open-auto-pos-menu % event))))) + + on-close-menu + (mf/use-callback + (fn [] + (swap! menu-state close-auto-pos-menu)))] (mf/use-effect (mf/deps (:editing @state)) @@ -596,7 +869,7 @@ :selected (contains? selected-colors (:id color))) :on-context-menu on-context-menu :on-click (when-not (:editing @state) - #(on-asset-click % (:id color) {"" colors} + #(on-asset-click % (:id color) (partial apply-color (:id color))))} [:& bc/color-bullet {:color color}] @@ -607,40 +880,88 @@ :on-blur input-blur :on-key-down input-key-down :auto-focus true - :default-value (:name color "")}] + :default-value (cp/merge-path-item (:path color) (:name color))}] [:div.name-block {:on-double-click rename-color-clicked} (:name color) (when-not (= (:name color) default-name) [:span default-name])]) (when local? - [:& context-menu - {:selectable false - :show (:menu-open @state) - :on-close #(swap! state assoc :menu-open false) - :top (:top @state) - :left (:left @state) + [:& auto-pos-menu + {:on-close on-close-menu + :state @menu-state :options [(when-not (or multi-colors? multi-assets?) [(t locale "workspace.assets.rename") rename-color-clicked]) (when-not (or multi-colors? multi-assets?) [(t locale "workspace.assets.edit") edit-color-clicked]) [(t locale "workspace.assets.delete") delete-color] (when-not multi-assets? - [(tr "workspace.assets.group") on-group])]}])])) + [(tr "workspace.assets.group") (on-group (:id color))])]}])])) + +(mf/defc colors-group + [{:keys [file-id prefix groups open-groups local? selected-colors + multi-colors? multi-assets? on-asset-click on-assets-delete + on-clear-selection on-group on-rename-group on-ungroup colors locale]}] + (let [group-open? (get open-groups prefix true)] + + [:* + [:& asset-group-title {:file-id file-id + :box :colors + :path prefix + :group-open? group-open? + :on-rename on-rename-group + :on-ungroup on-ungroup}] + (when group-open? + [:* + (let [colors (get groups "" [])] + [:div.asset-list + (for [color colors] + (let [color (cond-> color + (:value color) (assoc :color (:value color) :opacity 1) + (:value color) (dissoc :value) + true (assoc :file-id file-id))] + [:& color-item {:key (:id color) + :color color + :file-id file-id + :local? local? + :selected-colors selected-colors + :multi-colors? multi-colors? + :multi-assets? multi-assets? + :on-asset-click on-asset-click + :on-assets-delete on-assets-delete + :on-clear-selection on-clear-selection + :on-group on-group + :colors colors + :locale locale}]))]) + (for [[path-item content] groups] + (when-not (empty? path-item) + [:& colors-group {:file-id file-id + :prefix (cp/merge-path-item prefix path-item) + :groups content + :open-groups open-groups + :local? local? + :selected-colors selected-colors + :multi-colors? multi-colors? + :multi-assets? multi-assets? + :on-asset-click on-asset-click + :on-assets-delete on-assets-delete + :on-clear-selection on-clear-selection + :on-group on-group + :on-rename-group on-rename-group + :on-ungroup on-ungroup + :colors colors + :locale locale}]))])])) (mf/defc colors-box - [{:keys [file-id local? colors locale open? selected-assets + [{:keys [file-id local? colors locale open? open-groups selected-assets on-asset-click on-assets-delete on-clear-selection] :as props}] - (let [state (mf/use-state {:folded-groups empty-folded-groups}) - - selected-colors (:colors selected-assets) + (let [selected-colors (:colors selected-assets) multi-colors? (> (count selected-colors) 1) multi-assets? (or (not (empty? (:components selected-assets))) (not (empty? (:graphics selected-assets))) (not (empty? (:typographies selected-assets)))) groups (group-assets colors) - folded-groups (:folded-groups @state) add-color (mf/use-callback @@ -664,96 +985,157 @@ create-group (mf/use-callback (mf/deps colors selected-colors on-clear-selection file-id) - (fn [name] + (fn [color-id] + (fn [group-name] + (on-clear-selection) + (st/emit! (dwu/start-undo-transaction)) + (apply st/emit! + (->> colors + (filter #(if multi-colors? + (contains? selected-colors (:id %)) + (= color-id (:id %)))) + (map #(dwl/update-color + (assoc % :name + (add-group % group-name)) + file-id)))) + (st/emit! (dwu/commit-undo-transaction))))) + + rename-group + (mf/use-callback + (mf/deps colors) + (fn [path last-path] (on-clear-selection) (st/emit! (dwu/start-undo-transaction)) (apply st/emit! (->> colors - (filter #(contains? selected-colors (:id %))) + (filter #(str/starts-with? (:path %) path)) (map #(dwl/update-color (assoc % :name - (str name " / " - (cp/merge-path-item (:path %) (:name %)))) + (rename-group % path last-path)) file-id)))) (st/emit! (dwu/commit-undo-transaction)))) - on-fold-group - (mf/use-callback - (mf/deps groups folded-groups) - (fn [path] - (fn [event] - (dom/stop-propagation event) - (swap! state update :folded-groups - toggle-folded-group path)))) - on-group (mf/use-callback (mf/deps colors selected-colors) - (fn [event] + (fn [color-id] + (fn [event] + (dom/stop-propagation event) + (modal/show! :name-group-dialog {:accept (create-group color-id)})))) + + on-rename-group + (mf/use-callback + (mf/deps colors) + (fn [event path last-path] (dom/stop-propagation event) - (modal/show! :create-group-dialog {:create create-group})))] + (modal/show! :name-group-dialog {:path path + :last-path last-path + :accept rename-group}))) + on-ungroup + (mf/use-callback + (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))))] - [:div.asset-section - [:div.asset-title {:class (when (not open?) "closed")} - [:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :colors (not open?)))} - i/arrow-slide (t locale "workspace.assets.colors")] - [:span.num-assets (str "\u00A0(") (count colors) ")"] ;; Unicode 00A0 is non-breaking space + [:& asset-section {:file-id file-id + :title (tr "workspace.assets.colors") + :box :colors + :assets-count (count colors) + :open? open?} (when local? - [:div.assets-button {:on-click add-color-clicked} i/plus])] - (when open? - (for [group groups] - (let [path (first group) - colors (second group) - group-open? (not (contains? folded-groups path))] - [:* - (when-not (empty? path) - (let [[other-path last-path truncated] (cp/compact-path path 35)] - [:div.group-title {:class (when-not group-open? "closed") - :on-click (on-fold-group path)} - [:span i/arrow-slide] - (when-not (empty? other-path) - [:span.dim {:title (when truncated path)} - other-path "\u00A0/\u00A0"]) - [:span {:title (when truncated path)} - last-path]])) - (when group-open? - [:div.asset-list - (for [color colors] - (let [color (cond-> color - (:value color) (assoc :color (:value color) :opacity 1) - (:value color) (dissoc :value) - true (assoc :file-id file-id))] - [:& color-item {:key (:id color) - :color color - :file-id file-id - :local? local? - :selected-colors selected-colors - :multi-colors? multi-colors? - :multi-assets? multi-assets? - :on-asset-click on-asset-click - :on-assets-delete on-assets-delete - :on-clear-selection on-clear-selection - :on-group on-group - :colors colors - :locale locale}]))])])))])) + [:& asset-section-block {:role :title-button} + [:div.assets-button {:on-click add-color-clicked} + i/plus]]) + [:& asset-section-block {:role :content} + [:& colors-group {:file-id file-id + :prefix "" + :groups groups + :open-groups open-groups + :local? local? + :selected-colors selected-colors + :multi-colors? multi-colors? + :multi-assets? multi-assets? + :on-asset-click (partial on-asset-click groups) + :on-assets-delete on-assets-delete + :on-clear-selection on-clear-selection + :on-group on-group + :on-rename-group on-rename-group + :on-ungroup on-ungroup + :colors colors + :locale locale}]]])) ;; ---- Typography box ---- -(mf/defc typography-box - [{:keys [file file-id local? typographies locale open? selected-assets +(mf/defc typographies-group + [{:keys [file-id prefix groups open-groups file local? selected-typographies local + editting-id on-asset-click handle-change apply-typography + on-rename-group on-ungroup on-context-menu]}] + (let [group-open? (get open-groups prefix true)] + + [:* + [:& asset-group-title {:file-id file-id + :box :typographies + :path prefix + :group-open? group-open? + :on-rename on-rename-group + :on-ungroup on-ungroup}] + (when group-open? + [:* + (let [typographies (get groups "" [])] + [:div.asset-list + (for [typography typographies] + [:& typography-entry + {:key (:id typography) + :typography typography + :file file + :read-only? (not local?) + :on-context-menu #(on-context-menu (:id typography) %) + :on-change #(handle-change typography %) + :selected? (contains? selected-typographies (:id typography)) + :on-click #(on-asset-click % (:id typography) + (partial apply-typography typography)) + :editting? (= editting-id (:id typography)) + :focus-name? (= (:rename-typography local) (:id typography))}])]) + + (for [[path-item content] groups] + (when-not (empty? path-item) + [:& typographies-group {:file-id file-id + :prefix (cp/merge-path-item prefix path-item) + :groups content + :open-groups open-groups + :file file + :local? local? + :selected-typographies selected-typographies + :editting-id editting-id + :local local + :on-asset-click on-asset-click + :handle-change handle-change + :apply-typography apply-typography + :on-rename-group on-rename-group + :on-ungroup on-ungroup + :on-context-menu on-context-menu}]))])])) + +(mf/defc typographies-box + [{:keys [file file-id local? typographies locale open? open-groups selected-assets on-asset-click on-assets-delete on-clear-selection] :as props}] (let [state (mf/use-state {:detail-open? false - :menu-open? false - :top nil - :left nil - :id nil - :folded-groups empty-folded-groups}) + :id nil}) + + menu-state (mf/use-state auto-pos-menu-state) local (deref refs/workspace-local) groups (group-assets typographies) - folded-groups (:folded-groups @state) selected-typographies (:typographies selected-assets) multi-typographies? (> (count selected-typographies) 1) @@ -786,51 +1168,79 @@ create-group (mf/use-callback (mf/deps typographies selected-typographies on-clear-selection file-id) - (fn [name] + (fn [group-name] (on-clear-selection) (st/emit! (dwu/start-undo-transaction)) (apply st/emit! (->> typographies - (filter #(contains? selected-typographies (:id %))) + (filter #(if multi-typographies? + (contains? selected-typographies (:id %)) + (= (:id @state) (:id %)))) (map #(dwl/update-typography (assoc % :name - (str name " / " - (cp/merge-path-item (:path %) (:name %)))) + (add-group % group-name)) file-id)))) (st/emit! (dwu/commit-undo-transaction)))) - on-fold-group + rename-group (mf/use-callback - (mf/deps groups folded-groups) - (fn [path] - (fn [event] - (dom/stop-propagation event) - (swap! state update :folded-groups - toggle-folded-group path)))) + (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)))) on-group (mf/use-callback (mf/deps typographies selected-typographies) (fn [event] (dom/stop-propagation event) - (modal/show! :create-group-dialog {:create create-group}))) + (modal/show! :name-group-dialog {:accept create-group}))) + + on-rename-group + (mf/use-callback + (mf/deps typographies) + (fn [event path last-path] + (dom/stop-propagation event) + (modal/show! :name-group-dialog {:path path + :last-path last-path + :accept rename-group}))) + on-ungroup + (mf/use-callback + (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/update-typography + (assoc % :name + (ungroup % path)) + file-id)))) + (st/emit! (dwu/commit-undo-transaction)))) on-context-menu (mf/use-callback (mf/deps selected-typographies on-clear-selection) (fn [id event] (when local? - (let [pos (dom/get-client-position event) - top (:y pos) - left (- (:x pos) 20)] - (dom/prevent-default event) - (when-not (contains? selected-typographies id) - (on-clear-selection)) - (swap! state assoc - :menu-open? true - :top top - :left left - :id id))))) + (when-not (contains? selected-typographies id) + (on-clear-selection)) + (swap! state assoc :id id) + (swap! menu-state #(open-auto-pos-menu % event))))) + + on-close-menu + (mf/use-callback + (fn [] + (swap! menu-state close-auto-pos-menu))) closed-typography-edit (mf/use-callback @@ -863,58 +1273,45 @@ (when (:edit-typography local) (st/emit! #(update % :workspace-local dissoc :edit-typography))))) - [:div.asset-section - [:div.asset-title {:class (when (not open?) "closed")} - [:span {:on-click (st/emitf (dwl/set-assets-box-open file-id :typographies (not open?)))} - i/arrow-slide (t locale "workspace.assets.typography")] - [:span.num-assets (str "\u00A0(") (count typographies) ")"] ;; Unicode 00A0 is non-breaking space + [:& asset-section {:file-id file-id + :title (tr "workspace.assets.typography") + :box :typographies + :assets-count (count typographies) + :open? open?} (when local? - [:div.assets-button {:on-click add-typography} i/plus])] + [:& asset-section-block {:role :title-button} + [:div.assets-button {:on-click add-typography} + i/plus]]) - [:& context-menu - {:selectable false - :show (:menu-open? @state) - :on-close #(swap! state assoc :menu-open? false) - :top (:top @state) - :left (:left @state) - :options [(when-not (or multi-typographies? multi-assets?) - [(t locale "workspace.assets.rename") handle-rename-typography-clicked]) - (when-not (or multi-typographies? multi-assets?) - [(t locale "workspace.assets.edit") handle-edit-typography-clicked]) - [(t locale "workspace.assets.delete") handle-delete-typography] - (when-not multi-assets? - [(tr "workspace.assets.group") on-group])]}] - (when open? - (for [group groups] - (let [path (first group) - typographies (second group) - group-open? (not (contains? folded-groups path))] - [:* - (when-not (empty? path) - (let [[other-path last-path truncated] (cp/compact-path path 35)] - [:div.group-title {:class (when-not group-open? "closed") - :on-click (on-fold-group path)} - [:span i/arrow-slide] - (when-not (empty? other-path) - [:span.dim {:title (when truncated path)} - other-path "\u00A0/\u00A0"]) - [:span {:title (when truncated path)} - last-path]])) - (when group-open? - [:div.asset-list - (for [typography typographies] - [:& typography-entry - {:key (:id typography) - :typography typography - :file file - :read-only? (not local?) - :on-context-menu #(on-context-menu (:id typography) %) - :on-change #(handle-change typography %) - :selected? (contains? selected-typographies (:id typography)) - :on-click #(on-asset-click % (:id typography) {"" typographies} - (partial apply-typography typography)) - :editting? (= editting-id (:id typography)) - :focus-name? (= (:rename-typography local) (:id typography))}])])])))])) + [:& asset-section-block {:role :content} + [:& typographies-group {:file-id file-id + :prefix "" + :groups groups + :open-groups open-groups + :state state + :file file + :local? local? + :selected-typographies selected-typographies + :editting-id editting-id + :local local + :on-asset-click (partial on-asset-click groups) + :handle-change handle-change + :apply-typography apply-typography + :on-rename-group on-rename-group + :on-ungroup on-ungroup + :on-context-menu on-context-menu}] + + (when local? + [:& auto-pos-menu + {:on-close on-close-menu + :state @menu-state + :options [(when-not (or multi-typographies? multi-assets?) + [(t locale "workspace.assets.rename") handle-rename-typography-clicked]) + (when-not (or multi-typographies? multi-assets?) + [(t locale "workspace.assets.edit") handle-edit-typography-clicked]) + [(t locale "workspace.assets.delete") handle-delete-typography] + (when-not multi-assets? + [(tr "workspace.assets.group") on-group])]}])]])) ;; --- Assets toolbox ---- @@ -967,7 +1364,13 @@ (filter (fn [item] (or (matches-search (:name item "!$!") (:term filters)) (matches-search (:value item "!$!") (:term filters))))) - (sort-by #(str/lower (:name %)) comp-fn)))) + ; Sort by folder order, but putting all "root" items always first, + ; independently of sort order. + (sort-by #(str/lower (cp/merge-path-item (if (empty? (:path %)) + (if reverse-sort? "z" "a") + (:path %)) + (:name %))) + comp-fn)))) (mf/defc file-library [{:keys [file local? default-open? filters locale] :as props}] @@ -979,6 +1382,11 @@ (-> 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) @@ -1037,23 +1445,32 @@ extend-selected-assets (mf/use-callback (mf/deps @selected-assets) - (fn [asset-type asset-id asset-groups] - (swap! selected-assets update asset-type - (fn [selected] - (let [all-assets (-> asset-groups vals flatten) - clicked-idx (d/index-of-pred all-assets #(= (:id %) asset-id)) - selected-idx (->> selected - (map (fn [id] - (d/index-of-pred all-assets - #(= (:id %) id))))) - min-idx (apply min (conj selected-idx clicked-idx)) - max-idx (apply max (conj selected-idx clicked-idx))] + (fn [asset-type asset-groups asset-id] + (letfn [(flatten-groups + [groups] + (concat + (get groups "" []) + (reduce concat + [] + (->> (filter #(not (empty? (first %))) groups) + (map second) + (map flatten-groups)))))] + (swap! selected-assets update asset-type + (fn [selected] + (let [all-assets (flatten-groups asset-groups) + clicked-idx (d/index-of-pred all-assets #(= (:id %) asset-id)) + selected-idx (->> selected + (map (fn [id] + (d/index-of-pred all-assets + #(= (:id %) id))))) + min-idx (apply min (conj selected-idx clicked-idx)) + max-idx (apply max (conj selected-idx clicked-idx))] - (->> all-assets - d/enumerate - (filter #(<= min-idx (first %) max-idx)) - (map #(-> % second :id)) - set)))))) + (->> all-assets + d/enumerate + (filter #(<= min-idx (first %) max-idx)) + (map #(-> % second :id)) + set))))))) unselect-all (mf/use-callback @@ -1066,7 +1483,7 @@ on-asset-click (mf/use-callback (mf/deps toggle-selected-asset extend-selected-assets) - (fn [asset-type event asset-id all-assets default-click] + (fn [asset-type asset-groups event asset-id default-click] (cond (kbd/ctrl? event) (do @@ -1076,7 +1493,7 @@ (kbd/shift? event) (do (dom/stop-propagation event) - (extend-selected-assets asset-type asset-id all-assets)) + (extend-selected-assets asset-type asset-groups asset-id)) :else (when default-click @@ -1156,6 +1573,7 @@ :components components :listing-thumbs? listing-thumbs? :open? (open-box? :components) + :open-groups (open-groups :components) :selected-assets @selected-assets :on-asset-click (partial on-asset-click :components) :on-assets-delete on-assets-delete @@ -1167,6 +1585,7 @@ :objects media :listing-thumbs? listing-thumbs? :open? (open-box? :graphics) + :open-groups (open-groups :graphics) :selected-assets @selected-assets :on-asset-click (partial on-asset-click :graphics) :on-assets-delete on-assets-delete @@ -1177,22 +1596,24 @@ :locale locale :colors colors :open? (open-box? :colors) + :open-groups (open-groups :colors) :selected-assets @selected-assets :on-asset-click (partial on-asset-click :colors) :on-assets-delete on-assets-delete :on-clear-selection unselect-all}]) (when show-typography? - [:& typography-box {:file file - :file-id (:id file) - :local? local? - :locale locale - :typographies typographies - :open? (open-box? :typographies) - :selected-assets @selected-assets - :on-asset-click (partial on-asset-click :typographies) - :on-assets-delete on-assets-delete - :on-clear-selection unselect-all}]) + [:& typographies-box {:file file + :file-id (:id file) + :local? local? + :locale locale + :typographies typographies + :open? (open-box? :typographies) + :open-groups (open-groups :typographies) + :selected-assets @selected-assets + :on-asset-click (partial on-asset-click :typographies) + :on-assets-delete on-assets-delete + :on-clear-selection unselect-all}]) (when (and (not show-components?) (not show-graphics?) (not show-colors?)) [:div.asset-section diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a0cd82b375..3990c1d75f 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1591,6 +1591,10 @@ msgstr "No assets found" msgid "workspace.assets.rename" msgstr "Rename" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename-group" +msgstr "Rename group" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.search" msgstr "Search assets" @@ -1641,6 +1645,10 @@ msgstr "Ag" msgid "workspace.assets.typography.text-transform" msgstr "Text Transform" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.ungroup" +msgstr "Ungroup" + #: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs msgid "workspace.gradients.linear" msgstr "Linear gradient" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 7063b56497..a37ce99776 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1583,6 +1583,10 @@ msgstr "No se encontraron recursos" msgid "workspace.assets.rename" msgstr "Renombrar" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.rename-group" +msgstr "Renombrar grupo" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "workspace.assets.search" msgstr "Buscar recursos" @@ -1633,6 +1637,10 @@ msgstr "Ag" msgid "workspace.assets.typography.text-transform" msgstr "Transformar texto" +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.ungroup" +msgstr "Desagrupar" + #: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs msgid "workspace.gradients.linear" msgstr "Degradado lineal" From ebc79c278b49c4956ea710b540d659b2fc72eecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 31 May 2021 13:15:42 +0200 Subject: [PATCH 023/204] :recycle: Apply transducer-fu --- common/src/app/common/pages/helpers.cljc | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 9ad6aa04e9..7ab687dcaa 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -410,10 +410,10 @@ "Decompose a string in the form 'one / two / three' into a vector of strings, normalizing spaces." [path] - (->> (str/split path "/") - (map str/trim) - (remove str/empty?) - vec)) + (let [xf (comp (map str/trim) + (remove str/empty?))] + (->> (str/split path "/") + (into [] xf)))) (defn join-path "Regenerate a path as a string, from a vector." From 11f360bdab8675e3a40354058aba8093911b4c19 Mon Sep 17 00:00:00 2001 From: Antonio Date: Sun, 30 May 2021 11:58:52 +0000 Subject: [PATCH 024/204] :globe_with_meridians: Add translations for: Catalan. Currently translated at 30.5% (202 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ca/ --- frontend/translations/ca.po | 223 +++++++++++++++++++++++++++++++++++- 1 file changed, 219 insertions(+), 4 deletions(-) diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 0d59c23df1..6f3d1a3e2c 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-05-23 21:33+0000\n" +"PO-Revision-Date: 2021-06-01 00:38+0000\n" "Last-Translator: Antonio \n" "Language-Team: Catalan \n" @@ -18,7 +18,7 @@ msgstr "Ja teniu un compte?" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" msgstr "" -"Reviseu el correu i cliqueu l'enllaç per verificar i començar a utilitzar " +"Reviseu el correu i cliqueu l'enllaç per verificar i començar a utilitzar el " "Penpot." #: src/app/main/ui/auth/recovery.cljs @@ -436,7 +436,7 @@ msgstr "Descripció" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-go-to" -msgstr "" +msgstr "Ves als debats" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-subtitle1" @@ -450,7 +450,7 @@ msgstr "" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-title" -msgstr "" +msgstr "Debats de l'equip" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" @@ -603,3 +603,218 @@ msgstr "Entreu amb OpenID (SSO)" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-google-submit" msgstr "Entreu amb Google" + +#: 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 "Administració" + +msgid "history.alert-message" +msgstr "Esteu veient la versió %s" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Informació" + +msgid "handoff.tabs.code.selected.text" +msgstr "Text" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Rectangle" + +msgid "handoff.tabs.code.selected.path" +msgstr "Camí" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s seleccionats" + +msgid "handoff.tabs.code.selected.image" +msgstr "Imatge" + +msgid "handoff.tabs.code.selected.group" +msgstr "Grup" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Taula de treball" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Corba" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Cercle" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Espaiat de la lletra" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Codi" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "Majúscules" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Inicials en majúscules" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Cap" + +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "Minúscules" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Transformació del text" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Subratllat" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Barrat" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Cap" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Decoració del text" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Alçada de la línia" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Estil de la lletra" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Mida de la lletra" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-family" +msgstr "Família tipogràfica" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografia" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Amplada" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Sòlid" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Cap" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Mesclat" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Puntejat" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Exterior" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Interior" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Centre" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Traç" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.spread" +msgstr "S" + +#: 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.offset-x" +msgstr "X" + +#: 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" +msgstr "Ombra" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Amplada" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Superior" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Rotació" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Radi" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Esquerra" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Alçada" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout" +msgstr "Disposició" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Amplada" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "Alçada" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "Baixa la imatge original" + +#: src/app/main/ui/handoff/attributes/fill.cljs +msgid "handoff.attributes.fill" +msgstr "Emplenat" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.rgba" +msgstr "RGBA" + +#: 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.hex" +msgstr "HEX" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "Valor" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Difuminat" From de5e8f8e576227b6f41252414edbde04b4284332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=87a=C4=9Flar=20Ye=C5=9Filyurt?= Date: Sun, 30 May 2021 23:45:55 +0000 Subject: [PATCH 025/204] :globe_with_meridians: Add translations for: Turkish. Currently translated at 92.9% (615 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/tr/ --- frontend/translations/tr.po | 1392 ++++++++++++++++++++++++++++++++++- 1 file changed, 1384 insertions(+), 8 deletions(-) diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index ccf5c31cb4..f60df61da9 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -1,7 +1,7 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-05-23 21:33+0000\n" -"Last-Translator: Gizem Akgüney \n" +"PO-Revision-Date: 2021-06-01 00:38+0000\n" +"Last-Translator: Çağlar Yeşilyurt \n" "Language-Team: Turkish \n" "Language: tr\n" @@ -212,10 +212,10 @@ msgstr "Burada hiç dosyan yok" msgid "dashboard.fonts.hero-text2" msgstr "" "Sadece kendinize ait veya Penpot'ta kullanılabilecek bir lisansa sahip olan " -"fontları yükleyebilirsiniz. [Penpot's Terms of Service] içindeki İçerik " -"hakları bölümünden detaylı bilgi alabilirsiniz " -"(https://penpot.app/terms.html). Ayrıca [font licensing](2) hakkında daha " -"fazla bilgi almak isteyebilirsiniz." +"yazi tiplerini yükleyebilirsiniz. [Penpot'un Kullanım Şartları] içindeki " +"İçerik hakları bölümünden detaylı bilgi alabilirsiniz (https://penpot.app/" +"terms.html). Ayrıca [yazı tipi lisanslama](https://www.typography.com/faq) " +"hakkında daha fazla bilgi almak isteyebilirsiniz." #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" @@ -908,7 +908,7 @@ msgid "labels.upload" msgstr "Yükle" msgid "labels.uploading" -msgstr "Yükleniyor..." +msgstr "Yükleniyor…" msgid "modals.delete-font.message" msgstr "" @@ -1033,7 +1033,7 @@ msgstr "Üst" #: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.rotation" -msgstr "Rotasyon" +msgstr "Döndür" #: src/app/main/ui/handoff/attributes/layout.cljs msgid "handoff.attributes.layout.left" @@ -1054,3 +1054,1379 @@ msgid "feedback.discussions-subtitle2" msgstr "" "Soru sorabilir ve soruları cevaplayabilir, açık uçlu tartışmalar yapabilir " "ve projeyi etkileyen kararları takip edebilirsin." + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s tipografi" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Tüm tipografileri ayır" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography" +msgstr "Çoklu tipografiler" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Küçük önizlemeler" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Büyük önizlemeler" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Çeşit" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografiler" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Görünüm modu - Penpot" + +#: 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 "" +"Paylaşılmış bir kütüphanedeki bileşeni güncellemek üzeresin. Onu kullanan " +"diğer dosyalar etkilenebilir." + +#: 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 "Bileşeni güncelle" + +#: 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.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." + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "Sağdan sola" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "Soldan sağa" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.titlecase" +msgstr "İlk Harfi Büyük" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-vertical" +msgstr "Dikey ters çevir" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Yatay ters çevir" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Düzenlemek için biçim kütüphane dosyasına gidin" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hcenter" +msgstr "Yatay olarak ortaya hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.stretch" +msgstr "Ger" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Sağ" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Sol" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Orta" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Alt" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type" +msgstr "Tür" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.size" +msgstr "Boyut" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.set-default" +msgstr "Varsayılan olarak belirle" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.rows" +msgstr "Satırlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Kenar Boşluğu" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.height" +msgstr "Yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Sütunlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Sütunlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Otomatik" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Doldur" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.exporting-object" +msgstr "Dışarı aktarılıyor…" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +msgid "workspace.options.export.suffix" +msgstr "Son ek" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgstr "Şekli dışarı aktar" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export" +msgstr "Dışarı Aktar" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.design" +msgstr "Tasarım" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Bileşen" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Izgaraya tutturmayı kapat" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-scale-text" +msgstr "Metin ölçeklendirmeyi kapat" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Dinamik hizalamayı kapat" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Dairesel degrade" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Doğrusal degrade" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "Grup oluştur" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Bileşenler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Renkler" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Grafikler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Tüm varlıklar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Varlıklar" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vtop" +msgstr "Üste hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vdistribute" +msgstr "Dikeyde dağıt" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "Yatayda dağıt" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.edit-page" +msgstr "Sayfayı düzenle" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "Etkileşimleri gösterme" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.frame-not-found" +msgstr "Çerçeve bulunmadı." + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "Sayfada çerçeve bulunmuyor." + +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.font-providers" +msgstr "Yazıtipi Sağlayıcıları - %s - Penpot" + +#: 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 "İptal" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.accept" +msgstr "Paylaşılmış Kütüphane olarak kaldır" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.title" +msgstr "Sahip olarak terfi et" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.message" +msgstr "Bu kullanıcıyı sahip olarak terfi etmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.title" +msgstr "Takımdan ayrılmak" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint2" +msgstr "Ayrılmadan önce terfi etmek için başka bir üye seçin" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Karışık" + +#: 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.saturation" +msgstr "Doygunluk" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.overlay" +msgstr "Üst katman" + +#: 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.color-burn" +msgstr "Renk yanması" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.curve" +msgstr "Eğri (%s)" + +msgid "workspace.path.actions.snap-nodes" +msgstr "Düğümleri tuttur (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Üstü çizili" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Satır yüksekliği" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.letter-spacing" +msgstr "Harf Aralıkları" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Sabit" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Otomatik genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-height" +msgstr "Otomatik yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.google" +msgstr "Google" + +msgid "workspace.options.text-options.decoration" +msgstr "Süsleme" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-top" +msgstr "Üste hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-right" +msgstr "Sağa hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-middle" +msgstr "Merkeze hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-left" +msgstr "Sola hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-justify" +msgstr "İki yana yasla" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-center" +msgstr "Ortaya hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Alta hizala" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Çalışma Yüzeyi (%s)" + +msgid "workspace.undo.entry.single.frame" +msgstr "çalışma yüzeyi" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "çalışma yüzeyi" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-artboard" +msgstr "Çalışma yüzeyi seç" + +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"Birisini takımın sahibi yapmadan takımı bırakamazsın. Takımı silmek " +"isteyebilirsin." + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Takım üyesini sil" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Bu üyeyi takımdan silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Üyeyi sil" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Takımın silinmesi" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Bu takımı silmek istediğinden emin misin? Takımla ilişkili dosyalar ve " +"projeler kalıcı olarak silinecektir." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Takımı sil" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Projeyi sil" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Bu projeyi silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Projeyi sil" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Sayfayı sil" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Bu sayfayı silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "%s dosyanın silinmesi" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "%s dosyayı silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.accept" +msgstr "Dosyalar sil" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Dosya siliniyor" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "Bu dosyayı silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Dosyayı sil" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.title" +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." + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Konuşmayı sil" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Çalışma yüzeyi" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "İlk Harfleri Büyük" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Metin Dönüşümü" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Metin Süsleme" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.spread" +msgstr "Y" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vcenter" +msgstr "Dikey olarak ortaya hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vbottom" +msgstr "Alta hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hright" +msgstr "Sağa hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hleft" +msgstr "Sola hizala" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Site haritası" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions-on-click" +msgstr "Tıklamada etkileşimleri göster" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "Etkileşimleri göster" + +#: 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 "Bağlantıyı paylaş" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Bağlantıya sahip herkes erişebilecek" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Bağlantıyı kaldır" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "Paylaşım adresi burada görünecek" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Bağlantı oluştur" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "Bağlantıyı kopyala" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "Tam Ekran" + +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - 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 "Karışık" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Giriş yap" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "" +"%s adresine doğrulama e-postası gönderildi. E-postalarınızı kontrol edin!" + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Profil başarıyla kaydedildi!" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "Profilini silemezsin. Önce takımlarını birine atamalsın." + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Davet başarıyla iletildi" + +#: 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 "Paylaşılmış bir kütüphanede bir bileşen güncelle" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Ayarlar * %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Üyeler - %s - Penpot" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Profil - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Parola - Penpot" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Ayarlar - Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Geri bildirimde bulun - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Ara - %s - Penpot" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "Güncelleme gerektiren Paylaşılmış Kütüphane bulunmuyor" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.group" +msgstr "Gölge grubu" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Gölge" + +#: 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/component.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.go-main" +msgstr "Ana bileşen dosyasına git" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.front" +msgstr "En öne getir" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.forward" +msgstr "Öne getir" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Düzenle" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Çoğalt" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Sil" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Kes" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Bileşen oluştur" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Kopyala" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "En arkaya gönder" + +msgid "workspace.path.actions.move-nodes" +msgstr "Düğümleri taşı (%s)" + +msgid "workspace.path.actions.merge-nodes" +msgstr "Düğümleri birleştir (%s)" + +msgid "workspace.path.actions.make-curve" +msgstr "Eğriye (%s)" + +msgid "workspace.path.actions.make-corner" +msgstr "Köşeye (%s)" + +msgid "workspace.path.actions.join-nodes" +msgstr "Düğümleri birleştir (%s)" + +msgid "workspace.path.actions.draw-nodes" +msgstr "Düğüm çiz (%s)" + +msgid "workspace.path.actions.delete-node" +msgstr "Düğüm sil (%s)" + +msgid "workspace.options.text-options.vertical-align" +msgstr "Düşey hizalama" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Büyük Harf" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Altı Çizili" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +#, fuzzy +msgid "workspace.options.text-options.title-selection" +msgstr "Metin seçimi" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Grup metni" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Metin" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Küçük harf" + +msgid "workspace.undo.entry.multiple.group" +msgstr "gruplar" + +msgid "workspace.undo.entry.multiple.curve" +msgstr "eğriler" + +msgid "workspace.undo.entry.multiple.component" +msgstr "bileşenler" + +msgid "workspace.undo.entry.multiple.color" +msgstr "renk varlıkları" + +msgid "workspace.undo.entry.multiple.circle" +msgstr "daireler" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.move" +msgstr "Nesneler taşındı" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.modify" +msgstr "%s düzenlendi" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "%s silindi" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.empty" +msgstr "Şu ana kadar değişim geçmişi yok" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Metin (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Taşı" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Resim (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "Elips (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Yorumlar (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Varlıklar(%s)" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "workspace.sidebar.sitemap" +msgstr "Sayfalar" + +#: 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 Öznitelikleri İçeri Aktarıldı" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.layers" +msgstr "Katmanlar (%s)" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "Paylaşılmış kütüphanelerde güncellemeler mevcut" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Gözardı et" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.unknown" +msgstr "%s üstündeki işlem" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.title" +msgstr "Geçmiş" + +msgid "workspace.undo.entry.single.text" +msgstr "metin" + +msgid "workspace.undo.entry.single.shape" +msgstr "şekil" + +msgid "workspace.undo.entry.single.rect" +msgstr "dikdörtgen" + +msgid "workspace.undo.entry.single.page" +msgstr "sayfa" + +msgid "workspace.undo.entry.single.multiple" +msgstr "nesne" + +msgid "workspace.undo.entry.single.media" +msgstr "grafik varlığı" + +msgid "workspace.undo.entry.single.image" +msgstr "resim" + +msgid "workspace.undo.entry.single.group" +msgstr "grup" + +msgid "workspace.undo.entry.single.curve" +msgstr "eğri" + +msgid "workspace.undo.entry.single.component" +msgstr "bileşen" + +msgid "workspace.undo.entry.single.color" +msgstr "renk varlığı" + +msgid "workspace.undo.entry.single.circle" +msgstr "daire" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.new" +msgstr "Yeni %s" + +msgid "workspace.undo.entry.multiple.text" +msgstr "metinler" + +msgid "workspace.undo.entry.multiple.shape" +msgstr "şekiller" + +msgid "workspace.undo.entry.multiple.rect" +msgstr "dikdörtgenler" + +msgid "workspace.undo.entry.multiple.page" +msgstr "sayfalar" + +msgid "workspace.undo.entry.multiple.multiple" +msgstr "nesneler" + +msgid "workspace.undo.entry.multiple.media" +msgstr "grafik varlığı" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.update" +msgstr "Güncelle" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Noktalı" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Çizgili" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Merkez" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Renk" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.square" +msgstr "Kare" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.row" +msgstr "Satırlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.width" +msgstr "Genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Varsayılanı kullan" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Üst" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Bulanıklık" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Katman" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Arkaplan" + +msgid "workspace.library.own" +msgstr "Kütüphanelerim" + +msgid "workspace.library.libraries" +msgstr "Kütüphaneler" + +msgid "workspace.library.all" +msgstr "Tüm kütüphaneler" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "GÜNCELLEMELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Güncelle" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Paylaşılmış kütüphane ara" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "PAYLAŞILMIŞ KÜTÜPHANELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "Paylaşılmış Kütüphane bulunmuyor" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "“%s“ için eşleşme bulunmadı" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s bileşen" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "KÜTÜPHANE" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "KÜTÜPHANELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "BU DOSYADAKİ KÜTÜPHANELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s grafik" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "Dosya kütüphanesi" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Renk biçimini kaydet" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Son renkler" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Dosya kütüphanesi" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s renk" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Ekle" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Görünüm modu (%s)" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Kaydedilmemiş değişiklikler" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Kaydediliyor" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Kaydedildi" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Kaydetmede hata" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-rules" +msgstr "Cetveli göster" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Renk paletini 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-grid" +msgstr "Izgarayı 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.select-all" +msgstr "Tümünü seç" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Cetveli gizle" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Renk paletini 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-grid" +msgstr "Izgaraları gizle" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-assets" +msgstr "Varlıkları gizle" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +msgstr "Metin Dönüşümü" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Satır Yüksekliği" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Harf Boşluğu" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Boyut" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Yazı tipi" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "PAYLAŞILDI" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.selected-count" +msgid_plural "workspace.assets.selected-count" +msgstr[0] "Tek öge seçildi" +msgstr[1] "%s öge seçildi" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Varlık ara" + +#: 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 "Yeniden adlandır" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "Varlık bulunmadı" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Kütüphaneler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group-name" +msgstr "Grup adı" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Grup" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Grafikler" + +#: 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.edit" +msgstr "Düzenle" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Çoğalt" + +#: 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 "Sil" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.accept" +msgstr "Terfi et" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.message" +msgstr "Bu takımdan ayrılmak istediğinden emin misin?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.accept" +msgstr "Takımdan ayrıl" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.title" +msgstr "Terfi etmek için bir üye seçin" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.select-memeber-to-promote" +msgstr "Terfi etmek için bir üye seçin" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.promote-and-leave" +msgstr "Terfi et ve ayrıl" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "%s sahibisiniz." + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member.title" +msgstr "Takıma katılma daveti gönder" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Davet gönder" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.info" +msgstr "Hesabını silerek tüm projelerini ve arşivlerini kaybedeceksin." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.title" +msgstr "Hesabını silmek istediğinden emin misin?" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.confirm" +msgstr "Evet, hesabımı sil" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.cancel" +msgstr "İptal et ve hesabımı koru" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "E-postanızı değiştirin" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "E-postayı değiştir" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Yeni e-posta" + +#: 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." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.confirm-email" +msgstr "Yeni e-postayı doğrulayın" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.message" +msgstr "Paylaşılmış Kütüphane olarak “%s” Ekle" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.hint" +msgstr "" +"Paylaşılmış Kütüphane olarak eklenince, bu dosya kütüphanesindeki varlıklar " +"diğer dosyalarınızdan da ulaşılabilecek." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.accept" +msgstr "Paylaşılmış Kütüphane olarak Ekle" + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "Resim yükleniyor…" + +#: src/app/main/ui/comments.cljs +msgid "labels.write-new-comment" +msgstr "Yeni yorum yaz" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Görüntüler" + +msgid "labels.upload-custom-fonts" +msgstr "Özel yazı tipi yükle" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Takımı güncelle" + +msgid "labels.icons" +msgstr "Simgeler" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Yarı Çap" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot * Takımlar için Özgür Tasarım" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Paylaşılmış Kütüphaneler - %s - Penpot" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Projeler - %s - Penpot" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Izgaraya tuttur" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-scale-text" +msgstr "Metin ölçeklendirmeyi etkinleştir" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Dinamik hizalamayı etkinleştir" + +#: 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 "Boyut" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Gölge seçimi" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.spread" +msgstr "Yayılma" + +#: 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.inner-shadow" +msgstr "İç gölge" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Kabartı gölgesi" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Döndür" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.single-corners" +msgstr "Tek köşe" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Tüm köşeler" + +msgid "workspace.options.radius" +msgstr "Yarı çap" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Prototip" + +#: 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 "Konum" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.none" +msgstr "Hiç biri" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title.multiple" +msgstr "Seçili katmanlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title.group" +msgstr "Katman grubu" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Katman" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "Geçmiş (%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.update-main" +msgstr "Ana bileşeni güncelle" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unlock" +msgstr "Çöz" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Grubu dağıt" + +#: 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 "Ana bileşeni göster" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Göster" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Yapıştır" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Maskele" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Kilitle" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Gizle" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.group" +msgstr "Grup" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "Arkaya gönder" + +msgid "workspace.path.actions.separate-nodes" +msgstr "Düğümleri ayır (%s)" + +msgid "workspace.path.actions.add-node" +msgstr "Düğüm ekle (%s)" + +#: 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." + +#: 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 "Hiçbiri" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Dikdörtgen (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.color-palette" +msgstr "Renk Paketi (%s)" From e45f7598dbab831ad8174ca436ae38cfba36ed87 Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Tue, 25 May 2021 16:28:59 +0200 Subject: [PATCH 026/204] :sparkles: First viewer header changes --- .../styles/main/partials/viewer-header.scss | 27 ++++++++++++++----- frontend/src/app/main/data/workspace.cljs | 2 +- frontend/src/app/main/ui/viewer.cljs | 2 +- frontend/src/app/main/ui/viewer/header.cljs | 8 +++--- .../src/app/main/ui/workspace/header.cljs | 1 - frontend/translations/de.po | 2 +- frontend/translations/en.po | 2 +- frontend/translations/es.po | 2 +- frontend/translations/fr.po | 2 +- 9 files changed, 30 insertions(+), 18 deletions(-) diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss index 9e8449c461..652ff98979 100644 --- a/frontend/resources/styles/main/partials/viewer-header.scss +++ b/frontend/resources/styles/main/partials/viewer-header.scss @@ -56,17 +56,28 @@ } } - svg { - fill: $color-gray-30; - height: 16px; - width: 16px; - } - .dropdown { top: 40px; left: 0px; width: 260px; } + + .view-options-dropdown { + align-items: center; + cursor: pointer; + display: flex; + + span { + font-size: $fs13; + margin-right: $small; + } + + svg { + fill: $color-gray-20; + height: 12px; + width: 12px; + } + } } .sitemap-zone { @@ -165,6 +176,10 @@ height: 20px; } } + + .btn-primary { + flex-shrink: 0; + } } .share-link-dropdown { diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index f7017f541a..e973bf1dee 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1248,7 +1248,7 @@ params {:file-id (or file-id current-file-id) :page-id (or page-id current-page-id)}] (rx/of ::dwp/force-persist - (rt/nav :viewer params {:index 0}))))))) + (rt/nav-new-window :viewer params {:index 0}))))))) (defn go-to-dashboard ([] (go-to-dashboard nil)) diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index 930968526a..a5f330b7d8 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -273,7 +273,7 @@ (mf/use-effect (mf/deps (:file data)) #(when-let [name (get-in data [:file :name])] - (dom/set-html-title (tr "title.viewer" name)))) + (dom/set-html-title (str "\u25b6 " (tr "title.viewer" name))))) (when (and data state) [:& viewer-content diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 46a29b984a..9d465e2ee8 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -118,7 +118,9 @@ (st/emit! (dv/set-interactions-mode mode))))] [:div.view-options - [:div.icon {:on-click #(swap! show-dropdown? not)} i/eye] + [:div.view-options-dropdown {:on-click #(swap! show-dropdown? not)} + [:span "Interactions"] + i/arrow-down] [:& dropdown {:show @show-dropdown? :on-close hide-dropdown} [:ul.dropdown.with-check @@ -266,10 +268,6 @@ [:& share-link {:token (:token data) :page (:page data)}]) - (when has-permission? - [:a.btn-text-basic.btn-small {:on-click on-edit} - (t locale "viewer.header.edit-page")]) - [:& zoom-widget {:zoom (:zoom state) :on-increase (st/emitf dv/increase-zoom) diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 309803e9ee..9b5d230d82 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -282,7 +282,6 @@ [:a.btn-icon-dark.btn-small.tooltip.tooltip-bottom-left {:alt (tr "workspace.header.viewer" (sc/get-tooltip :open-viewer)) - :href (str "#" view-url) :on-click go-viewer} i/play]]])) diff --git a/frontend/translations/de.po b/frontend/translations/de.po index c5179aefad..087a3af41b 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1328,7 +1328,7 @@ msgstr "Jeder mit dem Link hat Zugriff" #: 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 "Link teilen" +msgstr "Prototyp teilen" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a0cd82b375..d36e224d0f 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1478,7 +1478,7 @@ msgstr "Anyone with the link will have access" #: 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 "Share link" +msgstr "Share prototype" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 7063b56497..7cc5bc35f1 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1468,7 +1468,7 @@ msgstr "Cualquiera con el enlace podrá acceder" #: 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 "Enlace" +msgstr "Compartir prototipo" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 34e70bab41..1883e1a36c 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -1224,7 +1224,7 @@ msgstr "Toute personne disposant du lien aura 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 "Lien de partage" +msgstr "Partager le prototype" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" From 3d5caf18e3fb15f99ec0b7b7de3e3e4255b8587a Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Wed, 26 May 2021 09:05:11 +0200 Subject: [PATCH 027/204] :sparkles: Add new interactions link and translations --- .../resources/styles/main/partials/viewer-header.scss | 9 +++++---- frontend/src/app/main/ui/viewer/header.cljs | 2 +- frontend/translations/de.po | 4 ++++ frontend/translations/en.po | 4 ++++ frontend/translations/es.po | 4 ++++ frontend/translations/fr.po | 4 ++++ frontend/translations/ro.po | 4 ++++ frontend/translations/ru.po | 4 ++++ 8 files changed, 30 insertions(+), 5 deletions(-) diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss index 652ff98979..891f836670 100644 --- a/frontend/resources/styles/main/partials/viewer-header.scss +++ b/frontend/resources/styles/main/partials/viewer-header.scss @@ -57,9 +57,9 @@ } .dropdown { - top: 40px; + min-width: 260px; left: 0px; - width: 260px; + top: 40px; } .view-options-dropdown { @@ -68,12 +68,13 @@ display: flex; span { + color: $color-gray-10; font-size: $fs13; - margin-right: $small; + margin-right: $x-small; } svg { - fill: $color-gray-20; + fill: $color-gray-10; height: 12px; width: 12px; } diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 9d465e2ee8..0b82dd4344 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -119,7 +119,7 @@ [:div.view-options [:div.view-options-dropdown {:on-click #(swap! show-dropdown? not)} - [:span "Interactions"] + [:span (t locale "viewer.header.interactions")] i/arrow-down] [:& dropdown {:show @show-dropdown? :on-close hide-dropdown} diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 087a3af41b..0fbb95166f 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -1330,6 +1330,10 @@ msgstr "Jeder mit dem Link hat Zugriff" msgid "viewer.header.share.title" msgstr "Prototyp teilen" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interaktionen" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Interaktionen anzeigen" diff --git a/frontend/translations/en.po b/frontend/translations/en.po index d36e224d0f..925e171c15 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1480,6 +1480,10 @@ msgstr "Anyone with the link will have access" msgid "viewer.header.share.title" msgstr "Share prototype" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interactions" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Show interactions" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 7cc5bc35f1..f4824a4784 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1470,6 +1470,10 @@ msgstr "Cualquiera con el enlace podrá acceder" msgid "viewer.header.share.title" msgstr "Compartir prototipo" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interacciones" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Mostrar interacciones" diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 1883e1a36c..313aa753d5 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -1226,6 +1226,10 @@ msgstr "Toute personne disposant du lien aura accès" msgid "viewer.header.share.title" msgstr "Partager le prototype" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interactions" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Afficher les interactions" diff --git a/frontend/translations/ro.po b/frontend/translations/ro.po index 6823ed2731..3a0e4402b5 100644 --- a/frontend/translations/ro.po +++ b/frontend/translations/ro.po @@ -1469,6 +1469,10 @@ msgstr "Distribuie link" msgid "viewer.header.show-interactions" msgstr "Afişează interacţiunile" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interacţiunile" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions-on-click" msgstr "Afişează interacţiunile la click" diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index 9118ae5359..b56f9f0789 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -599,6 +599,10 @@ msgstr "Любой, у кого есть ссылка будет иметь до msgid "viewer.header.share.title" msgstr "Поделиться ссылкой" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header-interactions" +msgstr "взаимодействия" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Показывать взаимодействия" From a244fbee4d206dc8acca050551c48a19bb846102 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 2 Jun 2021 13:20:25 +0200 Subject: [PATCH 028/204] :paperclip: Fix linter issue. --- backend/src/app/loggers/audit.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index bd7807eb28..71cf7e1ae6 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -80,7 +80,7 @@ :max-batch-age (* 10 1000) ; 10s :init []})] (a/go-loop [] - (when-let [[type events] (a/ Date: Wed, 2 Jun 2021 14:10:25 +0200 Subject: [PATCH 029/204] :sparkles: Adapt exporter to common changes. --- common/deps.edn | 1 + exporter/deps.edn | 18 ++++- exporter/package.json | 4 +- exporter/shadow-cljs.edn | 12 +--- exporter/yarn.lock | 145 ++++++++++++++++++++------------------- 5 files changed, 96 insertions(+), 84 deletions(-) diff --git a/common/deps.edn b/common/deps.edn index 9955b4cc85..2b6183ad41 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -23,6 +23,7 @@ selmer/selmer {:mvn/version "1.12.40"} expound/expound {:mvn/version "0.8.9"} com.cognitect/transit-clj {:mvn/version "1.0.324"} + com.cognitect/transit-cljs {:mvn/version "0.8.269"} java-http-clj/java-http-clj {:mvn/version "0.4.2"} funcool/promesa {:mvn/version "6.0.1"} diff --git a/exporter/deps.edn b/exporter/deps.edn index 86c907f65c..2d1b03b6c0 100644 --- a/exporter/deps.edn +++ b/exporter/deps.edn @@ -1,9 +1,23 @@ -{:paths [] - :deps {} +{:paths ["src" "vendor" "resources" "test"] + :deps + {penpot/common {:local/root "../common"} + binaryage/devtools {:mvn/version "RELEASE"} + metosin/reitit-core {:mvn/version "0.5.13"} + lambdaisland/glogi {:mvn/version "1.0.106"} + funcool/beicon {:mvn/version "2021.04.29-0"} + } :aliases {:outdated {:extra-deps {com.github.liquidz/antq {:mvn/version "RELEASE"} org.slf4j/slf4j-nop {:mvn/version "RELEASE"}} :main-opts ["-m" "antq.core"]} + + :dev + {:extra-deps + {thheller/shadow-cljs {:mvn/version "2.14.1"}}} + + :shadow-cljs + {:main-opts ["-m" "shadow.cljs.devtools.cli"]} + }} diff --git a/exporter/package.json b/exporter/package.json index fb08208872..6d36517e69 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -12,14 +12,14 @@ "inflation": "^2.0.0", "jszip": "^3.6.0", "koa": "^2.13.0", - "puppeteer": "^9.1.0", + "puppeteer": "^10.0.0", "puppeteer-cluster": "^0.22.0", "raw-body": "^2.4.1", "xml-js": "^1.6.11", "xregexp": "^5.0.2" }, "devDependencies": { - "shadow-cljs": "^2.12.5", + "shadow-cljs": "^2.14.2", "source-map-support": "^0.5.19" } } diff --git a/exporter/shadow-cljs.edn b/exporter/shadow-cljs.edn index 7a7ca5859c..da622bf434 100644 --- a/exporter/shadow-cljs.edn +++ b/exporter/shadow-cljs.edn @@ -1,14 +1,4 @@ -{:dependencies - [[com.cognitect/transit-cljs "0.8.269"] - [danlentz/clj-uuid "0.1.9"] - [frankiesardo/linked "1.3.0"] - [funcool/cuerdas "2021.05.02-0"] - [funcool/promesa "6.0.0"] - [integrant/integrant "0.8.0"] - [lambdaisland/glogi "1.0.106"] - [lambdaisland/uri "1.4.54"] - [metosin/reitit-core "0.5.13"]] - +{:deps {:aliases [:dev]} :source-paths ["src" "vendor" "../common"] :jvm-opts ["-Xmx512m" "-Xms50m" "-XX:+UseSerialGC"] diff --git a/exporter/yarn.lock b/exporter/yarn.lock index 43907db232..195118ebbb 100644 --- a/exporter/yarn.lock +++ b/exporter/yarn.lock @@ -11,9 +11,9 @@ regenerator-runtime "^0.13.4" "@types/node@*": - version "15.0.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.0.2.tgz#51e9c0920d1b45936ea04341aa3e2e58d339fb67" - integrity sha512-p68+a+KoxpoB47015IeYZYRrdqMUcpbK8re/zpFB8Ld46LHC1lPEbp3EXgkEhAYEcPvjJF6ZO+869SQ0aH1dcA== + version "15.6.2" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.6.2.tgz#c61d49f38af70da32424b5322eee21f97e627175" + integrity sha512-dxcOx8801kMo3KlU+C+/ctWrzREAH7YvoF3aoVpRdqgs+Kf7flp+PJDN/EX5bME3suDUZHsxes9hpvBmzYlWbA== "@types/yauzl@^2.9.1": version "2.9.1" @@ -272,9 +272,9 @@ cookies@~0.8.0: keygrip "~1.1.0" core-js-pure@^3.0.0: - version "3.12.1" - resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.12.1.tgz#934da8b9b7221e2a2443dc71dfa5bd77a7ea00b8" - integrity sha512-1cch+qads4JnDSWsvc7d6nzlKAippwjUlf6vykkTLW53VSV+NkE6muGBToAjEA8pG90cSfcud3JgVmW2ds5TaQ== + version "3.13.1" + resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.13.1.tgz#5d139d346780f015f67225f45ee2362a6bed6ba1" + integrity sha512-wVlh0IAi2t1iOEh16y4u1TRk6ubd4KvLE8dlMi+3QUI6SfKphQUh7tAwihGGSQ8affxEXpVIPpOdf9kjR4v4Pw== core-util-is@~1.0.0: version "1.0.2" @@ -329,7 +329,7 @@ crypto-browserify@^3.11.0: randombytes "^2.0.0" randomfill "^1.0.3" -debug@4, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@4.3.1, debug@^4.1.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -376,10 +376,10 @@ destroy@^1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -devtools-protocol@0.0.869402: - version "0.0.869402" - resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.869402.tgz#03ade701761742e43ae4de5dc188bcd80f156d8d" - integrity sha512-VvlVYY+VDJe639yHs5PHISzdWTLL3Aw8rO4cvUtwvoxFd6FHbE4OpHHcde52M6096uYYazAmd4l0o5VuFRO2WA== +devtools-protocol@0.0.883894: + version "0.0.883894" + resolved "https://registry.yarnpkg.com/devtools-protocol/-/devtools-protocol-0.0.883894.tgz#d403f2c75cd6d71c916aee8dde9258da988a4da9" + integrity sha512-33idhm54QJzf3Q7QofMgCvIVSd2o9H3kQPWaKT/fhoZh+digc+WSiMhbkeG3iN79WY4Hwr9G05NpbhEVrsOYAg== diffie-hellman@^5.0.0: version "5.0.3" @@ -443,7 +443,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" -extract-zip@^2.0.0: +extract-zip@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== @@ -564,7 +564,7 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@^5.0.0: +https-proxy-agent@5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== @@ -734,17 +734,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.47.0: - version "1.47.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" - integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== +mime-db@1.48.0: + version "1.48.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" + integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== mime-types@^2.1.18, mime-types@~2.1.24: - version "2.1.30" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" - integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== + version "2.1.31" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" + integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== dependencies: - mime-db "1.47.0" + mime-db "1.48.0" minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" @@ -763,10 +763,17 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -mkdirp-classic@^0.5.2: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz#fa10c9115cc6d8865be221ba47ee9bed78601113" - integrity sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A== +minimist@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" + integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== + +mkdirp@^0.5.1: + version "0.5.5" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" + integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== + dependencies: + minimist "^1.2.5" ms@2.0.0: version "2.0.0" @@ -783,7 +790,7 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -node-fetch@^2.6.1: +node-fetch@2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -917,7 +924,7 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= -pkg-dir@^4.2.0: +pkg-dir@4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== @@ -934,12 +941,12 @@ process@^0.11.10: resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= -progress@^2.0.1: - version "2.0.3" - resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" - integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +progress@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.1.tgz#c9242169342b1c29d275889c95734621b1952e31" + integrity sha512-OE+a6vzqazc+K6LxJrX5UPyKFvGnL5CYmq2jFGNIBWHpc4QyE49/YOumcrpQFJpfejmvRtbJzgO1zPmMCqlbBg== -proxy-from-env@^1.1.0: +proxy-from-env@1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== @@ -981,23 +988,23 @@ puppeteer-cluster@^0.22.0: dependencies: debug "^4.1.1" -puppeteer@^9.1.0: - version "9.1.1" - resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-9.1.1.tgz#f74b7facf86887efd6c6b9fabb7baae6fdce012c" - integrity sha512-W+nOulP2tYd/ZG99WuZC/I5ljjQQ7EUw/jQGcIb9eu8mDlZxNY2SgcJXTLG9h5gRvqA3uJOe4hZXYsd3EqioMw== +puppeteer@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/puppeteer/-/puppeteer-10.0.0.tgz#1b597c956103e2d989ca17f41ba4693b20a3640c" + integrity sha512-AxHvCb9IWmmP3gMW+epxdj92Gglii+6Z4sb+W+zc2hTTu10HF0yg6hGXot5O74uYkVqG3lfDRLfnRpi6WOwi5A== dependencies: - debug "^4.1.0" - devtools-protocol "0.0.869402" - extract-zip "^2.0.0" - https-proxy-agent "^5.0.0" - node-fetch "^2.6.1" - pkg-dir "^4.2.0" - progress "^2.0.1" - proxy-from-env "^1.1.0" - rimraf "^3.0.2" - tar-fs "^2.0.0" - unbzip2-stream "^1.3.3" - ws "^7.2.3" + debug "4.3.1" + devtools-protocol "0.0.883894" + extract-zip "2.0.1" + https-proxy-agent "5.0.0" + node-fetch "2.6.1" + pkg-dir "4.2.0" + progress "2.0.1" + proxy-from-env "1.1.0" + rimraf "3.0.2" + tar-fs "2.0.0" + unbzip2-stream "1.3.3" + ws "7.4.6" querystring-es3@^0.2.0: version "0.2.1" @@ -1066,7 +1073,7 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== -rimraf@^3.0.2: +rimraf@3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA== @@ -1134,10 +1141,10 @@ shadow-cljs-jar@1.3.2: resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== -shadow-cljs@^2.12.5: - version "2.12.5" - resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.12.5.tgz#d3cf29fc1f1e02dd875939549419979e0feadbf4" - integrity sha512-o3xo3coRgnlkI/iI55ccHjj6AU3F1+ovk3hhK86e3P2JGGOpNTAwsGNxUpMC5JAwS9Nz0v6sSk73hWjEOnm6fQ== +shadow-cljs@^2.14.2: + version "2.14.2" + resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.14.2.tgz#dba651ea124028064aea6fa9a390f257cb6eede4" + integrity sha512-ficaYfBAATzJ6OGt/GbIl393+cqLchzNkdTrM2PY4ttbsAOyBfWd39t+PZcYpCqemXjkgfBdZt9DJda7WaHJGA== dependencies: node-libs-browser "^2.2.1" readline-sync "^1.4.7" @@ -1209,17 +1216,17 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -tar-fs@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.1.1.tgz#489a15ab85f1f0befabb370b7de4f9eb5cbe8784" - integrity sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng== +tar-fs@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/tar-fs/-/tar-fs-2.0.0.tgz#677700fc0c8b337a78bee3623fdc235f21d7afad" + integrity sha512-vaY0obB6Om/fso8a8vakQBzwholQ7v5+uy+tF3Ozvxv1KNezmVQAiWtcNmMHFSFPqL3dJA8ha6gdtFbfX9mcxA== dependencies: chownr "^1.1.1" - mkdirp-classic "^0.5.2" + mkdirp "^0.5.1" pump "^3.0.0" - tar-stream "^2.1.4" + tar-stream "^2.0.0" -tar-stream@^2.1.4: +tar-stream@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-2.2.0.tgz#acad84c284136b060dc3faa64474aa9aebd77287" integrity sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ== @@ -1275,10 +1282,10 @@ ultron@~1.1.0: resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== -unbzip2-stream@^1.3.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz#b0da04c4371311df771cdc215e87f2130991ace7" - integrity sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg== +unbzip2-stream@1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.3.tgz#d156d205e670d8d8c393e1c02ebd506422873f6a" + integrity sha512-fUlAF7U9Ah1Q6EieQ4x4zLNejrRvDWUYmxXUpN3uziFYCHapjWFaCAnreY9bGgxzaMCFAPPpYNng57CypwJVhg== dependencies: buffer "^5.2.1" through "^2.3.8" @@ -1337,6 +1344,11 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +ws@7.4.6: + version "7.4.6" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" + integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== + ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" @@ -1346,11 +1358,6 @@ ws@^3.0.0: safe-buffer "~5.1.0" ultron "~1.1.0" -ws@^7.2.3: - version "7.4.5" - resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.5.tgz#a484dd851e9beb6fdb420027e3885e8ce48986c1" - integrity sha512-xzyu3hFvomRfXKH8vOFMU3OguG6oOvhXMo3xsGy3xWExqaM2dxBbVxuD99O7m3ZUFMvvscsZDqxfgMaRr/Nr1g== - xml-js@^1.6.11: version "1.6.11" resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" From f0e78f693fd5aca403980a229affa5c959cb8b60 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 2 Jun 2021 14:20:21 +0200 Subject: [PATCH 030/204] :bug: Add missing deps on exporter. --- exporter/package.json | 1 + exporter/yarn.lock | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/exporter/package.json b/exporter/package.json index 6d36517e69..a7422dbf47 100644 --- a/exporter/package.json +++ b/exporter/package.json @@ -12,6 +12,7 @@ "inflation": "^2.0.0", "jszip": "^3.6.0", "koa": "^2.13.0", + "luxon": "^1.27.0", "puppeteer": "^10.0.0", "puppeteer-cluster": "^0.22.0", "raw-body": "^2.4.1", diff --git a/exporter/yarn.lock b/exporter/yarn.lock index 195118ebbb..e381d7f927 100644 --- a/exporter/yarn.lock +++ b/exporter/yarn.lock @@ -712,6 +712,11 @@ locate-path@^5.0.0: dependencies: p-locate "^4.1.0" +luxon@^1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.27.0.tgz#ae10c69113d85dab8f15f5e8390d0cbeddf4f00f" + integrity sha512-VKsFsPggTA0DvnxtJdiExAucKdAnwbCCNlMM5ENvHlxubqWd0xhZcdb4XgZ7QFNhaRhilXCFxHuoObP5BNA4PA== + md5.js@^1.3.4: version "1.3.5" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" From 8da0e9adb27613a33fce3b6099379b50f21c68fc Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 2 Jun 2021 14:28:59 +0200 Subject: [PATCH 031/204] :paperclip: Adapt exporter and frontend build scripts. --- exporter/scripts/build | 2 +- frontend/scripts/build | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/exporter/scripts/build b/exporter/scripts/build index 61cc021d1a..1df9a17070 100755 --- a/exporter/scripts/build +++ b/exporter/scripts/build @@ -8,7 +8,7 @@ rm -rf target export NODE_ENV=production; # Build the application -npx shadow-cljs release main +clojure -M:dev:shadow-cljs release main # Remove source rm -rf target/app diff --git a/frontend/scripts/build b/frontend/scripts/build index 73b50e483b..1931f5431a 100755 --- a/frontend/scripts/build +++ b/frontend/scripts/build @@ -8,7 +8,7 @@ EXTRA_PARAMS=$SHADOWCLJS_EXTRA_PARAMS; yarn install || exit 1; npx gulp clean || exit 1; -npx shadow-cljs release main --config-merge "{:release-version \"${CURRENT_HASH}\"}" $EXTRA_PARAMS || exit 1 +clojure -M:dev:shadow-cljs release main --config-merge "{:release-version \"${CURRENT_HASH}\"}" $EXTRA_PARAMS || exit 1 npx gulp build || exit 1; npx gulp dist:clean || exit 1; npx gulp dist:copy || exit 1; From e9bbe9fca0fd946ed428be97abdd9fc2c9e31ca5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 2 Jun 2021 15:03:34 +0200 Subject: [PATCH 032/204] :arrow_up: Update beicon dep. --- frontend/deps.edn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/deps.edn b/frontend/deps.edn index 8d24465069..6327436acc 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -6,7 +6,7 @@ binaryage/devtools {:mvn/version "RELEASE"} metosin/reitit-core {:mvn/version "0.5.13"} - funcool/beicon {:mvn/version "2021.04.29-0"} + funcool/beicon {:mvn/version "2021.06.02-0"} funcool/okulary {:mvn/version "2020.04.14-0"} funcool/potok {:mvn/version "4.0.0"} funcool/rumext {:mvn/version "2021.05.12-1"} From 866d95149e8a8d26db5e077ee1b5b8b4206aff64 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 3 Jun 2021 11:59:20 +0200 Subject: [PATCH 033/204] :sparkles: Downgrade shadow-cljs version. Because the new compiler causes some bugs on compiling internal ES6 modules. --- common/deps.edn | 9 ++------- common/src/app/common/spec.cljc | 5 +++++ frontend/deps.edn | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/common/deps.edn b/common/deps.edn index 2b6183ad41..a01659a432 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -1,15 +1,10 @@ -{ - ;; :mvn/repos - ;; {"central" {:url "https://repo1.maven.org/maven2/"} - ;; "clojars" {:url "https://clojars.org/repo"} - ;; "jcenter" {:url "https://jcenter.bintray.com/"}} - :deps +{:deps {org.clojure/clojure {:mvn/version "1.10.3"} org.clojure/data.json {:mvn/version "2.3.1"} org.clojure/core.async {:mvn/version "1.3.618"} org.clojure/tools.cli {:mvn/version "1.0.206"} - org.clojure/clojurescript {:mvn/version "1.10.866"} metosin/jsonista {:mvn/version "0.3.3"} + org.clojure/clojurescript {:mvn/version "1.10.844"} ;; Logging org.clojure/tools.logging {:mvn/version "1.1.0"} diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index ea08717e5a..2ab5f6a8ee 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -11,6 +11,11 @@ (:require #?(:clj [clojure.spec.alpha :as s] :cljs [cljs.spec.alpha :as s]) + + ;; NOTE: don't remove this, causes exception on advanced build + ;; because of some strange interaction with cljs.spec.alpha and + ;; modules spliting. + [expound.alpha] [app.common.exceptions :as ex] [app.common.geom.point :as gpt] [app.common.uuid :as uuid] diff --git a/frontend/deps.edn b/frontend/deps.edn index 6327436acc..a109f5b5f8 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -23,7 +23,7 @@ :dev {:extra-deps - {thheller/shadow-cljs {:mvn/version "2.14.1"}}} + {thheller/shadow-cljs {:mvn/version "2.12.6"}}} :shadow-cljs {:main-opts ["-m" "shadow.cljs.devtools.cli"]} From bf5f845789820a8a2262fbae46fce47ca5e0736e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 28 May 2021 12:59:43 +0200 Subject: [PATCH 034/204] :sparkles: Import/Export framework first version --- common/src/app/common/file_builder.cljc | 70 +++++++++++++++++ common/src/app/common/pages/init.cljc | 7 +- common/src/app/common/uuid_impl.js | 3 +- frontend/shadow-cljs.edn | 23 +++++- frontend/src/app/libs/file_builder.cljs | 23 ++++++ frontend/src/app/libs/render.cljs | 28 +++++++ frontend/src/app/main/render.cljs | 74 ++++++++++++++++++ .../src/app/main/ui/dashboard/import.cljs | 50 ++++++++++++ frontend/src/app/main/ui/icons.cljs | 4 +- frontend/src/app/worker.cljs | 5 +- frontend/src/app/worker/export.cljs | 77 +++++++++++++++++++ frontend/src/app/worker/import.cljs | 56 ++++++++++++++ 12 files changed, 413 insertions(+), 7 deletions(-) create mode 100644 common/src/app/common/file_builder.cljc create mode 100644 frontend/src/app/libs/file_builder.cljs create mode 100644 frontend/src/app/libs/render.cljs create mode 100644 frontend/src/app/main/render.cljs create mode 100644 frontend/src/app/main/ui/dashboard/import.cljs create mode 100644 frontend/src/app/worker/export.cljs create mode 100644 frontend/src/app/worker/import.cljs diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc new file mode 100644 index 0000000000..aa36f5e0c9 --- /dev/null +++ b/common/src/app/common/file_builder.cljc @@ -0,0 +1,70 @@ +;; 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.file-builder + "A version parsing helper." + (:require + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.common.pages.init :as init] + [app.common.pages.changes :as ch] + )) + +(def root-frame uuid/zero) + +(defn create-file + ([name] + (let [id (uuid/next)] + {:id id + :name name + :data (-> init/empty-file-data + (assoc :id id)) + + ;; We keep the changes so we can send them to the backend + :changes []}))) + +;; TODO: Change to `false` +(def verify-on-commit? true) + +(defn commit-change [file change] + (-> file + (update :changes conj change) + (update :data ch/process-changes [change] verify-on-commit?))) + +(defn add-page + [file name] + + (let [page-id (uuid/next)] + (-> file + (commit-change + {:type :add-page + :id page-id + :name name + :page (-> init/empty-page-data + (assoc :name name))}) + + ;; Current page being edited + (assoc :current-page-id page-id) + + ;; Current parent stack we'll be nesting + (assoc :parent-stack [root-frame])))) + +(defn add-artboard [file data]) + +(defn close-artboard [file]) + +(defn add-group [file data]) +(defn close-group [file data]) + +(defn create-rect [file data]) +(defn create-circle [file data]) +(defn create-path [file data]) +(defn create-text [file data]) +(defn create-image [file data]) + +(defn close-page [file]) + +(defn generate-changes [file]) diff --git a/common/src/app/common/pages/init.cljc b/common/src/app/common/pages/init.cljc index 08d222f34c..0b19a6f0ac 100644 --- a/common/src/app/common/pages/init.cljc +++ b/common/src/app/common/pages/init.cljc @@ -126,10 +126,11 @@ :height (:height selection-rect)}) (defn make-file-data - ([file-id] (make-file-data file-id(uuid/next))) + ([file-id] + (make-file-data file-id (uuid/next))) + ([file-id page-id] - (let [ - pd (assoc empty-page-data + (let [pd (assoc empty-page-data :id page-id :name "Page-1")] (-> empty-file-data diff --git a/common/src/app/common/uuid_impl.js b/common/src/app/common/uuid_impl.js index 2c2a9f45bd..e05f358536 100644 --- a/common/src/app/common/uuid_impl.js +++ b/common/src/app/common/uuid_impl.js @@ -16,7 +16,8 @@ goog.scope(function() { const self = app.common.uuid_impl; const fill = (() => { - if (typeof global.crypto !== "undefined") { + if (typeof global.crypto !== "undefined" && + typeof global.crypto.getRandomValues !== "undefined") { return (buf) => { global.crypto.getRandomValues(buf); return buf; diff --git a/frontend/shadow-cljs.edn b/frontend/shadow-cljs.edn index 82c7116a65..1d95275526 100644 --- a/frontend/shadow-cljs.edn +++ b/frontend/shadow-cljs.edn @@ -4,6 +4,7 @@ :jvm-opts ["-Xmx700m" "-Xms100m" "-XX:+UseSerialGC" "-XX:-OmitStackTraceInFastThrow"] :dev-http {8888 "classpath:public"} + :builds {:main {:target :browser @@ -35,6 +36,27 @@ :anon-fn-naming-policy :off :source-map-detail-level :all}}} + :lib-penpot + {:target :esm + :output-dir "resources/public/libs" + + :modules + {:penpot {:exports {:renderPage app.libs.render/render-page-export + :createFile app.libs.file-builder/create-file-export}}} + + :compiler-options + {:output-feature-set :es8 + :output-wrapper false + :warnings {:fn-deprecated false}} + + :release + {:compiler-options + {:fn-invoke-direct true + :source-map true + :elide-asserts true + :anon-fn-naming-policy :off + :source-map-detail-level :all}}} + :test {:target :node-test :output-to "target/tests.js" @@ -45,4 +67,3 @@ {:output-feature-set :es8 :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 new file mode 100644 index 0000000000..4f1c6a207c --- /dev/null +++ b/frontend/src/app/libs/file_builder.cljs @@ -0,0 +1,23 @@ +;; 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.libs.file-builder + (:require + [app.common.data :as d] + [app.common.file-builder :as fb])) + +(deftype File [^:mutable file] + Object + (addPage [self name] + (set! file (fb/add-page file name)) + (str (:current-page-id file)))) + + +(defn create-file-export [^string name] + (File. (fb/create-file name))) + +(defn exports [] + #js { :createFile create-file-export }) diff --git a/frontend/src/app/libs/render.cljs b/frontend/src/app/libs/render.cljs new file mode 100644 index 0000000000..73006a840d --- /dev/null +++ b/frontend/src/app/libs/render.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) UXBOX Labs SL + +(ns app.libs.render + (:require + [app.common.uuid :as uuid] + [app.main.render :as r] + [beicon.core :as rx] + [promesa.core :as p])) + +(defn render-page-export + [file ^string page-id] + + ;; Better to expose the api as a promise to be consumed from JS + (let [page-id (uuid/uuid page-id) + file-data (.-file file) + data (get-in file-data [:data :pages-index page-id])] + (p/create + (fn [resolve reject] + (->> (r/render-page data) + (rx/take 1) + (rx/subs resolve reject))) ))) + +(defn exports [] + #js {:renderPage render-page-export}) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs new file mode 100644 index 0000000000..5ba1ef0d3b --- /dev/null +++ b/frontend/src/app/main/render.cljs @@ -0,0 +1,74 @@ +;; 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.main.render + (:require + ["react-dom/server" :as rds] + [app.config :as cfg] + [app.main.exports :as exports] + [app.main.exports :as svg] + [app.main.fonts :as fonts] + [app.util.http :as http] + [beicon.core :as rx] + [clojure.set :as set] + [rumext.alpha :as mf])) + +(defn- text? [{type :type}] + (= type :text)) + +(defn- get-image-data [shape] + (cond + (= :image (:type shape)) + [(:metadata shape)] + + (some? (:fill-image shape)) + [(:fill-image shape)] + + :else + [])) + +(defn populate-images-cache + ([data] + (populate-images-cache data nil)) + + ([data {:keys [resolve-media?] :or {resolve-media? false}}] + (let [images (->> (:objects data) + (vals) + (mapcat get-image-data))] + (->> (rx/from images) + (rx/map #(cfg/resolve-file-media %)) + (rx/flat-map http/fetch-data-uri))))) + +(defn populate-fonts-cache [data] + (let [texts (->> (:objects data) + (vals) + (filterv text?) + (mapv :content)) ] + + (->> (rx/from texts) + (rx/map fonts/get-content-fonts) + (rx/reduce set/union #{}) + (rx/flat-map identity) + (rx/flat-map fonts/fetch-font-css) + (rx/flat-map fonts/extract-fontface-urls) + (rx/flat-map http/fetch-data-uri)))) + +(defn render-page + [data] + (rx/concat + (->> (rx/merge + (populate-images-cache data) + (populate-fonts-cache data)) + (rx/ignore)) + + (->> (rx/of data) + (rx/map + (fn [data] + (let [elem (mf/element exports/page-svg #js {:data data :embed? true})] + (rds/renderToStaticMarkup elem))))))) + + + diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs new file mode 100644 index 0000000000..cd7e86cea6 --- /dev/null +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -0,0 +1,50 @@ +;; 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.main.ui.dashboard.import + (:require + [app.main.ui.components.file-uploader :refer [file-uploader]] + [app.main.ui.icons :as i] + [app.main.worker :as uw] + [app.util.dom :as dom] + [app.util.logging :as log] + [beicon.core :as rx] + [rumext.alpha :as mf])) + +(log/set-level! :warn) + +(defn use-import-file + [project-id] + (mf/use-callback + (mf/deps project-id) + (fn [files] + (when files + (let [files (->> files (mapv dom/create-uri))] + (->> (uw/ask-many! + {:cmd :import-file + :project-id project-id + :files files}) + + (rx/subs + (fn [result] + (log/debug :action "import-result" :result result))))))))) + +(mf/defc import-button + [{:keys [project-id]}] + + (let [file-input (mf/use-ref nil) + on-file-selected (use-import-file project-id)] + [:form.import-file + [:button.import-file-icon {:type "button" + :on-click #(dom/click (mf/ref-val file-input))} i/import] + [:& file-uploader {:accept "application/zip" + :multi true + :input-ref file-input + :on-selected on-file-selected}]])) + + + + diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index 598ba3b480..e577e082be 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -5,6 +5,7 @@ ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.icons + (:refer-clojure :exclude [import]) (:require-macros [app.main.ui.icons :refer [icon-xref]]) (:require [rumext.alpha :as mf])) @@ -53,6 +54,7 @@ (def icon-set (icon-xref :icon-set)) (def icon-verify (icon-xref :icon-verify)) (def image (icon-xref :image)) +(def import (icon-xref :import)) (def infocard (icon-xref :infocard)) (def interaction (icon-xref :interaction)) (def layers (icon-xref :layers)) @@ -60,9 +62,9 @@ (def libraries (icon-xref :libraries)) (def library (icon-xref :library)) (def line (icon-xref :line)) +(def line-height (icon-xref :line-height)) (def listing-enum (icon-xref :listing-enum)) (def listing-thumbs (icon-xref :listing-thumbs)) -(def line-height (icon-xref :line-height)) (def loader (icon-xref :loader)) (def lock (icon-xref :lock)) (def logo (icon-xref :uxbox-logo)) diff --git a/frontend/src/app/worker.cljs b/frontend/src/app/worker.cljs index 05d9ffc51b..44cd628f6d 100644 --- a/frontend/src/app/worker.cljs +++ b/frontend/src/app/worker.cljs @@ -6,6 +6,7 @@ (ns app.worker (:require + [app.common.exceptions :as ex] [app.common.spec :as us] [app.common.transit :as t] @@ -14,6 +15,9 @@ [app.util.worker :as w] [app.worker.impl :as impl] [app.worker.selection] + + [app.worker.import] + [app.worker.export] [app.worker.snaps] [app.worker.thumbnails] [beicon.core :as rx] @@ -159,4 +163,3 @@ (set! process-message-sub (subscribe-buffer-messages)) (.addEventListener js/self "message" on-message)) - diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs new file mode 100644 index 0000000000..bb86dbfca6 --- /dev/null +++ b/frontend/src/app/worker/export.cljs @@ -0,0 +1,77 @@ +;; 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.worker.export + (:require + [app.main.render :as r] + [app.util.dom :as dom] + [app.util.http :as http] + [app.util.zip :as uz] + [app.worker.impl :as impl] + [beicon.core :as rx])) + +(defn- handle-response + [response] + (cond + (http/success? response) + (rx/of (:body response)) + + (http/client-error? response) + (rx/throw (:body response)) + + :else + (rx/throw {:type :unexpected + :code (:error response)}))) + +(defn get-page-data + [{file-name :file-name {:keys [id name] :as data} :data}] + (->> (r/render-page data) + (rx/map (fn [markup] + {:id id + :name name + :file-name file-name + :markup markup})))) + +(defn query-file [file-id] + (->> (http/send! {:uri "/api/rpc/query/file" + :query {:id file-id} + :method :get}) + (rx/map http/conditional-decode-transit) + (rx/mapcat handle-response))) + +(defn process-pages [file] + (let [pages (get-in file [:data :pages]) + pages-index (get-in file [:data :pages-index])] + (->> pages + (map #(hash-map + :file-name (:name file) + :data (get pages-index %)))))) + +(defn collect-page + [coll {:keys [id file-name name markup] :as page}] + (conj coll [(str file-name "/" name ".svg") markup])) + +(defmethod impl/handler :export-file + [{:keys [team-id files] :as message}] + + (let [render-stream + (->> (rx/from (->> files (mapv :id))) + (rx/merge-map query-file) + (rx/flat-map process-pages) + (rx/observe-on :async) + (rx/flat-map get-page-data) + (rx/share))] + + (rx/merge + (->> render-stream + (rx/map #(hash-map :type :progress + :data (str "Render " (:file-name %) " - " (:name %))))) + (->> render-stream + (rx/reduce collect-page []) + (rx/tap #(prn %)) + (rx/flat-map uz/compress-files) + (rx/map #(hash-map :type :finish + :data (dom/create-uri %))))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs new file mode 100644 index 0000000000..5ac5247262 --- /dev/null +++ b/frontend/src/app/worker/import.cljs @@ -0,0 +1,56 @@ +;; 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.worker.import + (:require + [app.common.data :as d] + [app.common.file-builder :as fb] + [app.util.zip :as uz] + [app.worker.impl :as impl] + [beicon.core :as rx] + [cuerdas.core :as str] + [tubax.core :as tubax])) + +(defn parse-file-name + [dir] + (if (str/ends-with? dir "/") + (subs dir 0 (dec (count dir))) + dir)) + +(defn parse-page-name [path] + (let [[file page] (str/split path "/")] + (str/replace page ".svg" ""))) + +(defn import-page [file {:keys [path data]}] + (let [page-name (parse-page-name path)] + (-> file + (fb/add-page page-name)))) + +(defmethod impl/handler :import-file + [{:keys [project-id files]}] + + (let [extract-stream + (->> (rx/from files) + (rx/merge-map uz/extract-files)) + + dir-str + (->> extract-stream + (rx/filter #(contains? % :dir)) + (rx/map :dir)) + + file-str + (->> extract-stream + (rx/filter #(not (contains? % :dir))) + (rx/map #(d/update-when % :content tubax/xml->clj)))] + + (->> dir-str + (rx/merge-map + (fn [dir] + (->> file-str + (rx/filter #(str/starts-with? (:path %) dir)) + (rx/reduce import-page (fb/create-file (parse-file-name dir)))))) + + (rx/map #(select-keys % [:id :name]))))) From 6cbbfa6499d8ca6237a7b3327c2815c1fad995a1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 31 May 2021 18:06:28 +0200 Subject: [PATCH 035/204] :recycle: Refactor custom stroke --- frontend/src/app/main/ui/shapes/circle.cljs | 5 +- .../src/app/main/ui/shapes/custom_stroke.cljs | 220 ++++++++++-------- frontend/src/app/main/ui/shapes/path.cljs | 14 +- frontend/src/app/main/ui/shapes/rect.cljs | 26 +-- frontend/src/app/main/ui/shapes/shape.cljs | 4 +- .../app/main/ui/workspace/shapes/path.cljs | 3 +- frontend/src/app/util/object.cljs | 31 ++- 7 files changed, 166 insertions(+), 137 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/circle.cljs b/frontend/src/app/main/ui/shapes/circle.cljs index b3cdecbf0c..da19c2003e 100644 --- a/frontend/src/app/main/ui/shapes/circle.cljs +++ b/frontend/src/app/main/ui/shapes/circle.cljs @@ -32,6 +32,5 @@ :ry ry :transform transform}))] - [:& shape-custom-stroke {:shape shape - :base-props props - :elem-name "ellipse"}])) + [:& shape-custom-stroke {:shape shape} + [:> :ellipse props]])) diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 643f074853..f3759ce0c2 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -9,7 +9,116 @@ [rumext.alpha :as mf] [app.common.uuid :as uuid] [app.common.geom.shapes :as geom] - [app.util.object :as obj])) + [app.util.object :as obj] + [app.main.ui.context :as muc])) + +(defn add-props + [props new-props] + (-> props + (obj/merge (clj->js new-props)))) + +(defn add-style + [props new-style] + (let [old-style (obj/get props "style") + style (obj/merge old-style (clj->js new-style))] + (-> props (obj/merge #js {:style style})))) + +(mf/defc inner-stroke-clip-path + [{:keys [shape render-id]}] + (let [clip-id (str "inner-stroke-" render-id) + shape-id (str "stroke-shape-" render-id)] + [:> "clipPath" #js {:id clip-id} + [:use {:href (str "#" shape-id)}]])) + +(mf/defc outer-stroke-mask + [{:keys [shape render-id]}] + (let [stroke-mask-id (str "outer-stroke-" render-id) + shape-id (str "stroke-shape-" render-id) + stroke-width (:stroke-width shape 0)] + [:mask {:id stroke-mask-id} + [:use {:href (str "#" shape-id) + :style #js {:fill "none" :stroke "white" :strokeWidth (* stroke-width 2)}}] + + [:use {:href (str "#" shape-id) + :style #js {:fill "black"}}]])) + +(mf/defc stroke-defs + [{:keys [shape render-id]}] + (cond + (and (= :inner (:stroke-alignment shape :center)) + (> (:stroke-width shape 0) 0)) + [:& inner-stroke-clip-path {:shape shape + :render-id render-id}] + + (and (= :outer (:stroke-alignment shape :center)) + (> (:stroke-width shape 0) 0)) + [:& outer-stroke-mask {:shape shape + :render-id render-id}])) + +;; Outer alingmnent: display the shape in two layers. One +;; without stroke (only fill), and another one only with stroke +;; at double width (transparent fill) and passed through a mask +;; that shows the whole shape, but hides the original shape +;; without stroke +(mf/defc outer-stroke + {::mf/wrap-props false} + [props] + + (let [render-id (mf/use-ctx muc/render-ctx) + child (obj/get props "children") + base-props (obj/get child "props") + elem-name (obj/get child "type") + shape (obj/get props "shape") + stroke-width (:stroke-width shape 0) + stroke-mask-id (str "outer-stroke-" render-id) + shape-id (str "stroke-shape-" render-id)] + + [:g.outer-stroke-shape + [:symbol + [:> elem-name (-> (obj/clone base-props) + (obj/set! "id" shape-id) + (obj/without ["style"]))]] + + [:use {:href (str "#" shape-id) + :mask (str "url(#" stroke-mask-id ")") + :style (-> (obj/get base-props "style") + (obj/clone) + (obj/update! "strokeWidth" * 2) + (obj/without ["fill" "fillOpacity"]) + (obj/set! "fill" "none"))}] + + [:use {:href (str "#" shape-id) + :style (-> (obj/get base-props "style") + (obj/clone) + (obj/without ["stroke" "strokeWidth" "strokeOpacity" "strokeStyle" "strokeDasharray"]))}]])) + + +;; Inner alignment: display the shape with double width stroke, +;; and clip the result with the original shape without stroke. +(mf/defc inner-stroke + {::mf/wrap-props false} + [props] + (let [render-id (mf/use-ctx muc/render-ctx) + child (obj/get props "children") + base-props (obj/get child "props") + elem-name (obj/get child "type") + shape (obj/get props "shape") + transform (obj/get base-props "transform") + + stroke-width (:stroke-width shape 0) + + clip-id (str "inner-stroke-" render-id) + shape-id (str "stroke-shape-" render-id) + + shape-props (-> base-props + (add-props {:id shape-id + :transform nil + :clipPath (str "url('#" clip-id "')")}) + (add-style {:strokeWidth (* stroke-width 2)}))] + + [:g.inner-stroke-shape {:transform transform} + [:> elem-name shape-props]])) + ; The SVG standard does not implement yet the 'stroke-alignment' ; attribute, to define the position of the stroke relative to the @@ -19,100 +128,25 @@ (mf/defc shape-custom-stroke {::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - base-props (unchecked-get props "base-props") - elem-name (unchecked-get props "elem-name") - base-style (obj/get base-props "style") - {:keys [x y width height]} (:selrect shape) - stroke-id (mf/use-var (uuid/next)) + (let [child (obj/get props "children") + shape (obj/get props "shape") + stroke-width (:stroke-width shape 0) stroke-style (:stroke-style shape :none) - stroke-position (:stroke-alignment shape :center)] + stroke-position (:stroke-alignment shape :center) + has-stroke? (and (and (> stroke-width 0) + (not= stroke-style :none))) + inner? (= :inner stroke-position) + outer? (= :outer stroke-position)] + (cond - ;; Center alignment (or no stroke): the default in SVG - (or (= stroke-style :none) (= stroke-position :center)) - [:> elem-name (obj/merge! #js {} base-props)] + (and has-stroke? inner?) + [:& inner-stroke {:shape shape} + child] - ;; Inner alignment: display the shape with double width stroke, - ;; and clip the result with the original shape without stroke. - (= stroke-position :inner) - (let [clip-id (str "clip-" @stroke-id) + (and has-stroke? outer?) + [:& outer-stroke {:shape shape} + child] - clip-props (obj/merge - base-props - #js {:transform nil - :style (obj/merge - base-style - #js {:stroke nil - :strokeWidth nil - :strokeOpacity nil - :strokeDasharray nil - :fill "white" - :fillOpacity 1})}) - - stroke-width (obj/get base-style "strokeWidth" 0) - shape-props (obj/merge - base-props - #js {:clipPath (str "url('#" clip-id "')") - :style (obj/merge - base-style - #js {:strokeWidth (* stroke-width 2)})})] - [:* - [:> "clipPath" #js {:id clip-id} - [:> elem-name clip-props]] - [:> elem-name shape-props]]) - - ;; Outer alingmnent: display the shape in two layers. One - ;; without stroke (only fill), and another one only with stroke - ;; at double width (transparent fill) and passed through a mask - ;; that shows the whole shape, but hides the original shape - ;; without stroke - - (= stroke-position :outer) - (let [stroke-mask-id (str "mask-" @stroke-id) - stroke-width (obj/get base-style "strokeWidth" 0) - mask-props1 (obj/merge - base-props - #js {:transform nil - :style (obj/merge - base-style - #js {:stroke "white" - :strokeWidth (* stroke-width 2) - :strokeOpacity 1 - :strokeDasharray nil - :fill "white" - :fillOpacity 1})}) - mask-props2 (obj/merge - base-props - #js {:transform nil - :style (obj/merge - base-style - #js {:stroke nil - :strokeWidth nil - :strokeOpacity nil - :strokeDasharray nil - :fill "black" - :fillOpacity 1})}) - - shape-props1 (obj/merge - base-props - #js {:style (obj/merge - base-style - #js {:stroke nil - :strokeWidth nil - :strokeOpacity nil - :strokeDasharray nil})}) - shape-props2 (obj/merge - base-props - #js {:mask (str "url('#" stroke-mask-id "')") - :style (obj/merge - base-style - #js {:strokeWidth (* stroke-width 2) - :fill "none" - :fillOpacity 0})})] - [:* - [:mask {:id stroke-mask-id} - [:> elem-name mask-props1] - [:> elem-name mask-props2]] - [:> elem-name shape-props1] - [:> elem-name shape-props2]])))) + :else + child))) diff --git a/frontend/src/app/main/ui/shapes/path.cljs b/frontend/src/app/main/ui/shapes/path.cljs index 256730eb22..9726803051 100644 --- a/frontend/src/app/main/ui/shapes/path.cljs +++ b/frontend/src/app/main/ui/shapes/path.cljs @@ -26,16 +26,6 @@ props (-> (attrs/extract-style-attrs shape) (obj/merge! #js {:d pdata}))] - (if background? - [:g - [:path {:stroke "none" - :fill "none" - :stroke-width "20px" - :d pdata}] - [:& shape-custom-stroke {:shape shape - :base-props props - :elem-name "path"}]] - [:& shape-custom-stroke {:shape shape - :base-props props - :elem-name "path"}]))) + [:& shape-custom-stroke {:shape shape} + [:> :path props]])) diff --git a/frontend/src/app/main/ui/shapes/rect.cljs b/frontend/src/app/main/ui/shapes/rect.cljs index aeb61b17a8..bb0ccd60a8 100644 --- a/frontend/src/app/main/ui/shapes/rect.cljs +++ b/frontend/src/app/main/ui/shapes/rect.cljs @@ -6,23 +6,19 @@ (ns app.main.ui.shapes.rect (:require - [rumext.alpha :as mf] + [app.common.geom.shapes :as gsh] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]] - [app.common.geom.shapes :as geom] - [app.util.object :as obj] [app.main.ui.shapes.gradients :refer [gradient]] - - [cuerdas.core :as str] - [app.common.uuid :as uuid] - [app.common.geom.point :as gpt])) + [app.util.object :as obj] + [rumext.alpha :as mf])) (mf/defc rect-shape {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") {:keys [id x y width height]} shape - transform (geom/transform-matrix shape) + transform (gsh/transform-matrix shape) props (-> (attrs/extract-style-attrs shape) (obj/merge! @@ -30,11 +26,11 @@ :y y :transform transform :width width - :height height}))] + :height height})) - [:& shape-custom-stroke {:shape shape - :base-props props - :elem-name - (if (.-d props) - "path" - "rect")}])) + path? (some? (.-d props))] + + [:& shape-custom-stroke {:shape shape} + (if path? + [:> :path props] + [:> :rect props])])) diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 82dddd2386..cd05e9d11b 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.uuid :as uuid] [app.main.ui.context :as muc] + [app.main.ui.shapes.custom-stroke :as cs] [app.main.ui.shapes.fill-image :as fim] [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.gradients :as grad] @@ -56,5 +57,6 @@ [:& filters/filters {:shape shape :filter-id filter-id}] [:& grad/gradient {:shape shape :attr :fill-color-gradient}] [:& grad/gradient {:shape shape :attr :stroke-color-gradient}] - [:& fim/fill-image-pattern {:shape shape :render-id render-id}]] + [:& fim/fill-image-pattern {:shape shape :render-id render-id}] + [:& cs/stroke-defs {:shape shape :render-id render-id}]] children]])) diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index 22232ca011..c9183c3fe9 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -28,5 +28,4 @@ [:> shape-container {:shape shape :pointer-events (when editing? "none")} - [:& path/path-shape {:shape shape - :background? true}]])) + [:& path/path-shape {:shape shape}]])) diff --git a/frontend/src/app/util/object.cljs b/frontend/src/app/util/object.cljs index ad6697a428..03c2447048 100644 --- a/frontend/src/app/util/object.cljs +++ b/frontend/src/app/util/object.cljs @@ -27,17 +27,18 @@ (js/Object.keys ^js obj)) (defn get-in - [obj keys] - (loop [key (first keys) - keys (rest keys) - res obj] - (if (nil? key) - res - (if (nil? res) - res - (recur (first keys) - (rest keys) - (unchecked-get res key)))))) + ([obj keys] + (get-in obj keys nil)) + + ([obj keys default] + (loop [key (first keys) + keys (rest keys) + res obj] + (if (or (nil? key) (nil? res)) + (or res default) + (recur (first keys) + (rest keys) + (unchecked-get res key)))))) (defn without [obj keys] @@ -68,6 +69,14 @@ (unchecked-set obj key value) obj) +(defn update! + [obj key f & args] + (let [found (get obj key ::not-found)] + (if-not (identical? ::not-found found) + (do (unchecked-set obj key (apply f found args)) + obj) + obj))) + (defn- props-key-fn [key] (if (or (= key :class) (= key :class-name)) From a76bf1d0b25d726b4ea7cb4318de22b54ec666e8 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 2 Jun 2021 15:08:28 +0200 Subject: [PATCH 036/204] :bug: Fix problem with export assets --- frontend/src/app/util/dom.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 5864e285cd..22c3840acc 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -333,4 +333,5 @@ (defn trigger-download [filename blob] - (trigger-download-uri filename (.-type ^js blob) (dom/create-uri blob))) + (trigger-download-uri filename (.-type ^js blob) (create-uri blob))) + From 9f36f4fbe79bbd2a5e9c25e8b4a122b989aafbba Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 2 Jun 2021 15:09:34 +0200 Subject: [PATCH 037/204] :sparkles: Save as dialog option --- frontend/src/app/util/dom.cljs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 22c3840acc..b5acc982a0 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -8,10 +8,11 @@ (:require [app.common.exceptions :as ex] [app.common.geom.point :as gpt] - [app.util.object :as obj] [app.util.globals :as globals] + [app.util.object :as obj] [cuerdas.core :as str] - [goog.dom :as dom])) + [goog.dom :as dom] + [promesa.core :as p])) ;; --- Deprecated methods @@ -335,3 +336,21 @@ [filename blob] (trigger-download-uri filename (.-type ^js blob) (create-uri blob))) +(defn save-as + [uri filename mtype description] + + ;; Only chrome supports the save dialog + (if (obj/contains? globals/window "showSaveFilePicker") + (let [extension (mtype->extension mtype) + opts {:suggestedName (str filename "." extension) + :types [{:description description + :accept { mtype [(str "." extension)]}}]}] + + (p/let [file-system (.showSaveFilePicker globals/window (clj->js opts)) + writable (.createWritable file-system) + response (js/fetch uri) + blob (.blob response) + _ (.write writable blob)] + (.close writable))) + + (trigger-download-uri filename mtype uri))) From b76fef1e445ec65583825d174194cc8e96ff8f3e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 2 Jun 2021 15:46:41 +0200 Subject: [PATCH 038/204] :sparkles: Change create file to send data from the frontend --- backend/src/app/rpc/mutations/files.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index d6fb24c595..d90e453fb6 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -54,11 +54,11 @@ (db/insert! conn :file-profile-rel)))) (defn create-file - [conn {:keys [id name project-id is-shared] + [conn {:keys [id name project-id is-shared data] :or {is-shared false} :as params}] - (let [id (or id (uuid/next)) - data (cp/make-file-data id) + (let [id (or id (:id data) (uuid/next)) + data (or data (cp/make-file-data id)) file (db/insert! conn :file {:id id :project-id project-id From f197124ee5ad7a7322300c30d96bbaacd68488c1 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 2 Jun 2021 15:50:32 +0200 Subject: [PATCH 039/204] :sparkles: Changes to render to support exporting --- frontend/src/app/main/ui/render.cljs | 3 +- .../src/app/main/ui/shapes/custom_stroke.cljs | 21 ++++-- frontend/src/app/main/ui/shapes/shape.cljs | 64 +++++++++++++++---- .../main/ui/workspace/viewport/gradients.cljs | 5 +- 4 files changed, 70 insertions(+), 23 deletions(-) diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index f7de7b8628..c6853ecd2e 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -78,7 +78,8 @@ :height height :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns "http://www.w3.org/2000/svg"} + :xmlns "http://www.w3.org/2000/svg" + :xmlns:penpot "https://penpot.app/xmlns"} (case (:type object) :frame [:& frame-wrapper {:shape object :view-box vbox}] :group [:> shape-container {:shape object} diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index f3759ce0c2..1e19f219fd 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -6,11 +6,13 @@ (ns app.main.ui.shapes.custom-stroke (:require - [rumext.alpha :as mf] - [app.common.uuid :as uuid] + [app.common.data :as d] [app.common.geom.shapes :as geom] + [app.common.uuid :as uuid] + [app.main.ui.context :as muc] [app.util.object :as obj] - [app.main.ui.context :as muc])) + [cuerdas.core :as str] + [rumext.alpha :as mf])) (defn add-props [props new-props] @@ -71,13 +73,22 @@ shape (obj/get props "shape") stroke-width (:stroke-width shape 0) stroke-mask-id (str "outer-stroke-" render-id) - shape-id (str "stroke-shape-" render-id)] + shape-id (str "stroke-shape-" render-id) + + style-str (->> (obj/get base-props "style") + (js->clj) + (mapv (fn [[k v]] + (-> (d/name k) + (str/kebab) + (str ":" v)))) + (str/join ";"))] [:g.outer-stroke-shape [:symbol [:> elem-name (-> (obj/clone base-props) (obj/set! "id" shape-id) - (obj/without ["style"]))]] + (obj/set! "data-style" style-str) + (obj/without ["style"]))]] [:use {:href (str "#" shape-id) :mask (str "url(#" stroke-mask-id ")") diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index cd05e9d11b..5a8d4fecdf 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.uuid :as uuid] + [app.common.geom.matrix :as gmt] [app.main.ui.context :as muc] [app.main.ui.shapes.custom-stroke :as cs] [app.main.ui.shapes.fill-image :as fim] @@ -17,6 +18,36 @@ [app.util.object :as obj] [rumext.alpha :as mf])) +(defn add-metadata + "Adds as metadata properties that we cannot deduce from the exported SVG" + [props shape] + (let [add! + (fn [props attr val] + (let [ns-attr (str "penpot:" (-> attr d/name))] + (-> props + (obj/set! ns-attr val)))) + frame? (= :frame type)] + (-> props + (add! :name (-> shape :name)) + (add! :blocked (-> shape (:blocked false) str)) + (add! :hidden (-> shape (:hidden false) str)) + (add! :type (-> shape :type d/name)) + + (add! :stroke-style (-> shape (:stroke-style :none) d/name)) + (add! :stroke-alignment (-> shape (:stroke-alignment :center) d/name)) + + (add! :transform (-> shape (:transform (gmt/matrix)) str)) + (add! :transform-inverse (-> shape (:transform-inverse (gmt/matrix)) str)) + + (cond-> (some? (:r1 shape)) + (-> (add! :r1 (-> shape (:r1 0) str)) + (add! :r2 (-> shape (:r2 0) str)) + (add! :r3 (-> shape (:r3 0) str)) + (add! :r4 (-> shape (:r4 0) str)))) + + (cond-> frame? + (obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))))) + (mf/defc shape-container {::mf/forward-ref true ::mf/wrap-props false} @@ -34,24 +65,29 @@ {:keys [x y width height type]} shape frame? (= :frame type) - group-props (-> (obj/clone props) - (obj/without ["shape" "children"]) - (obj/set! "ref" ref) - (obj/set! "id" (str "shape-" (:id shape))) - (obj/set! "filter" (filters/filter-str filter-id shape)) - (obj/set! "style" styles) - (cond-> frame? - (-> (obj/set! "x" x) - (obj/set! "y" y) - (obj/set! "width" width) - (obj/set! "height" height) - (obj/set! "xmlnsXlink" "http://www.w3.org/1999/xlink") - (obj/set! "xmlns" "http://www.w3.org/2000/svg")))) + wrapper-props + (-> (obj/clone props) + (obj/without ["shape" "children"]) + (obj/set! "ref" ref) + (obj/set! "id" (str "shape-" (:id shape))) + (obj/set! "filter" (filters/filter-str filter-id shape)) + (obj/set! "style" styles) + + (cond-> frame? + (-> (obj/set! "x" x) + (obj/set! "y" y) + (obj/set! "width" width) + (obj/set! "height" height) + (obj/set! "xmlnsXlink" "http://www.w3.org/1999/xlink") + (obj/set! "xmlns" "http://www.w3.org/2000/svg"))) + + (add-metadata shape)) wrapper-tag (if frame? "svg" "g")] + [:& (mf/provider muc/render-ctx) {:value render-id} - [:> wrapper-tag group-props + [:> wrapper-tag wrapper-props [:defs [:& defs/svg-defs {:shape shape :render-id render-id}] [:& filters/filters {:shape shape :filter-id filter-id}] diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs index 380c96f777..e65bc12d05 100644 --- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -278,7 +278,6 @@ (let [point (gpt/transform point transform-inverse) end-x (/ (- (:x point) x) width) end-y (/ (- (:y point) y) height) - end-x (mth/precision end-x 2) end-y (mth/precision end-y 2)] (change! {:end-x end-x :end-y end-y}))) @@ -287,8 +286,8 @@ (let [scale-factor-y (/ gradient-length (/ height 2)) norm-dist (/ (gpt/distance point from-p) (* (/ width 2) scale-factor-y))] - - (change! {:width norm-dist})))] + (when (and norm-dist (mth/finite? norm-dist)) + (change! {:width norm-dist}))))] (when (and gradient (= id (:shape-id gradient)) From 21aa23e7f5f9cc9a1aa36504a2c1153114be7a67 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 2 Jun 2021 15:51:20 +0200 Subject: [PATCH 040/204] :sparkles: Parsing and file builder --- common/src/app/common/file_builder.cljc | 137 +++++++++++++++---- common/src/app/common/geom/matrix.cljc | 10 ++ common/src/app/common/geom/shapes.cljc | 4 + common/src/app/common/pages.cljc | 1 + common/src/app/common/pages/init.cljc | 6 + frontend/src/app/util/import/parser.cljc | 164 +++++++++++++++++++++++ frontend/src/app/util/object.cljs | 6 +- frontend/src/app/util/path/parser.cljs | 9 +- frontend/src/app/util/svg.cljs | 2 - 9 files changed, 307 insertions(+), 32 deletions(-) create mode 100644 frontend/src/app/util/import/parser.cljc diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index aa36f5e0c9..807d507c27 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -7,14 +7,52 @@ (ns app.common.file-builder "A version parsing helper." (:require - [app.common.spec :as us] - [app.common.uuid :as uuid] - [app.common.pages.init :as init] + [app.common.geom.shapes :as gsh] [app.common.pages.changes :as ch] - )) + [app.common.pages.init :as init] + [app.common.pages.spec :as spec] + [app.common.spec :as us] + [app.common.spec :as us] + [app.common.uuid :as uuid])) (def root-frame uuid/zero) +;; This flag controls if we should execute spec validation after every commit +(def verify-on-commit? true) + +(defn- commit-change [file change] + (when verify-on-commit? + (us/assert ::spec/change change)) + (-> file + (update :changes conj change) + (update :data ch/process-changes [change] verify-on-commit?))) + +(defn- lookup-objects + ([file] + (lookup-objects file (:current-page-id file))) + + ([file page-id] + (get-in file [:data :pages-index page-id :objects]))) + +(defn- lookup-shape [file shape-id] + (-> (lookup-objects file) + (get shape-id))) + +(defn- commit-shape [file obj] + (let [page-id (:current-page-id file) + frame-id (:current-frame-id file) + parent-id (-> file :parent-stack peek)] + (-> file + (commit-change + {:type :add-obj + :id (:id obj) + :page-id page-id + :frame-id frame-id + :parent-id parent-id + :obj obj})))) + +;; PUBLIC API + (defn create-file ([name] (let [id (uuid/next)] @@ -26,17 +64,8 @@ ;; We keep the changes so we can send them to the backend :changes []}))) -;; TODO: Change to `false` -(def verify-on-commit? true) - -(defn commit-change [file change] - (-> file - (update :changes conj change) - (update :data ch/process-changes [change] verify-on-commit?))) - (defn add-page [file name] - (let [page-id (uuid/next)] (-> file (commit-change @@ -49,22 +78,82 @@ ;; Current page being edited (assoc :current-page-id page-id) + ;; Current frame-id + (assoc :current-frame-id root-frame) + ;; Current parent stack we'll be nesting (assoc :parent-stack [root-frame])))) -(defn add-artboard [file data]) +(defn add-artboard [file data] + (let [obj (-> (init/make-minimal-shape :frame) + (merge data))] + (-> file + (commit-shape obj) + (assoc :current-frame-id (:id obj)) + (update :parent-stack conj (:id obj))))) -(defn close-artboard [file]) +(defn close-artboard [file] + (-> file + (assoc :current-frame-id root-frame) + (update :parent-stack pop))) -(defn add-group [file data]) -(defn close-group [file data]) +(defn add-group [file data] + (let [frame-id (:current-frame-id file) + selrect init/empty-selrect + name (:name data) + obj (-> (init/make-minimal-group frame-id selrect name) + (merge data))] + (-> file + (commit-shape obj) + (update :parent-stack conj (:id obj))))) -(defn create-rect [file data]) -(defn create-circle [file data]) -(defn create-path [file data]) -(defn create-text [file data]) -(defn create-image [file data]) +(defn close-group [file] + (let [group-id (-> file :parent-stack peek) + group (lookup-shape file group-id) + shapes (->> group :shapes (mapv #(lookup-shape file %))) + selrect (gsh/selection-rect shapes) + points (gsh/rect->points selrect)] -(defn close-page [file]) + (-> file + (commit-change + {:type :mod-obj + :page-id (:current-page-id file) + :id group-id + :operations + [{:type :set :attr :selrect :val selrect} + {:type :set :attr :points :val points}]}) + (update :parent-stack pop)))) -(defn generate-changes [file]) +(defn create-shape [file type data] + (let [frame-id (:current-frame-id file) + frame (when-not (= frame-id root-frame) + (lookup-shape file frame-id)) + obj (-> (init/make-minimal-shape type) + (merge data) + (cond-> frame + (gsh/translate-from-frame frame)))] + (commit-shape file obj))) + +(defn create-rect [file data] + (create-shape file :rect data)) + +(defn create-circle [file data] + (create-shape file :circle data)) + +(defn create-path [file data] + (create-shape file :path data)) + +(defn create-text [file data] + (create-shape file :text data)) + +(defn create-image [file data] + (create-shape file :image data)) + +(defn close-page [file] + (-> file + (dissoc :current-page-id) + (dissoc :parent-stack))) + +(defn generate-changes + [file] + (:changes file)) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index 1c0a834825..fc2513f4a8 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -8,6 +8,7 @@ (:require #?(:cljs [cljs.pprint :as pp] :clj [clojure.pprint :as pp]) + [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.math :as mth])) @@ -25,6 +26,15 @@ ([a b c d e f] (Matrix. a b c d e f))) +(def number-regex #"[+-]?\d*(\.\d+)?(e[+-]?\d+)?") + +(defn str->matrix + [matrix-str] + (let [params (->> (re-seq number-regex matrix-str) + (filter #(-> % first empty? not)) + (map (comp d/parse-double first)))] + (apply matrix params))) + (defn multiply ([{m1a :a m1b :b m1c :c m1d :d m1e :e m1f :f} {m2a :a m2b :b m2c :c m2d :d m2e :e m2f :f}] diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 96e4891576..75fc70661c 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -100,6 +100,10 @@ [shape {:keys [x y]}] (gtr/move shape (gpt/negate (gpt/point x y))) ) +(defn translate-from-frame + [shape {:keys [x y]}] + (gtr/move shape (gpt/point x y)) ) + ;; --- Helpers (defn fully-contained? diff --git a/common/src/app/common/pages.cljc b/common/src/app/common/pages.cljc index 32bd260845..0e69c9d53c 100644 --- a/common/src/app/common/pages.cljc +++ b/common/src/app/common/pages.cljc @@ -84,6 +84,7 @@ (d/export init/make-file-data) (d/export init/make-minimal-shape) (d/export init/make-minimal-group) +(d/export init/empty-file-data) ;; Specs diff --git a/common/src/app/common/pages/init.cljc b/common/src/app/common/pages/init.cljc index 0b19a6f0ac..ca42025f43 100644 --- a/common/src/app/common/pages/init.cljc +++ b/common/src/app/common/pages/init.cljc @@ -85,6 +85,12 @@ {:type :svg-raw}]) +(def empty-selrect + {:x 0 :y 0 + :x1 0 :y1 0 + :x2 1 :y2 1 + :width 1 :height 1}) + (defn make-minimal-shape [type] (let [type (cond (= type :curve) :path diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc new file mode 100644 index 0000000000..6847b194f6 --- /dev/null +++ b/frontend/src/app/util/import/parser.cljc @@ -0,0 +1,164 @@ +;; 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.util.import.parser + (:require + [app.common.data :as d] + [app.common.geom.matrix :as gmt] + [app.common.geom.shapes :as gsh] + [cuerdas.core :as str] + [app.util.path.parser :as upp])) + +(defn valid? + [root] + (contains? (:attrs root) :xmlns:penpot)) + +(defn branch? + [node] + (and (contains? node :content) + (some? (:content node)))) + +(defn close? + [node] + (and (vector? node) + (= ::close (first node)))) + +(defn get-type + [node] + (if (close? node) + (second node) + (-> (get-in node [:attrs :penpot:type]) + (keyword)))) + +(defn shape? + [node] + (or (close? node) + (contains? (:attrs node) :penpot:type))) + +(defn get-attr + ([m att] + (get-attr m att identity)) + ([m att val-fn] + (let [ns-att (->> att d/name (str "penpot:") keyword) + val (get-in m [:attrs ns-att])] + (when val (val-fn val))))) + +(defn get-children + [node] + (cond-> (:content node) + ;; We add a "fake" node to know when we are leaving the shape children + (shape? node) + (conj [::close (get-type node)]))) + +(defn node-seq + [content] + (->> content (tree-seq branch? get-children))) + +(defn get-transform + [type node]) + +(defn parse-style + "Transform style list into a map" + [style-str] + (if (string? style-str) + (->> (str/split style-str ";") + (map str/trim) + (map #(str/split % ":")) + (group-by first) + (map (fn [[key val]] + (vector (keyword key) (second (first val))))) + (into {})) + style-str)) + +(defn add-attrs + [m attrs] + (reduce-kv + (fn [m k v] + (if (#{:style :data-style} k) + (assoc m :style (parse-style v)) + (assoc m k v))) + m + attrs)) + +(defn get-data-node + [node] + + (let [data-tags #{:ellipse :rect :path}] + (->> node + (node-seq) + (filter #(contains? data-tags (:tag %))) + (map #(:attrs %)) + (reduce add-attrs {})))) + +(def search-data-node? #{:rect :image :path :text :circle}) +(def has-position? #{:frame :rect :image :text}) + +(defn parse-position + [props data] + (let [values (->> (select-keys data [:x :y :width :height]) + (d/mapm (fn [_ val] (d/parse-double val))))] + (d/merge props values))) + +(defn parse-circle + [props data] + (let [values (->> (select-keys data [:cx :cy :rx :ry]) + (d/mapm (fn [_ val] (d/parse-double val))))] + + {:x (- (:cx values) (:rx values)) + :y (- (:cy values) (:ry values)) + :width (* (:rx values) 2) + :height (* (:ry values) 2)})) + +(defn parse-path + [props data] + (let [content (upp/parse-path (:d data)) + selrect (gsh/content->selrect content) + points (gsh/rect->points selrect)] + + (-> props + (assoc :content content) + (assoc :selrect selrect) + (assoc :points points)))) + +(defn extract-data + [type node] + (let [data (if (search-data-node? type) + (get-data-node node) + (:attrs node))] + (cond-> {} + (has-position? type) + (-> (parse-position data) + (gsh/setup-selrect)) + + (= type :circle) + (-> (parse-circle data) + (gsh/setup-selrect)) + + (= type :path) + (parse-path data)))) + +(defn str->bool + [val] + (= val "true")) + +(defn parse-data + [type node] + + (when-not (close? node) + (let [name (get-attr node :name) + blocked (get-attr node :blocked str->bool) + hidden (get-attr node :hidden str->bool) + transform (get-attr node :transform gmt/str->matrix) + transform-inverse (get-attr node :transform-inverse gmt/str->matrix)] + + (-> (extract-data type node) + (assoc :name name) + (assoc :blocked blocked) + (assoc :hidden hidden) + (cond-> (some? transform) + (assoc :transform transform)) + (cond-> (some? transform-inverse) + (assoc :transform-inverse transform-inverse)))))) diff --git a/frontend/src/app/util/object.cljs b/frontend/src/app/util/object.cljs index 03c2447048..12aabcb609 100644 --- a/frontend/src/app/util/object.cljs +++ b/frontend/src/app/util/object.cljs @@ -6,7 +6,7 @@ (ns app.util.object "A collection of helpers for work with javascript objects." - (:refer-clojure :exclude [set! get get-in merge clone]) + (:refer-clojure :exclude [set! get get-in merge clone contains?]) (:require [cuerdas.core :as str] [goog.object :as gobj] @@ -22,6 +22,10 @@ (let [result (get obj k)] (if (undefined? result) default result)))) +(defn contains? + [obj k] + (some? (unchecked-get obj k))) + (defn get-keys [obj] (js/Object.keys ^js obj)) diff --git a/frontend/src/app/util/path/parser.cljs b/frontend/src/app/util/path/parser.cljs index 09f491555a..fc23adc619 100644 --- a/frontend/src/app/util/path/parser.cljs +++ b/frontend/src/app/util/path/parser.cljs @@ -9,14 +9,13 @@ [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as gshp] + [app.common.math :as mth] [app.util.path.arc-to-curve :refer [a2c]] [app.util.path.commands :as upc] - [app.util.svg :as usvg] - [cuerdas.core :as str] - [clojure.set :as set] - [app.common.math :as mth] [app.util.path.geom :as upg] - )) + [app.util.svg :as usvg] + [clojure.set :as set] + [cuerdas.core :as str])) ;; (def commands-regex #"(?i)[mzlhvcsqta][^mzlhvcsqta]*") diff --git a/frontend/src/app/util/svg.cljs b/frontend/src/app/util/svg.cljs index 48e64746e1..12c9e0d97c 100644 --- a/frontend/src/app/util/svg.cljs +++ b/frontend/src/app/util/svg.cljs @@ -745,8 +745,6 @@ (reduce gmt/multiply (gmt/matrix) matrices)) (gmt/matrix))) - - (defn format-move [[x y]] (str "M" x " " y)) (defn format-line [[x y]] (str "L" x " " y)) From 61545ea13ebeca7cd88a6be88d5b5f2de11a20ad Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 2 Jun 2021 15:52:51 +0200 Subject: [PATCH 041/204] :sparkles: Import/export workers --- frontend/src/app/worker/export.cljs | 25 +------ frontend/src/app/worker/import.cljs | 103 +++++++++++++++++++++++++--- 2 files changed, 97 insertions(+), 31 deletions(-) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index bb86dbfca6..1d1dcb1081 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -7,25 +7,12 @@ (ns app.worker.export (:require [app.main.render :as r] + [app.main.repo :as rp] [app.util.dom :as dom] - [app.util.http :as http] [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx])) -(defn- handle-response - [response] - (cond - (http/success? response) - (rx/of (:body response)) - - (http/client-error? response) - (rx/throw (:body response)) - - :else - (rx/throw {:type :unexpected - :code (:error response)}))) - (defn get-page-data [{file-name :file-name {:keys [id name] :as data} :data}] (->> (r/render-page data) @@ -35,13 +22,6 @@ :file-name file-name :markup markup})))) -(defn query-file [file-id] - (->> (http/send! {:uri "/api/rpc/query/file" - :query {:id file-id} - :method :get}) - (rx/map http/conditional-decode-transit) - (rx/mapcat handle-response))) - (defn process-pages [file] (let [pages (get-in file [:data :pages]) pages-index (get-in file [:data :pages-index])] @@ -59,7 +39,7 @@ (let [render-stream (->> (rx/from (->> files (mapv :id))) - (rx/merge-map query-file) + (rx/merge-map #(rp/query :file {:id %})) (rx/flat-map process-pages) (rx/observe-on :async) (rx/flat-map get-page-data) @@ -71,7 +51,6 @@ :data (str "Render " (:file-name %) " - " (:name %))))) (->> render-stream (rx/reduce collect-page []) - (rx/tap #(prn %)) (rx/flat-map uz/compress-files) (rx/map #(hash-map :type :finish :data (dom/create-uri %))))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 5ac5247262..350035ed90 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -8,26 +8,109 @@ (:require [app.common.data :as d] [app.common.file-builder :as fb] + [app.common.pages :as cp] + [app.common.uuid :as uuid] + [app.main.repo :as rp] + [app.util.import.parser :as cip] [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx] [cuerdas.core :as str] [tubax.core :as tubax])) +;; Upload changes batches size +(def change-batch-size 100) + +(defn create-empty-file + "Create a new file on the back-end" + [project-id file] + (rp/mutation + :create-file + {:id (:id file) + :name (:name file) + :project-id project-id + :data (-> cp/empty-file-data + (assoc :id (:id file)))})) + +(defn send-changes + "Creates batches of changes to be sent to the backend" + [file init-revn] + (let [revn (atom init-revn) + file-id (:id file) + session-id (uuid/next) + changes-batches + (->> (fb/generate-changes file) + (partition change-batch-size change-batch-size nil) + (mapv vec))] + + (->> (rx/from changes-batches) + (rx/merge-map + (fn [cur-changes-batch] + (rp/mutation + :update-file + {:id file-id + :session-id session-id + :revn @revn + :changes cur-changes-batch}))) + + (rx/tap #(reset! revn (:revn %)))))) + +(defn persist-file + "Sends to the back-end the imported data" + [project-id file] + (->> (create-empty-file project-id file) + (rx/flat-map #(send-changes file (:revn %))))) + (defn parse-file-name [dir] (if (str/ends-with? dir "/") (subs dir 0 (dec (count dir))) dir)) -(defn parse-page-name [path] +(defn parse-page-name + [path] (let [[file page] (str/split path "/")] (str/replace page ".svg" ""))) -(defn import-page [file {:keys [path data]}] +(defn add-shape-file + [file node] + + (let [type (cip/get-type node) + close? (cip/close? node) + data (cip/parse-data type node)] + + (if close? + (case type + :frame + (fb/close-artboard file) + + :group + (fb/close-group file) + + ;; default + file) + + (case type + :frame (fb/add-artboard file data) + :group (fb/add-group file data) + :rect (fb/create-rect file data) + :circle (fb/create-circle file data) + :path (fb/create-path file data) + :text (fb/create-text file data) + :image (fb/create-image file data) + + ;; default + file)))) + +(defn import-page + [file {:keys [path content]}] (let [page-name (parse-page-name path)] - (-> file - (fb/add-page page-name)))) + (when (cip/valid? content) + (let [nodes (->> content cip/node-seq)] + (->> nodes + (filter cip/shape?) + (reduce add-shape-file (fb/add-page file page-name)) + (fb/close-page)))))) (defmethod impl/handler :import-file [{:keys [project-id files]}] @@ -49,8 +132,12 @@ (->> dir-str (rx/merge-map (fn [dir] - (->> file-str - (rx/filter #(str/starts-with? (:path %) dir)) - (rx/reduce import-page (fb/create-file (parse-file-name dir)))))) + (let [file (fb/create-file (parse-file-name dir))] + (rx/concat + (->> file-str + (rx/filter #(str/starts-with? (:path %) dir)) + (rx/reduce import-page file) + (rx/flat-map #(persist-file project-id %)) + (rx/ignore)) - (rx/map #(select-keys % [:id :name]))))) + (rx/of (select-keys file [:id :name]))))))))) From d855b930c52470f23f9f7738b16e1ff8c1a3fa2d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 2 Jun 2021 16:09:50 +0200 Subject: [PATCH 042/204] :sparkles: Temporary UI --- .../styles/main/partials/dashboard.scss | 27 +++++++++++++++++++ .../src/app/main/ui/dashboard/file_menu.cljs | 22 +++++++++++++-- .../src/app/main/ui/dashboard/import.cljs | 23 ++++++++++------ .../src/app/main/ui/dashboard/projects.cljs | 15 +++++++++-- frontend/translations/en.po | 8 +++++- 5 files changed, 82 insertions(+), 13 deletions(-) diff --git a/frontend/resources/styles/main/partials/dashboard.scss b/frontend/resources/styles/main/partials/dashboard.scss index f8423ce48c..780dd8794e 100644 --- a/frontend/resources/styles/main/partials/dashboard.scss +++ b/frontend/resources/styles/main/partials/dashboard.scss @@ -165,3 +165,30 @@ } } } + +.import-file-btn { + align-items: center; + display: flex; + flex-direction: column; + height: 2rem; + justify-content: center; + overflow: hidden; + padding: 4px; + width: 2rem; + + background: none; + border: 1px solid $color-gray-20; + border-radius: 2px; + cursor: pointer; + transition: all 0.4s; + margin-left: 1rem; + + &:hover { + background: $color-primary; + } + + svg { + width: 16px; + height: 16px; + } +} diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index f24a8d412f..6dcedd52ab 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -11,8 +11,9 @@ [app.main.data.modal :as modal] [app.main.repo :as rp] [app.main.store :as st] - [app.main.ui.context :as ctx] [app.main.ui.components.context-menu :refer [context-menu]] + [app.main.ui.context :as ctx] + [app.main.worker :as uw] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] @@ -150,7 +151,22 @@ :hint (tr "modals.remove-shared-confirm.hint") :cancel-label :omit :accept-label (tr "modals.remove-shared-confirm.accept") - :on-accept del-shared})))] + :on-accept del-shared}))) + + on-export-files + (fn [event] + (->> (uw/ask-many! + {:cmd :export-file + :team-id current-team-id + :files files}) + (rx/subs + (fn [{:keys [type data] :as msg}] + (case type + :progress + (prn "[Progress]" data) + + :finish + (dom/save-as data "export" "application/zip" "Export package (*.zip)"))))))] (mf/use-effect (fn [] @@ -176,6 +192,7 @@ [[(tr "dashboard.duplicate-multi" file-count) on-duplicate] (when (or (seq current-projects) (seq other-teams)) [(tr "dashboard.move-to-multi" file-count) nil sub-options]) + #_[(tr "dashboard.export-multi" file-count) on-export-files] [:separator] [(tr "labels.delete-multi-files" file-count) on-delete]] @@ -187,6 +204,7 @@ (if (:is-shared file) [(tr "dashboard.remove-shared") on-del-shared] [(tr "dashboard.add-shared") on-add-shared]) + #_[(tr "dashboard.export-single") on-export-files] [:separator] [(tr "labels.delete") on-delete]])] diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index cd7e86cea6..c30e48f03f 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -14,12 +14,12 @@ [beicon.core :as rx] [rumext.alpha :as mf])) -(log/set-level! :warn) +(log/set-level! :debug) (defn use-import-file - [project-id] + [project-id on-finish-import] (mf/use-callback - (mf/deps project-id) + (mf/deps project-id on-finish-import) (fn [files] (when files (let [files (->> files (mapv dom/create-uri))] @@ -30,16 +30,23 @@ (rx/subs (fn [result] - (log/debug :action "import-result" :result result))))))))) + (log/debug :action "import-result" :result result)) + + (fn [err] + (log/debug :action "import-error" :result err)) + + (fn [] + (log/debug :action "import-end") + (when on-finish-import (on-finish-import)))))))))) (mf/defc import-button - [{:keys [project-id]}] + [{:keys [project-id on-finish-import]}] (let [file-input (mf/use-ref nil) - on-file-selected (use-import-file project-id)] + on-file-selected (use-import-file project-id on-finish-import)] [:form.import-file - [:button.import-file-icon {:type "button" - :on-click #(dom/click (mf/ref-val file-input))} i/import] + [:button.import-file-btn {:type "button" + :on-click #(dom/click (mf/ref-val file-input))} i/import] [:& file-uploader {:accept "application/zip" :multi true :input-ref file-input diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 342d377f00..7921624aa7 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -21,7 +21,8 @@ [app.util.router :as rt] [app.util.time :as dt] [okulary.core :as l] - [rumext.alpha :as mf])) + [rumext.alpha :as mf] + [app.main.ui.dashboard.import :refer [import-button]])) (mf/defc header {::mf/wrap [mf/memo]} @@ -30,6 +31,7 @@ [:header.dashboard-header [:div.dashboard-title [:h1 (tr "dashboard.projects-title")]] + [:a.btn-secondary.btn-small {:on-click create} (tr "dashboard.new-project")]])) @@ -96,7 +98,13 @@ (fn [] (let [mdata {:on-success on-file-created} params {:project-id (:id project)}] - (st/emit! (dd/create-file (with-meta params mdata))))))] + (st/emit! (dd/create-file (with-meta params mdata)))))) + + on-finish-import + (mf/use-callback + (fn [] + (st/emit! (dd/fetch-recent-files) + (dd/clear-selected-files))))] [:div.dashboard-project-row {:class (when first? "first")} [:div.project @@ -130,6 +138,9 @@ (dt/timeago {:locale locale}))] [:span.recent-files-row-title-info (str ", " time)])) + #_[:& import-button {:project-id (:id project) + :on-finish-import on-finish-import}] + [:a.btn-secondary.btn-small {:on-click create-file} (tr "dashboard.new-file")]] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 1721be2099..31d41d091d 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2633,4 +2633,10 @@ 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 "Click to close the path" + +msgid "dashboard.export-single" +msgstr "Export file" + +msgid "dashboard.export-multi" +msgstr "Export %s files" From a97c7cada4d2a79192b8cda65a915ce4f718f447 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 4 Jun 2021 15:52:18 +0200 Subject: [PATCH 043/204] :bug: Fix problem with namespace --- frontend/src/app/main/ui/shapes/shape.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 5a8d4fecdf..86420ee124 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -26,7 +26,7 @@ (let [ns-attr (str "penpot:" (-> attr d/name))] (-> props (obj/set! ns-attr val)))) - frame? (= :frame type)] + frame? (= :frame (:type shape))] (-> props (add! :name (-> shape :name)) (add! :blocked (-> shape (:blocked false) str)) From c4d3023fd3e3594e189f62f1b5aa93f16f89aff2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Jun 2021 09:22:26 +0200 Subject: [PATCH 044/204] :arrow_up: Upgrade potok. Includes many performance improvements. --- frontend/deps.edn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/deps.edn b/frontend/deps.edn index a109f5b5f8..edf430c8b3 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -8,7 +8,7 @@ funcool/beicon {:mvn/version "2021.06.02-0"} funcool/okulary {:mvn/version "2020.04.14-0"} - funcool/potok {:mvn/version "4.0.0"} + funcool/potok {:mvn/version "2021.06.07-0"} funcool/rumext {:mvn/version "2021.05.12-1"} funcool/tubax {:mvn/version "2021.05.20-0"} From 8f3c5b5cea3e68496b757a27af170936db673291 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Jun 2021 09:44:12 +0200 Subject: [PATCH 045/204] :paperclip: Add minior adaptations to main docker files. --- docker/images/Dockerfile.backend | 4 ++-- docker/images/Dockerfile.frontend | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/images/Dockerfile.backend b/docker/images/Dockerfile.backend index 443bb3371d..458418e0e6 100644 --- a/docker/images/Dockerfile.backend +++ b/docker/images/Dockerfile.backend @@ -40,6 +40,6 @@ RUN set -eux; \ rm -rf /tmp/openjdk.tar.gz; ENV JAVA_HOME=/usr/lib/jvm/openjdk16 PATH="/usr/lib/jvm/openjdk16/bin:$PATH" -ADD ./bundle-app/backend/ /opt/bundle/ -WORKDIR /opt/bundle +ADD ./bundle-backend/ /opt/penpot/backend/ +WORKDIR /opt/penpot/backend CMD ["/bin/bash", "run.sh"] diff --git a/docker/images/Dockerfile.frontend b/docker/images/Dockerfile.frontend index c1946adea2..697a9c0768 100644 --- a/docker/images/Dockerfile.frontend +++ b/docker/images/Dockerfile.frontend @@ -1,7 +1,7 @@ FROM nginx:latest LABEL maintainer="Andrey Antukh " -ADD ./bundle-app/frontend /var/www/app/ +ADD ./bundle-frontend/ /var/www/app/ ADD ./files/config.js /var/www/app/js/config.js ADD ./files/nginx.conf /etc/nginx/nginx.conf ADD ./files/nginx-entrypoint.sh /entrypoint.sh From 1232f93f1a297facd3b421441e5f3debdcb7d5a1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Jun 2021 10:55:50 +0200 Subject: [PATCH 046/204] :bug: Fix shadow-cljs version on common/deps.edn file. --- common/deps.edn | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/deps.edn b/common/deps.edn index a01659a432..2019e5504f 100644 --- a/common/deps.edn +++ b/common/deps.edn @@ -42,7 +42,7 @@ {org.clojure/tools.namespace {:mvn/version "RELEASE"} org.clojure/test.check {:mvn/version "RELEASE"} org.clojure/tools.deps.alpha {:mvn/version "RELEASE"} - thheller/shadow-cljs {:mvn/version "2.14.1"} + thheller/shadow-cljs {:mvn/version "2.12.6"} criterium/criterium {:mvn/version "RELEASE"} mockery/mockery {:mvn/version "RELEASE"}} :extra-paths ["test" "dev"]} From 8c223b9fb86ae0be6203e903511105dfa9682dcf Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Jun 2021 10:56:21 +0200 Subject: [PATCH 047/204] :sparkles: Allow future dates on get-by-params method. --- backend/src/app/db.clj | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 6527fa2a8d..2b98c5e31f 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -221,14 +221,20 @@ (sql/delete table params opts) (assoc opts :return-keys true)))) +(defn- is-deleted? + [{:keys [deleted-at]}] + (and (dt/instant? deleted-at) + (< (inst-ms deleted-at) + (inst-ms (dt/now))))) + (defn get-by-params ([ds table params] (get-by-params ds table params nil)) ([ds table params {:keys [uncheked] :or {uncheked false} :as opts}] (let [res (exec-one! ds (sql/select table params opts))] - (when (and (not uncheked) - (or (:deleted-at res) (not res))) + (when (and (not uncheked) (or (not res) (is-deleted? res))) (ex/raise :type :not-found + :table table :hint "database object not found")) res))) From 44f44413720737ac2d8c5d4aba7c9245f21f5392 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 7 Jun 2021 11:22:09 +0200 Subject: [PATCH 048/204] :bug: Fix error when opening assets of external library --- frontend/src/app/main/ui/workspace/sidebar/assets.cljs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 228f7dc124..26fd4d30b9 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -204,7 +204,8 @@ (mf/defc asset-section [{:keys [children file-id title box assets-count open?]}] - (let [children (if (array? children) children [children]) + (let [children (->> (if (array? children) children [children]) + (filter some?)) get-role #(.. % -props -role) title-buttons (filter #(= (get-role %) :title-button) children) content (filter #(= (get-role %) :content) children)] From 075f0a1bb0a2abd8ab2a4cd9c127d1747364d5b2 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 7 Jun 2021 12:10:41 +0200 Subject: [PATCH 049/204] :sparkles: Move frame style block to workspace wrapper --- frontend/src/app/main/ui/shapes/frame.cljs | 7 ------- frontend/src/app/main/ui/workspace/shapes/frame.cljs | 4 +++- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index 4146200273..bf48f0f378 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -8,15 +8,11 @@ (:require [app.common.data :as d] [app.main.ui.shapes.attrs :as attrs] - [app.main.ui.shapes.text.fontfaces :as ff] [app.util.object :as obj] [rumext.alpha :as mf])) (def frame-default-props {:fill-color "#ffffff"}) -(defn is-text? [{type :type}] - (= :text type)) - (defn frame-shape [shape-wrapper] (mf/fnc frame-shape @@ -26,8 +22,6 @@ shape (unchecked-get props "shape") {:keys [id width height]} shape - text-childs (->> childs (filterv is-text?)) - props (-> (merge frame-default-props shape) (attrs/extract-style-attrs) (obj/merge! @@ -37,7 +31,6 @@ :height height :className "frame-background"}))] [:* - [:& ff/fontfaces-style {:shapes text-childs}] [:> :rect props] (for [[i item] (d/enumerate childs)] [:& shape-wrapper {:frame shape diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 592bb5d451..9387ad1ca8 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -10,6 +10,7 @@ [app.main.refs :as refs] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.shape :refer [shape-container]] + [app.main.ui.shapes.text.fontfaces :as ff] [app.util.object :as obj] [app.util.timers :as ts] [beicon.core :as rx] @@ -92,11 +93,12 @@ (fn [node] (ts/schedule-on-idle #(reset! rendered? (some? node)))))] - (when (and shape (not (:hidden shape))) + (when (some? shape) [:g.frame-wrapper {:display (when (:hidden shape) "none")} (when-not show-thumbnail? [:> shape-container {:shape shape :ref on-dom} + [:& ff/fontfaces-style {:shapes children}] [:& frame-shape {:shape shape :childs children}]]) From d4b02e36a7a2df514acb9926925f96895c4223c0 Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Thu, 3 Jun 2021 18:35:01 +0200 Subject: [PATCH 050/204] :lipstick: Change shadow options css --- .../partials/sidebar-element-options.scss | 22 +++++++++-- .../sidebar/options/menus/shadow.cljs | 39 ++++++++++++------- 2 files changed, 44 insertions(+), 17 deletions(-) diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index af2a9d22af..446a97c62d 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -816,13 +816,13 @@ .advanced-options-wrapper { width: 100%; - padding-left: 0.25rem; } .advanced-options { - background-color: #303236; + border: 1px solid $color-gray-60; + background-color: $color-gray-50; border-radius: 4px; - padding: 0.5rem; + padding: 8px 3px; position: relative; top: 2px; width: 100%; @@ -897,6 +897,22 @@ .download-button { margin-top: 10px; } + + .input-element { + width: 100%; + flex-shrink: initial; + } + + .row-grid-2 { + grid-column-gap: 1em; + } + + .color-info { + input { + margin-right: 1em; + width: 74px; + } + } } .shadow-options .color-row-wrap { 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 0f84e67c6b..edb6622569 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 @@ -104,19 +104,27 @@ {:on-click #(reset! open-shadow true)} i/actions] - [:> numeric-input {:ref basic-offset-x-ref - :on-change (update-attr index :offset-x valid-number?) - :on-click (select-text basic-offset-x-ref) - :value (:offset-x value)}] - [:> numeric-input {:ref basic-offset-y-ref - :on-change (update-attr index :offset-y valid-number?) - :on-click (select-text basic-offset-y-ref) - :value (:offset-y value)}] - [:> numeric-input {:ref basic-blur-ref - :on-click (select-text basic-blur-ref) - :on-change (update-attr index :blur valid-number?) - :min 0 - :value (:blur value)}] + ;; [:> numeric-input {:ref basic-offset-x-ref + ;; :on-change (update-attr index :offset-x valid-number?) + ;; :on-click (select-text basic-offset-x-ref) + ;; :value (:offset-x value)}] + ;; [:> numeric-input {:ref basic-offset-y-ref + ;; :on-change (update-attr index :offset-y valid-number?) + ;; :on-click (select-text basic-offset-y-ref) + ;; :value (:offset-y value)}] + ;; [:> numeric-input {:ref basic-blur-ref + ;; :on-click (select-text basic-blur-ref) + ;; :on-change (update-attr index :blur valid-number?) + ;; :min 0 + ;; :value (:blur value)}] + + [:select.input-select + {:default-value (str (:style value)) + :on-change (fn [event] + (let [value (-> event dom/get-target dom/get-value d/read-string)] + (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))} + [:option {:value ":drop-shadow"} (t locale "workspace.options.shadow-options.drop-shadow")] + [:option {:value ":inner-shadow"} (t locale "workspace.options.shadow-options.inner-shadow")]] [:div.element-set-actions [:div.element-set-actions-button {:on-click (toggle-visibility index)} @@ -126,7 +134,10 @@ [:& advanced-options {:visible? @open-shadow :on-close #(reset! open-shadow false)} - [:div.row-grid-2 + [:div.color-data + [:div.element-set-actions-button + {:on-click #(reset! open-shadow false)} + i/actions] [:select.input-select {:default-value (str (:style value)) :on-change (fn [event] From 64049076991917fa9ca04f55614401a8fe9b93b0 Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Mon, 7 Jun 2021 07:55:21 +0200 Subject: [PATCH 051/204] :sparkles: Add new asset advanced optios css --- .../styles/main/partials/sidebar-assets.scss | 9 +++++++++ .../main/partials/sidebar-element-options.scss | 5 +++-- .../sidebar/options/menus/typography.cljs | 14 ++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss index 4389cf8b6e..122f5e8aa1 100644 --- a/frontend/resources/styles/main/partials/sidebar-assets.scss +++ b/frontend/resources/styles/main/partials/sidebar-assets.scss @@ -400,6 +400,15 @@ top: 10px; left: 10px; } + + .advanced-options { + border-color: $color-black; + background-color: $color-gray-60; + + .input-text, .input-select, .adv-typography-name { + background-color: $color-gray-60; + } + } } } diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 446a97c62d..31bf7f4ede 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -543,7 +543,7 @@ padding: 0 $x-small; &:first-child { - justify-content: space-between; + justify-content: flex-start; margin-left: 0; } @@ -1001,6 +1001,7 @@ .spacing-options { display: flex; + width: 100%; } .asset-section { @@ -1023,7 +1024,7 @@ width: 100%; max-width: none; margin: 0; - background: #303236; + background-color: #303236; border-top: none; border-left: none; border-right: none; 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 5fa6300e64..3d6a779291 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 @@ -424,7 +424,8 @@ [:div.element-set-content [:> font-options opts] [:div.row-flex - [:> spacing-options opts] + [:> spacing-options opts]] + [:div.row-flex [:> text-transform-options opts]]])) @@ -501,6 +502,10 @@ [:span.label (tr "workspace.assets.typography.font-id")] [:span (:font-id typography)]] + [:div.element-set-actions-button + {:on-click #(reset! open? true)} + i/actions] + [:div.row-flex [:span.label (tr "workspace.assets.typography.font-variant-id")] [:span (:font-variant-id typography)]] @@ -532,6 +537,11 @@ {:type "text" :ref name-input-ref :default-value (cp/merge-path-item (:path typography) (:name typography)) - :on-blur on-name-blur}]]] + :on-blur on-name-blur}] + + [:div.element-set-actions-button + {:on-click #(reset! open? false)} + i/actions]]] + [:& typography-options {:values typography :on-change on-change}]])]])) From a2b03051622f685c5fde25cd982cc203e34288a0 Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Mon, 7 Jun 2021 18:14:49 +0200 Subject: [PATCH 052/204] :sparkles: Add new text and grid advanced opt css --- .../styles/main/partials/colorpicker.scss | 1 + .../partials/sidebar-element-options.scss | 23 +++++++++++++++---- .../sidebar/options/menus/frame_grid.cljs | 2 ++ .../sidebar/options/menus/typography.cljs | 4 ++-- 4 files changed, 23 insertions(+), 7 deletions(-) diff --git a/frontend/resources/styles/main/partials/colorpicker.scss b/frontend/resources/styles/main/partials/colorpicker.scss index 410437ae6e..e0b0ed1788 100644 --- a/frontend/resources/styles/main/partials/colorpicker.scss +++ b/frontend/resources/styles/main/partials/colorpicker.scss @@ -480,6 +480,7 @@ .color-data { align-items: center; display: flex; + margin-bottom: $small; position: relative; .color-name { diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 31bf7f4ede..efb9c084e1 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -733,6 +733,16 @@ .grid-option { margin-bottom: 0.5rem; + .advanced-options { + .row-flex { + justify-content: flex-end; + } + .custom-button { + left: 0; + position: absolute; + top: 12px; + } + } } .element-set-content .custom-select.input-option { @@ -822,7 +832,7 @@ border: 1px solid $color-gray-60; background-color: $color-gray-50; border-radius: 4px; - padding: 8px 3px; + padding: 8px; position: relative; top: 2px; width: 100%; @@ -936,6 +946,10 @@ &:hover svg { fill: $color-primary; } + &.actions-inside { + position: absolute; + right: 0; + } } .element-set-label { @@ -1062,17 +1076,16 @@ .go-to-lib-button { transition: border 0.3s, color 0.3s; text-align: center; - background: $color-gray-60; + background: $color-gray-50; padding: 0.5rem; border-radius: 2px; cursor: pointer; font-size: 14px; margin-top: 1rem; - border: 1px solid $color-gray-60; &:hover { - border: 1px solid $color-primary; - color: $color-primary; + background: $color-primary; + color: $color-black; } } } diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs index 40bbe0a244..f5aaa4524c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs @@ -150,6 +150,8 @@ [:& advanced-options {:visible? (:show-advanced-options @state) :on-close toggle-advanced-options} + [:button.custom-button {:class (when (:show-advanced-options @state) "is-active") + :on-click toggle-advanced-options} i/actions] (when (= :square type) [:& input-row {:label (t locale "workspace.options.grid.params.size") :class "pixels" 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 3d6a779291..9c15b74dbc 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 @@ -502,8 +502,8 @@ [:span.label (tr "workspace.assets.typography.font-id")] [:span (:font-id typography)]] - [:div.element-set-actions-button - {:on-click #(reset! open? true)} + [:div.element-set-actions-button.actions-inside + {:on-click #(reset! open? false)} i/actions] [:div.row-flex From c4b4976be07ea89e52186b30375c39cdafb058d4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 9 Jun 2021 11:18:47 +0200 Subject: [PATCH 053/204] :sparkles: Remove advanced options overlay and single option when advanced options displayed --- .../main/ui/workspace/sidebar/options/common.cljs | 8 +++----- .../ui/workspace/sidebar/options/menus/blur.cljs | 1 - .../workspace/sidebar/options/menus/frame_grid.cljs | 13 +++++++------ .../ui/workspace/sidebar/options/menus/shadow.cljs | 3 +-- .../workspace/sidebar/options/menus/typography.cljs | 3 ++- 5 files changed, 13 insertions(+), 15 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs index 5fbabea126..6468e1b4d1 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs @@ -22,9 +22,7 @@ (.scrollIntoViewIfNeeded ^js node))))) (when visible? - [:* - [:div.focus-overlay {:on-click handle-click}] - [:div.advanced-options-wrapper {:ref ref} - [:div.advanced-options {} - children]]]))) + [:div.advanced-options-wrapper {:ref ref} + [:div.advanced-options {} + children]]))) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs index f6c8698cad..cb2e9fd808 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs @@ -13,7 +13,6 @@ [app.main.data.workspace.changes :as dch] [app.main.store :as st] [app.main.ui.icons :as i] - [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] [app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs index f5aaa4524c..1c30246c86 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs @@ -117,11 +117,13 @@ (on-save-grid grid))) is-default (= (->> grid :params) - (->> grid :type default-grid-params))] + (->> grid :type default-grid-params)) + + open? (:show-advanced-options @state)] [:div.grid-option - [:div.grid-option-main - [:button.custom-button {:class (when (:show-advanced-options @state) "is-active") + [:div.grid-option-main {:style {:display (when open? "none")}} + [:button.custom-button {:class (when open? "is-active") :on-click toggle-advanced-options} i/actions] [:& select {:class "flex-grow" @@ -148,10 +150,9 @@ [:button.custom-button {:on-click handle-toggle-visibility} (if display i/eye i/eye-closed)] [:button.custom-button {:on-click handle-remove-grid} i/minus]]] - [:& advanced-options {:visible? (:show-advanced-options @state) + [:& advanced-options {:visible? open? :on-close toggle-advanced-options} - [:button.custom-button {:class (when (:show-advanced-options @state) "is-active") - :on-click toggle-advanced-options} i/actions] + [:button.custom-button {:on-click toggle-advanced-options} i/actions] (when (= :square type) [:& input-row {:label (t locale "workspace.options.grid.params.size") :class "pixels" 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 edb6622569..9b6e487353 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 @@ -98,8 +98,7 @@ (fn [] (st/emit! (dch/update-shapes ids #(update-in % [:shadow index :hidden] not)))))] [:* - [:div.element-set-options-group - + [:div.element-set-options-group {:style {:display (when @open-shadow "none")}} [:div.element-set-actions-button {:on-click #(reset! open-shadow true)} i/actions] 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 9c15b74dbc..36e8acdf0b 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 @@ -468,7 +468,8 @@ [:* [:div.element-set-options-group.typography-entry - {:class (when selected? "selected")} + {:class (when selected? "selected") + :style {:display (when @open? "none")}} [:div.typography-selection-wrapper {:class (when on-click "is-selectable") :on-click on-click From 860e0227af780736891f14d8d61098045d4e131e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 7 Jun 2021 16:51:09 +0200 Subject: [PATCH 054/204] :recycle: Reimplement GC mechanism for penpot database objects. --- backend/src/app/main.clj | 15 +- backend/src/app/migrations.clj | 9 ++ .../0056-add-missing-index-on-deleted-at.sql | 15 ++ .../0057-del-profile-on-delete-trigger.sql | 2 + .../sql/0058-del-team-on-delete-trigger.sql | 2 + backend/src/app/rpc/mutations/demo.clj | 9 +- backend/src/app/rpc/mutations/files.clj | 10 -- backend/src/app/rpc/mutations/fonts.clj | 19 +-- backend/src/app/rpc/mutations/profile.clj | 17 +- backend/src/app/rpc/mutations/projects.clj | 10 -- backend/src/app/rpc/mutations/teams.clj | 9 -- backend/src/app/rpc/queries/profile.clj | 14 +- backend/src/app/tasks/delete_object.clj | 3 + backend/src/app/tasks/delete_profile.clj | 3 + backend/src/app/tasks/file_media_gc.clj | 1 + backend/src/app/tasks/objects_gc.clj | 152 ++++++++++++++++++ backend/test/app/services_files_test.clj | 67 ++++++++ backend/test/app/services_profile_test.clj | 37 ++--- backend/test/app/services_projects_test.clj | 72 ++++++++- backend/test/app/services_teams_test.clj | 75 +++++++++ 20 files changed, 437 insertions(+), 104 deletions(-) create mode 100644 backend/src/app/migrations/sql/0056-add-missing-index-on-deleted-at.sql create mode 100644 backend/src/app/migrations/sql/0057-del-profile-on-delete-trigger.sql create mode 100644 backend/src/app/migrations/sql/0058-del-team-on-delete-trigger.sql create mode 100644 backend/src/app/tasks/objects_gc.clj diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 06765d5ff9..6ba884197d 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -172,18 +172,21 @@ {:cron #app/cron "0 0 * * * ?" ;; hourly :task :file-xlog-gc} - {:cron #app/cron "0 0 1 * * ?" ;; daily (1 hour shift) + {:cron #app/cron "0 0 0 * * ?" ;; daily :task :storage-deleted-gc} - {:cron #app/cron "0 0 2 * * ?" ;; daily (2 hour shift) + {:cron #app/cron "0 0 0 * * ?" ;; daily :task :storage-touched-gc} - {:cron #app/cron "0 0 3 * * ?" ;; daily (3 hour shift) + {:cron #app/cron "0 0 0 * * ?" ;; daily :task :session-gc} {:cron #app/cron "0 0 * * * ?" ;; hourly :task :storage-recheck} + {:cron #app/cron "0 0 0 * * ?" ;; daily + :task :objects-gc} + {:cron #app/cron "0 0 0 * * ?" ;; daily :task :tasks-gc} @@ -203,6 +206,7 @@ {:metrics (ig/ref :app.metrics/metrics) :tasks {:sendmail (ig/ref :app.emails/sendmail-handler) + :objects-gc (ig/ref :app.tasks.objects-gc/handler) :delete-object (ig/ref :app.tasks.delete-object/handler) :delete-profile (ig/ref :app.tasks.delete-profile/handler) :file-media-gc (ig/ref :app.tasks.file-media-gc/handler) @@ -236,6 +240,11 @@ {:pool (ig/ref :app.db/pool) :storage (ig/ref :app.storage/storage)} + :app.tasks.objects-gc/handler + {:pool (ig/ref :app.db/pool) + :storage (ig/ref :app.storage/storage) + :max-age cf/deletion-delay} + :app.tasks.delete-profile/handler {:pool (ig/ref :app.db/pool)} diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index 8e63509952..022abb69b8 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -175,6 +175,15 @@ {:name "0055-mod-file-media-object-table" :fn (mg/resource "app/migrations/sql/0055-mod-file-media-object-table.sql")} + + {:name "0056-add-missing-index-on-deleted-at" + :fn (mg/resource "app/migrations/sql/0056-add-missing-index-on-deleted-at.sql")} + + {:name "0057-del-profile-on-delete-trigger" + :fn (mg/resource "app/migrations/sql/0057-del-profile-on-delete-trigger.sql")} + + {:name "0058-del-team-on-delete-trigger" + :fn (mg/resource "app/migrations/sql/0058-del-team-on-delete-trigger.sql")} ]) diff --git a/backend/src/app/migrations/sql/0056-add-missing-index-on-deleted-at.sql b/backend/src/app/migrations/sql/0056-add-missing-index-on-deleted-at.sql new file mode 100644 index 0000000000..7a74deed2f --- /dev/null +++ b/backend/src/app/migrations/sql/0056-add-missing-index-on-deleted-at.sql @@ -0,0 +1,15 @@ +CREATE INDEX profile_deleted_at_idx + ON profile(deleted_at, id) + WHERE deleted_at IS NOT NULL; + +CREATE INDEX project_deleted_at_idx + ON project(deleted_at, id) + WHERE deleted_at IS NOT NULL; + +CREATE INDEX team_deleted_at_idx + ON team(deleted_at, id) + WHERE deleted_at IS NOT NULL; + +CREATE INDEX team_font_variant_deleted_at_idx + ON team_font_variant(deleted_at, id) + WHERE deleted_at IS NOT NULL; diff --git a/backend/src/app/migrations/sql/0057-del-profile-on-delete-trigger.sql b/backend/src/app/migrations/sql/0057-del-profile-on-delete-trigger.sql new file mode 100644 index 0000000000..b748c3ce68 --- /dev/null +++ b/backend/src/app/migrations/sql/0057-del-profile-on-delete-trigger.sql @@ -0,0 +1,2 @@ +DROP TRIGGER profile__on_delete__tgr ON profile CASCADE; +DROP FUNCTION on_delete_profile (); diff --git a/backend/src/app/migrations/sql/0058-del-team-on-delete-trigger.sql b/backend/src/app/migrations/sql/0058-del-team-on-delete-trigger.sql new file mode 100644 index 0000000000..d36c0e187d --- /dev/null +++ b/backend/src/app/migrations/sql/0058-del-team-on-delete-trigger.sql @@ -0,0 +1,2 @@ +DROP TRIGGER team__on_delete__tgr ON team CASCADE; +DROP FUNCTION on_delete_team (); diff --git a/backend/src/app/rpc/mutations/demo.clj b/backend/src/app/rpc/mutations/demo.clj index a74f8b4f80..fc7f184bf9 100644 --- a/backend/src/app/rpc/mutations/demo.clj +++ b/backend/src/app/rpc/mutations/demo.clj @@ -15,7 +15,7 @@ [app.rpc.mutations.profile :as profile] [app.setup.initial-data :as sid] [app.util.services :as sv] - [app.worker :as wrk] + [app.util.time :as dt] [buddy.core.codecs :as bc] [buddy.core.nonce :as bn] [clojure.spec.alpha :as s])) @@ -35,6 +35,7 @@ :email email :fullname fullname :is-demo true + :deleted-at (dt/in-future cfg/deletion-delay) :password password :props {:onboarding-viewed true}}] @@ -48,12 +49,6 @@ (#'profile/create-profile-relations conn) (sid/load-initial-project! conn)) - ;; Schedule deletion of the demo profile - (wrk/submit! {::wrk/task :delete-profile - ::wrk/delay cfg/deletion-delay - ::wrk/conn conn - :profile-id id}) - (with-meta {:email email :password password} {::audit/profile-id id})))) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index d90e453fb6..02e8963a9d 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -11,7 +11,6 @@ [app.common.pages.migrations :as pmg] [app.common.spec :as us] [app.common.uuid :as uuid] - [app.config :as cfg] [app.db :as db] [app.rpc.permissions :as perms] [app.rpc.queries.files :as files] @@ -19,7 +18,6 @@ [app.util.blob :as blob] [app.util.services :as sv] [app.util.time :as dt] - [app.worker :as wrk] [clojure.spec.alpha :as s])) ;; --- Helpers & Specs @@ -121,14 +119,6 @@ [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) - - ;; Schedule object deletion - (wrk/submit! {::wrk/task :delete-object - ::wrk/delay cfg/deletion-delay - ::wrk/conn conn - :id id - :type :file}) - (mark-file-deleted conn params))) (defn mark-file-deleted diff --git a/backend/src/app/rpc/mutations/fonts.clj b/backend/src/app/rpc/mutations/fonts.clj index ca1d2263e7..9037550f13 100644 --- a/backend/src/app/rpc/mutations/fonts.clj +++ b/backend/src/app/rpc/mutations/fonts.clj @@ -104,21 +104,10 @@ (db/with-atomic [conn pool] (teams/check-edition-permissions! conn profile-id team-id) - (let [items (db/query conn :team-font-variant - {:font-id id :team-id team-id} - {:for-update true})] - (doseq [item items] - ;; Schedule object deletion - (wrk/submit! {::wrk/task :delete-object - ::wrk/delay cf/deletion-delay - ::wrk/conn conn - :id (:id item) - :type :team-font-variant})) - - (db/update! conn :team-font-variant - {:deleted-at (dt/now)} - {:font-id id :team-id team-id}) - nil))) + (db/update! conn :team-font-variant + {:deleted-at (dt/now)} + {:font-id id :team-id team-id}) + nil)) ;; --- DELETE FONT VARIANT diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 71c2a0d900..0cef24832b 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -22,7 +22,6 @@ [app.storage :as sto] [app.util.services :as sv] [app.util.time :as dt] - [app.worker :as wrk] [buddy.hashers :as hashers] [clojure.spec.alpha :as s] [cuerdas.core :as str])) @@ -179,9 +178,9 @@ :valid false}))) (defn create-profile - "Create the profile entry on the database with limited input - filling all the other fields with defaults." - [conn {:keys [id fullname email password is-active is-muted is-demo opts] + "Create the profile entry on the database with limited input filling + all the other fields with defaults." + [conn {:keys [id fullname email password is-active is-muted is-demo opts deleted-at] :or {is-active false is-muted false is-demo false} :as params}] (let [id (or id (uuid/next)) @@ -193,6 +192,7 @@ :email (str/lower email) :auth-backend "penpot" :password password + :deleted-at deleted-at :props props :is-active is-active :is-muted is-muted @@ -264,7 +264,8 @@ (let [profile (->> (profile/retrieve-profile-data-by-email conn email) (validate-profile) (profile/strip-private-attrs) - (profile/populate-additional-data conn))] + (profile/populate-additional-data conn)) + profile (update profile :props db/decode-transit-pgobject)] (if-let [token (:invitation-token params)] ;; If the request comes with an invitation token, this means ;; that user wants to accept it with different user. A very @@ -619,12 +620,6 @@ (db/with-atomic [conn pool] (check-can-delete-profile! conn profile-id) - ;; Schedule a complete deletion of profile - (wrk/submit! {::wrk/task :delete-profile - ::wrk/delay cfg/deletion-delay - ::wrk/conn conn - :profile-id profile-id}) - (db/update! conn :profile {:deleted-at (dt/now)} {:id profile-id}) diff --git a/backend/src/app/rpc/mutations/projects.clj b/backend/src/app/rpc/mutations/projects.clj index bc25ef8ff3..357b3841cb 100644 --- a/backend/src/app/rpc/mutations/projects.clj +++ b/backend/src/app/rpc/mutations/projects.clj @@ -8,14 +8,12 @@ (:require [app.common.spec :as us] [app.common.uuid :as uuid] - [app.config :as cfg] [app.db :as db] [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] - [app.worker :as wrk] [clojure.spec.alpha :as s])) ;; --- Helpers & Specs @@ -123,14 +121,6 @@ [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (proj/check-edition-permissions! conn profile-id id) - - ;; Schedule object deletion - (wrk/submit! {::wrk/task :delete-object - ::wrk/delay cfg/deletion-delay - ::wrk/conn conn - :id id - :type :project}) - (db/update! conn :project {:deleted-at (dt/now)} {:id id}) diff --git a/backend/src/app/rpc/mutations/teams.clj b/backend/src/app/rpc/mutations/teams.clj index 662d2dc354..3b71c43c48 100644 --- a/backend/src/app/rpc/mutations/teams.clj +++ b/backend/src/app/rpc/mutations/teams.clj @@ -10,7 +10,6 @@ [app.common.exceptions :as ex] [app.common.spec :as us] [app.common.uuid :as uuid] - [app.config :as cfg] [app.db :as db] [app.emails :as eml] [app.media :as media] @@ -21,7 +20,6 @@ [app.storage :as sto] [app.util.services :as sv] [app.util.time :as dt] - [app.worker :as wrk] [clojure.spec.alpha :as s] [datoteka.core :as fs])) @@ -135,13 +133,6 @@ (ex/raise :type :validation :code :only-owner-can-delete-team)) - ;; Schedule object deletion - (wrk/submit! {::wrk/task :delete-object - ::wrk/delay cfg/deletion-delay - ::wrk/conn conn - :id id - :type :team}) - (db/update! conn :team {:deleted-at (dt/now)} {:id id}) diff --git a/backend/src/app/rpc/queries/profile.clj b/backend/src/app/rpc/queries/profile.clj index ee974c3596..4508b740eb 100644 --- a/backend/src/app/rpc/queries/profile.clj +++ b/backend/src/app/rpc/queries/profile.clj @@ -11,7 +11,8 @@ [app.common.uuid :as uuid] [app.db :as db] [app.util.services :as sv] - [clojure.spec.alpha :as s])) + [clojure.spec.alpha :as s] + [cuerdas.core :as str])) ;; --- Helpers & Specs @@ -90,16 +91,11 @@ profile)) -(def sql:retrieve-profile-by-email - "select p.* from profile as p - where p.email = lower(?) - and p.deleted_at is null") - (defn retrieve-profile-data-by-email [conn email] - (let [sql [sql:retrieve-profile-by-email email]] - (some-> (db/exec-one! conn sql) - (decode-profile-row)))) + (try + (db/get-by-params conn :profile {:email (str/lower email)}) + (catch Exception _e))) ;; --- Attrs Helpers diff --git a/backend/src/app/tasks/delete_object.clj b/backend/src/app/tasks/delete_object.clj index d2d9420e89..2a30a63242 100644 --- a/backend/src/app/tasks/delete_object.clj +++ b/backend/src/app/tasks/delete_object.clj @@ -4,6 +4,9 @@ ;; ;; Copyright (c) UXBOX Labs SL +;; TODO: DEPRECATED +;; Should be removed in the 1.8.x + (ns app.tasks.delete-object "Generic task for permanent deletion of objects." (:require diff --git a/backend/src/app/tasks/delete_profile.clj b/backend/src/app/tasks/delete_profile.clj index 17e2facb4c..67a1733df6 100644 --- a/backend/src/app/tasks/delete_profile.clj +++ b/backend/src/app/tasks/delete_profile.clj @@ -14,6 +14,9 @@ [clojure.spec.alpha :as s] [integrant.core :as ig])) +;; TODO: DEPRECATED +;; Should be removed in the 1.8.x + (declare delete-profile-data) ;; --- INIT diff --git a/backend/src/app/tasks/file_media_gc.clj b/backend/src/app/tasks/file_media_gc.clj index 8b8bc3d281..bc1675447f 100644 --- a/backend/src/app/tasks/file_media_gc.clj +++ b/backend/src/app/tasks/file_media_gc.clj @@ -100,6 +100,7 @@ :id (:id mobj) :media-id (:media-id mobj) :thumbnail-id (:thumbnail-id mobj)) + ;; NOTE: deleting the file-media-object in the database ;; automatically marks as toched the referenced storage ;; objects. The touch mechanism is needed because many files can diff --git a/backend/src/app/tasks/objects_gc.clj b/backend/src/app/tasks/objects_gc.clj new file mode 100644 index 0000000000..b146c85afa --- /dev/null +++ b/backend/src/app/tasks/objects_gc.clj @@ -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) UXBOX Labs SL + +(ns app.tasks.objects-gc + "A maintenance task that performs a general purpose garbage collection + of deleted objects." + (:require + [app.db :as db] + [app.storage :as sto] + [app.util.logging :as l] + [app.util.time :as dt] + [clojure.spec.alpha :as s] + [cuerdas.core :as str] + [integrant.core :as ig])) + +(def target-tables + ["profile" + "team" + "file" + "project" + "team_font_variant"]) + +(defmulti delete-objects :table) + +(def sql:delete-objects + "with deleted as ( + select id from %(table)s + where deleted_at is not null + and deleted_at < now() - ?::interval + order by deleted_at + limit %(limit)s + ) + delete from %(table)s + where id in (select id from deleted) + returning *") + +;; --- IMPL: generic object deletion + +(defmethod delete-objects :default + [{:keys [conn max-age table] :as cfg}] + (let [sql (str/fmt sql:delete-objects + {:table table :limit 50}) + result (db/exec! conn [sql max-age])] + + (doseq [{:keys [id] :as item} result] + (l/trace :action "delete object" :table table :id id)) + + (count result))) + +;; --- IMPL: team-font-variant deletion + +(defmethod delete-objects "team_font_variant" + [{:keys [conn max-age storage table] :as cfg}] + (let [sql (str/fmt sql:delete-objects + {:table table :limit 50}) + fonts (db/exec! conn [sql max-age]) + storage (assoc storage :conn conn)] + (doseq [{:keys [id] :as font} fonts] + (l/trace :action "delete object" :table table :id id) + (some->> (:woff1-file-id font) (sto/del-object storage)) + (some->> (:woff2-file-id font) (sto/del-object storage)) + (some->> (:otf-file-id font) (sto/del-object storage)) + (some->> (:ttf-file-id font) (sto/del-object storage))) + (count fonts))) + +;; --- IMPL: team deletion + +(defmethod delete-objects "team" + [{:keys [conn max-age storage table] :as cfg}] + (let [sql (str/fmt sql:delete-objects + {:table table :limit 50}) + teams (db/exec! conn [sql max-age]) + storage (assoc storage :conn conn)] + + (doseq [{:keys [id] :as team} teams] + (l/trace :action "delete object" :table table :id id) + (some->> (:photo-id team) (sto/del-object storage))) + + (count teams))) + +;; --- IMPL: profile deletion + +(def sql:retrieve-deleted-profiles + "select id, photo_id from profile + where deleted_at is not null + and deleted_at < now() - ?::interval + order by deleted_at + limit %(limit)s + for update") + +(def sql:mark-owned-teams-deleted + "with owned as ( + select tpr.team_id as id + from team_profile_rel as tpr + where tpr.is_owner is true + and tpr.profile_id = ? + ) + update team set deleted_at = now() - ?::interval + where id in (select id from owned)") + +(defmethod delete-objects "profile" + [{:keys [conn max-age storage table] :as cfg}] + (let [sql (str/fmt sql:retrieve-deleted-profiles {:limit 50}) + profiles (db/exec! conn [sql max-age]) + storage (assoc storage :conn conn)] + + (doseq [{:keys [id] :as profile} profiles] + (l/trace :action "delete object" :table table :id id) + + ;; Mark the owned teams as deleted; this enables them to be procesed + ;; in the same transaction in the "team" table step. + (db/exec-one! conn [sql:mark-owned-teams-deleted id max-age]) + + ;; Mark as deleted the storage object related with the photo-id + ;; field. + (some->> (:photo-id profile) (sto/del-object storage)) + + ;; And finally, permanently delete the profile. + (db/delete! conn :profile {:id id})) + + (count profiles))) + +;; --- INIT + +(defn- process-table + [{:keys [table] :as cfg}] + (loop [n 0] + (let [res (delete-objects cfg)] + (if (pos? res) + (recur (+ n res)) + (l/debug :hint "table gc summary" :table table :deleted n))))) + +(s/def ::max-age ::dt/duration) + +(defmethod ig/pre-init-spec ::handler [_] + (s/keys :req-un [::db/pool ::sto/storage ::max-age])) + +(defmethod ig/init-key ::handler + [_ {:keys [pool max-age] :as cfg}] + (fn [task] + ;; Checking first on task argument allows properly testing it. + (let [max-age (get task :max-age max-age)] + (db/with-atomic [conn pool] + (let [max-age (db/interval max-age) + cfg (-> cfg + (assoc :max-age max-age) + (assoc :conn conn))] + (doseq [table target-tables] + (process-table (assoc cfg :table table)))))))) diff --git a/backend/test/app/services_files_test.clj b/backend/test/app/services_files_test.clj index b9a1c44264..e22d8afc8d 100644 --- a/backend/test/app/services_files_test.clj +++ b/backend/test/app/services_files_test.clj @@ -11,6 +11,7 @@ [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])) @@ -337,3 +338,69 @@ (t/is (th/ex-info? error)) (t/is (th/ex-of-type? error :not-found)))) +(t/deftest deletion-test + (let [task (:app.tasks.objects-gc/handler th/*system*) + profile1 (th/create-profile* 1) + file (th/create-file* 1 {:project-id (:default-project-id profile1) + :profile-id (:id profile1)})] + ;; file is not deleted because it does not meet all + ;; conditions to be deleted. + (let [result (task {:max-age (dt/duration 0)})] + (t/is (nil? result))) + + ;; query the list of files + (let [data {::th/type :project-files + :project-id (:default-project-id profile1) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= 1 (count result))))) + + ;; Request file to be deleted + (let [params {::th/type :delete-file + :id (:id file) + :profile-id (:id profile1)} + out (th/mutation! params)] + (t/is (nil? (:error out)))) + + ;; query the list of files after soft deletion + (let [data {::th/type :project-files + :project-id (:default-project-id profile1) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= 0 (count result))))) + + ;; run permanent deletion (should be noop) + (let [result (task {:max-age (dt/duration {:minutes 1})})] + (t/is (nil? result))) + + ;; query the list of file libraries of a after hard deletion + (let [data {::th/type :file-libraries + :file-id (:id file) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= 0 (count result))))) + + ;; run permanent deletion + (let [result (task {:max-age (dt/duration 0)})] + (t/is (nil? result))) + + ;; query the list of file libraries of a after hard deletion + (let [data {::th/type :file-libraries + :file-id (:id file) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (let [error (:error out) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :not-found)))) + )) diff --git a/backend/test/app/services_profile_test.clj b/backend/test/app/services_profile_test.clj index 3fc1ab3a61..0f64da41dc 100644 --- a/backend/test/app/services_profile_test.clj +++ b/backend/test/app/services_profile_test.clj @@ -9,6 +9,7 @@ [app.db :as db] [app.rpc.mutations.profile :as profile] [app.test-helpers :as th] + [app.util.time :as dt] [clojure.java.io :as io] [clojure.test :as t] [cuerdas.core :as str] @@ -117,7 +118,7 @@ )) (t/deftest profile-deletion-simple - (let [task (:app.tasks.delete-profile/handler th/*system*) + (let [task (:app.tasks.objects-gc/handler th/*system*) prof (th/create-profile* 1) file (th/create-file* 1 {:profile-id (:id prof) :project-id (:default-project-id prof) @@ -125,23 +126,14 @@ ;; profile is not deleted because it does not meet all ;; conditions to be deleted. - (let [result (task {:props {:profile-id (:id prof)}})] + (let [result (task {:max-age (dt/duration 0)})] (t/is (nil? result))) ;; Request profile to be deleted - (with-mocks [mock {:target 'app.worker/submit! :return nil}] - (let [params {::th/type :delete-profile - :profile-id (:id prof)} - out (th/mutation! params)] - (t/is (nil? (:error out))) - - ;; check the mock - (let [mock (deref mock) - mock-params (first (:call-args mock))] - (t/is (:called? mock)) - (t/is (= 1 (:call-count mock))) - (t/is (= :delete-profile (:app.worker/task mock-params))) - (t/is (= (:id prof) (:profile-id mock-params)))))) + (let [params {::th/type :delete-profile + :profile-id (:id prof)} + out (th/mutation! params)] + (t/is (nil? (:error out)))) ;; query files after profile soft deletion (let [params {::th/type :files @@ -153,8 +145,8 @@ (t/is (= 1 (count (:result out))))) ;; execute permanent deletion task - (let [result (task {:props {:profile-id (:id prof)}})] - (t/is (true? result))) + (let [result (task {:max-age (dt/duration "-1m")})] + (t/is (nil? result))) ;; query profile after delete (let [params {::th/type :profile @@ -165,17 +157,6 @@ error-data (ex-data error)] (t/is (th/ex-info? error)) (t/is (= (:type error-data) :not-found)))) - - ;; query files after profile soft deletion - (let [params {::th/type :files - :project-id (:default-project-id prof) - :profile-id (:id prof)} - out (th/query! params)] - ;; (th/print-result! out) - (let [error (:error out) - error-data (ex-data error)] - (t/is (th/ex-info? error)) - (t/is (= (:type error-data) :not-found)))) )) (t/deftest registration-domain-whitelist diff --git a/backend/test/app/services_projects_test.clj b/backend/test/app/services_projects_test.clj index 6137bb0ff6..5f23577f35 100644 --- a/backend/test/app/services_projects_test.clj +++ b/backend/test/app/services_projects_test.clj @@ -10,8 +10,8 @@ [app.db :as db] [app.http :as http] [app.test-helpers :as th] - [clojure.test :as t] - [promesa.core :as p])) + [app.util.time :as dt] + [clojure.test :as t])) (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) @@ -170,3 +170,71 @@ (t/is (th/ex-info? error)) (t/is (th/ex-of-type? error :not-found)))) + +(t/deftest test-deletion + (let [task (:app.tasks.objects-gc/handler th/*system*) + profile1 (th/create-profile* 1) + project (th/create-project* 1 {:team-id (:default-team-id profile1) + :profile-id (:id profile1)})] + + ;; project is not deleted because it does not meet all + ;; conditions to be deleted. + (let [result (task {:max-age (dt/duration 0)})] + (t/is (nil? result))) + + ;; query the list of projects + (let [data {::th/type :projects + :team-id (:default-team-id profile1) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= 2 (count result))))) + + ;; Request project to be deleted + (let [params {::th/type :delete-project + :id (:id project) + :profile-id (:id profile1)} + out (th/mutation! params)] + (t/is (nil? (:error out)))) + + ;; query the list of projects after soft deletion + (let [data {::th/type :projects + :team-id (:default-team-id profile1) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= 1 (count result))))) + + ;; run permanent deletion (should be noop) + (let [result (task {:max-age (dt/duration {:minutes 1})})] + (t/is (nil? result))) + + ;; query the list of files of a after soft deletion + (let [data {::th/type :project-files + :project-id (:id project) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= 0 (count result))))) + + ;; run permanent deletion + (let [result (task {:max-age (dt/duration 0)})] + (t/is (nil? result))) + + ;; query the list of files of a after hard deletion + (let [data {::th/type :project-files + :project-id (:id project) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (let [error (:error out) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :not-found)))) + )) diff --git a/backend/test/app/services_teams_test.clj b/backend/test/app/services_teams_test.clj index 144d652f8c..2831ea4ee9 100644 --- a/backend/test/app/services_teams_test.clj +++ b/backend/test/app/services_teams_test.clj @@ -11,6 +11,7 @@ [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] [mockery.core :refer [with-mocks]])) @@ -80,6 +81,80 @@ ))) +(t/deftest test-deletion + (let [task (:app.tasks.objects-gc/handler th/*system*) + profile1 (th/create-profile* 1 {:is-active true}) + team (th/create-team* 1 {:profile-id (:id profile1)}) + pool (:app.db/pool th/*system*) + data {::th/type :delete-team + :team-id (:id team) + :profile-id (:id profile1)}] + + ;; team is not deleted because it does not meet all + ;; conditions to be deleted. + (let [result (task {:max-age (dt/duration 0)})] + (t/is (nil? result))) + + ;; query the list of teams + (let [data {::th/type :teams + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= 2 (count result))) + (t/is (= (:id team) (get-in result [1 :id]))) + (t/is (= (:default-team-id profile1) (get-in result [0 :id]))))) + + ;; Request team to be deleted + (let [params {::th/type :delete-team + :id (:id team) + :profile-id (:id profile1)} + out (th/mutation! params)] + (t/is (nil? (:error out)))) + + ;; query the list of teams after soft deletion + (let [data {::th/type :teams + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= 1 (count result))) + (t/is (= (:default-team-id profile1) (get-in result [0 :id]))))) + + ;; run permanent deletion (should be noop) + (let [result (task {:max-age (dt/duration {:minutes 1})})] + (t/is (nil? result))) + + ;; query the list of projects of a after hard deletion + (let [data {::th/type :projects + :team-id (:id team) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (= 0 (count result))))) + + ;; run permanent deletion + (let [result (task {:max-age (dt/duration 0)})] + (t/is (nil? result))) + + ;; query the list of projects of a after hard deletion + (let [data {::th/type :projects + :team-id (:id team) + :profile-id (:id profile1)} + out (th/query! data)] + ;; (th/print-result! out) + (let [error (:error out) + error-data (ex-data error)] + (t/is (th/ex-info? error)) + (t/is (= (:type error-data) :not-found)))) + )) + + From 2202f90d741701d103e698a9111687687bbf510f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 9 Jun 2021 15:01:17 +0200 Subject: [PATCH 055/204] :bug: Fix wrong spec definition on invite email. --- backend/src/app/emails.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/app/emails.clj b/backend/src/app/emails.clj index 9033ca7145..ac55b688b9 100644 --- a/backend/src/app/emails.clj +++ b/backend/src/app/emails.clj @@ -127,9 +127,9 @@ (s/def :internal.emails.invite-to-team/token ::us/string) (s/def ::invite-to-team - (s/keys :keys [:internal.emails.invite-to-team/invited-by - :internal.emails.invite-to-team/token - :internal.emails.invite-to-team/team])) + (s/keys :req-un [:internal.emails.invite-to-team/invited-by + :internal.emails.invite-to-team/token + :internal.emails.invite-to-team/team])) (def invite-to-team "Teams member invitation email." From 144127224ced06dfd301f3f0468861e78eeb9918 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 9 Jun 2021 14:47:40 +0200 Subject: [PATCH 056/204] :sparkles: Reduce contention on file-update using advisory locks and weaker row locking. --- backend/src/app/db.clj | 23 +++++++++++++++++++++++ backend/src/app/db/sql.clj | 4 ++-- backend/src/app/rpc/mutations/files.clj | 3 ++- common/src/app/common/uuid.cljc | 17 ++++++++++------- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index 2b98c5e31f..e11e5b8bcd 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -11,6 +11,7 @@ [app.common.geom.point :as gpt] [app.common.spec :as us] [app.common.transit :as t] + [app.common.uuid :as uuid] [app.db.sql :as sql] [app.metrics :as mtx] [app.util.json :as json] @@ -366,3 +367,25 @@ (defn pgarray->vector [v] (vec (.getArray ^PgArray v))) + + +;; --- Locks + +(defn- xact-check-param + [n] + (cond + (uuid? n) (uuid/get-word-high n) + (int? n) n + :else (throw (IllegalArgumentException. "uuid or number allowed")))) + +(defn xact-lock! + [conn n] + (let [n (xact-check-param n)] + (exec-one! conn ["select pg_advisory_xact_lock(?::bigint) as lock" n]) + true)) + +(defn xact-try-lock! + [conn n] + (let [n (xact-check-param n) + row (exec-one! conn ["select pg_try_advisory_xact_lock(?::bigint) as lock" n])] + (:lock row))) diff --git a/backend/src/app/db/sql.clj b/backend/src/app/db/sql.clj index 6ee5d3073c..0ce621f235 100644 --- a/backend/src/app/db/sql.clj +++ b/backend/src/app/db/sql.clj @@ -43,8 +43,8 @@ ([table where-params opts] (let [opts (merge default-opts opts) opts (cond-> opts - (:for-update opts) - (assoc :suffix "FOR UPDATE"))] + (:for-update opts) (assoc :suffix "FOR UPDATE") + (:for-key-share opts) (assoc :suffix "FOR KEY SHARE"))] (sql/for-query table where-params opts)))) (defn update diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 02e8963a9d..90e79860cd 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -268,8 +268,9 @@ (sv/defmethod ::update-file [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] - (let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-update true})] + (let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true})] (files/check-edition-permissions! conn profile-id id) + (db/xact-lock! conn id) (update-file (assoc cfg :conn conn) (assoc params :file file))))) diff --git a/common/src/app/common/uuid.cljc b/common/src/app/common/uuid.cljc index f7530aae37..25f9893815 100644 --- a/common/src/app/common/uuid.cljc +++ b/common/src/app/common/uuid.cljc @@ -6,13 +6,13 @@ (ns app.common.uuid (:refer-clojure :exclude [next uuid zero?]) - #?(:clj (:import java.util.UUID)) - #?(:clj - (:require [clj-uuid :as impl] - [clojure.core :as c]) - :cljs - (:require [app.common.uuid-impl :as impl] - [cljs.core :as c]))) + (:require + [app.common.data :as d] + #?(:clj [clj-uuid :as impl]) + #?(:clj [clojure.core :as c]) + #?(:cljs [app.common.uuid-impl :as impl]) + #?(:cljs [cljs.core :as c])) + #?(:clj (:import java.util.UUID))) (def zero #uuid "00000000-0000-0000-0000-000000000000") @@ -45,3 +45,6 @@ (defn custom ([a] #?(:clj (UUID. 0 a) :cljs (c/uuid (impl/custom 0 a)))) ([b a] #?(:clj (UUID. b a) :cljs (c/uuid (impl/custom b a))))) + +#?(:clj + (d/export impl/get-word-high)) From b9df489962a5fcbc8664126d7bb2998eacafe3f0 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 9 Jun 2021 14:48:21 +0200 Subject: [PATCH 057/204] :arrow_up: Update clj-kondo and babashka dependencies on devenv dockerfile. --- docker/devenv/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index f7e2eebdcb..0f7400cf74 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -5,8 +5,8 @@ ARG DEBIAN_FRONTEND=noninteractive ENV NODE_VERSION=v14.16.1 \ CLOJURE_VERSION=1.10.3.822 \ - CLJKONDO_VERSION=2021.04.23 \ - BABASHKA_VERSION=0.4.0 \ + CLJKONDO_VERSION=2021.06.01 \ + BABASHKA_VERSION=0.4.4 \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 From 55b0f6e950dced6b2d61c763f01a811f6a4d90b5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 9 Jun 2021 15:53:38 +0200 Subject: [PATCH 058/204] :paperclip: Minor change on locking order on update-file. --- backend/src/app/rpc/mutations/files.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 90e79860cd..55a0c5c28f 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -268,9 +268,9 @@ (sv/defmethod ::update-file [{:keys [pool] :as cfg} {:keys [id profile-id] :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})] (files/check-edition-permissions! conn profile-id id) - (db/xact-lock! conn id) (update-file (assoc cfg :conn conn) (assoc params :file file))))) From 092a973f9a1fb973cab3af9c4be92af3e8200304 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 1 Jun 2021 12:38:00 +0200 Subject: [PATCH 059/204] :tada: Add resize constraints to shapes --- common/src/app/common/geom/shapes.cljc | 1 + common/src/app/common/geom/shapes/rect.cljc | 5 + .../app/common/geom/shapes/transforms.cljc | 133 +++++++++++++++ common/src/app/common/pages/spec.cljc | 28 +++ .../partials/sidebar-element-options.scss | 150 +++++++++++++++++ .../app/main/data/workspace/transforms.cljs | 71 ++++---- .../sidebar/options/menus/measures.cljs | 159 ++++++++++++++++-- frontend/translations/en.po | 48 ++++++ frontend/translations/es.po | 50 +++++- 9 files changed, 602 insertions(+), 43 deletions(-) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 75fc70661c..24165ff5cd 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -188,6 +188,7 @@ (d/export gpr/center->rect) (d/export gtr/transform-shape) +(d/export gtr/calc-child-modifiers) (d/export gtr/transform-matrix) (d/export gtr/inverse-transform-matrix) (d/export gtr/transform-point-center) diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 91e7d18a9a..853f7d7f48 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -9,6 +9,11 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco])) +(defn left-of [rect] (:x rect)) +(defn right-of [rect] (+ (:x rect) (:width rect))) +(defn top-of [rect] (:y rect)) +(defn bottom-of [rect] (+ (:y rect) (:height rect))) + (defn rect->points [{:keys [x y width height]}] ;; (assert (number? x)) ;; (assert (number? y)) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index cd78e72d97..09dfb3f8b5 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.spec :as spec] [app.common.text :as txt])) ;; --- Relative Movement @@ -362,6 +363,138 @@ (dissoc :modifiers))) shape)))) + +(defn calc-child-modifiers + [parent transformed-parent child parent-modifiers] + (let [parent-rect (:selrect parent) + transformed-parent-rect (:selrect transformed-parent) + child-rect (:selrect child) + + origin (:resize-origin parent-modifiers) + + orig-h (when origin + (cond + (mth/close? (:x origin) (gpr/left-of parent-rect)) :left + (mth/close? (:x origin) (gpr/right-of parent-rect)) :right + :else :middle)) + + orig-v (when origin + (cond + (mth/close? (:y origin) (gpr/top-of parent-rect)) :top + (mth/close? (:y origin) (gpr/bottom-of parent-rect)) :bottom + :else :middle)) + + delta-h (when orig-h + (cond (= orig-h :left) + (- (gpr/right-of transformed-parent-rect) (gpr/right-of parent-rect)) + + (= orig-h :right) + (- (gpr/left-of transformed-parent-rect) (gpr/left-of parent-rect)) + + :else 0)) + + delta-v (when orig-v + (cond (= orig-v :top) + (- (gpr/bottom-of transformed-parent-rect) (gpr/bottom-of parent-rect)) + + (= orig-v :bottom) + (- (gpr/top-of transformed-parent-rect) (gpr/top-of parent-rect)) + + :else 0)) + + constraints-h (get child :constraints-h (spec/default-constraints-h child)) + constraints-v (get child :constraints-v (spec/default-constraints-v child)) + + modifiers-h (case constraints-h + :left + (if (= orig-h :right) + {:displacement (gpt/point delta-h 0)} ;; we convert to matrix below + {}) + + :right + (if (= orig-h :left) + {:displacement (gpt/point delta-h 0)} + {}) + + :leftright + (cond (= orig-h :left) + {:resize-origin (gpt/point (gpr/left-of child-rect) (gpr/top-of child-rect)) + :resize-vector (gpt/point (/ (+ (:width child-rect) delta-h) + (:width child-rect)) + 1)} + + (= orig-h :right) + {:resize-origin (gpt/point (gpr/right-of child-rect) (gpr/top-of child-rect)) + :resize-vector (gpt/point (/ (- (:width child-rect) delta-h) + (:width child-rect)) + 1)} + + :else {}) + + :center + {:displacement (gpt/point (/ delta-h 2) 0)} + + :scale + (if (:resize-origin parent-modifiers) + {:resize-origin (:resize-origin parent-modifiers) + :resize-vector (gpt/point (:x (:resize-vector parent-modifiers)) 1)} + {}) + + {}) + + modifiers-v (case constraints-v + :top + (if (= orig-v :bottom) + {:displacement (gpt/point 0 delta-v)} + {}) + + :bottom + (if (= orig-v :top) + {:displacement (gpt/point 0 delta-v)} + {}) + + :topbottom + (cond (= orig-v :top) + {:resize-origin (gpt/point (gpr/left-of child-rect) (gpr/top-of child-rect)) + :resize-vector (gpt/point 1 + (/ (+ (:height child-rect) delta-v) + (:height child-rect)))} + + (= orig-v :bottom) + {:resize-origin (gpt/point (gpr/left-of child-rect) (gpr/bottom-of child-rect)) + :resize-vector (gpt/point 1 + (/ (- (:height child-rect) delta-v) + (:height child-rect)))} + + :else {}) + + :center + {:displacement (gpt/point 0 (/ delta-v 2))} + + :scale + (if (:resize-origin parent-modifiers) + {:resize-origin (:resize-origin parent-modifiers) + :resize-vector (gpt/point 1 (:y (:resize-vector parent-modifiers)))} + {}) + + {})] + + (cond-> {} + (or (:displacement modifiers-h) (:displacement modifiers-v)) + (assoc :displacement (gmt/translate-matrix + (gpt/point (get (:displacement modifiers-h) :x 0) + (get (:displacement modifiers-v) :y 0)))) + + (or (:resize-vector modifiers-h) (:resize-vector modifiers-v)) + (assoc :resize-origin (or (:resize-origin modifiers-h) ;; we assume that the origin is the same + (:resize-origin modifiers-v)) ;; in any direction + :resize-vector (gpt/point (get (:resize-vector modifiers-h) :x 1) + (get (:resize-vector modifiers-v) :y 1))) + (:displacement parent-modifiers) + (update :displacement #(if (nil? %) + (:displacement parent-modifiers) + (gmt/multiply % (:displacement parent-modifiers))))))) + (defn update-group-viewbox "Updates the viewbox for groups imported from SVG's" [{:keys [selrect svg-viewbox] :as group} new-selrect] diff --git a/common/src/app/common/pages/spec.cljc b/common/src/app/common/pages/spec.cljc index 24c523fd0c..32c010cfd8 100644 --- a/common/src/app/common/pages/spec.cljc +++ b/common/src/app/common/pages/spec.cljc @@ -9,6 +9,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.spec :as us] + [app.common.uuid :as uuid] [clojure.spec.alpha :as s])) ;; --- Specs @@ -195,6 +196,30 @@ (s/def :internal.shape/interactions (s/coll-of :internal.shape/interaction :kind vector?)) +;; Size constraints + +(s/def :internal.shape/constraints-h #{:left :right :leftright :center :scale}) +(s/def :internal.shape/constraints-v #{:top :bottom :topbottom :center :scale}) +(s/def :internal.shape/fixed-scroll boolean?) + +; Shapes in the top frame have no constraints. Shapes directly below some +; frame are left-top constrained. Else (shapes in a group) are scaled. +(defn default-constraints-h + [shape] + (if (= (:parent-id shape) uuid/zero) + nil + (if (= (:parent-id shape) (:frame-id shape)) + :left + :scale))) + +(defn default-constraints-v + [shape] + (if (= (:parent-id shape) uuid/zero) + nil + (if (= (:parent-id shape) (:frame-id shape)) + :top + :scale))) + ;; Page Data related (s/def :internal.shape/blocked boolean?) (s/def :internal.shape/collapsed boolean?) @@ -297,6 +322,9 @@ :internal.shape/locked :internal.shape/proportion :internal.shape/proportion-lock + :internal.shape/constraints-h + :internal.shape/constraints-v + :internal.shape/fixed-scroll :internal.shape/rx :internal.shape/ry :internal.shape/r1 diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index af2a9d22af..ff8060411a 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -1257,4 +1257,154 @@ } } +.row-flex.align-top { + align-items: flex-start; +} +.constraints-widget { + min-width: 72px; + min-height: 72px; + position: relative; + background-color: $color-gray-60; + flex-grow: 0; + + .constraints-box { + width: 28px; + height: 28px; + position: absolute; + top: 22px; + left: 22px; + border: 2px solid $color-gray-50; + } + + .constraint-button { + position: absolute; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; + + &::after { + content: ' '; + background-color: $color-gray-20; + } + + &.active, + &:hover { + &::after { + background-color: $color-primary; + } + } + + &.top, + &.bottom { + width: 28px; + height: 22px; + left: calc(50% - 14px); + + &::after { + width: 3px; + height: 15px; + } + } + + &.top { + top: 0; + } + + &.bottom { + bottom: 0; + } + + &.left, + &.right { + width: 22px; + height: 28px; + top: calc(50% - 14px); + + &::after { + width: 15px; + height: 3px; + } + } + + &.left { + left: 0; + } + + &.right { + right: 0; + } + + &.centerv { + width: 28px; + height: 28px; + left: calc(50% - 14px); + top: calc(50% - 14px); + + &::after { + width: 3px; + height: 15px; + } + } + + &.centerh { + width: 28px; + height: 15px; + left: calc(50% - 14px); + top: calc(50% - 7px); + + &::after { + width: 15px; + height: 3px; + } + } + } +} + +.constraints-form { + display: flex; + flex-grow: 1; + flex-direction: column; + align-items: stretch; + justify-content: flex-start; + + .input-select { + font-size: $fs11; + margin: 0 $x-small; + } + + svg { + width: 15px; + height: 15px; + margin-left: $medium; + fill: $color-gray-20; + } + + .left-right svg { + transform: rotate(45deg); + } + + .top-down svg { + transform: rotate(-45deg); + } + + .fix-when { + font-size: $fs11; + cursor: pointer; + + span { + margin-left: $small; + } + + &:hover, + &.active { + color: $color-primary; + + svg { + fill: $color-primary; + } + } + } + +} diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 0e2c7f230b..8f3955b104 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -151,8 +151,7 @@ :resize-origin origin :resize-transform shape-transform :resize-scale-text scale-text - :resize-transform-inverse shape-transform-inverse} - false)))) + :resize-transform-inverse shape-transform-inverse})))) ;; Unifies the instantaneous proportion lock modifier ;; activated by Shift key and the shapes own proportion @@ -426,10 +425,34 @@ ;; -- Apply modifiers +(defn- set-modifiers-recursive + [modif-tree objects shape modifiers] + (let [children (->> (get shape :shapes []) + (map #(get objects %))) + + transformed-shape (when (seq children) ; <- don't calculate it if not needed + (gsh/transform-shape + (assoc shape :modifiers (select-keys modifiers + [:resize-origin + :resize-vector])))) + + set-child (fn [modif-tree child] + (let [child-modifiers (gsh/calc-child-modifiers shape + transformed-shape + child + modifiers)] + (set-modifiers-recursive modif-tree + objects + child + child-modifiers)))] + + (reduce set-child + (update-in modif-tree [(:id shape) :modifiers] #(merge % modifiers)) + children))) + (defn set-modifiers - ([ids] (set-modifiers ids nil true)) - ([ids modifiers] (set-modifiers ids modifiers true)) - ([ids modifiers recurse-frames?] + ([ids] (set-modifiers ids nil)) + ([ids modifiers] (us/verify (s/coll-of uuid?) ids) (ptk/reify ::set-modifiers ptk/UpdateEvent @@ -438,25 +461,16 @@ page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false)))) - - not-frame-id? - (fn [shape-id] - (let [shape (get objects shape-id)] - (or recurse-frames? (not (= :frame (:type shape)))))) - - ;; For each shape updates the modifiers given as arguments - update-shape - (fn [objects shape-id] - (update-in objects [shape-id :modifiers] #(merge % modifiers))) - - ;; ID's + Children but remove frame children if the flag is set to false - ids-with-children (concat ids (mapcat #(cp/get-children % objects) - (filter not-frame-id? ids)))] - - (update state :workspace-modifiers - #(reduce update-shape % ids-with-children))))))) + ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false))))] + (reduce (fn [state id] + (update state :workspace-modifiers + #(set-modifiers-recursive % + objects + (get objects id) + modifiers))) + state + ids)))))) ;; Set-rotation is custom because applies different modifiers to each ;; shape adjusting their position. @@ -567,10 +581,7 @@ (fn [objects shape-id] (let [shape (get objects shape-id) modifier (gsh/resize-modifiers shape attr value)] - (-> objects - (assoc-in [shape-id :modifiers] modifier) - (cond-> (not (= :frame (:type shape))) - (update-children (cp/get-children shape-id objects) modifier)))))] + (set-modifiers-recursive objects objects shape modifier)))] (d/update-in-when state @@ -597,8 +608,7 @@ (rx/of (set-modifiers selected {:resize-vector (gpt/point -1.0 1.0) :resize-origin origin - :displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))} - false) + :displacement (gmt/translate-matrix (gpt/point (- (:width selrect)) 0))}) (apply-modifiers selected)))))) (defn flip-vertical-selected [] @@ -614,8 +624,7 @@ (rx/of (set-modifiers selected {:resize-vector (gpt/point 1.0 -1.0) :resize-origin origin - :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))} - false) + :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))}) (apply-modifiers selected)))))) (defn start-local-displacement [point] 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 0d02a54196..f7e8fa6323 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 @@ -6,6 +6,7 @@ (ns app.main.ui.workspace.sidebar.options.menus.measures (:require + [cuerdas.core :as str] [rumext.alpha :as mf] [app.main.ui.icons :as i] [app.main.store :as st] @@ -15,12 +16,14 @@ [app.util.data :refer [classnames]] [app.common.geom.shapes :as gsh] [app.common.geom.point :as gpt] + [app.common.pages.spec :as spec] + [app.common.uuid :as uuid] [app.main.data.workspace :as udw] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.changes :as dch] [app.main.ui.components.numeric-input :refer [numeric-input]] [app.common.math :as math] - [app.util.i18n :refer [t] :as i18n])) + [app.util.i18n :refer [tr] :as i18n])) (def measure-attrs [:proportion-lock :width :height @@ -28,7 +31,12 @@ :rotation :rx :ry :r1 :r2 :r3 :r4 - :selrect]) + :selrect + :constraints-h + :constraints-v + :fixed-scroll + :parent-id + :frame-id]) (defn- attr->string [attr values] (let [value (attr values)] @@ -42,7 +50,6 @@ (mf/defc measures-menu [{:keys [options ids ids-with-children values] :as props}] (let [options (or options #{:size :position :rotation :radius}) - locale (i18n/use-locale) ids-with-children (or ids-with-children ids) @@ -65,6 +72,13 @@ proportion-lock (:proportion-lock values) + in-frame? (not= (:parent-id values) uuid/zero) + first-level? (and in-frame? + (= (:parent-id values) (:frame-id values))) + + constraints-h (get values :constraints-h (spec/default-constraints-h values)) + constraints-v (get values :constraints-v (spec/default-constraints-v values)) + on-size-change (mf/use-callback (mf/deps ids) @@ -161,15 +175,75 @@ on-radius-r2-change #(on-radius-4-change % :r2) on-radius-r3-change #(on-radius-4-change % :r3) on-radius-r4-change #(on-radius-4-change % :r4) - select-all #(-> % (dom/get-target) (.select))] + select-all #(-> % (dom/get-target) (.select)) + + on-constraint-button-clicked + (mf/use-callback + (mf/deps [ids values]) + (fn [button] + (fn [event] + (let [constraints-h (get values :constraints-h :scale) + constraints-v (get values :constraints-v :scale) + + [constraint new-value] + (case button + :top (case constraints-v + :top [:constraints-v :scale] + :topbottom [:constraints-v :bottom] + :bottom [:constraints-v :topbottom] + [:constraints-v :top]) + :bottom (case constraints-v + :bottom [:constraints-v :scale] + :topbottom [:constraints-v :top] + :top [:constraints-v :topbottom] + [:constraints-v :bottom]) + :left (case constraints-h + :left [:constraints-h :scale] + :leftright [:constraints-h :right] + :right [:constraints-h :leftright] + [:constraints-h :left]) + :right (case constraints-h + :right [:constraints-h :scale] + :leftright [:constraints-h :left] + :left [:constraints-h :leftright] + [:constraints-h :right]) + :centerv (case constraints-v + :center [:constraints-v :scale] + [:constraints-v :center]) + :centerh (case constraints-h + :center [:constraints-h :scale] + [:constraints-h :center]))] + (st/emit! (dch/update-shapes + ids + #(assoc % constraint new-value))))))) + + on-constraint-select-changed + (mf/use-callback + (mf/deps [ids values]) + (fn [constraint] + (fn [event] + (let [value (-> (dom/get-target-val event) (keyword))] + (when-not (str/empty? value) + (st/emit! (dch/update-shapes + ids + #(assoc % constraint value)))))))) + + on-fixed-scroll-clicked + (mf/use-callback + (mf/deps [ids values]) + (fn [event] + (st/emit! (dch/update-shapes + ids + #(update % :fixed-scroll not)))))] + [:* [:div.element-set [:div.element-set-content ;; WIDTH & HEIGHT (when (options :size) [:div.row-flex - [:span.element-set-subtitle (t locale "workspace.options.size")] + [:span.element-set-subtitle (tr "workspace.options.size")] [:div.input-element.width [:> numeric-input {:min 1 :no-validate true @@ -197,7 +271,7 @@ ;; POSITION (when (options :position) [:div.row-flex - [:span.element-set-subtitle (t locale "workspace.options.position")] + [:span.element-set-subtitle (tr "workspace.options.position")] [:div.input-element.Xaxis [:> numeric-input {:no-validate true :placeholder "--" @@ -214,7 +288,7 @@ ;; ROTATION (when (options :rotation) [:div.row-flex - [:span.element-set-subtitle (t locale "workspace.options.rotation")] + [:span.element-set-subtitle (tr "workspace.options.rotation")] [:div.input-element.degrees [:> numeric-input {:no-validate true @@ -244,14 +318,14 @@ {:class (classnames :selected (and radius-1? (not radius-4?))) - :alt (t locale "workspace.options.radius.all-corners") + :alt (tr "workspace.options.radius.all-corners") :on-click on-switch-to-radius-1} i/radius-1] [:div.radius-icon.tooltip.tooltip-bottom {:class (classnames :selected (and radius-4? (not radius-1?))) - :alt (t locale "workspace.options.radius.single-corners") + :alt (tr "workspace.options.radius.single-corners") :on-click on-switch-to-radius-4} i/radius-4]] (if radius-1? @@ -291,5 +365,68 @@ :min 0 :on-click select-all :on-change on-radius-r4-change - :value (attr->string :r4 values)}]]]) - ]))]])) + :value (attr->string :r4 values)}]]])]))]] + + ;; CONSTRAINTS + (when in-frame? + [:div.element-set + [:div.element-set-title + [:span (tr "workspace.options.constraints")]] + + [:div.element-set-content + [:div.row-flex.align-top + + [:div.constraints-widget + [:div.constraints-box] + [:div.constraint-button.top + {:class (classnames :active (or (= constraints-v :top) + (= constraints-v :topbottom))) + :on-click (on-constraint-button-clicked :top)}] + [:div.constraint-button.bottom + {:class (classnames :active (or (= constraints-v :bottom) + (= constraints-v :topbottom))) + :on-click (on-constraint-button-clicked :bottom)}] + [:div.constraint-button.left + {:class (classnames :active (or (= constraints-h :left) + (= constraints-h :leftright))) + :on-click (on-constraint-button-clicked :left)}] + [:div.constraint-button.right + {:class (classnames :active (or (= constraints-h :right) + (= constraints-h :leftright))) + :on-click (on-constraint-button-clicked :right)}] + [:div.constraint-button.centerv + {:class (classnames :active (= constraints-v :center)) + :on-click (on-constraint-button-clicked :centerv)}] + [:div.constraint-button.centerh + {:class (classnames :active (= constraints-h :center)) + :on-click (on-constraint-button-clicked :centerh)}]] + + [:div.constraints-form + [:div.row-flex + [:span.left-right i/full-screen] + [:select.input-select {:on-change (on-constraint-select-changed :constraints-h) + :value (d/name constraints-h "scale")} + (when (= constraints-h :multiple) + [:option {:value ""} (tr "settings.multiple")]) + [:option {:value "left"} (tr "workspace.options.constraints.left")] + [:option {:value "right"} (tr "workspace.options.constraints.right")] + [:option {:value "leftright"} (tr "workspace.options.constraints.leftright")] + [:option {:value "center"} (tr "workspace.options.constraints.center")] + [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] + [:div.row-flex + [:span.top-bottom i/full-screen] + [:select.input-select {:on-change (on-constraint-select-changed :constraints-v) + :value (d/name constraints-v "scale")} + (when (= constraints-v :multiple) + [:option {:value ""} (tr "settings.multiple")]) + [:option {:value "top"} (tr "workspace.options.constraints.top")] + [:option {:value "bottom"} (tr "workspace.options.constraints.bottom")] + [:option {:value "topbottom"} (tr "workspace.options.constraints.topbottom")] + [:option {:value "center"} (tr "workspace.options.constraints.center")] + [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] + (when first-level? + [:div.row-flex + [:div.fix-when {:class (classnames :active (:fixed-scroll values)) + :on-click on-fixed-scroll-clicked} + i/pin + [:span (tr "workspace.options.constraints.fix-when-scrolling")]]])]]]])])) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 5434901e77..a745b6c076 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1884,6 +1884,54 @@ msgstr "Canvas background" msgid "workspace.options.component" msgstr "Component" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints" +msgstr "Constraints" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.left" +msgstr "Left" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.right" +msgstr "Right" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.leftright" +msgstr "Left & Right" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.center" +msgstr "Center" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.scale" +msgstr "Scale" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.top" +msgstr "Top" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.bottom" +msgstr "Bottom" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.topbottom" +msgstr "Top & Bottom" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.center" +msgstr "Center" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.scale" +msgstr "Scale" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.fix-when-scrolling" +msgstr "Fix when scrolling" + #: src/app/main/ui/workspace/sidebar/options.cljs msgid "workspace.options.design" msgstr "Design" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index e1a6588853..bb520ed395 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1876,6 +1876,54 @@ msgstr "Color de fondo" msgid "workspace.options.component" msgstr "Componente" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints" +msgstr "Restricciones" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.left" +msgstr "Izquierda" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.right" +msgstr "Derecha" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.leftright" +msgstr "Izq. y Der." + +#: 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.scale" +msgstr "Escalar" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.top" +msgstr "Arriba" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.bottom" +msgstr "Abajo" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.topbottom" +msgstr "Arriba y Abajo" + +#: 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.scale" +msgstr "Escalar" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.fix-when-scrolling" +msgstr "Fijo al desplazar" + #: src/app/main/ui/workspace/sidebar/options.cljs msgid "workspace.options.design" msgstr "Diseño" @@ -2632,4 +2680,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 69fe8bc9b5adc993b70000ca39234d9d358b7ab9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 9 Jun 2021 08:41:41 +0200 Subject: [PATCH 060/204] :recycle: Add some small performance refactors --- common/src/app/common/geom/matrix.cljc | 16 ++++++++++++ common/src/app/common/geom/shapes/rect.cljc | 5 ---- .../app/common/geom/shapes/transforms.cljc | 26 +++++++++---------- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index fc2513f4a8..761e641ff0 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -48,6 +48,22 @@ ([m1 m2 & others] (reduce multiply (multiply m1 m2) others))) +(defn add-translate + "Given two TRANSLATE matrixes (only e and f have significative + values), combine them. Quicker than multiplying them, for this + precise case." + ([{m1a :a m1b :b m1c :c m1d :d m1e :e m1f :f} + {m2a :a m2b :b m2c :c m2d :d m2e :e m2f :f}] + (Matrix. + 1 + 0 + 0 + 1 + (+ m1e m2e) + (+ m1f m2f))) + ([m1 m2 & others] + (reduce add-translate (add-translate m1 m2) others))) + (defn substract [{m1a :a m1b :b m1c :c m1d :d m1e :e m1f :f} {m2a :a m2b :b m2c :c m2d :d m2e :e m2f :f}] diff --git a/common/src/app/common/geom/shapes/rect.cljc b/common/src/app/common/geom/shapes/rect.cljc index 853f7d7f48..91e7d18a9a 100644 --- a/common/src/app/common/geom/shapes/rect.cljc +++ b/common/src/app/common/geom/shapes/rect.cljc @@ -9,11 +9,6 @@ [app.common.geom.point :as gpt] [app.common.geom.shapes.common :as gco])) -(defn left-of [rect] (:x rect)) -(defn right-of [rect] (+ (:x rect) (:width rect))) -(defn top-of [rect] (:y rect)) -(defn bottom-of [rect] (+ (:y rect) (:height rect))) - (defn rect->points [{:keys [x y width height]}] ;; (assert (number? x)) ;; (assert (number? y)) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 09dfb3f8b5..cddd6e4af3 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -374,31 +374,31 @@ orig-h (when origin (cond - (mth/close? (:x origin) (gpr/left-of parent-rect)) :left - (mth/close? (:x origin) (gpr/right-of parent-rect)) :right + (mth/close? (:x origin) (:x1 parent-rect)) :left + (mth/close? (:x origin) (:x2 parent-rect)) :right :else :middle)) orig-v (when origin (cond - (mth/close? (:y origin) (gpr/top-of parent-rect)) :top - (mth/close? (:y origin) (gpr/bottom-of parent-rect)) :bottom + (mth/close? (:y origin) (:y1 parent-rect)) :top + (mth/close? (:y origin) (:y2 parent-rect)) :bottom :else :middle)) delta-h (when orig-h (cond (= orig-h :left) - (- (gpr/right-of transformed-parent-rect) (gpr/right-of parent-rect)) + (- (:x2 transformed-parent-rect) (:x2 parent-rect)) (= orig-h :right) - (- (gpr/left-of transformed-parent-rect) (gpr/left-of parent-rect)) + (- (:x1 transformed-parent-rect) (:x1 parent-rect)) :else 0)) delta-v (when orig-v (cond (= orig-v :top) - (- (gpr/bottom-of transformed-parent-rect) (gpr/bottom-of parent-rect)) + (- (:y2 transformed-parent-rect) (:y2 parent-rect)) (= orig-v :bottom) - (- (gpr/top-of transformed-parent-rect) (gpr/top-of parent-rect)) + (- (:y1 transformed-parent-rect) (:y1 parent-rect)) :else 0)) @@ -418,13 +418,13 @@ :leftright (cond (= orig-h :left) - {:resize-origin (gpt/point (gpr/left-of child-rect) (gpr/top-of child-rect)) + {:resize-origin (gpt/point (:x1 child-rect) (:y1 child-rect)) :resize-vector (gpt/point (/ (+ (:width child-rect) delta-h) (:width child-rect)) 1)} (= orig-h :right) - {:resize-origin (gpt/point (gpr/right-of child-rect) (gpr/top-of child-rect)) + {:resize-origin (gpt/point (:x2 child-rect) (:y1 child-rect)) :resize-vector (gpt/point (/ (- (:width child-rect) delta-h) (:width child-rect)) 1)} @@ -455,13 +455,13 @@ :topbottom (cond (= orig-v :top) - {:resize-origin (gpt/point (gpr/left-of child-rect) (gpr/top-of child-rect)) + {:resize-origin (gpt/point (:x1 child-rect) (:y1 child-rect)) :resize-vector (gpt/point 1 (/ (+ (:height child-rect) delta-v) (:height child-rect)))} (= orig-v :bottom) - {:resize-origin (gpt/point (gpr/left-of child-rect) (gpr/bottom-of child-rect)) + {:resize-origin (gpt/point (:x1 child-rect) (:y2 child-rect)) :resize-vector (gpt/point 1 (/ (- (:height child-rect) delta-v) (:height child-rect)))} @@ -493,7 +493,7 @@ (:displacement parent-modifiers) (update :displacement #(if (nil? %) (:displacement parent-modifiers) - (gmt/multiply % (:displacement parent-modifiers))))))) + (gmt/add-translate % (:displacement parent-modifiers))))))) (defn update-group-viewbox "Updates the viewbox for groups imported from SVG's" From 4c4dac8e90736f013c9f4e0e10ed790f66193d7a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 9 Jun 2021 16:56:49 +0200 Subject: [PATCH 061/204] :sparkles: Allow check for pgobject type. --- backend/src/app/db.clj | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index e11e5b8bcd..e1455e7193 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -252,8 +252,11 @@ (exec! ds (sql/select table params opts)))) (defn pgobject? - [v] - (instance? PGobject v)) + ([v] + (instance? PGobject v)) + ([v type] + (and (instance? PGobject v) + (= type (.getType ^PGobject v))))) (defn pginterval? [v] From ff3caec36cddae27c6fc98224df907ba6357e672 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 9 Jun 2021 16:57:10 +0200 Subject: [PATCH 062/204] :tada: Add decode-inet helper on app.db ns. --- backend/src/app/db.clj | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index e1455e7193..6e2e086bb2 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -349,6 +349,12 @@ (.setType "inet") (.setValue (str ip-addr)))) +(defn decode-inet + [^PGobject o] + (if (= "inet" (.getType o)) + (.getValue o) + nil)) + (defn tjson "Encode as transit json." [data] From f95705d2d644d85086a51d03ef1e9e4fca5ad392 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 9 Jun 2021 16:59:04 +0200 Subject: [PATCH 063/204] :sparkles: Add source ip to the audit-log. --- backend/src/app/loggers/audit.clj | 27 +++++++----- backend/src/app/migrations.clj | 3 ++ .../sql/0059-mod-audit-log-table.sql | 2 + backend/src/app/rpc.clj | 41 ++++++++++++++----- 4 files changed, 51 insertions(+), 22 deletions(-) create mode 100644 backend/src/app/migrations/sql/0059-mod-audit-log-table.sql diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 71cf7e1ae6..deb58cfd47 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -101,12 +101,13 @@ (:name event) (:type event) (:profile-id event) + (some-> (:ip-addr event) db/inet) (db/tjson (:props event))])] (aa/with-thread executor (db/with-atomic [conn pool] (db/insert-multi! conn :audit-log - [:id :name :type :profile-id :props] + [:id :name :type :profile-id :ip-addr :props] (sequence (map event->row) events)))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -147,17 +148,22 @@ (defn archive-events [{:keys [pool uri tokens] :as cfg}] - (letfn [(decode-row [{:keys [props] :as row}] + (letfn [(decode-row [{:keys [props ip-addr] :as row}] (cond-> row (db/pgobject? props) - (assoc :props (db/decode-transit-pgobject props)))) + (assoc :props (db/decode-transit-pgobject props)) - (row->event [{:keys [name type created-at profile-id props]}] - {:type type - :name name - :timestamp created-at - :profile-id profile-id - :props props}) + (db/pgobject? ip-addr "inet") + (assoc :ip-addr (db/decode-inet ip-addr)))) + + (row->event [{:keys [name type created-at profile-id props ip-addr]}] + (cond-> {:type type + :name name + :timestamp created-at + :profile-id profile-id + :props props} + (some? ip-addr) + (update :context assoc :source-ip ip-addr))) (send [events] (let [token (tokens :generate {:iss "authentication" @@ -168,7 +174,7 @@ "origin" (cf/get :public-uri) "cookie" (u/map->query-string {:auth-token token})} params {:uri uri - :timeout 5000 + :timeout 6000 :method :post :headers headers :body body} @@ -187,7 +193,6 @@ (db/with-atomic [conn pool] (let [rows (db/exec! conn [sql:retrieve-batch-of-audit-log]) - xform (comp (map decode-row) (map row->event)) events (into [] xform rows)] diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index 022abb69b8..f8decf5e3c 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -184,6 +184,9 @@ {:name "0058-del-team-on-delete-trigger" :fn (mg/resource "app/migrations/sql/0058-del-team-on-delete-trigger.sql")} + + {:name "0059-mod-audit-log-table" + :fn (mg/resource "app/migrations/sql/0059-mod-audit-log-table.sql")} ]) diff --git a/backend/src/app/migrations/sql/0059-mod-audit-log-table.sql b/backend/src/app/migrations/sql/0059-mod-audit-log-table.sql new file mode 100644 index 0000000000..1a2497a0a6 --- /dev/null +++ b/backend/src/app/migrations/sql/0059-mod-audit-log-table.sql @@ -0,0 +1,2 @@ +ALTER TABLE audit_log + ADD COLUMN ip_addr inet NULL; diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 45598854d7..947936940f 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -32,9 +32,10 @@ [methods {:keys [profile-id] :as request}] (let [type (keyword (get-in request [:path-params :type])) - data (d/merge (:params request) - (:body-params request) - (:uploads request)) + data (merge (:params request) + (:body-params request) + (:uploads request) + {::request request}) data (if profile-id (assoc data :profile-id profile-id) @@ -50,12 +51,15 @@ (defn- rpc-mutation-handler [methods {:keys [profile-id] :as request}] (let [type (keyword (get-in request [:path-params :type])) - data (d/merge (:params request) - (:body-params request) - (:uploads request)) + data (merge (:params request) + (:body-params request) + (:uploads request) + {::request request}) + data (if profile-id (assoc data :profile-id profile-id) (dissoc data :profile-id)) + result ((get methods type default-handler) data) mdata (meta result)] (cond->> {:status 200 :body result} @@ -85,6 +89,11 @@ (rlm/execute rlinst (f cfg params)))) f)) +(defn- parse-client-ip + [{:keys [headers] :as request}] + (or (some-> (get headers "x-forwarded-for") (str/split ",") first) + (get headers "x-real-ip") + (get request :remote-addr))) (defn- wrap-impl [{:keys [audit] :as cfg} f mdata] @@ -95,15 +104,23 @@ (l/trace :action "register" :name (::sv/name mdata)) (fn [params] + + ;; Raise authentication error when rpc method requires auth but + ;; no profile-id is found in the request. (when (and auth? (not (uuid? (:profile-id params)))) (ex/raise :type :authentication :code :authentication-required :hint "authentication required for this endpoint")) - (let [params (us/conform spec params) - result (f cfg params) - resultm (meta result)] - (when (and (::type cfg) (fn? audit)) - (let [profile-id (or (:profile-id params) + + (let [params' (dissoc params ::request) + params' (us/conform spec params') + result (f cfg params')] + + ;; When audit log is enabled (default false). + (when (fn? audit) + (let [resultm (meta result) + request (::request params) + profile-id (or (:profile-id params') (:profile-id result) (::audit/profile-id resultm)) props (d/merge params (::audit/props resultm))] @@ -111,7 +128,9 @@ :name (or (::audit/name resultm) (::sv/name mdata)) :profile-id profile-id + :ip-addr (parse-client-ip request) :props props}))) + result)))) (defn- process-method From 6988ae83c9ac864936076494322b71d36518bb70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 10 Jun 2021 11:19:25 +0200 Subject: [PATCH 064/204] :bug: Fix mini visual bug --- .../resources/styles/main/partials/sidebar-element-options.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/resources/styles/main/partials/sidebar-element-options.scss b/frontend/resources/styles/main/partials/sidebar-element-options.scss index 8b5606eec4..72faa15efd 100644 --- a/frontend/resources/styles/main/partials/sidebar-element-options.scss +++ b/frontend/resources/styles/main/partials/sidebar-element-options.scss @@ -1415,7 +1415,7 @@ transform: rotate(45deg); } - .top-down svg { + .top-bottom svg { transform: rotate(-45deg); } From 371c78b1d37a2060bf8407418df33be310a9e467 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 10 Jun 2021 13:01:28 +0200 Subject: [PATCH 065/204] :recycle: Refactor delete-shapes event. Properly handle parent deletion + performance. --- .../src/app/main/data/workspace/common.cljs | 205 ++++++++++-------- 1 file changed, 112 insertions(+), 93 deletions(-) diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 3bffa6bdb9..0e313686bc 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -341,26 +341,18 @@ :undo-changes uchanges :origin it})))))) +(s/def ::set-of-uuid + (s/every ::us/uuid :kind set?)) + (defn delete-shapes [ids] - (us/assert (s/coll-of ::us/uuid) ids) + (us/assert ::set-of-uuid ids) (ptk/reify ::delete-shapes ptk/WatchEvent (watch [it state stream] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - get-empty-parents - (fn [parents] - (->> parents - (map (fn [id] - (let [obj (get objects id)] - (when (and (= :group (:type obj)) - (= 1 (count (:shapes obj)))) - obj)))) - (take-while (complement nil?)) - (map :id))) - groups-to-unmask (reduce (fn [group-ids id] ;; When the shape to delete is the mask of a masked group, @@ -381,90 +373,117 @@ (some ids (map :destination interactions)))) (vals objects)) - rchanges - (d/concat - (reduce (fn [res id] - (let [children (cp/get-children id objects) - parents (cp/get-parents id objects) - del-change #(array-map - :type :del-obj - :page-id page-id - :id %)] - (d/concat res - (map del-change (reverse children)) - [(del-change id)] - (map del-change (get-empty-parents parents)) - [{:type :reg-objects - :page-id page-id - :shapes (vec parents)}]))) - [] - ids) - (map #(array-map - :type :mod-obj - :page-id page-id - :id % - :operations [{:type :set - :attr :masked-group? - :val false}]) - groups-to-unmask) - (map #(array-map - :type :mod-obj - :page-id page-id - :id (:id %) - :operations [{:type :set - :attr :interactions - :val (vec (remove (fn [interaction] - (contains? ids (:destination interaction))) - (:interactions %)))}]) - interacting-shapes)) + empty-parents-xform + (comp + (map (fn [id] (get objects id))) + (map (fn [{:keys [shapes type] :as obj}] + (when (and (= :group type) + (zero? (count (remove #(contains? ids %) shapes)))) + obj))) + (take-while some?) + (map :id)) + all-parents + (reduce (fn [res id] + (into res (cp/get-parents id objects))) + (d/ordered-set) + ids) + + all-children + (reduce (fn [res id] + (into res (cp/get-children id objects))) + (d/ordered-set) + ids) + + empty-parents + (into (d/ordered-set) empty-parents-xform all-parents) + + mk-del-obj-xf + (map (fn [id] + {:type :del-obj + :page-id page-id + :id id})) + + mk-add-obj-xf + (map (fn [id] + (let [item (get objects id)] + {:type :add-obj + :id (:id item) + :page-id page-id + :index (cp/position-on-parent id objects) + :frame-id (:frame-id item) + :parent-id (:parent-id item) + :obj item}))) + + mk-mod-touched-xf + (map (fn [id] + (let [parent (get objects id)] + {:type :mod-obj + :page-id page-id + :id (:id parent) + :operations [{:type :set-touched + :touched (:touched parent)}]}))) + + mk-mod-int-del-xf + (map (fn [obj] + {:type :mod-obj + :page-id page-id + :id (:id obj) + :operations [{:type :set + :attr :interactions + :val (vec (remove (fn [interaction] + (contains? ids (:destination interaction))) + (:interactions obj)))}]})) + mk-mod-int-add-xf + (map (fn [obj] + {:type :mod-obj + :page-id page-id + :id (:id obj) + :operations [{:type :set + :attr :interactions + :val (:interactions obj)}]})) + + mk-mod-unmask-xf + (map (fn [id] + {:type :mod-obj + :page-id page-id + :id id + :operations [{:type :set + :attr :masked-group? + :val false}]})) + + mk-mod-mask-xf + (map (fn [id] + {:type :mod-obj + :page-id page-id + :id id + :operations [{:type :set + :attr :masked-group? + :val true}]})) + + rchanges + (-> [] + (into mk-del-obj-xf all-children) + (into mk-del-obj-xf ids) + (into mk-del-obj-xf empty-parents) + (conj {:type :reg-objects + :page-id page-id + :shapes (vec all-parents)}) + (into mk-mod-unmask-xf groups-to-unmask) + (into mk-mod-int-del-xf interacting-shapes)) uchanges - (d/concat - (reduce (fn [res id] - (let [children (cp/get-children id objects) - parents (cp/get-parents id objects) - parent (get objects (first parents)) - add-change (fn [id] - (let [item (get objects id)] - {:type :add-obj - :id (:id item) - :page-id page-id - :index (cp/position-on-parent id objects) - :frame-id (:frame-id item) - :parent-id (:parent-id item) - :obj item}))] - (d/concat res - (map add-change (reverse (get-empty-parents parents))) - [(add-change id)] - (map add-change children) - [{:type :reg-objects - :page-id page-id - :shapes (vec parents)}] - (when (some? parent) - [{:type :mod-obj - :page-id page-id - :id (:id parent) - :operations [{:type :set-touched - :touched (:touched parent)}]}])))) - [] - ids) - (map #(array-map - :type :mod-obj - :page-id page-id - :id % - :operations [{:type :set - :attr :masked-group? - :val true}]) - groups-to-unmask) - (map #(array-map - :type :mod-obj - :page-id page-id - :id (:id %) - :operations [{:type :set - :attr :interactions - :val (:interactions %)}]) - interacting-shapes))] + (-> [] + (into mk-add-obj-xf (reverse empty-parents)) + (into mk-add-obj-xf (reverse ids)) + (into mk-add-obj-xf (reverse all-children)) + (conj {:type :reg-objects + :page-id page-id + :shapes (vec all-parents)}) + (into mk-mod-touched-xf (reverse all-parents)) + (into mk-mod-mask-xf groups-to-unmask) + (into mk-mod-int-add-xf interacting-shapes)) + ] ;; (println "================ rchanges") ;; (cljs.pprint/pprint rchanges) From eee0cf569ea9e2823696739f05ad67c873fde0ab Mon Sep 17 00:00:00 2001 From: Amine Gdoura Date: Wed, 9 Jun 2021 15:05:34 +0000 Subject: [PATCH 066/204] :globe_with_meridians: Add translations for: Arabic. Currently translated at 16.7% (111 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 156 +++++++++++++++++++++++++++++++++++- 1 file changed, 155 insertions(+), 1 deletion(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index f0de33d7c5..4fe8cbd344 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-05-23 21:33+0000\n" +"PO-Revision-Date: 2021-06-10 15:33+0000\n" "Last-Translator: Amine Gdoura \n" "Language-Team: Arabic \n" @@ -341,3 +341,157 @@ msgstr "تم إرسال رابط استعادة كلمة المرور إلى ص #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.profile-not-verified" msgstr "لم يتم التعرف على الحساب الشخصي ، يرجى التحقق قبل المتابعة." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "تم التحقق من صحة البريد الإلكتروني." + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "البريد الإلكتروني مستخدم بالفعل" + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "لا يمكن للمتصفح إجراء هذه العملية" + +#: src/app/main/ui/dashboard/grid.cljs +#, fuzzy +msgid "ds.updated-at" +msgstr "محدث: ٪s" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "هل أنت متأكد؟" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "حسنا" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +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 +msgid "dashboard.your-penpot" +msgstr "Penpot الخاص بك" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "اسمك" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "البريد الالكتروني" + +#: src/app/main/ui/settings.cljs +#, fuzzy +msgid "dashboard.your-account-title" +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 "تحديث الإعدادات" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "اكتب لإظهار نتائج البحث" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "نتائج البحث" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "مشاريع الفريق" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "أعضاء الفريق" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "معلومات الفريق" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "تبديل الفريق" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "تم نقل مشروعك بنجاح" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +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 "تم نقل ملفك بنجاح" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "تم نسخ مشروعك بنجاح" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "تم تكرار ملفك بنجاح" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "تم حذف مشروعك بنجاح" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "تم حذف ملفك بنجاح" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "إظهار كافة الملفات" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "اختر نمطا" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "حدد لغة واجهة المستخدم" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "البحث عن \"٪s\"…" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +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/settings/profile.cljs +msgid "dashboard.remove-account" +msgstr "هل تريد إزالة حسابك؟" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "الترقية إلى مالك" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "المشاريع" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "تثبيت / إلغاء التثبيت" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "تغيير كلمة المرور" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "فتح ملف في علامة تبويب جديدة" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "٪s الأعضاء" From 024cc8873806d5a6d453fe77df33e226e067318b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 10 Jun 2021 15:37:56 +0200 Subject: [PATCH 067/204] :sparkles: Reduce the file-change snapshot taking ratio. Until now, a file `data` snapshot was persisted on every file_change row. That causes a lot of IO load and increase disk usage without a real benefit. This commit reduces the snapshot generation; now the snapshot is persisted every 20 update-file or when a file is not touched in 3 hours or more. --- backend/src/app/migrations.clj | 3 +++ .../migrations/sql/0060-mod-file-change-table.sql | 2 ++ backend/src/app/rpc/mutations/files.clj | 14 ++++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 backend/src/app/migrations/sql/0060-mod-file-change-table.sql diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index f8decf5e3c..070f6cae81 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -187,6 +187,9 @@ {:name "0059-mod-audit-log-table" :fn (mg/resource "app/migrations/sql/0059-mod-audit-log-table.sql")} + + {:name "0060-mod-file-change-table" + :fn (mg/resource "app/migrations/sql/0060-mod-file-change-table.sql")} ]) diff --git a/backend/src/app/migrations/sql/0060-mod-file-change-table.sql b/backend/src/app/migrations/sql/0060-mod-file-change-table.sql new file mode 100644 index 0000000000..cb3acdd4e4 --- /dev/null +++ b/backend/src/app/migrations/sql/0060-mod-file-change-table.sql @@ -0,0 +1,2 @@ +ALTER TABLE file_change +ALTER COLUMN data DROP NOT NULL; diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 55a0c5c28f..d89bd3874e 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -43,7 +43,6 @@ (proj/check-edition-permissions! conn profile-id project-id) (create-file conn params))) - (defn create-file-role [conn {:keys [file-id profile-id role]}] (let [params {:file-id file-id @@ -274,10 +273,20 @@ (update-file (assoc cfg :conn conn) (assoc params :file file))))) +(defn- take-snapshot? + "Defines the rule when file `data` snapshot should be saved." + [{:keys [revn modified-at] :as file}] + ;; The snapshot will be saved every 20 changes or if the last + ;; modification is older than 3 hour. + (or (zero? (mod revn 20)) + (> (inst-ms (dt/diff modified-at (dt/now))) + (inst-ms (dt/duration {:hours 3}))))) + (defn- update-file [{:keys [conn] :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." @@ -304,7 +313,8 @@ :profile-id profile-id :file-id (:id file) :revn (:revn file) - :data (:data file) + :data (when (take-snapshot? file) + (:data file)) :changes (blob/encode changes)}) ;; Update file From 840430c1893e2afe8b90e29018c35f9ac63e3410 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 10 Jun 2021 15:41:19 +0200 Subject: [PATCH 068/204] :sparkles: Increment the file-change garbage collection time window. The previous value was 24 hours because the snapshot stated to consume a lot of disk space. Since we reduced snapshot generation considerably, we now can increase the gc time window to 72 hours. --- backend/src/app/main.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 6ba884197d..218f318e9f 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -254,7 +254,7 @@ :app.tasks.file-xlog-gc/handler {:pool (ig/ref :app.db/pool) - :max-age (dt/duration {:hours 24})} + :max-age (dt/duration {:hours 72})} :app.tasks.telemetry/handler {:pool (ig/ref :app.db/pool) From 4928f875b33d4dce8af5ab007b1e7aadaa325449 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 10 Jun 2021 15:43:13 +0200 Subject: [PATCH 069/204] :sparkles: Strip incoming changes from update-file response. Until now, `update-file` always returned a ordered set of change-groups plus the one created by the ongoing request. A change-group corresponds to a list of changes commited in a single update-file (file_change table row). Including the ongoing request change-group on response with increase load stated causing considerable amount of memmory pressure. Since this changes are no longer necessary on frontend side, with this commit we strip the changes list from the ongoing request change-group, sending back an empty entry with the increased `revn` number. --- backend/src/app/rpc/mutations/files.clj | 32 ++++++++++++++----------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index d89bd3874e..a4f37867f2 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -332,6 +332,24 @@ ;; 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 [msgbus conn] :as cfg} {:keys [file changes session-id] :as params}] (let [lchanges (filter library-change? changes)] @@ -363,17 +381,3 @@ [conn project-id] (:team-id (db/get-by-id conn :project project-id {:columns [:team-id]}))) -(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)]) - (mapv files/decode-row))) - From 022d57ef42ef9907fa246174bb09cd9dcabf10d6 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 10 Jun 2021 15:52:37 +0200 Subject: [PATCH 070/204] :sparkles: Increase a little bit the compression level of blob encoding. --- backend/src/app/util/blob.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/app/util/blob.clj b/backend/src/app/util/blob.clj index 3c740516bb..42539b9342 100644 --- a/backend/src/app/util/blob.clj +++ b/backend/src/app/util/blob.clj @@ -108,7 +108,7 @@ cdata (byte-array mlen) clen (Zstd/compressByteArray ^bytes cdata 0 mlen ^bytes data 0 dlen - 4)] + 6)] (with-open [^ByteArrayOutputStream baos (ByteArrayOutputStream. (+ (alength cdata) 2 4)) ^DataOutputStream dos (DataOutputStream. baos)] (.writeShort dos (short 3)) ;; version number From 8d703a3fb4de55ed6b5558e80cb6278e4d4dde95 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 10 Jun 2021 16:11:24 +0200 Subject: [PATCH 071/204] :sparkles: Write transit data to response output-stream. Previously, all responses from GET and POST requests are serialized to a byte array (using transit) which is returned as response body. With this commit, the response body of POST requests is written directly to the response output-stream, reducing the memmory need to perform that operation. The responses for GET request still uses the old mechanism because we need the whole response as byte array for calculate the ETAG and check it before returning the body. --- backend/src/app/http/middleware.clj | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/backend/src/app/http/middleware.clj b/backend/src/app/http/middleware.clj index 91c275cf02..b979b8431a 100644 --- a/backend/src/app/http/middleware.clj +++ b/backend/src/app/http/middleware.clj @@ -13,6 +13,7 @@ [buddy.core.codecs :as bc] [buddy.core.hash :as bh] [clojure.java.io :as io] + [ring.core.protocols :as rp] [ring.middleware.cookies :refer [wrap-cookies]] [ring.middleware.keyword-params :refer [wrap-keyword-params]] [ring.middleware.multipart-params :refer [wrap-multipart-params]] @@ -73,17 +74,28 @@ {:name ::parse-request-body :compile (constantly wrap-parse-request-body)}) +(defn- transit-streamable-body + [data opts] + (reify rp/StreamableResponseBody + (write-body-to-stream [_ response output-stream] + (try + (let [tw (t/writer output-stream opts)] + (t/write! tw data)) + (finally + (.close ^java.io.OutputStream output-stream)))))) + (defn- impl-format-response-body - [response] + [response request] (let [body (:body response) - type :json-verbose] + opts {:type :json-verbose}] (cond (coll? body) (-> response - (assoc :body (t/encode body {:type type})) - (update :headers assoc - "content-type" - "application/transit+json")) + (update :headers assoc "content-type" "application/transit+json") + (assoc :body + (if (= :post (:request-method request)) + (transit-streamable-body body opts) + (t/encode body opts)))) (nil? body) (assoc response :status 204 :body "") @@ -96,7 +108,7 @@ (fn [request] (let [response (handler request)] (cond-> response - (map? response) (impl-format-response-body))))) + (map? response) (impl-format-response-body request))))) (def format-response-body {:name ::format-response-body From 83879fb931ec3f8e3dfef220d62773caf523973e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 3 Jun 2021 15:01:24 +0200 Subject: [PATCH 072/204] :sparkles: Support for fill,stroke,gradient,text --- common/src/app/common/file_builder.cljc | 3 +- .../src/app/main/ui/shapes/gradients.cljs | 27 ++- frontend/src/app/main/ui/shapes/shape.cljs | 10 +- frontend/src/app/util/import/parser.cljc | 165 ++++++++++++++---- frontend/src/app/util/json.cljs | 19 ++ 5 files changed, 179 insertions(+), 45 deletions(-) create mode 100644 frontend/src/app/util/json.cljs diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 807d507c27..1b0dbe2e13 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -7,12 +7,12 @@ (ns app.common.file-builder "A version parsing helper." (:require + [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.pages.changes :as ch] [app.common.pages.init :as init] [app.common.pages.spec :as spec] [app.common.spec :as us] - [app.common.spec :as us] [app.common.uuid :as uuid])) (def root-frame uuid/zero) @@ -130,6 +130,7 @@ (lookup-shape file frame-id)) obj (-> (init/make-minimal-shape type) (merge data) + (d/without-nils) (cond-> frame (gsh/translate-from-frame frame)))] (commit-shape file obj))) diff --git a/frontend/src/app/main/ui/shapes/gradients.cljs b/frontend/src/app/main/ui/shapes/gradients.cljs index 9475ec8d36..a6e091cbc4 100644 --- a/frontend/src/app/main/ui/shapes/gradients.cljs +++ b/frontend/src/app/main/ui/shapes/gradients.cljs @@ -30,6 +30,15 @@ :stop-color color :stop-opacity opacity}])])) +(defn add-metadata [props gradient] + (-> props + (obj/set! "penpot:start-x" (:start-x gradient)) + (obj/set! "penpot:start-x" (:start-x gradient)) + (obj/set! "penpot:start-y" (:start-y gradient)) + (obj/set! "penpot:end-x" (:end-x gradient)) + (obj/set! "penpot:end-y" (:end-y gradient)) + (obj/set! "penpot:width" (:width gradient)))) + (mf/defc radial-gradient [{:keys [id gradient shape]}] (let [{:keys [x y width height]} (:selrect shape) center (gsh/center-shape shape) @@ -59,13 +68,17 @@ transform (gmt/multiply transform (gmt/translate-matrix translate-vec) (gmt/rotate-matrix angle) - (gmt/scale-matrix scale-vec))] - [:radialGradient {:id id - :cx 0 - :cy 0 - :r 1 - :gradientUnits "userSpaceOnUse" - :gradientTransform transform} + (gmt/scale-matrix scale-vec)) + + base-props #js {:id id + :cx 0 + :cy 0 + :r 1 + :gradientUnits "userSpaceOnUse" + :gradientTransform transform} + + props (-> base-props (add-metadata gradient))] + [:> :radialGradient props (for [{:keys [offset color opacity]} (:stops gradient)] [:stop {:key (str id "-stop-" offset) :offset (or offset 0) diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 86420ee124..a91e2c7e79 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -16,7 +16,8 @@ [app.main.ui.shapes.gradients :as grad] [app.main.ui.shapes.svg-defs :as defs] [app.util.object :as obj] - [rumext.alpha :as mf])) + [rumext.alpha :as mf] + [app.util.json :as json])) (defn add-metadata "Adds as metadata properties that we cannot deduce from the exported SVG" @@ -26,7 +27,8 @@ (let [ns-attr (str "penpot:" (-> attr d/name))] (-> props (obj/set! ns-attr val)))) - frame? (= :frame (:type shape))] + frame? (= :frame (:type shape)) + text? (= :text (:type shape))] (-> props (add! :name (-> shape :name)) (add! :blocked (-> shape (:blocked false) str)) @@ -45,6 +47,10 @@ (add! :r3 (-> shape (:r3 0) str)) (add! :r4 (-> shape (:r4 0) str)))) + (cond-> text? + (-> (add! :grow-type (-> shape :grow-type)) + (add! :content (-> shape :content json/encode)))) + (cond-> frame? (obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))))) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 6847b194f6..88929ad5bc 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -9,8 +9,10 @@ [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] - [cuerdas.core :as str] - [app.util.path.parser :as upp])) + [app.util.color :as uc] + [app.util.json :as json] + [app.util.path.parser :as upp] + [cuerdas.core :as str])) (defn valid? [root] @@ -38,9 +40,9 @@ (or (close? node) (contains? (:attrs node) :penpot:type))) -(defn get-attr +(defn get-meta ([m att] - (get-attr m att identity)) + (get-meta m att identity)) ([m att val-fn] (let [ns-att (->> att d/name (str "penpot:") keyword) val (get-in m [:attrs ns-att])] @@ -78,22 +80,25 @@ (reduce-kv (fn [m k v] (if (#{:style :data-style} k) - (assoc m :style (parse-style v)) + (merge m (parse-style v)) (assoc m k v))) m attrs)) -(defn get-data-node - [node] - - (let [data-tags #{:ellipse :rect :path}] - (->> node - (node-seq) - (filter #(contains? data-tags (:tag %))) - (map #(:attrs %)) - (reduce add-attrs {})))) - (def search-data-node? #{:rect :image :path :text :circle}) + +(defn get-shape-data + [type node] + + (if (search-data-node? type) + (let [data-tags #{:ellipse :rect :path :text :foreignObject}] + (->> node + (node-seq) + (filter #(contains? data-tags (:tag %))) + (map #(:attrs %)) + (reduce add-attrs {}))) + (:attrs node))) + (def has-position? #{:frame :rect :image :text}) (defn parse-position @@ -123,22 +128,103 @@ (assoc :selrect selrect) (assoc :points points)))) -(defn extract-data - [type node] - (let [data (if (search-data-node? type) - (get-data-node node) - (:attrs node))] - (cond-> {} - (has-position? type) - (-> (parse-position data) - (gsh/setup-selrect)) +(def url-regex #"url\(#([^\)]*)\)") - (= type :circle) - (-> (parse-circle data) - (gsh/setup-selrect)) +(defn seek-node [id coll] + (->> coll (d/seek #(= id (-> % :attrs :id))))) - (= type :path) - (parse-path data)))) +(defn parse-stops [gradient-node] + (->> gradient-node + (node-seq) + (filter #(= :stop (:tag %))) + (mapv (fn [{{:keys [offset stop-color stop-opacity]} :attrs}] + {:color stop-color + :opacity (d/parse-double stop-opacity) + :offset (d/parse-double offset)})))) + +(defn parse-gradient + [node ref-url] + (let [[_ url] (re-matches url-regex ref-url) + gradient-node (->> node (node-seq) (seek-node url)) + stops (parse-stops gradient-node)] + + (cond-> {:stops stops} + (= :linearGradient (:tag gradient-node)) + (assoc :type :linear + :start-x (-> gradient-node :attrs :x1 d/parse-double) + :start-y (-> gradient-node :attrs :y1 d/parse-double) + :end-x (-> gradient-node :attrs :x2 d/parse-double) + :end-y (-> gradient-node :attrs :y2 d/parse-double) + :width 1) + + (= :radialGradient (:tag gradient-node)) + (assoc :type :radial + :start-x (get-meta gradient-node :start-x d/parse-double) + :start-y (get-meta gradient-node :start-y d/parse-double) + :end-x (get-meta gradient-node :end-x d/parse-double) + :end-y (get-meta gradient-node :end-y d/parse-double) + :width (get-meta gradient-node :width d/parse-double))))) + +(defn add-position + [props type node data] + (cond-> props + (has-position? type) + (-> (parse-position data) + (gsh/setup-selrect)) + + (= type :circle) + (-> (parse-circle data) + (gsh/setup-selrect)) + + (= type :path) + (parse-path data))) + +(defn add-fill + [props type node data] + + (let [fill (:fill data)] + (cond-> props + (= fill "none") + (assoc :fill-color nil + :fill-opacity nil) + + (str/starts-with? fill "url") + (assoc :fill-color-gradient (parse-gradient node fill) + :fill-color nil + :fill-opacity nil) + + (uc/hex? fill) + (assoc :fill-color fill + :fill-opacity (-> data (:fill-opacity "1") d/parse-double))))) + +(defn add-stroke + [props type node data] + + (let [stroke-style (get-meta node :stroke-style keyword) + stroke-alignment (get-meta node :stroke-alignment keyword) + stroke (:stroke data)] + + (cond-> props + :always + (assoc :stroke-alignment stroke-alignment + :stroke-style stroke-style + :stroke-color (-> data (:stroke "#000000")) + :stroke-opacity (-> data (:stroke-opacity "1") d/parse-double) + :stroke-width (-> data (:stroke-width "0") d/parse-double)) + + (str/starts-with? stroke "url") + (assoc :stroke-color-gradient (parse-gradient node stroke) + :stroke-color nil + :stroke-opacity nil) + + (= stroke-alignment :inner) + (update :stroke-width / 2)))) + +(defn add-text-data + [props node] + (-> props + (assoc :grow-type (get-meta node :grow-type keyword)) + (assoc :content (get-meta node :content json/decode)))) (defn str->bool [val] @@ -148,17 +234,26 @@ [type node] (when-not (close? node) - (let [name (get-attr node :name) - blocked (get-attr node :blocked str->bool) - hidden (get-attr node :hidden str->bool) - transform (get-attr node :transform gmt/str->matrix) - transform-inverse (get-attr node :transform-inverse gmt/str->matrix)] + (let [name (get-meta node :name) + blocked (get-meta node :blocked str->bool) + hidden (get-meta node :hidden str->bool) + transform (get-meta node :transform gmt/str->matrix) + transform-inverse (get-meta node :transform-inverse gmt/str->matrix) + data (get-shape-data type node)] - (-> (extract-data type node) + (-> {} + (add-position type node data) + (add-fill type node data) + (add-stroke type node data) (assoc :name name) (assoc :blocked blocked) (assoc :hidden hidden) + + (cond-> (= :text type) + (add-text-data node)) + (cond-> (some? transform) (assoc :transform transform)) + (cond-> (some? transform-inverse) (assoc :transform-inverse transform-inverse)))))) diff --git a/frontend/src/app/util/json.cljs b/frontend/src/app/util/json.cljs new file mode 100644 index 0000000000..02ff7d58db --- /dev/null +++ b/frontend/src/app/util/json.cljs @@ -0,0 +1,19 @@ +;; 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.util.json) + +(defn decode + [data] + (-> data + (js/JSON.parse) + (js->clj :keywordize-keys true))) + +(defn encode + [data] + (-> data + (clj->js) + (js/JSON.stringify))) From 152bcf451aace4b1a8bbd36565cf619306c6dde9 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 3 Jun 2021 22:11:25 +0200 Subject: [PATCH 073/204] :sparkles: Import images and upload media --- common/src/app/common/file_builder.cljc | 2 +- frontend/deps.edn | 2 +- frontend/src/app/util/import/parser.cljc | 24 +++++- frontend/src/app/worker/import.cljs | 104 ++++++++++++++++------- 4 files changed, 97 insertions(+), 35 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 1b0dbe2e13..b83653b111 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -24,7 +24,7 @@ (when verify-on-commit? (us/assert ::spec/change change)) (-> file - (update :changes conj change) + (update :changes (fnil conj []) change) (update :data ch/process-changes [change] verify-on-commit?))) (defn- lookup-objects diff --git a/frontend/deps.edn b/frontend/deps.edn index edf430c8b3..9bcb8861eb 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -6,7 +6,7 @@ binaryage/devtools {:mvn/version "RELEASE"} metosin/reitit-core {:mvn/version "0.5.13"} - funcool/beicon {:mvn/version "2021.06.02-0"} + funcool/beicon {:mvn/version "2021.06.03-0"} funcool/okulary {:mvn/version "2020.04.14-0"} funcool/potok {:mvn/version "2021.06.07-0"} funcool/rumext {:mvn/version "2021.05.12-1"} diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 88929ad5bc..3f8cb26272 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -91,7 +91,7 @@ [type node] (if (search-data-node? type) - (let [data-tags #{:ellipse :rect :path :text :foreignObject}] + (let [data-tags #{:ellipse :rect :path :text :foreignObject :image}] (->> node (node-seq) (filter #(contains? data-tags (:tag %))) @@ -220,11 +220,19 @@ (= stroke-alignment :inner) (update :stroke-width / 2)))) +(defn add-image-data + [props node data] + (-> props + (assoc-in [:metadata :id] (get-meta node :media-id)) + (assoc-in [:metadata :width] (get-meta node :media-width)) + (assoc-in [:metadata :height] (get-meta node :media-height)) + (assoc-in [:metadata :mtype] (get-meta node :media-mtype)))) + (defn add-text-data [props node] (-> props (assoc :grow-type (get-meta node :grow-type keyword)) - (assoc :content (get-meta node :content json/decode)))) + (assoc :content (get-meta node :content json/decode)))) (defn str->bool [val] @@ -249,6 +257,9 @@ (assoc :blocked blocked) (assoc :hidden hidden) + (cond-> (= :image type) + (add-image-data node data)) + (cond-> (= :text type) (add-text-data node)) @@ -257,3 +268,12 @@ (cond-> (some? transform-inverse) (assoc :transform-inverse transform-inverse)))))) + +(defn get-image-name + [node] + (get-in node [:attrs :penpot:name])) + +(defn get-image-data + [node] + (let [data (get-shape-data :image node)] + (:xlink:href data))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 350035ed90..dfe240b8e4 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -11,6 +11,7 @@ [app.common.pages :as cp] [app.common.uuid :as uuid] [app.main.repo :as rp] + [app.util.http :as http] [app.util.import.parser :as cip] [app.util.zip :as uz] [app.worker.impl :as impl] @@ -21,21 +22,21 @@ ;; Upload changes batches size (def change-batch-size 100) -(defn create-empty-file +(defn create-file "Create a new file on the back-end" - [project-id file] - (rp/mutation - :create-file - {:id (:id file) - :name (:name file) - :project-id project-id - :data (-> cp/empty-file-data - (assoc :id (:id file)))})) + [project-id name] + (let [file-id (uuid/next)] + (rp/mutation + :create-file + {:id file-id + :name name + :project-id project-id + :data (-> cp/empty-file-data (assoc :id file-id))}))) (defn send-changes "Creates batches of changes to be sent to the backend" - [file init-revn] - (let [revn (atom init-revn) + [file] + (let [revn (atom (:revn file)) file-id (:id file) session-id (uuid/next) changes-batches @@ -55,11 +56,21 @@ (rx/tap #(reset! revn (:revn %)))))) -(defn persist-file - "Sends to the back-end the imported data" - [project-id file] - (->> (create-empty-file project-id file) - (rx/flat-map #(send-changes file (:revn %))))) +(defn upload-media-files + "Upload a image to the backend and returns its id" + [file-id name data-uri] + (->> (http/send! + {:uri data-uri + :response-type :blob + :method :get}) + (rx/map :body) + (rx/map + (fn [blob] + {:name name + :file-id file-id + :content blob + :is-local true})) + (rx/flat-map #(rp/mutation! :upload-file-media-object %)))) (defn parse-file-name [dir] @@ -102,15 +113,41 @@ ;; default file)))) +(defn merge-reduce [f seed ob] + (->> (rx/concat + (rx/of seed) + (rx/merge-scan f seed ob)) + (rx/last))) + +(defn resolve-images + [file-id node] + (if (and (cip/shape? node) (= (cip/get-type node) :image) (not (cip/close? node))) + (let [name (cip/get-image-name node) + data-uri (cip/get-image-data node)] + (->> (upload-media-files file-id name data-uri) + (rx/map + (fn [media] + (-> node + (assoc-in [:attrs :penpot:media-id] (:id media)) + (assoc-in [:attrs :penpot:media-width] (:width media)) + (assoc-in [:attrs :penpot:media-height] (:height media)) + (assoc-in [:attrs :penpot:media-mtype] (:mtype media))))))) + + ;; If the node is not an image just return the node + (rx/of node))) + (defn import-page [file {:keys [path content]}] (let [page-name (parse-page-name path)] - (when (cip/valid? content) - (let [nodes (->> content cip/node-seq)] - (->> nodes - (filter cip/shape?) - (reduce add-shape-file (fb/add-page file page-name)) - (fb/close-page)))))) + (if (cip/valid? content) + (let [nodes (->> content cip/node-seq) + file-id (:id file)] + (->> (rx/from nodes) + (rx/filter cip/shape?) + (rx/mapcat (partial resolve-images file-id)) + (rx/reduce add-shape-file (fb/add-page file page-name)) + (rx/map fb/close-page))) + (rx/empty)))) (defmethod impl/handler :import-file [{:keys [project-id files]}] @@ -130,14 +167,19 @@ (rx/map #(d/update-when % :content tubax/xml->clj)))] (->> dir-str + (rx/merge-map #(create-file project-id (parse-file-name %))) (rx/merge-map - (fn [dir] - (let [file (fb/create-file (parse-file-name dir))] - (rx/concat - (->> file-str - (rx/filter #(str/starts-with? (:path %) dir)) - (rx/reduce import-page file) - (rx/flat-map #(persist-file project-id %)) - (rx/ignore)) + (fn [file] + (rx/concat + (->> file-str + (rx/filter #(str/starts-with? (:path %) (:name file))) + (merge-reduce import-page file) + (rx/flat-map send-changes) + (rx/catch (fn [err] + (.error js/console "ERROR" err (clj->js (.-data err))) - (rx/of (select-keys file [:id :name]))))))))) + ;; We delete the file when there is an error + (rp/mutation! :delete-file {:id (:id file)}))) + (rx/ignore)) + + (rx/of (select-keys file [:id :name])))))))) From cc2c249a0769e2fd034a95742d0d475a92095a5e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 4 Jun 2021 09:34:13 +0200 Subject: [PATCH 074/204] :sparkles: Import masks --- frontend/src/app/main/ui/shapes/shape.cljs | 10 ++++++++-- frontend/src/app/util/import/parser.cljc | 10 ++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index a91e2c7e79..62ce224777 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -28,7 +28,10 @@ (-> props (obj/set! ns-attr val)))) frame? (= :frame (:type shape)) - text? (= :text (:type shape))] + group? (= :group (:type shape)) + rect? (= :text (:type shape)) + text? (= :text (:type shape)) + mask? (and group? (:masked-group? shape))] (-> props (add! :name (-> shape :name)) (add! :blocked (-> shape (:blocked false) str)) @@ -41,7 +44,7 @@ (add! :transform (-> shape (:transform (gmt/matrix)) str)) (add! :transform-inverse (-> shape (:transform-inverse (gmt/matrix)) str)) - (cond-> (some? (:r1 shape)) + (cond-> (and rect? (some? (:r1 shape))) (-> (add! :r1 (-> shape (:r1 0) str)) (add! :r2 (-> shape (:r2 0) str)) (add! :r3 (-> shape (:r3 0) str)) @@ -51,6 +54,9 @@ (-> (add! :grow-type (-> shape :grow-type)) (add! :content (-> shape :content json/encode)))) + (cond-> mask? + (add! :masked-group "true")) + (cond-> frame? (obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))))) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 3f8cb26272..3753c4af28 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -238,6 +238,13 @@ [val] (= val "true")) +(defn add-group-data + [props node] + (let [mask? (get-meta node :masked-group str->bool)] + (cond-> props + mask? + (assoc :masked-group? true)))) + (defn parse-data [type node] @@ -257,6 +264,9 @@ (assoc :blocked blocked) (assoc :hidden hidden) + (cond-> (= :group type) + (add-group-data node)) + (cond-> (= :image type) (add-image-data node data)) From 4af83eadc45438966308c651090656f8ba9c7aa4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 4 Jun 2021 15:04:09 +0200 Subject: [PATCH 075/204] :sparkles: Import shadows,blur,exports --- .../src/app/main/ui/shapes/custom_stroke.cljs | 22 +-- frontend/src/app/main/ui/shapes/export.cljs | 78 +++++++++ frontend/src/app/main/ui/shapes/shape.cljs | 52 +----- frontend/src/app/util/import/parser.cljc | 149 ++++++++++++------ 4 files changed, 201 insertions(+), 100 deletions(-) create mode 100644 frontend/src/app/main/ui/shapes/export.cljs diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 1e19f219fd..0fa42483c7 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -30,7 +30,7 @@ (let [clip-id (str "inner-stroke-" render-id) shape-id (str "stroke-shape-" render-id)] [:> "clipPath" #js {:id clip-id} - [:use {:href (str "#" shape-id)}]])) + [:use {:xlinkHref (str "#" shape-id)}]])) (mf/defc outer-stroke-mask [{:keys [shape render-id]}] @@ -38,10 +38,10 @@ shape-id (str "stroke-shape-" render-id) stroke-width (:stroke-width shape 0)] [:mask {:id stroke-mask-id} - [:use {:href (str "#" shape-id) + [:use {:xlinkHref (str "#" shape-id) :style #js {:fill "none" :stroke "white" :strokeWidth (* stroke-width 2)}}] - [:use {:href (str "#" shape-id) + [:use {:xlinkHref (str "#" shape-id) :style #js {:fill "black"}}]])) (mf/defc stroke-defs @@ -84,13 +84,13 @@ (str/join ";"))] [:g.outer-stroke-shape - [:symbol + [:defs [:> elem-name (-> (obj/clone base-props) (obj/set! "id" shape-id) (obj/set! "data-style" style-str) (obj/without ["style"]))]] - [:use {:href (str "#" shape-id) + [:use {:xlinkHref (str "#" shape-id) :mask (str "url(#" stroke-mask-id ")") :style (-> (obj/get base-props "style") (obj/clone) @@ -98,7 +98,7 @@ (obj/without ["fill" "fillOpacity"]) (obj/set! "fill" "none"))}] - [:use {:href (str "#" shape-id) + [:use {:xlinkHref (str "#" shape-id) :style (-> (obj/get base-props "style") (obj/clone) (obj/without ["stroke" "strokeWidth" "strokeOpacity" "strokeStyle" "strokeDasharray"]))}]])) @@ -121,14 +121,18 @@ clip-id (str "inner-stroke-" render-id) shape-id (str "stroke-shape-" render-id) + clip-path (str "url('#" clip-id "')") shape-props (-> base-props (add-props {:id shape-id - :transform nil - :clipPath (str "url('#" clip-id "')")}) + :transform nil}) (add-style {:strokeWidth (* stroke-width 2)}))] [:g.inner-stroke-shape {:transform transform} - [:> elem-name shape-props]])) + [:defs + [:> elem-name shape-props]] + + [:use {:xlinkHref (str "#" shape-id) + :clipPath clip-path}]])) ; The SVG standard does not implement yet the 'stroke-alignment' diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs new file mode 100644 index 0000000000..924ddc7eba --- /dev/null +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -0,0 +1,78 @@ +;; 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.main.ui.shapes.export + (:require + [app.common.data :as d] + [app.common.geom.matrix :as gmt] + [app.util.json :as json] + [app.util.object :as obj] + [rumext.alpha :as mf])) + +(defn add-data + "Adds as metadata properties that we cannot deduce from the exported SVG" + [props shape] + (let [add! + (fn [props attr val] + (let [ns-attr (str "penpot:" (-> attr d/name))] + (-> props + (obj/set! ns-attr val)))) + frame? (= :frame (:type shape)) + group? (= :group (:type shape)) + rect? (= :text (:type shape)) + text? (= :text (:type shape)) + mask? (and group? (:masked-group? shape))] + (-> props + (add! :name (-> shape :name)) + (add! :blocked (-> shape (:blocked false) str)) + (add! :hidden (-> shape (:hidden false) str)) + (add! :type (-> shape :type d/name)) + + (add! :stroke-style (-> shape (:stroke-style :none) d/name)) + (add! :stroke-alignment (-> shape (:stroke-alignment :center) d/name)) + + (add! :transform (-> shape (:transform (gmt/matrix)) str)) + (add! :transform-inverse (-> shape (:transform-inverse (gmt/matrix)) str)) + + (cond-> (and rect? (some? (:r1 shape))) + (-> (add! :r1 (-> shape (:r1 0) str)) + (add! :r2 (-> shape (:r2 0) str)) + (add! :r3 (-> shape (:r3 0) str)) + (add! :r4 (-> shape (:r4 0) str)))) + + (cond-> text? + (-> (add! :grow-type (-> shape :grow-type)) + (add! :content (-> shape :content json/encode)))) + + (cond-> mask? + (add! :masked-group "true"))))) + +(mf/defc export-data + [{:keys [shape]}] + (let [props (-> (obj/new) + (add-data shape))] + [:> "penpot:shape" props + (for [{:keys [style hidden color offset-x offset-y blur spread]} (:shadow shape)] + [:> "penpot:shadow" #js {:penpot:shadow-type (d/name style) + :penpot:hidden (str hidden) + :penpot:color (str (:color color)) + :penpot:opacity (str (:opacity color)) + :penpot:offset-x (str offset-x) + :penpot:offset-y (str offset-y) + :penpot:blur (str blur) + :penpot:spread (str spread)}]) + + (when (some? (:blur shape)) + (let [{:keys [type hidden value]} (:blur shape)] + [:> "penpot:blur" #js {:penpot:blur-type (d/name type) + :penpot:hidden (str hidden) + :penpot:value (str value)}])) + + (for [{:keys [scale suffix type]} (:exports shape)] + [:> "penpot:export" #js {:penpot:type (d/name type) + :penpot:suffix suffix + :penpot:scale (str scale)}])])) + diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 62ce224777..4d3f4fc686 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -8,57 +8,15 @@ (:require [app.common.data :as d] [app.common.uuid :as uuid] - [app.common.geom.matrix :as gmt] [app.main.ui.context :as muc] [app.main.ui.shapes.custom-stroke :as cs] [app.main.ui.shapes.fill-image :as fim] [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.gradients :as grad] + [app.main.ui.shapes.export :as ed] [app.main.ui.shapes.svg-defs :as defs] [app.util.object :as obj] - [rumext.alpha :as mf] - [app.util.json :as json])) - -(defn add-metadata - "Adds as metadata properties that we cannot deduce from the exported SVG" - [props shape] - (let [add! - (fn [props attr val] - (let [ns-attr (str "penpot:" (-> attr d/name))] - (-> props - (obj/set! ns-attr val)))) - frame? (= :frame (:type shape)) - group? (= :group (:type shape)) - rect? (= :text (:type shape)) - text? (= :text (:type shape)) - mask? (and group? (:masked-group? shape))] - (-> props - (add! :name (-> shape :name)) - (add! :blocked (-> shape (:blocked false) str)) - (add! :hidden (-> shape (:hidden false) str)) - (add! :type (-> shape :type d/name)) - - (add! :stroke-style (-> shape (:stroke-style :none) d/name)) - (add! :stroke-alignment (-> shape (:stroke-alignment :center) d/name)) - - (add! :transform (-> shape (:transform (gmt/matrix)) str)) - (add! :transform-inverse (-> shape (:transform-inverse (gmt/matrix)) str)) - - (cond-> (and rect? (some? (:r1 shape))) - (-> (add! :r1 (-> shape (:r1 0) str)) - (add! :r2 (-> shape (:r2 0) str)) - (add! :r3 (-> shape (:r3 0) str)) - (add! :r4 (-> shape (:r4 0) str)))) - - (cond-> text? - (-> (add! :grow-type (-> shape :grow-type)) - (add! :content (-> shape :content json/encode)))) - - (cond-> mask? - (add! :masked-group "true")) - - (cond-> frame? - (obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))))) + [rumext.alpha :as mf])) (mf/defc shape-container {::mf/forward-ref true @@ -92,14 +50,14 @@ (obj/set! "width" width) (obj/set! "height" height) (obj/set! "xmlnsXlink" "http://www.w3.org/1999/xlink") - (obj/set! "xmlns" "http://www.w3.org/2000/svg"))) - - (add-metadata shape)) + (obj/set! "xmlns" "http://www.w3.org/2000/svg") + (obj/set! "xmlns:penpot" "https://penpot.app/xmlns")))) wrapper-tag (if frame? "svg" "g")] [:& (mf/provider muc/render-ctx) {:value render-id} [:> wrapper-tag wrapper-props + [:& ed/export-data {:shape shape}] [:defs [:& defs/svg-defs {:shape shape :render-id render-id}] [:& filters/filters {:shape shape :filter-id filter-id}] diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 3753c4af28..8fd551a6d4 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] + [app.common.uuid :as uuid] [app.util.color :as uc] [app.util.json :as json] [app.util.path.parser :as upp] @@ -28,24 +29,29 @@ (and (vector? node) (= ::close (first node)))) +(defn get-data [node] + (->> node :content (d/seek #(= :penpot:shape (:tag %))))) + (defn get-type [node] (if (close? node) (second node) - (-> (get-in node [:attrs :penpot:type]) - (keyword)))) + (let [data (get-data node)] + (-> (get-in data [:attrs :penpot:type]) + (keyword))))) (defn shape? [node] (or (close? node) - (contains? (:attrs node) :penpot:type))) + (some? (get-data node)))) (defn get-meta ([m att] (get-meta m att identity)) ([m att val-fn] (let [ns-att (->> att d/name (str "penpot:") keyword) - val (get-in m [:attrs ns-att])] + val (or (get-in m [:attrs ns-att]) + (get-in (get-data m) [:attrs ns-att]))] (when val (val-fn val))))) (defn get-children @@ -87,7 +93,7 @@ (def search-data-node? #{:rect :image :path :text :circle}) -(defn get-shape-data +(defn get-svg-data [type node] (if (search-data-node? type) @@ -102,14 +108,14 @@ (def has-position? #{:frame :rect :image :text}) (defn parse-position - [props data] - (let [values (->> (select-keys data [:x :y :width :height]) + [props svg-data] + (let [values (->> (select-keys svg-data [:x :y :width :height]) (d/mapm (fn [_ val] (d/parse-double val))))] (d/merge props values))) (defn parse-circle - [props data] - (let [values (->> (select-keys data [:cx :cy :rx :ry]) + [props svg-data] + (let [values (->> (select-keys svg-data [:cx :cy :rx :ry]) (d/mapm (fn [_ val] (d/parse-double val))))] {:x (- (:cx values) (:rx values)) @@ -118,8 +124,8 @@ :height (* (:ry values) 2)})) (defn parse-path - [props data] - (let [content (upp/parse-path (:d data)) + [props svg-data] + (let [content (upp/parse-path (:d svg-data)) selrect (gsh/content->selrect content) points (gsh/rect->points selrect)] @@ -130,10 +136,12 @@ (def url-regex #"url\(#([^\)]*)\)") -(defn seek-node [id coll] +(defn seek-node + [id coll] (->> coll (d/seek #(= id (-> % :attrs :id))))) -(defn parse-stops [gradient-node] +(defn parse-stops + [gradient-node] (->> gradient-node (node-seq) (filter #(= :stop (:tag %))) @@ -166,23 +174,23 @@ :width (get-meta gradient-node :width d/parse-double))))) (defn add-position - [props type node data] + [props type node svg-data] (cond-> props (has-position? type) - (-> (parse-position data) + (-> (parse-position svg-data) (gsh/setup-selrect)) (= type :circle) - (-> (parse-circle data) + (-> (parse-circle svg-data) (gsh/setup-selrect)) (= type :path) - (parse-path data))) + (parse-path svg-data))) (defn add-fill - [props type node data] + [props type node svg-data] - (let [fill (:fill data)] + (let [fill (:fill svg-data)] (cond-> props (= fill "none") (assoc :fill-color nil @@ -195,22 +203,22 @@ (uc/hex? fill) (assoc :fill-color fill - :fill-opacity (-> data (:fill-opacity "1") d/parse-double))))) + :fill-opacity (-> svg-data (:fill-opacity "1") d/parse-double))))) (defn add-stroke - [props type node data] + [props type node svg-data] (let [stroke-style (get-meta node :stroke-style keyword) stroke-alignment (get-meta node :stroke-alignment keyword) - stroke (:stroke data)] + stroke (:stroke svg-data)] (cond-> props :always (assoc :stroke-alignment stroke-alignment - :stroke-style stroke-style - :stroke-color (-> data (:stroke "#000000")) - :stroke-opacity (-> data (:stroke-opacity "1") d/parse-double) - :stroke-width (-> data (:stroke-width "0") d/parse-double)) + :stroke-style stroke-style + :stroke-color (-> svg-data (:stroke "#000000")) + :stroke-opacity (-> svg-data (:stroke-opacity "1") d/parse-double) + :stroke-width (-> svg-data (:stroke-width "0") d/parse-double)) (str/starts-with? stroke "url") (assoc :stroke-color-gradient (parse-gradient node stroke) @@ -221,12 +229,12 @@ (update :stroke-width / 2)))) (defn add-image-data - [props node data] + [props node] (-> props - (assoc-in [:metadata :id] (get-meta node :media-id)) - (assoc-in [:metadata :width] (get-meta node :media-width)) + (assoc-in [:metadata :id] (get-meta node :media-id)) + (assoc-in [:metadata :width] (get-meta node :media-width)) (assoc-in [:metadata :height] (get-meta node :media-height)) - (assoc-in [:metadata :mtype] (get-meta node :media-mtype)))) + (assoc-in [:metadata :mtype] (get-meta node :media-mtype)))) (defn add-text-data [props node] @@ -245,6 +253,65 @@ mask? (assoc :masked-group? true)))) +(defn parse-shadow [node] + {:id (uuid/next) + :style (get-meta node :shadow-type keyword) + :hidden (get-meta node :hidden str->bool) + :color {:color (get-meta node :color) + :opacity (get-meta node :opacity d/parse-double)} + :offset-x (get-meta node :offset-x d/parse-double) + :offset-y (get-meta node :offset-y d/parse-double) + :blur (get-meta node :blur d/parse-double) + :spread (get-meta node :spread d/parse-double)}) + +(defn parse-blur [node] + {:id (uuid/next) + :type (get-meta node :blur-type keyword) + :hidden (get-meta node :hidden str->bool) + :value (get-meta node :value d/parse-double)}) + +(defn parse-export [node] + {:type (get-meta node :type keyword) + :suffix (get-meta node :suffix) + :scale (get-meta node :scale d/parse-double)}) + +(defn extract-from-data [node tag parse-fn] + (let [shape-data (get-data node)] + (->> shape-data + (node-seq) + (filter #(= (:tag %) tag)) + (mapv parse-fn)))) + +(defn add-shadows + [props node] + (let [shadows (extract-from-data node :penpot:shadow parse-shadow)] + (cond-> props + (not (empty? shadows)) + (assoc :shadow shadows)))) + +(defn add-blur + [props node] + (let [blur (->> (extract-from-data node :penpot:blur parse-blur) (first))] + (cond-> props + (some? blur) + (assoc :blur blur)))) + +(defn add-exports + [props node] + (let [exports (extract-from-data node :penpot:export parse-export)] + (cond-> props + (not (empty? exports)) + (assoc :exports exports)))) + +(defn get-image-name + [node] + (get-in node [:attrs :penpot:name])) + +(defn get-image-data + [node] + (let [svg-data (get-svg-data :image node)] + (:xlink:href svg-data))) + (defn parse-data [type node] @@ -254,12 +321,15 @@ hidden (get-meta node :hidden str->bool) transform (get-meta node :transform gmt/str->matrix) transform-inverse (get-meta node :transform-inverse gmt/str->matrix) - data (get-shape-data type node)] + svg-data (get-svg-data type node)] (-> {} - (add-position type node data) - (add-fill type node data) - (add-stroke type node data) + (add-position type node svg-data) + (add-fill type node svg-data) + (add-stroke type node svg-data) + (add-shadows node) + (add-blur node) + (add-exports node) (assoc :name name) (assoc :blocked blocked) (assoc :hidden hidden) @@ -268,7 +338,7 @@ (add-group-data node)) (cond-> (= :image type) - (add-image-data node data)) + (add-image-data node)) (cond-> (= :text type) (add-text-data node)) @@ -278,12 +348,3 @@ (cond-> (some? transform-inverse) (assoc :transform-inverse transform-inverse)))))) - -(defn get-image-name - [node] - (get-in node [:attrs :penpot:name])) - -(defn get-image-data - [node] - (let [data (get-shape-data :image node)] - (:xlink:href data))) From 0647fa832a325c2aeb890a1d8b1538c00744a0ec Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 7 Jun 2021 22:02:16 +0200 Subject: [PATCH 076/204] :sparkles: Read files info from manifest --- backend/src/app/rpc/mutations/files.clj | 36 ++++++- frontend/src/app/util/zip.cljs | 67 +++++++------ frontend/src/app/worker/export.cljs | 63 ++++++++++--- frontend/src/app/worker/import.cljs | 120 +++++++++++------------- 4 files changed, 181 insertions(+), 105 deletions(-) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index a4f37867f2..1093e9dc38 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -11,6 +11,7 @@ [app.common.pages.migrations :as pmg] [app.common.spec :as us] [app.common.uuid :as uuid] + [app.db :as db] [app.rpc.permissions :as perms] [app.rpc.queries.files :as files] @@ -18,6 +19,7 @@ [app.util.blob :as blob] [app.util.services :as sv] [app.util.time :as dt] + [clojure.spec.alpha :as s])) ;; --- Helpers & Specs @@ -43,6 +45,7 @@ (proj/check-edition-permissions! conn profile-id project-id) (create-file conn params))) + (defn create-file-role [conn {:keys [file-id profile-id role]}] (let [params {:file-id file-id @@ -51,8 +54,9 @@ (db/insert! conn :file-profile-rel)))) (defn create-file - [conn {:keys [id name project-id is-shared data] - :or {is-shared false} + [conn {:keys [id name project-id is-shared data deleted-at] + :or {is-shared false + deleted-at nil} :as params}] (let [id (or id (:id data) (uuid/next)) data (or data (cp/make-file-data id)) @@ -61,7 +65,8 @@ :project-id project-id :name name :is-shared is-shared - :data (blob/encode data)})] + :data (blob/encode data) + :deleted-at deleted-at})] (->> (assoc params :file-id id :role :owner) (create-file-role conn)) (assoc file :data data))) @@ -118,6 +123,7 @@ [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) + (mark-file-deleted conn params))) (defn mark-file-deleted @@ -268,7 +274,8 @@ [{:keys [pool] :as cfg} {:keys [id profile-id] :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})] + (let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true + :uncheked true})] (files/check-edition-permissions! conn profile-id id) (update-file (assoc cfg :conn conn) (assoc params :file file))))) @@ -381,3 +388,24 @@ [conn project-id] (:team-id (db/get-by-id conn :project project-id {:columns [:team-id]}))) + +;; TEMPORARY FILE CREATION + +(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 ::make-permanent + (s/keys :req-un [::id ::profile-id])) + +(sv/defmethod ::make-permanent + [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] + (db/with-atomic [conn pool] + (files/check-edition-permissions! conn profile-id id) + (db/update! conn :file + {:deleted-at nil} + {:id id}))) diff --git a/frontend/src/app/util/zip.cljs b/frontend/src/app/util/zip.cljs index 071a7c456c..84a3759d27 100644 --- a/frontend/src/app/util/zip.cljs +++ b/frontend/src/app/util/zip.cljs @@ -10,7 +10,8 @@ ["jszip" :as zip] [app.common.data :as d] [beicon.core :as rx] - [promesa.core :as p])) + [promesa.core :as p] + [app.util.http :as http])) (defn compress-files [files] @@ -21,32 +22,44 @@ (->> (.generateAsync zobj #js {:type "blob"}) (rx/from))))) +(defn load-from-url + "Loads the data from a blob url" + [url] + (->> (http/send! + {:uri url + :response-type :blob + :method :get}) + (rx/map :body) + (rx/flat-map zip/loadAsync))) + +(defn- process-file [entry path] + (cond + (nil? entry) + (p/rejected "No file found") + + (.-dir entry) + (p/resolved {:dir path}) + + :else + (-> (.async entry "text") + (p/then #(hash-map :path path :content %))))) + +(defn get-file + "Gets a single file from the zip archive" + [zip path] + (-> (.file zip path) + (process-file path) + (rx/from))) + (defn extract-files "Creates a stream that will emit values for every file in the zip" - [file] - (rx/create - (fn [subs] - (let [process-entry - (fn [path entry] - (if (.-dir entry) - (rx/push! subs {:dir path}) - (p/then - (.async entry "text") - (fn [content] - (rx/push! subs - {:path path - :content content})))))] + [zip] + (let [promises (atom []) + get-file + (fn [path entry] + (let [current (process-file entry path)] + (swap! promises conj current)))] + (.forEach zip get-file) - (p/let [response (js/fetch file) - data (.blob response) - content (zip/loadAsync data)] - - (let [promises (atom [])] - (.forEach content - (fn [path entry] - (let [current (process-entry path entry)] - (swap! promises conj current)))) - - (p/then (p/all @promises) - #(rx/end! subs)))) - nil)))) + (->> (rx/from (p/all @promises)) + (rx/flat-map identity)))) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 1d1dcb1081..26ae4a89f6 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -10,16 +10,39 @@ [app.main.repo :as rp] [app.util.dom :as dom] [app.util.zip :as uz] + [app.util.json :as json] [app.worker.impl :as impl] [beicon.core :as rx])) +(defn create-manifest + "Creates a manifest entry for the given files" + [team-id files] + (letfn [(format-page [manifest page] + (-> manifest + (assoc (str (:id page)) + {:name (:name page)}))) + + (format-file [manifest file] + (let [name (:name file) + pages (->> (get-in file [:data :pages]) (mapv str)) + index (->> (get-in file [:data :pages-index]) (vals) + (reduce format-page {}))] + (-> manifest + (assoc (str (:id file)) + {:name name + :pages pages + :pagesIndex index}))))] + (let [manifest {:teamId (str team-id) + :files (->> (vals files) (reduce format-file {}))}] + (json/encode manifest)))) + (defn get-page-data - [{file-name :file-name {:keys [id name] :as data} :data}] + [{file-id :file-id {:keys [id name] :as data} :data}] (->> (r/render-page data) (rx/map (fn [markup] {:id id :name name - :file-name file-name + :file-id file-id :markup markup})))) (defn process-pages [file] @@ -27,30 +50,48 @@ pages-index (get-in file [:data :pages-index])] (->> pages (map #(hash-map - :file-name (:name file) + :file-id (:id file) :data (get pages-index %)))))) (defn collect-page - [coll {:keys [id file-name name markup] :as page}] - (conj coll [(str file-name "/" name ".svg") markup])) + [{:keys [id file-id markup] :as page}] + [(str file-id "/" id ".svg") markup]) (defmethod impl/handler :export-file - [{:keys [team-id files] :as message}] + [{:keys [team-id project-id files] :as message}] - (let [render-stream - (->> (rx/from (->> files (mapv :id))) + (let [files-ids (->> files (mapv :id)) + + files-stream + (->> (rx/from files-ids) (rx/merge-map #(rp/query :file {:id %})) + (rx/reduce #(assoc %1 (:id %2) %2) {}) + (rx/share)) + + manifest-stream + (->> files-stream + (rx/map #(create-manifest team-id %)) + (rx/map #(vector "manifest.json" %))) + + render-stream + (->> files-stream + (rx/flat-map vals) (rx/flat-map process-pages) (rx/observe-on :async) (rx/flat-map get-page-data) - (rx/share))] + (rx/share)) + + pages-stream + (->> render-stream + (rx/map collect-page))] (rx/merge (->> render-stream (rx/map #(hash-map :type :progress :data (str "Render " (:file-name %) " - " (:name %))))) - (->> render-stream - (rx/reduce collect-page []) + (->> (rx/merge pages-stream + manifest-stream) + (rx/reduce conj []) (rx/flat-map uz/compress-files) (rx/map #(hash-map :type :finish :data (dom/create-uri %))))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index dfe240b8e4..e2f1cd785b 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -13,6 +13,7 @@ [app.main.repo :as rp] [app.util.http :as http] [app.util.import.parser :as cip] + [app.util.json :as json] [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx] @@ -27,7 +28,7 @@ [project-id name] (let [file-id (uuid/next)] (rp/mutation - :create-file + :create-temp-file {:id file-id :name name :project-id project-id @@ -44,17 +45,20 @@ (partition change-batch-size change-batch-size nil) (mapv vec))] - (->> (rx/from changes-batches) - (rx/merge-map - (fn [cur-changes-batch] - (rp/mutation - :update-file - {:id file-id - :session-id session-id - :revn @revn - :changes cur-changes-batch}))) + (rx/concat + (->> (rx/from changes-batches) + (rx/mapcat + (fn [cur-changes-batch] + (rp/mutation + :update-file + {:id file-id + :session-id session-id + :revn @revn + :changes cur-changes-batch}))) - (rx/tap #(reset! revn (:revn %)))))) + (rx/tap #(reset! revn (:revn %)))) + + (rp/mutation :make-permanent {:id (:id file)})))) (defn upload-media-files "Upload a image to the backend and returns its id" @@ -72,17 +76,6 @@ :is-local true})) (rx/flat-map #(rp/mutation! :upload-file-media-object %)))) -(defn parse-file-name - [dir] - (if (str/ends-with? dir "/") - (subs dir 0 (dec (count dir))) - dir)) - -(defn parse-page-name - [path] - (let [[file page] (str/split path "/")] - (str/replace page ".svg" ""))) - (defn add-shape-file [file node] @@ -137,49 +130,50 @@ (rx/of node))) (defn import-page - [file {:keys [path content]}] - (let [page-name (parse-page-name path)] - (if (cip/valid? content) - (let [nodes (->> content cip/node-seq) - file-id (:id file)] - (->> (rx/from nodes) - (rx/filter cip/shape?) - (rx/mapcat (partial resolve-images file-id)) - (rx/reduce add-shape-file (fb/add-page file page-name)) - (rx/map fb/close-page))) - (rx/empty)))) + [file [page-name content]] + (if (cip/valid? content) + (let [nodes (->> content cip/node-seq) + file-id (:id file)] + (->> (rx/from nodes) + (rx/filter cip/shape?) + (rx/mapcat (partial resolve-images file-id)) + (rx/reduce add-shape-file (fb/add-page file page-name)) + (rx/map fb/close-page))) + (rx/empty))) + +(defn get-page-path [dir-id id] + (str dir-id "/" id ".svg")) + +(defn process-page [file-id zip [page-id page-name]] + (->> (uz/get-file zip (get-page-path (d/name file-id) page-id)) + (rx/map (comp tubax/xml->clj :content)) + (rx/map #(vector page-name %)))) + +(defn process-file + [file file-id file-desc zip] + (let [index (:pagesIndex file-desc) + pages (->> (:pages file-desc) + (mapv #(vector % (get-in index [(keyword %) :name]))))] + (->> (rx/from pages) + (rx/flat-map #(process-page file-id zip %)) + (merge-reduce import-page file) + (rx/flat-map send-changes) + (rx/ignore)))) (defmethod impl/handler :import-file [{:keys [project-id files]}] - (let [extract-stream - (->> (rx/from files) - (rx/merge-map uz/extract-files)) + (let [zip-str (->> (rx/from files) + (rx/flat-map uz/load-from-url) + (rx/share))] - dir-str - (->> extract-stream - (rx/filter #(contains? % :dir)) - (rx/map :dir)) - - file-str - (->> extract-stream - (rx/filter #(not (contains? % :dir))) - (rx/map #(d/update-when % :content tubax/xml->clj)))] - - (->> dir-str - (rx/merge-map #(create-file project-id (parse-file-name %))) - (rx/merge-map - (fn [file] - (rx/concat - (->> file-str - (rx/filter #(str/starts-with? (:path %) (:name file))) - (merge-reduce import-page file) - (rx/flat-map send-changes) - (rx/catch (fn [err] - (.error js/console "ERROR" err (clj->js (.-data err))) - - ;; We delete the file when there is an error - (rp/mutation! :delete-file {:id (:id file)}))) - (rx/ignore)) - - (rx/of (select-keys file [:id :name])))))))) + (->> zip-str + (rx/flat-map #(uz/get-file % "manifest.json")) + (rx/flat-map (comp :files json/decode :content)) + (rx/with-latest-from zip-str) + (rx/flat-map + (fn [[[file-id file-desc] zip]] + (->> (create-file project-id (:name file-desc)) + (rx/flat-map #(process-file % file-id file-desc zip)) + (rx/catch (fn [err] + (.error js/console "ERROR" err (clj->js (.-data err))))))))))) From e880d94f51eb19cfa4376cad25f5b61a099ec2a3 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 8 Jun 2021 12:23:50 +0200 Subject: [PATCH 077/204] :sparkles: Add import blend modes --- frontend/src/app/main/ui/shapes/attrs.cljs | 43 +++++++++++-------- frontend/src/app/main/ui/shapes/group.cljs | 38 ++++++++-------- frontend/src/app/main/ui/shapes/shape.cljs | 29 ++++++++----- .../src/app/main/ui/workspace/shapes.cljs | 23 +++++----- .../app/main/ui/workspace/shapes/group.cljs | 9 ++-- frontend/src/app/util/import/parser.cljc | 38 ++++++++++------ 6 files changed, 101 insertions(+), 79 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index 83f1debbe5..a70dd66b94 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -141,23 +141,28 @@ styles (-> svg-attrs (:style {}) (clj->js))] [attrs styles])) +(defn add-style-attrs + [props shape] + (let [render-id (mf/use-ctx muc/render-ctx) + svg-defs (:svg-defs shape {}) + svg-attrs (:svg-attrs shape {}) + + [svg-attrs svg-styles] (mf/use-memo + (mf/deps render-id svg-defs svg-attrs) + #(extract-svg-attrs render-id svg-defs svg-attrs)) + + styles (-> (obj/get props "style" (obj/new)) + (obj/merge! svg-styles) + (add-fill shape render-id) + (add-stroke shape render-id) + (add-layer-props shape))] + + (-> props + (obj/merge! svg-attrs) + (add-border-radius shape) + (obj/set! "style" styles)))) + (defn extract-style-attrs - ([shape] - (let [render-id (mf/use-ctx muc/render-ctx) - svg-defs (:svg-defs shape {}) - svg-attrs (:svg-attrs shape {}) - - [svg-attrs svg-styles] (mf/use-memo - (mf/deps render-id svg-defs svg-attrs) - #(extract-svg-attrs render-id svg-defs svg-attrs)) - - styles (-> (obj/new) - (obj/merge! svg-styles) - (add-fill shape render-id) - (add-stroke shape render-id) - (add-layer-props shape))] - - (-> (obj/new) - (obj/merge! svg-attrs) - (add-border-radius shape) - (obj/set! "style" styles))))) + [shape] + (-> (obj/new) + (add-style-attrs shape))) diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs index fd7d8552ff..9dc4ea0e9f 100644 --- a/frontend/src/app/main/ui/shapes/group.cljs +++ b/frontend/src/app/main/ui/shapes/group.cljs @@ -20,33 +20,29 @@ (let [frame (unchecked-get props "frame") shape (unchecked-get props "shape") childs (unchecked-get props "childs") - expand-mask (unchecked-get props "expand-mask") pointer-events (unchecked-get props "pointer-events") - {:keys [id x y width height]} shape + {:keys [id x y width height masked-group?]} shape - show-mask? (and (:masked-group? shape) (not expand-mask)) - mask (when show-mask? (first childs)) - childs (if show-mask? (rest childs) childs) + [mask childs] (if masked-group? + [(first childs) (rest childs)] + [nil childs]) - mask-props (when (and mask (not expand-mask)) - #js {:clipPath (clip-str mask) - :mask (mask-str mask)}) - mask-wrapper (if (and mask (not expand-mask)) - "g" - mf/Fragment) + [mask-wrapper mask-props] + (if masked-group? + ["g" (-> (obj/new) + (obj/set! "clipPath" (clip-str mask)) + (obj/set! "mask" (mask-str mask)))] + [mf/Fragment nil])] - props (-> (attrs/extract-style-attrs shape))] + [:> mask-wrapper mask-props + (when masked-group? + [:> render-mask #js {:frame frame :mask mask}]) - [:> :g (attrs/extract-style-attrs shape) - [:> mask-wrapper mask-props - (when mask - [:> render-mask #js {:frame frame :mask mask}]) - - (for [item childs] - [:& shape-wrapper {:frame frame - :shape item - :key (:id item)}])]])))) + (for [item childs] + [:& shape-wrapper {:frame frame + :shape item + :key (:id item)}])])))) diff --git a/frontend/src/app/main/ui/shapes/shape.cljs b/frontend/src/app/main/ui/shapes/shape.cljs index 4d3f4fc686..0b8d294774 100644 --- a/frontend/src/app/main/ui/shapes/shape.cljs +++ b/frontend/src/app/main/ui/shapes/shape.cljs @@ -9,11 +9,12 @@ [app.common.data :as d] [app.common.uuid :as uuid] [app.main.ui.context :as muc] + [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :as cs] + [app.main.ui.shapes.export :as ed] [app.main.ui.shapes.fill-image :as fim] [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.gradients :as grad] - [app.main.ui.shapes.export :as ed] [app.main.ui.shapes.svg-defs :as defs] [app.util.object :as obj] [rumext.alpha :as mf])) @@ -35,6 +36,7 @@ {:keys [x y width height type]} shape frame? (= :frame type) + group? (= :group type) wrapper-props (-> (obj/clone props) @@ -42,16 +44,23 @@ (obj/set! "ref" ref) (obj/set! "id" (str "shape-" (:id shape))) (obj/set! "filter" (filters/filter-str filter-id shape)) - (obj/set! "style" styles) + (obj/set! "style" styles)) - (cond-> frame? - (-> (obj/set! "x" x) - (obj/set! "y" y) - (obj/set! "width" width) - (obj/set! "height" height) - (obj/set! "xmlnsXlink" "http://www.w3.org/1999/xlink") - (obj/set! "xmlns" "http://www.w3.org/2000/svg") - (obj/set! "xmlns:penpot" "https://penpot.app/xmlns")))) + wrapper-props + (cond-> wrapper-props + frame? + (-> (obj/set! "x" x) + (obj/set! "y" y) + (obj/set! "width" width) + (obj/set! "height" height) + (obj/set! "xmlnsXlink" "http://www.w3.org/1999/xlink") + (obj/set! "xmlns" "http://www.w3.org/2000/svg") + (obj/set! "xmlns:penpot" "https://penpot.app/xmlns"))) + + wrapper-props + (cond-> wrapper-props + group? + (attrs/add-style-attrs shape)) wrapper-tag (if frame? "svg" "g")] diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index 48b6f00e86..b62038189d 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -83,20 +83,19 @@ (when (and shape (not (:hidden shape))) [:* (if-not svg-element? - [:g.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] + (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] - ;; Only used when drawing a new frame. - :frame [:> frame-wrapper {:shape shape}] + ;; Only used when drawing a new frame. + :frame [:> frame-wrapper {:shape shape}] - nil)] + nil) ;; Don't wrap svg elements inside a otherwise some can break [:> svg-raw-wrapper opts]) diff --git a/frontend/src/app/main/ui/workspace/shapes/group.cljs b/frontend/src/app/main/ui/workspace/shapes/group.cljs index db7ece44ae..8e7f22fb48 100644 --- a/frontend/src/app/main/ui/workspace/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/group.cljs @@ -42,9 +42,8 @@ childs (mf/deref childs-ref)] [:> shape-container {:shape shape} - [:g.group-shape - [:& group-shape - {:frame frame - :shape shape - :childs childs}]]])))) + [:& group-shape + {:frame frame + :shape shape + :childs childs}]])))) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 8fd551a6d4..fed82c64be 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -96,14 +96,15 @@ (defn get-svg-data [type node] - (if (search-data-node? type) - (let [data-tags #{:ellipse :rect :path :text :foreignObject :image}] - (->> node - (node-seq) - (filter #(contains? data-tags (:tag %))) - (map #(:attrs %)) - (reduce add-attrs {}))) - (:attrs node))) + (let [node-attrs (add-attrs {} (:attrs node))] + (if (search-data-node? type) + (let [data-tags #{:ellipse :rect :path :text :foreignObject :image}] + (->> node + (node-seq) + (filter #(contains? data-tags (:tag %))) + (map #(:attrs %)) + (reduce add-attrs node-attrs))) + node-attrs))) (def has-position? #{:frame :rect :image :text}) @@ -188,7 +189,7 @@ (parse-path svg-data))) (defn add-fill - [props type node svg-data] + [props node svg-data] (let [fill (:fill svg-data)] (cond-> props @@ -206,7 +207,7 @@ :fill-opacity (-> svg-data (:fill-opacity "1") d/parse-double))))) (defn add-stroke - [props type node svg-data] + [props node svg-data] (let [stroke-style (get-meta node :stroke-style keyword) stroke-alignment (get-meta node :stroke-alignment keyword) @@ -303,6 +304,18 @@ (not (empty? exports)) (assoc :exports exports)))) +(defn add-layer-options + [props svg-data] + (let [blend-mode (get svg-data :mix-blend-mode) + opacity (-> (get svg-data :opacity) d/parse-double)] + + (cond-> props + (some? blend-mode) + (assoc :blend-mode (keyword blend-mode)) + + (some? opacity) + (assoc :opacity opacity)))) + (defn get-image-name [node] (get-in node [:attrs :penpot:name])) @@ -325,8 +338,9 @@ (-> {} (add-position type node svg-data) - (add-fill type node svg-data) - (add-stroke type node svg-data) + (add-fill node svg-data) + (add-stroke node svg-data) + (add-layer-options svg-data) (add-shadows node) (add-blur node) (add-exports node) From 3aa5fda6954894ddfe40d0d857a6ddb293b9205c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 10 Jun 2021 15:45:03 +0200 Subject: [PATCH 078/204] :sparkles: Import pages with imported svgs --- backend/src/app/rpc/mutations/files.clj | 7 +- common/src/app/common/data.cljc | 48 ++++ common/src/app/common/file_builder.cljc | 101 +++++-- frontend/src/app/main/exports.cljs | 12 +- frontend/src/app/main/ui/shapes/export.cljs | 49 +++- .../src/app/main/ui/shapes/gradients.cljs | 14 +- .../app/main/ui/workspace/shapes/svg_raw.cljs | 16 +- frontend/src/app/util/import/parser.cljc | 250 +++++++++++++++--- frontend/src/app/worker/import.cljs | 24 +- 9 files changed, 422 insertions(+), 99 deletions(-) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 1093e9dc38..822c39d3dc 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -274,8 +274,7 @@ [{:keys [pool] :as cfg} {:keys [id profile-id] :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 - :uncheked true})] + (let [{:keys [id] :as file} (db/get-by-id conn :file id {:for-key-share true})] (files/check-edition-permissions! conn profile-id id) (update-file (assoc cfg :conn conn) (assoc params :file file))))) @@ -399,10 +398,10 @@ (proj/check-edition-permissions! conn profile-id project-id) (create-file conn (assoc params :deleted-at (dt/in-future {:days 1}))))) -(s/def ::make-permanent +(s/def ::persist-temp-file (s/keys :req-un [::id ::profile-id])) -(sv/defmethod ::make-permanent +(sv/defmethod ::persist-temp-file [{:keys [pool] :as cfg} {:keys [id profile-id] :as params}] (db/with-atomic [conn pool] (files/check-edition-permissions! conn profile-id id) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index e034f00e8d..acc7bc0633 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -503,3 +503,51 @@ (->> keys (reduce diff-attr {})))) + +(defn- extract-numeric-suffix + [basename] + (if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] + [p1 (+ 1 (parse-integer p2))] + [basename 1])) + +(defn unique-name + "A unique name generator" + ([basename used] + (unique-name basename used false)) + + ([basename used prefix-first?] + (assert (string? basename)) + (assert (set? used)) + + (let [[prefix initial] (extract-numeric-suffix basename)] + (if (and (not prefix-first?) + (not (contains? used basename))) + basename + (loop [counter initial] + (let [candidate (if (and (= 1 counter) prefix-first?) + (str prefix) + (str prefix "-" counter))] + (if (contains? used candidate) + (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 [[k v]] + (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))) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index b83653b111..64ae5dff46 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -13,7 +13,8 @@ [app.common.pages.init :as init] [app.common.pages.spec :as spec] [app.common.spec :as us] - [app.common.uuid :as uuid])) + [app.common.uuid :as uuid] + [cuerdas.core :as str])) (def root-frame uuid/zero) @@ -51,6 +52,27 @@ :parent-id parent-id :obj obj})))) +(defn generate-name + [type data] + (if (= type :svg-raw) + (let [tag (get-in data [:content :tag])] + (str "svg-" (cond (string? tag) tag + (keyword? tag) (d/name tag) + (nil? tag) "node" + :else (str tag)))) + (str/capital (d/name type)))) + +(defn check-name + "Given a tag returns its layer name" + [data file type] + + (cond-> data + (nil? (:name data)) + (assoc :name (generate-name type data)) + + :always + (update :name d/unique-name (:unames file)))) + ;; PUBLIC API (defn create-file @@ -82,14 +104,31 @@ (assoc :current-frame-id root-frame) ;; Current parent stack we'll be nesting - (assoc :parent-stack [root-frame])))) + (assoc :parent-stack [root-frame]) + + ;; Last object id added + (assoc :last-id nil) + + ;; Current used names + (assoc :unames #{})))) + +(defn close-page [file] + (-> file + (dissoc :current-page-id) + (dissoc :parent-stack) + (dissoc :last-id) + (dissoc :unames))) (defn add-artboard [file data] (let [obj (-> (init/make-minimal-shape :frame) - (merge data))] + (merge data) + (check-name file :frame) + (d/without-nils))] (-> file (commit-shape obj) (assoc :current-frame-id (:id obj)) + (assoc :last-id (:id obj)) + (update :unames conj (:name obj)) (update :parent-stack conj (:id obj))))) (defn close-artboard [file] @@ -102,9 +141,13 @@ selrect init/empty-selrect name (:name data) obj (-> (init/make-minimal-group frame-id selrect name) - (merge data))] + (merge data) + (check-name file :group) + (d/without-nils))] (-> file (commit-shape obj) + (assoc :last-id (:id obj)) + (update :unames conj (:name obj)) (update :parent-stack conj (:id obj))))) (defn close-group [file] @@ -115,13 +158,14 @@ points (gsh/rect->points selrect)] (-> file - (commit-change - {:type :mod-obj - :page-id (:current-page-id file) - :id group-id - :operations - [{:type :set :attr :selrect :val selrect} - {:type :set :attr :points :val points}]}) + (cond-> (not (empty? shapes)) + (commit-change + {:type :mod-obj + :page-id (:current-page-id file) + :id group-id + :operations + [{:type :set :attr :selrect :val selrect} + {:type :set :attr :points :val points}]})) (update :parent-stack pop)))) (defn create-shape [file type data] @@ -130,10 +174,14 @@ (lookup-shape file frame-id)) obj (-> (init/make-minimal-shape type) (merge data) - (d/without-nils) - (cond-> frame - (gsh/translate-from-frame frame)))] - (commit-shape file obj))) + (check-name file :type) + (d/without-nils)) + obj (cond-> obj + frame (gsh/translate-from-frame frame))] + (-> file + (commit-shape obj) + (assoc :last-id (:id obj)) + (update :unames conj (:name obj))))) (defn create-rect [file data] (create-shape file :rect data)) @@ -150,10 +198,27 @@ (defn create-image [file data] (create-shape file :image data)) -(defn close-page [file] +(declare close-svg-raw) + +(defn create-svg-raw [file data] + (let [file (as-> file $ + (create-shape $ :svg-raw data) + (update $ :parent-stack conj (:last-id $))) + + create-child + (fn [file child] + (-> file + (create-svg-raw (assoc data :content child)) + (close-svg-raw)))] + + ;; First :content is the the shape attribute, the other content is the + ;; XML children + (reduce create-child file (get-in data [:content :content])))) + +(defn close-svg-raw [file] (-> file - (dissoc :current-page-id) - (dissoc :parent-stack))) + (update :parent-stack pop))) + (defn generate-changes [file] diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index 760f2b5d46..2ee9d6db72 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -85,9 +85,15 @@ (mf/fnc svg-raw-wrapper [{:keys [shape frame] :as props}] (let [childs (mapv #(get objects %) (:shapes shape))] - [:& svg-raw-shape {:frame frame - :shape shape - :childs childs}])))) + (if (and (contains? shape :svg-attrs) (map? (:content shape))) + [:> shape-container {:shape shape} + [:& svg-raw-shape {:frame frame + :shape shape + :childs childs}]] + + [:& svg-raw-shape {:frame frame + :shape shape + :childs childs}]))))) (defn shape-wrapper-factory [objects] diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 924ddc7eba..196061efbd 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -10,8 +10,25 @@ [app.common.geom.matrix :as gmt] [app.util.json :as json] [app.util.object :as obj] + [app.util.svg :as usvg] + [cuerdas.core :as str] [rumext.alpha :as mf])) +(mf/defc render-xml + [{{:keys [tag attrs content] :as node} :xml}] + + (cond + (map? node) + [:> (d/name tag) (clj->js (usvg/clean-attrs attrs)) + (for [child content] + [:& render-xml {:xml child}])] + + (string? node) + node + + :else + nil)) + (defn add-data "Adds as metadata properties that we cannot deduce from the exported SVG" [props shape] @@ -22,7 +39,7 @@ (obj/set! ns-attr val)))) frame? (= :frame (:type shape)) group? (= :group (:type shape)) - rect? (= :text (:type shape)) + rect? (= :rect (:type shape)) text? (= :text (:type shape)) mask? (and group? (:masked-group? shape))] (-> props @@ -74,5 +91,33 @@ (for [{:keys [scale suffix type]} (:exports shape)] [:> "penpot:export" #js {:penpot:type (d/name type) :penpot:suffix suffix - :penpot:scale (str scale)}])])) + :penpot:scale (str scale)}]) + + (when (contains? shape :svg-attrs) + (let [svg-transform (get shape :svg-transform) + svg-attrs (->> shape :svg-attrs keys (mapv d/name) (str/join ",") ) + svg-defs (->> shape :svg-defs keys (mapv d/name) (str/join ","))] + [:> "penpot:svg-import" #js {:penpot:svg-attrs (when-not (empty? svg-attrs) svg-attrs) + :penpot:svg-defs (when-not (empty? svg-defs) svg-defs) + :penpot:svg-transform (when svg-transform (str svg-transform)) + :penpot:svg-viewbox-x (get-in shape [:svg-viewbox :x]) + :penpot:svg-viewbox-y (get-in shape [:svg-viewbox :y]) + :penpot:svg-viewbox-width (get-in shape [:svg-viewbox :width]) + :penpot:svg-viewbox-height (get-in shape [:svg-viewbox :height])} + (for [[def-id def-xml] (:svg-defs shape)] + [:> "penpot:svg-def" #js {:def-id def-id} + [:& render-xml {:xml def-xml}]])])) + + (when (= (:type shape) :svg-raw) + (let [props (-> (obj/new) + (obj/set! "penpot:x" (:x shape)) + (obj/set! "penpot:y" (:y shape)) + (obj/set! "penpot:width" (:width shape)) + (obj/set! "penpot:height" (:height shape)) + (obj/set! "penpot:tag" (-> (get-in shape [:content :tag]) d/name)) + (obj/merge! (-> (get-in shape [:content :attrs]) + (clj->js))))] + [:> "penpot:svg-content" props + (for [leaf (->> shape :content :content (filter string?))] + [:> "penpot:svg-child" {} leaf])]))])) diff --git a/frontend/src/app/main/ui/shapes/gradients.cljs b/frontend/src/app/main/ui/shapes/gradients.cljs index a6e091cbc4..4cee1d330d 100644 --- a/frontend/src/app/main/ui/shapes/gradients.cljs +++ b/frontend/src/app/main/ui/shapes/gradients.cljs @@ -18,12 +18,13 @@ (mf/defc linear-gradient [{:keys [id gradient shape]}] (let [{:keys [x y width height]} (:selrect shape) transform (when (= :path (:type shape)) (gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))] - [:linearGradient {:id id - :x1 (:start-x gradient) - :y1 (:start-y gradient) - :x2 (:end-x gradient) - :y2 (:end-y gradient) - :gradientTransform transform} + [:> :linearGradient #js {:id id + :x1 (:start-x gradient) + :y1 (:start-y gradient) + :x2 (:end-x gradient) + :y2 (:end-y gradient) + :gradientTransform transform + :penpot:gradient "true"} (for [{:keys [offset color opacity]} (:stops gradient)] [:stop {:key (str id "-stop-" offset) :offset (or offset 0) @@ -32,6 +33,7 @@ (defn add-metadata [props gradient] (-> props + (obj/set! "penpot:gradient" "true") (obj/set! "penpot:start-x" (:start-x gradient)) (obj/set! "penpot:start-x" (:start-x gradient)) (obj/set! "penpot:start-y" (:start-y gradient)) diff --git a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs index a07307048d..d2a34d3162 100644 --- a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs @@ -35,23 +35,13 @@ def-ctx? (mf/use-ctx muc/def-ctx)] - (cond - (and (svg-raw/graphic-element? tag) (not def-ctx?)) - [:> shape-container { :shape shape } - [:& svg-raw-shape - {:frame frame - :shape shape - :childs childs}]] - - ;; We cannot wrap inside groups the shapes that go inside the defs tag - ;; we use the context so we know when we should not render the container - (= tag :defs) - [:& (mf/provider muc/def-ctx) {:value true} + (if (or (= (get-in shape [:content :tag]) :svg) + (and (contains? shape :svg-attrs) (map? (:content shape)))) + [:> shape-container {:shape shape} [:& svg-raw-shape {:frame frame :shape shape :childs childs}]] - :else [:& svg-raw-shape {:frame frame :shape shape :childs childs}]))))) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index fed82c64be..e207ecdebb 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -29,8 +29,13 @@ (and (vector? node) (= ::close (first node)))) -(defn get-data [node] - (->> node :content (d/seek #(= :penpot:shape (:tag %))))) +(defn get-data + ([node] + (->> node :content (d/seek #(= :penpot:shape (:tag %))))) + ([node tag] + (->> (get-data node) + :content + (d/seek #(= tag (:tag %)))))) (defn get-type [node] @@ -65,9 +70,6 @@ [content] (->> content (tree-seq branch? get-children))) -(defn get-transform - [type node]) - (defn parse-style "Transform style list into a map" [style-str] @@ -97,13 +99,19 @@ [type node] (let [node-attrs (add-attrs {} (:attrs node))] - (if (search-data-node? type) + (cond + (search-data-node? type) (let [data-tags #{:ellipse :rect :path :text :foreignObject :image}] (->> node (node-seq) (filter #(contains? data-tags (:tag %))) (map #(:attrs %)) (reduce add-attrs node-attrs))) + + (= type :svg-raw) + (->> node :content last) + + :else node-attrs))) (def has-position? #{:frame :rect :image :text}) @@ -153,26 +161,42 @@ (defn parse-gradient [node ref-url] - (let [[_ url] (re-matches url-regex ref-url) + (let [[_ url] (re-find url-regex ref-url) gradient-node (->> node (node-seq) (seek-node url)) stops (parse-stops gradient-node)] - (cond-> {:stops stops} - (= :linearGradient (:tag gradient-node)) - (assoc :type :linear - :start-x (-> gradient-node :attrs :x1 d/parse-double) - :start-y (-> gradient-node :attrs :y1 d/parse-double) - :end-x (-> gradient-node :attrs :x2 d/parse-double) - :end-y (-> gradient-node :attrs :y2 d/parse-double) - :width 1) + (when (contains? (:attrs gradient-node) :penpot:gradient) + (cond-> {:stops stops} + (= :linearGradient (:tag gradient-node)) + (assoc :type :linear + :start-x (-> gradient-node :attrs :x1 d/parse-double) + :start-y (-> gradient-node :attrs :y1 d/parse-double) + :end-x (-> gradient-node :attrs :x2 d/parse-double) + :end-y (-> gradient-node :attrs :y2 d/parse-double) + :width 1) - (= :radialGradient (:tag gradient-node)) - (assoc :type :radial - :start-x (get-meta gradient-node :start-x d/parse-double) - :start-y (get-meta gradient-node :start-y d/parse-double) - :end-x (get-meta gradient-node :end-x d/parse-double) - :end-y (get-meta gradient-node :end-y d/parse-double) - :width (get-meta gradient-node :width d/parse-double))))) + (= :radialGradient (:tag gradient-node)) + (assoc :type :radial + :start-x (get-meta gradient-node :start-x d/parse-double) + :start-y (get-meta gradient-node :start-y d/parse-double) + :end-x (get-meta gradient-node :end-x d/parse-double) + :end-y (get-meta gradient-node :end-y d/parse-double) + :width (get-meta gradient-node :width d/parse-double)))))) + +(defn add-svg-position [props node] + (let [svg-content (get-data node :penpot:svg-content)] + (cond-> props + (contains? (:attrs svg-content) :penpot:x) + (assoc :x (-> svg-content :attrs :penpot:x d/parse-double)) + + (contains? (:attrs svg-content) :penpot:y) + (assoc :y (-> svg-content :attrs :penpot:y d/parse-double)) + + (contains? (:attrs svg-content) :penpot:width) + (assoc :width (-> svg-content :attrs :penpot:width d/parse-double)) + + (contains? (:attrs svg-content) :penpot:height) + (assoc :height (-> svg-content :attrs :penpot:height d/parse-double))))) (defn add-position [props type node svg-data] @@ -181,6 +205,10 @@ (-> (parse-position svg-data) (gsh/setup-selrect)) + (= type :svg-raw) + (-> (add-svg-position node) + (gsh/setup-selrect)) + (= type :circle) (-> (parse-circle svg-data) (gsh/setup-selrect)) @@ -191,14 +219,16 @@ (defn add-fill [props node svg-data] - (let [fill (:fill svg-data)] + (let [fill (:fill svg-data) + gradient (when (str/starts-with? fill "url") + (parse-gradient node fill))] (cond-> props - (= fill "none") + :always (assoc :fill-color nil :fill-opacity nil) - (str/starts-with? fill "url") - (assoc :fill-color-gradient (parse-gradient node fill) + (some? gradient) + (assoc :fill-color-gradient gradient :fill-color nil :fill-opacity nil) @@ -211,24 +241,44 @@ (let [stroke-style (get-meta node :stroke-style keyword) stroke-alignment (get-meta node :stroke-alignment keyword) - stroke (:stroke svg-data)] + stroke (:stroke svg-data) + gradient (when (str/starts-with? stroke "url") + (parse-gradient node stroke))] (cond-> props :always (assoc :stroke-alignment stroke-alignment :stroke-style stroke-style - :stroke-color (-> svg-data (:stroke "#000000")) - :stroke-opacity (-> svg-data (:stroke-opacity "1") d/parse-double) - :stroke-width (-> svg-data (:stroke-width "0") d/parse-double)) + :stroke-color (-> svg-data :stroke) + :stroke-opacity (-> svg-data :stroke-opacity d/parse-double) + :stroke-width (-> svg-data :stroke-width d/parse-double)) - (str/starts-with? stroke "url") - (assoc :stroke-color-gradient (parse-gradient node stroke) + (some? gradient) + (assoc :stroke-color-gradient gradient :stroke-color nil :stroke-opacity nil) (= stroke-alignment :inner) (update :stroke-width / 2)))) +(defn add-rect-data + [props node svg-data] + (let [r1 (get-meta node :r1 d/parse-double) + r2 (get-meta node :r2 d/parse-double) + r3 (get-meta node :r3 d/parse-double) + r4 (get-meta node :r4 d/parse-double) + + rx (-> (get svg-data :rx) d/parse-double) + ry (-> (get svg-data :ry) d/parse-double)] + + (cond-> props + (some? r1) + (assoc :r1 r1 :r2 r2 :r3 r3 :r4 r4 + :rx nil :ry nil) + + (and (nil? r1) (some? rx)) + (assoc :rx rx :ry ry)))) + (defn add-image-data [props node] (-> props @@ -276,12 +326,16 @@ :suffix (get-meta node :suffix) :scale (get-meta node :scale d/parse-double)}) -(defn extract-from-data [node tag parse-fn] - (let [shape-data (get-data node)] - (->> shape-data - (node-seq) - (filter #(= (:tag %) tag)) - (mapv parse-fn)))) +(defn extract-from-data + ([node tag] + (extract-from-data node tag identity)) + + ([node tag parse-fn] + (let [shape-data (get-data node)] + (->> shape-data + (node-seq) + (filter #(= (:tag %) tag)) + (mapv parse-fn))))) (defn add-shadows [props node] @@ -313,8 +367,111 @@ (some? blend-mode) (assoc :blend-mode (keyword blend-mode)) - (some? opacity) - (assoc :opacity opacity)))) + (some? opacity) + (assoc :opacity opacity)))) + +(defn remove-prefix [s] + (cond-> s + (string? s) + (str/replace #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}-" ""))) + +(defn get-svg-attrs + [svg-data svg-attrs] + (let [assoc-key + (fn [acc prop] + (let [key (keyword prop)] + (if-let [v (or (get svg-data key) + (get-in svg-data [:attrs key]))] + (assoc acc key (remove-prefix v)) + acc)))] + + (->> (str/split svg-attrs ",") + (reduce assoc-key {})))) + +(defn get-svg-defs + [node svg-defs] + + (let [svg-import (get-data node :penpot:svg-import)] + (->> svg-import + :content + (filter #(= (:tag %) :penpot:svg-def)) + (map #(vector (-> % :attrs :def-id) + (-> % :content first))) + (into {})))) + +(defn add-svg-attrs + [props node svg-data] + + (let [svg-import (get-data node :penpot:svg-import)] + (if (some? svg-import) + (let [svg-attrs (get-in svg-import [:attrs :penpot:svg-attrs]) + svg-defs (get-in svg-import [:attrs :penpot:svg-defs]) + svg-transform (get-in svg-import [:attrs :penpot:svg-transform]) + viewbox-x (get-in svg-import [:attrs :penpot:svg-viewbox-x]) + viewbox-y (get-in svg-import [:attrs :penpot:svg-viewbox-y]) + viewbox-width (get-in svg-import [:attrs :penpot:svg-viewbox-width]) + viewbox-height (get-in svg-import [:attrs :penpot:svg-viewbox-height])] + + (cond-> props + :true + (assoc :svg-attrs (get-svg-attrs svg-data svg-attrs)) + + (some? viewbox-x) + (assoc :svg-viewbox {:x (d/parse-double viewbox-x) + :y (d/parse-double viewbox-y) + :width (d/parse-double viewbox-width) + :height (d/parse-double viewbox-height)}) + + (some? svg-transform) + (assoc :svg-transform (gmt/str->matrix svg-transform)) + + + (some? svg-defs) + (assoc :svg-defs (get-svg-defs node svg-defs)))) + + props))) + +(defn without-penpot-prefix + [m] + (let [no-penpot-prefix? + (fn [[k v]] + (not (str/starts-with? (d/name k) "penpot:")))] + (into {} (filter no-penpot-prefix?) m))) + +(defn camelize [[k v]] + [(-> k d/name str/camel keyword) v]) + +(defn camelize-keys + [m] + (assert (map? m) (str m)) + + (into {} (map camelize) m)) + +(defn fix-style-attr + [m] + (let [fix-style + (fn [[k v]] + (if (= k :style) + [k (-> v parse-style camelize-keys)] + [k v]))] + + (d/deep-mapm (comp camelize fix-style) m))) + +(defn add-svg-content + [props node] + (let [svg-content (get-data node :penpot:svg-content) + attrs (-> (:attrs svg-content) (without-penpot-prefix)) + tag (-> svg-content :attrs :penpot:tag keyword) + content {:attrs attrs + :tag tag + :content (cond + (= tag :svg) + (->> node :content last :content last :content fix-style-attr) + + (= tag :text) + (-> node :content last :content))}] + (-> props + (assoc :content content)))) (defn get-image-name [node] @@ -337,6 +494,9 @@ svg-data (get-svg-data type node)] (-> {} + (assoc :name name) + (assoc :blocked blocked) + (assoc :hidden hidden) (add-position type node svg-data) (add-fill node svg-data) (add-stroke node svg-data) @@ -344,13 +504,17 @@ (add-shadows node) (add-blur node) (add-exports node) - (assoc :name name) - (assoc :blocked blocked) - (assoc :hidden hidden) + (add-svg-attrs node svg-data) + + (cond-> (= :svg-raw type) + (add-svg-content node)) (cond-> (= :group type) (add-group-data node)) + (cond-> (= :rect type) + (add-rect-data node svg-data)) + (cond-> (= :image type) (add-image-data node)) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index e2f1cd785b..6a1c338a9c 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -48,17 +48,16 @@ (rx/concat (->> (rx/from changes-batches) (rx/mapcat - (fn [cur-changes-batch] - (rp/mutation - :update-file - {:id file-id - :session-id session-id - :revn @revn - :changes cur-changes-batch}))) - + #(rp/mutation + :update-file + {:id file-id + :session-id session-id + :revn @revn + :changes %})) + (rx/map first) (rx/tap #(reset! revn (:revn %)))) - (rp/mutation :make-permanent {:id (:id file)})))) + (rp/mutation :persist-temp-file {:id (:id file)})))) (defn upload-media-files "Upload a image to the backend and returns its id" @@ -91,6 +90,9 @@ :group (fb/close-group file) + :svg-raw + (fb/close-svg-raw file) + ;; default file) @@ -102,6 +104,7 @@ :path (fb/create-path file data) :text (fb/create-text file data) :image (fb/create-image file data) + :svg-raw (fb/create-svg-raw file data) ;; default file)))) @@ -127,7 +130,8 @@ (assoc-in [:attrs :penpot:media-mtype] (:mtype media))))))) ;; If the node is not an image just return the node - (rx/of node))) + (->> (rx/of node) + (rx/observe-on :async)))) (defn import-page [file [page-name content]] From dd15bf732805c52c04364fad9196555c8beb40e4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 11 Jun 2021 15:47:51 +0200 Subject: [PATCH 079/204] :sparkles: Adds flip,proportion and rotation --- common/src/app/common/geom/shapes/path.cljc | 3 +- frontend/src/app/main/ui/shapes/export.cljs | 76 +++++++----- frontend/src/app/util/import/parser.cljc | 129 +++++++++++++------- 3 files changed, 130 insertions(+), 78 deletions(-) diff --git a/common/src/app/common/geom/shapes/path.cljc b/common/src/app/common/geom/shapes/path.cljc index ae2d1d6f3a..ff39fa7db1 100644 --- a/common/src/app/common/geom/shapes/path.cljc +++ b/common/src/app/common/geom/shapes/path.cljc @@ -156,7 +156,8 @@ (mapv #(update % :params transform-params) content))) -(defn transform-content [content transform] +(defn transform-content + [content transform] (let [set-tr (fn [params px py] (let [tr-point (-> (gpt/point (get params px) (get params py)) (gpt/transform transform))] diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 196061efbd..6cda48b11c 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -8,6 +8,7 @@ (:require [app.common.data :as d] [app.common.geom.matrix :as gmt] + [app.common.geom.shapes :as gsh] [app.util.json :as json] [app.util.object :as obj] [app.util.svg :as usvg] @@ -29,43 +30,58 @@ :else nil)) +(defn bool->str [val] + (when (some? val) (str val))) + (defn add-data "Adds as metadata properties that we cannot deduce from the exported SVG" [props shape] - (let [add! - (fn [props attr val] - (let [ns-attr (str "penpot:" (-> attr d/name))] - (-> props - (obj/set! ns-attr val)))) - frame? (= :frame (:type shape)) - group? (= :group (:type shape)) - rect? (= :rect (:type shape)) - text? (= :text (:type shape)) - mask? (and group? (:masked-group? shape))] - (-> props - (add! :name (-> shape :name)) - (add! :blocked (-> shape (:blocked false) str)) - (add! :hidden (-> shape (:hidden false) str)) - (add! :type (-> shape :type d/name)) + (letfn [(add! + ([props attr] + (add! props attr str)) - (add! :stroke-style (-> shape (:stroke-style :none) d/name)) - (add! :stroke-alignment (-> shape (:stroke-alignment :center) d/name)) + ([props attr trfn] + (let [val (get shape attr) + val (if (keyword? val) (d/name val) val) + ns-attr (str "penpot:" (-> attr d/name))] + (cond-> props + (some? val) + (obj/set! ns-attr (trfn val))))))] + (let [frame? (= :frame (:type shape)) + group? (= :group (:type shape)) + rect? (= :rect (:type shape)) + text? (= :text (:type shape)) + mask? (and group? (:masked-group? shape)) + center (gsh/center-shape shape)] + (-> props + (add! :name) + (add! :blocked) + (add! :hidden) + (add! :type) + (add! :stroke-style) + (add! :stroke-alignment) + (add! :transform) + (add! :transform-inverse) + (add! :flip-x) + (add! :flip-y) + (add! :proportion) + (add! :proportion-lock) + (add! :rotation) + (obj/set! "penpot:center-x" (-> center :x str)) + (obj/set! "penpot:center-y" (-> center :y str)) - (add! :transform (-> shape (:transform (gmt/matrix)) str)) - (add! :transform-inverse (-> shape (:transform-inverse (gmt/matrix)) str)) + (cond-> (and rect? (some? (:r1 shape))) + (-> (add! :r1) + (add! :r2) + (add! :r3) + (add! :r4))) - (cond-> (and rect? (some? (:r1 shape))) - (-> (add! :r1 (-> shape (:r1 0) str)) - (add! :r2 (-> shape (:r2 0) str)) - (add! :r3 (-> shape (:r3 0) str)) - (add! :r4 (-> shape (:r4 0) str)))) + (cond-> text? + (-> (add! :grow-type) + (add! :content json/encode))) - (cond-> text? - (-> (add! :grow-type (-> shape :grow-type)) - (add! :content (-> shape :content json/encode)))) - - (cond-> mask? - (add! :masked-group "true"))))) + (cond-> mask? + (obj/set! "penpot:masked-group" "true")))))) (mf/defc export-data [{:keys [shape]}] diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index e207ecdebb..97f8113214 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -9,6 +9,7 @@ [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] + [app.common.geom.point :as gpt] [app.common.uuid :as uuid] [app.util.color :as uc] [app.util.json :as json] @@ -50,6 +51,10 @@ (or (close? node) (some? (get-data node)))) +(defn str->bool + [val] + (when (some? val) (= val "true"))) + (defn get-meta ([m att] (get-meta m att identity)) @@ -133,16 +138,32 @@ :height (* (:ry values) 2)})) (defn parse-path - [props svg-data] - (let [content (upp/parse-path (:d svg-data)) - selrect (gsh/content->selrect content) - points (gsh/rect->points selrect)] - + [props center svg-data] + (let [transform-inverse (:transform-inverse props (gmt/matrix)) + transform (:transform props (gmt/matrix)) + content (upp/parse-path (:d svg-data)) + content-tr (gsh/transform-content + content + (gmt/transform-in center transform-inverse)) + selrect (gsh/content->selrect content-tr) + points (-> (gsh/rect->points selrect) + (gsh/transform-points center transform))] (-> props (assoc :content content) (assoc :selrect selrect) (assoc :points points)))) +(defn setup-selrect [props] + (let [data (select-keys props [:x :y :width :height]) + transform (:transform props (gmt/matrix)) + selrect (gsh/rect->selrect data) + points (gsh/rect->points data) + center (gsh/center-rect data)] + + (assoc props + :selrect selrect + :points (gsh/transform-points points center transform)))) + (def url-regex #"url\(#([^\)]*)\)") (defn seek-node @@ -198,23 +219,52 @@ (contains? (:attrs svg-content) :penpot:height) (assoc :height (-> svg-content :attrs :penpot:height d/parse-double))))) +(defn add-common-data + [props node] + + (let [name (get-meta node :name) + blocked (get-meta node :blocked str->bool) + hidden (get-meta node :hidden str->bool) + transform (get-meta node :transform gmt/str->matrix) + transform-inverse (get-meta node :transform-inverse gmt/str->matrix) + flip-x (get-meta node :flip-x str->bool) + flip-y (get-meta node :flip-y str->bool) + proportion (get-meta node :proportion d/parse-double) + proportion-lock (get-meta node :proportion-lock str->bool) + rotation (get-meta node :rotation d/parse-double)] + + (-> props + (assoc :name name) + (assoc :blocked blocked) + (assoc :hidden hidden) + (assoc :transform transform) + (assoc :transform-inverse transform-inverse) + (assoc :flip-x flip-x) + (assoc :flip-y flip-y) + (assoc :proportion proportion) + (assoc :proportion-lock proportion-lock) + (assoc :rotation rotation)))) + (defn add-position [props type node svg-data] - (cond-> props - (has-position? type) - (-> (parse-position svg-data) - (gsh/setup-selrect)) + (let [center-x (get-meta node :center-x d/parse-double) + center-y (get-meta node :center-y d/parse-double) + center (gpt/point center-x center-y)] + (cond-> props + (has-position? type) + (parse-position svg-data) - (= type :svg-raw) - (-> (add-svg-position node) - (gsh/setup-selrect)) + (= type :svg-raw) + (add-svg-position node) - (= type :circle) - (-> (parse-circle svg-data) - (gsh/setup-selrect)) + (= type :circle) + (parse-circle svg-data) - (= type :path) - (parse-path svg-data))) + (= type :path) + (parse-path center svg-data) + + (or (has-position? type) (= type :svg-raw) (= type :circle)) + (setup-selrect)))) (defn add-fill [props node svg-data] @@ -293,10 +343,6 @@ (assoc :grow-type (get-meta node :grow-type keyword)) (assoc :content (get-meta node :content json/decode)))) -(defn str->bool - [val] - (= val "true")) - (defn add-group-data [props node] (let [mask? (get-meta node :masked-group str->bool)] @@ -462,16 +508,19 @@ (let [svg-content (get-data node :penpot:svg-content) attrs (-> (:attrs svg-content) (without-penpot-prefix)) tag (-> svg-content :attrs :penpot:tag keyword) - content {:attrs attrs - :tag tag - :content (cond - (= tag :svg) - (->> node :content last :content last :content fix-style-attr) - (= tag :text) - (-> node :content last :content))}] - (-> props - (assoc :content content)))) + node-content + (cond + (= tag :svg) + (->> node :content last :content last :content fix-style-attr) + + (= tag :text) + (-> node :content last :content))] + (assoc + props :content + {:attrs attrs + :tag tag + :content node-content}))) (defn get-image-name [node] @@ -486,17 +535,9 @@ [type node] (when-not (close? node) - (let [name (get-meta node :name) - blocked (get-meta node :blocked str->bool) - hidden (get-meta node :hidden str->bool) - transform (get-meta node :transform gmt/str->matrix) - transform-inverse (get-meta node :transform-inverse gmt/str->matrix) - svg-data (get-svg-data type node)] - + (let [svg-data (get-svg-data type node)] (-> {} - (assoc :name name) - (assoc :blocked blocked) - (assoc :hidden hidden) + (add-common-data node) (add-position type node svg-data) (add-fill node svg-data) (add-stroke node svg-data) @@ -519,10 +560,4 @@ (add-image-data node)) (cond-> (= :text type) - (add-text-data node)) - - (cond-> (some? transform) - (assoc :transform transform)) - - (cond-> (some? transform-inverse) - (assoc :transform-inverse transform-inverse)))))) + (add-text-data node)))))) From 0c97a44a2aff5c36b1a8e041a5bdb7cb21b36730 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 14 Jun 2021 11:50:26 +0200 Subject: [PATCH 080/204] :tada: Add file offloading to external storage mechanism. --- CHANGES.md | 2 + backend/src/app/config.clj | 35 +++++--- backend/src/app/http/assets.clj | 2 +- backend/src/app/main.clj | 44 +++++++--- backend/src/app/metrics.clj | 10 ++- backend/src/app/migrations.clj | 3 + .../migrations/sql/0061-mod-file-table.sql | 10 +++ backend/src/app/rpc/mutations/files.clj | 21 +++-- backend/src/app/rpc/queries/files.clj | 80 +++++++++++++------ backend/src/app/rpc/queries/viewer.clj | 5 +- backend/src/app/storage.clj | 67 ++++++++-------- backend/src/app/storage/db.clj | 5 ++ backend/src/app/storage/fs.clj | 4 + backend/src/app/storage/impl.clj | 42 ++++++++-- backend/src/app/storage/s3.clj | 63 ++++++++++----- backend/src/app/tasks/file_offload.clj | 64 +++++++++++++++ backend/test/app/storage_test.clj | 1 - .../app/main/data/workspace/libraries.cljs | 1 + 18 files changed, 334 insertions(+), 125 deletions(-) create mode 100644 backend/src/app/migrations/sql/0061-mod-file-table.sql create mode 100644 backend/src/app/tasks/file_offload.clj diff --git a/CHANGES.md b/CHANGES.md index acebd9e0d5..7459428f52 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ - Allow to ungroup assets [Taiga #1719](https://tree.taiga.io/project/penpot/us/1719) - Allow to rename assets groups [Taiga #1721](https://tree.taiga.io/project/penpot/us/1721) - Memorize collapse state of assets in panel [Taiga #1718](https://tree.taiga.io/project/penpot/us/1718) +- Add the ability to offload file data to a cheaper storage when file + becomes inactive. ### :bug: Bugs fixed ### :arrow_up: Deps updates diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 30ca83d38b..f7c587d487 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -58,11 +58,8 @@ :srepl-host "127.0.0.1" :srepl-port 6062 - :storage-backend :fs - - :storage-fs-directory "assets" - :storage-s3-region :eu-central-1 - :storage-s3-bucket "penpot-devenv-assets-pre" + :assets-storage-backend :fs + :storage-assets-fs-directory "assets" :feedback-destination "info@example.com" :feedback-enabled false @@ -175,10 +172,14 @@ (s/def ::smtp-username (s/nilable ::us/string)) (s/def ::srepl-host ::us/string) (s/def ::srepl-port ::us/integer) -(s/def ::storage-backend ::us/keyword) -(s/def ::storage-fs-directory ::us/string) -(s/def ::storage-s3-bucket ::us/string) -(s/def ::storage-s3-region ::us/keyword) +(s/def ::assets-storage-backend ::us/keyword) +(s/def ::fdata-storage-backend ::us/keyword) +(s/def ::storage-assets-fs-directory ::us/string) +(s/def ::storage-assets-s3-bucket ::us/string) +(s/def ::storage-assets-s3-region ::us/keyword) +(s/def ::storage-fdata-s3-bucket ::us/string) +(s/def ::storage-fdata-s3-region ::us/keyword) +(s/def ::storage-fdata-s3-prefix ::us/string) (s/def ::telemetry-enabled ::us/boolean) (s/def ::telemetry-uri ::us/string) (s/def ::telemetry-with-taiga ::us/boolean) @@ -257,12 +258,20 @@ ::smtp-ssl ::smtp-tls ::smtp-username + ::srepl-host ::srepl-port - ::storage-backend - ::storage-fs-directory - ::storage-s3-bucket - ::storage-s3-region + + ::assets-storage-backend + ::storage-assets-fs-directory + ::storage-assets-s3-bucket + ::storage-assets-s3-region + + ::fdata-storage-backend + ::storage-fdata-s3-bucket + ::storage-fdata-s3-region + ::storage-fdata-s3-prefix + ::telemetry-enabled ::telemetry-uri ::telemetry-referer diff --git a/backend/src/app/http/assets.clj b/backend/src/app/http/assets.clj index 678c4bdddd..18c14462e9 100644 --- a/backend/src/app/http/assets.clj +++ b/backend/src/app/http/assets.clj @@ -49,7 +49,7 @@ {:status 200 :headers {"content-type" (:content-type mdata) "cache-control" (str "max-age=" (inst-ms cache-max-age))} - :body (sto/get-object-data storage obj)} + :body (sto/get-object-bytes storage obj)} :s3 (let [url (sto/get-object-url storage obj {:max-age signature-max-age})] diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 218f318e9f..a7593ee4d1 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -166,7 +166,7 @@ :tasks (ig/ref :app.worker/registry) :pool (ig/ref :app.db/pool) :schedule - [{:cron #app/cron "0 0 0 * * ? *" ;; daily + [{:cron #app/cron "0 0 0 * * ?" ;; daily :task :file-media-gc} {:cron #app/cron "0 0 * * * ?" ;; hourly @@ -190,6 +190,10 @@ {:cron #app/cron "0 0 0 * * ?" ;; daily :task :tasks-gc} + (when (cf/get :fdata-storage-backed) + {:cron #app/cron "0 0 * * * ?" ;; hourly + :task :file-offload}) + (when (cf/get :audit-archive-enabled) {:cron #app/cron "0 0 * * * ?" ;; every 1h :task :audit-archive}) @@ -217,6 +221,7 @@ :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) + :file-offload (ig/ref :app.tasks.file-offload/handler) :audit-archive (ig/ref :app.loggers.audit/archive-task) :audit-archive-gc (ig/ref :app.loggers.audit/archive-gc-task)}} @@ -256,6 +261,12 @@ {:pool (ig/ref :app.db/pool) :max-age (dt/duration {:hours 72})} + :app.tasks.file-offload/handler + {:pool (ig/ref :app.db/pool) + :max-age (dt/duration {:seconds 5}) + :storage (ig/ref :app.storage/storage) + :backend (cf/get :fdata-storage-backed :fdata-s3)} + :app.tasks.telemetry/handler {:pool (ig/ref :app.db/pool) :version (:full cf/version) @@ -306,23 +317,32 @@ :app.storage/storage {:pool (ig/ref :app.db/pool) :executor (ig/ref :app.worker/executor) - :backend (cf/get :storage-backend :fs) - :backends {:s3 (ig/ref [::main :app.storage.s3/backend]) - :db (ig/ref [::main :app.storage.db/backend]) - :fs (ig/ref [::main :app.storage.fs/backend]) - :tmp (ig/ref [::tmp :app.storage.fs/backend])}} + :backend (cf/get :assets-storage-backend :assets-fs) + :backends {:assets-s3 (ig/ref [::assets :app.storage.s3/backend]) + :assets-db (ig/ref [::assets :app.storage.db/backend]) + :assets-fs (ig/ref [::assets :app.storage.fs/backend]) + :s3 (ig/ref [::assets :app.storage.s3/backend]) + :db (ig/ref [::assets :app.storage.db/backend]) + :fs (ig/ref [::assets :app.storage.fs/backend]) + :tmp (ig/ref [::tmp :app.storage.fs/backend]) + :fdata-s3 (ig/ref [::fdata :app.storage.s3/backend])}} - [::main :app.storage.s3/backend] - {:region (cf/get :storage-s3-region) - :bucket (cf/get :storage-s3-bucket)} + [::fdata :app.storage.s3/backend] + {:region (cf/get :storage-fdata-s3-region) + :bucket (cf/get :storage-fdata-s3-bucket) + :prefix (cf/get :storage-fdata-s3-prefix)} - [::main :app.storage.fs/backend] - {:directory (cf/get :storage-fs-directory)} + [::assets :app.storage.s3/backend] + {:region (cf/get :storage-assets-s3-region) + :bucket (cf/get :storage-assets-s3-bucket)} + + [::assets :app.storage.fs/backend] + {:directory (cf/get :storage-assets-fs-directory)} [::tmp :app.storage.fs/backend] {:directory "/tmp/penpot"} - [::main :app.storage.db/backend] + [::assets :app.storage.db/backend] {:pool (ig/ref :app.db/pool)}}) (def system nil) diff --git a/backend/src/app/metrics.clj b/backend/src/app/metrics.clj index 52567eb821..74fb318ac9 100644 --- a/backend/src/app/metrics.clj +++ b/backend/src/app/metrics.clj @@ -210,9 +210,15 @@ ([a b] (mobj :inc) (origf a b)) - ([a b & more] + ([a b c] (mobj :inc) - (apply origf a b more))) + (origf a b c)) + ([a b c d] + (mobj :inc) + (origf a b c d)) + ([a b c d & more] + (mobj :inc) + (apply origf a b c d more))) (assoc mdata ::original origf)))) ([rootf mobj labels] (let [mdata (meta rootf) diff --git a/backend/src/app/migrations.clj b/backend/src/app/migrations.clj index 070f6cae81..c0f62cf3f1 100644 --- a/backend/src/app/migrations.clj +++ b/backend/src/app/migrations.clj @@ -190,6 +190,9 @@ {:name "0060-mod-file-change-table" :fn (mg/resource "app/migrations/sql/0060-mod-file-change-table.sql")} + + {:name "0061-mod-file-table" + :fn (mg/resource "app/migrations/sql/0061-mod-file-table.sql")} ]) diff --git a/backend/src/app/migrations/sql/0061-mod-file-table.sql b/backend/src/app/migrations/sql/0061-mod-file-table.sql new file mode 100644 index 0000000000..8be8c3bc14 --- /dev/null +++ b/backend/src/app/migrations/sql/0061-mod-file-table.sql @@ -0,0 +1,10 @@ +CREATE INDEX IF NOT EXISTS file__modified_at__with__data__idx + ON file (modified_at, id) + WHERE data IS NOT NULL; + +ALTER TABLE file + ADD COLUMN data_backend text NULL, +ALTER COLUMN data_backend SET STORAGE EXTERNAL; + +DROP TRIGGER file_on_update_tgr ON file; +DROP FUNCTION handle_file_update (); diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 822c39d3dc..a08da44f8b 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -22,6 +22,8 @@ [clojure.spec.alpha :as s])) +(declare create-file) + ;; --- Helpers & Specs (s/def ::id ::us/uuid) @@ -32,8 +34,6 @@ ;; --- Mutation: Create File -(declare create-file) - (s/def ::is-shared ::us/boolean) (s/def ::create-file (s/keys :req-un [::profile-id ::name ::project-id] @@ -67,10 +67,11 @@ :is-shared is-shared :data (blob/encode data) :deleted-at deleted-at})] + (->> (assoc params :file-id id :role :owner) (create-file-role conn)) - (assoc file :data data))) + (assoc file :data data))) ;; --- Mutation: Rename File @@ -202,7 +203,6 @@ {:file-id file-id :library-file-id library-id})) - ;; --- Mutation: Ignore updates in linked files (declare ignore-sync) @@ -303,7 +303,8 @@ (mapcat :changes changes-with-metadata) changes) - file (-> file + ts (dt/now) + file (-> (files/retrieve-data cfg file) (update :revn inc) (update :data (fn [data] (-> data @@ -317,6 +318,7 @@ {: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) @@ -327,11 +329,16 @@ (db/update! conn :file {:revn (:revn file) :data (:data file) + :data-backend nil + :modified-at ts :has-media-trimmed false} {:id (:id file)}) - (let [params (-> params (assoc :file file - :changes changes))] + (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) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index dd36c64134..730efe8d5e 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -9,10 +9,12 @@ [app.common.pages.migrations :as pmg] [app.common.spec :as us] [app.common.uuid :as uuid] + [app.config :as cf] [app.db :as db] [app.rpc.permissions :as perms] [app.rpc.queries.projects :as projects] [app.rpc.queries.teams :as teams] + [app.storage.impl :as simpl] [app.util.blob :as blob] [app.util.services :as sv] [clojure.spec.alpha :as s])) @@ -171,11 +173,23 @@ ;; --- Query: File (By ID) +(defn- retrieve-data* + [{:keys [storage] :as cfg} file] + (when-let [backend (simpl/resolve-backend storage (cf/get :fdata-storage-backend))] + (simpl/get-object-bytes backend file))) + +(defn retrieve-data + [cfg file] + (if (bytes? (:data file)) + file + (assoc file :data (retrieve-data* cfg file)))) + (defn retrieve-file - [conn id] - (-> (db/get-by-id conn :file id) - (decode-row) - (pmg/migrate-file))) + [{:keys [conn] :as cfg} id] + (->> (db/get-by-id conn :file id) + (retrieve-data cfg) + (decode-row) + (pmg/migrate-file))) (s/def ::file (s/keys :req-un [::profile-id ::id])) @@ -183,8 +197,9 @@ (sv/defmethod ::file [{:keys [pool] :as cfg} {:keys [profile-id id] :as params}] (db/with-atomic [conn pool] - (check-edition-permissions! conn profile-id id) - (retrieve-file conn id))) + (let [cfg (assoc cfg :conn conn)] + (check-edition-permissions! conn profile-id id) + (retrieve-file cfg id)))) (s/def ::page (s/keys :req-un [::profile-id ::file-id])) @@ -218,10 +233,10 @@ (sv/defmethod ::page [{:keys [pool] :as cfg} {:keys [profile-id file-id id strip-thumbnails]}] - [{:keys [pool] :as cfg} {:keys [profile-id file-id]}] (db/with-atomic [conn pool] (check-edition-permissions! conn profile-id file-id) - (let [file (retrieve-file conn file-id) + (let [cfg (assoc cfg :conn conn) + file (retrieve-file cfg file-id) page-id (get-in file [:data :pages 0])] (cond-> (get-in file [:data :pages-index page-id]) strip-thumbnails @@ -277,23 +292,36 @@ ;; --- Query: File Libraries used by a File (def ^:private sql:file-libraries - "select fl.*, - - flr.synced_at as synced_at - from file as fl - inner join file_library_rel as flr on (flr.library_file_id = fl.id) - where flr.file_id = ? - and fl.deleted_at is null") + "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 - [conn is-indirect file-id] - (let [libraries (->> (db/exec! conn [sql:file-libraries file-id]) - (map #(assoc % :is-indirect is-indirect)) - (into #{} decode-row-xf))] - (reduce #(into %1 (retrieve-file-libraries conn true %2)) - libraries - (map :id libraries)))) - + [{:keys [conn] :as cfg} is-indirect file-id] + (let [xform (comp + (map #(assoc % :is-indirect is-indirect)) + (map #(retrieve-data cfg %)) + (map decode-row))] + (into #{} xform (db/exec! conn [sql:file-libraries file-id])))) (s/def ::file-libraries (s/keys :req-un [::profile-id ::file-id])) @@ -301,8 +329,9 @@ (sv/defmethod ::file-libraries [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] - (check-edition-permissions! conn profile-id file-id) - (retrieve-file-libraries conn false file-id))) + (let [cfg (assoc cfg :conn conn)] + (check-edition-permissions! conn profile-id file-id) + (retrieve-file-libraries cfg false file-id)))) ;; --- QUERY: team-recent-files @@ -334,7 +363,6 @@ (teams/check-read-permissions! conn profile-id team-id) (db/exec! conn [sql:team-recent-files team-id]))) - ;; --- Helpers (defn decode-row diff --git a/backend/src/app/rpc/queries/viewer.clj b/backend/src/app/rpc/queries/viewer.clj index f346e53efa..dfe95314cc 100644 --- a/backend/src/app/rpc/queries/viewer.clj +++ b/backend/src/app/rpc/queries/viewer.clj @@ -42,12 +42,13 @@ (sv/defmethod ::viewer-bundle {:auth false} [{:keys [pool] :as cfg} {:keys [profile-id file-id page-id token] :as params}] (db/with-atomic [conn pool] - (let [file (files/retrieve-file conn file-id) + (let [cfg (assoc cfg :conn conn) + file (files/retrieve-file cfg file-id) project (retrieve-project conn (:project-id file)) page (get-in file [:data :pages-index page-id]) file (merge (dissoc file :data) (select-keys (:data file) [:colors :media :typographies])) - libs (files/retrieve-file-libraries conn false file-id) + libs (files/retrieve-file-libraries cfg false file-id) users (teams/retrieve-users conn (:team-id project)) fonts (db/query conn :team-font-variant diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj index 0b1c1ffe15..f8a923a2de 100644 --- a/backend/src/app/storage.clj +++ b/backend/src/app/storage.clj @@ -5,7 +5,7 @@ ;; Copyright (c) UXBOX Labs SL (ns app.storage - "File Storage abstraction layer." + "Objects storage abstraction layer." (:require [app.common.data :as d] [app.common.exceptions :as ex] @@ -20,13 +20,9 @@ [app.util.time :as dt] [app.worker :as wrk] [clojure.spec.alpha :as s] - [cuerdas.core :as str] [datoteka.core :as fs] [integrant.core :as ig] - [promesa.exec :as px]) - (:import - java.io.InputStream)) - + [promesa.exec :as px])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Storage Module State @@ -39,7 +35,11 @@ (s/def ::db ::sdb/backend) (s/def ::backends - (s/keys :opt-un [::s3 ::fs ::db])) + (s/map-of ::us/keyword + (s/nilable + (s/or :s3 ::ss3/backend + :fs ::sfs/backend + :db ::sdb/backend)))) (defmethod ig/pre-init-spec ::storage [_] (s/keys :req-un [::backend ::wrk/executor ::db/pool ::backends])) @@ -50,8 +50,9 @@ (assoc :backends (d/without-nils backends)))) (defmethod ig/init-key ::storage - [_ cfg] - cfg) + [_ {:keys [backends] :as cfg}] + (-> (d/without-nils cfg) + (assoc :backends (d/without-nils backends)))) (s/def ::storage (s/keys :req-un [::backends ::wrk/executor ::db/pool ::backend])) @@ -151,8 +152,6 @@ ;; API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(declare resolve-backend) - (defn object->relative-path [{:keys [id] :as obj}] (impl/id->path id)) @@ -185,7 +184,7 @@ (px/run! executor #(register-recheck storage backend (:id object))) ;; Store the data finally on the underlying storage subsystem. - (-> (resolve-backend storage backend) + (-> (impl/resolve-backend storage backend) (impl/put-object object content)) object)) @@ -201,28 +200,37 @@ ;; if the source and destination backends are the same, we ;; proceed to use the fast path with specific copy ;; implementation on backend. - (-> (resolve-backend storage (:backend storage)) + (-> (impl/resolve-backend storage (:backend storage)) (impl/copy-object object object*)) ;; if the source and destination backends are different, we just ;; need to obtain the streams and proceed full copy of the data - (with-open [^InputStream input - (-> (resolve-backend storage (:backend object)) - (impl/get-object-data object))] - (-> (resolve-backend storage (:backend storage)) - (impl/put-object object* (impl/content input (:size object)))))) - + (with-open [is (-> (impl/resolve-backend storage (:backend object)) + (impl/get-object-data object))] + (-> (impl/resolve-backend storage (:backend storage)) + (impl/put-object object* (impl/content is (:size object)))))) object*)) (defn get-object-data + "Return an input stream instance of the object content." [{:keys [pool conn] :as storage} object] (us/assert ::storage storage) (when (or (nil? (:expired-at object)) (dt/is-after? (:expired-at object) (dt/now))) (-> (assoc storage :conn (or conn pool)) - (resolve-backend (:backend object)) + (impl/resolve-backend (:backend object)) (impl/get-object-data object)))) +(defn get-object-bytes + "Returns a byte array of object content." + [{:keys [pool conn] :as storage} object] + (us/assert ::storage storage) + (when (or (nil? (:expired-at object)) + (dt/is-after? (:expired-at object) (dt/now))) + (-> (assoc storage :conn (or conn pool)) + (impl/resolve-backend (:backend object)) + (impl/get-object-bytes object)))) + (defn get-object-url ([storage object] (get-object-url storage object nil)) @@ -231,14 +239,14 @@ (when (or (nil? (:expired-at object)) (dt/is-after? (:expired-at object) (dt/now))) (-> (assoc storage :conn (or conn pool)) - (resolve-backend (:backend object)) + (impl/resolve-backend (:backend object)) (impl/get-object-url object options))))) (defn get-object-path "Get the Path to the object. Only works with `:fs` type of storages." [storage object] - (let [backend (resolve-backend storage (:backend object))] + (let [backend (impl/resolve-backend storage (:backend object))] (when (not= :fs (:type backend)) (ex/raise :type :internal :code :operation-not-allowed @@ -254,16 +262,7 @@ (-> (assoc storage :conn (or conn pool)) (delete-database-object (if (uuid? id-or-obj) id-or-obj (:id id-or-obj))))) -;; --- impl - -(defn resolve-backend - [{:keys [conn pool] :as storage} backend-id] - (let [backend (get-in storage [:backends backend-id])] - (when-not backend - (ex/raise :type :internal - :code :backend-not-configured - :hint (str/fmt "backend '%s' not configured" backend-id))) - (assoc backend :conn (or conn pool)))) +(d/export impl/resolve-backend) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Garbage Collection: Permanently delete objects @@ -295,7 +294,7 @@ (some-> (seq rows) (group-by-backend)))) (delete-in-bulk [conn [backend ids]] - (let [backend (resolve-backend storage backend) + (let [backend (impl/resolve-backend storage backend) backend (assoc backend :conn conn)] (impl/del-objects-in-bulk backend ids)))] @@ -445,7 +444,7 @@ (some-> (seq rows) (group-results)))) (delete-group [conn [backend ids]] - (let [backend (resolve-backend storage backend) + (let [backend (impl/resolve-backend storage backend) backend (assoc backend :conn conn)] (impl/del-objects-in-bulk backend ids))) diff --git a/backend/src/app/storage/db.clj b/backend/src/app/storage/db.clj index a7ed7adbc1..e5814c8509 100644 --- a/backend/src/app/storage/db.clj +++ b/backend/src/app/storage/db.clj @@ -46,6 +46,11 @@ (let [result (db/exec-one! conn ["select data from storage_data where id=?" id])] (ByteArrayInputStream. (:data result)))) +(defmethod impl/get-object-bytes :db + [{:keys [conn] :as backend} {:keys [id] :as object}] + (let [result (db/exec-one! conn ["select data from storage_data where id=?" id])] + (:data result))) + (defmethod impl/get-object-url :db [_ _] (throw (UnsupportedOperationException. "not supported"))) diff --git a/backend/src/app/storage/fs.clj b/backend/src/app/storage/fs.clj index 4f9d059693..e467bd1681 100644 --- a/backend/src/app/storage/fs.clj +++ b/backend/src/app/storage/fs.clj @@ -79,6 +79,10 @@ :path (str full))) (io/input-stream full))) +(defmethod impl/get-object-bytes :fs + [backend object] + (fs/slurp-bytes (impl/get-object-data backend object))) + (defmethod impl/get-object-url :fs [{:keys [uri] :as backend} {:keys [id] :as object} _] (update uri :path diff --git a/backend/src/app/storage/impl.clj b/backend/src/app/storage/impl.clj index a28184df5f..e39aaa7f9c 100644 --- a/backend/src/app/storage/impl.clj +++ b/backend/src/app/storage/impl.clj @@ -8,10 +8,10 @@ "Storage backends abstraction layer." (:require [app.common.exceptions :as ex] - [app.common.spec :as us] [app.common.uuid :as uuid] [buddy.core.codecs :as bc] - [clojure.java.io :as io]) + [clojure.java.io :as io] + [cuerdas.core :as str]) (:import java.nio.ByteBuffer java.util.UUID @@ -45,6 +45,14 @@ :code :invalid-storage-backend :context cfg)) +(defmulti get-object-bytes (fn [cfg _] (:type cfg))) + +(defmethod get-object-bytes :default + [cfg _] + (ex/raise :type :internal + :code :invalid-storage-backend + :context cfg)) + (defmulti get-object-url (fn [cfg _ _] (:type cfg))) (defmethod get-object-url :default @@ -109,7 +117,10 @@ (make-output-stream [_ opts] (throw (UnsupportedOperationException. "not implemented"))) clojure.lang.Counted - (count [_] size)))) + (count [_] size) + + java.lang.AutoCloseable + (close [_])))) (defn string->content [^String v] @@ -129,7 +140,10 @@ clojure.lang.Counted (count [_] - (alength data))))) + (alength data)) + + java.lang.AutoCloseable + (close [_])))) (defn- input-stream->content [^InputStream is size] @@ -137,7 +151,7 @@ IContentObject io/IOFactory (make-reader [_ opts] - (io/make-reader is opts)) + (io/make-reader is opts)) (make-writer [_ opts] (throw (UnsupportedOperationException. "not implemented"))) (make-input-stream [_ opts] @@ -146,7 +160,11 @@ (throw (UnsupportedOperationException. "not implemented"))) clojure.lang.Counted - (count [_] size))) + (count [_] size) + + java.lang.AutoCloseable + (close [_] + (.close is)))) (defn content ([data] (content data nil)) @@ -179,10 +197,20 @@ (defn slurp-bytes [content] - (us/assert content? content) (with-open [input (io/input-stream content) output (java.io.ByteArrayOutputStream. (count content))] (io/copy input output) (.toByteArray output))) +(defn resolve-backend + [{:keys [conn pool] :as storage} backend-id] + (when backend-id + (let [backend (get-in storage [:backends backend-id])] + (when-not backend + (ex/raise :type :internal + :code :backend-not-configured + :hint (str/fmt "backend '%s' not configured" backend-id))) + (assoc backend + :conn (or conn pool) + :id backend-id)))) diff --git a/backend/src/app/storage/s3.clj b/backend/src/app/storage/s3.clj index da7928bdff..335b284a7c 100644 --- a/backend/src/app/storage/s3.clj +++ b/backend/src/app/storage/s3.clj @@ -5,7 +5,7 @@ ;; Copyright (c) UXBOX Labs SL (ns app.storage.s3 - "Storage backends abstraction layer." + "S3 Storage backend implementation." (:require [app.common.data :as d] [app.common.exceptions :as ex] @@ -18,8 +18,11 @@ [integrant.core :as ig]) (:import java.time.Duration + java.io.InputStream java.util.Collection software.amazon.awssdk.core.sync.RequestBody + software.amazon.awssdk.core.ResponseBytes + ;; software.amazon.awssdk.core.ResponseInputStream software.amazon.awssdk.regions.Region software.amazon.awssdk.services.s3.S3Client software.amazon.awssdk.services.s3.model.Delete @@ -29,13 +32,17 @@ software.amazon.awssdk.services.s3.model.GetObjectRequest software.amazon.awssdk.services.s3.model.ObjectIdentifier software.amazon.awssdk.services.s3.model.PutObjectRequest + ;; software.amazon.awssdk.services.s3.model.GetObjectResponse software.amazon.awssdk.services.s3.presigner.S3Presigner software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest - software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest)) + software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest + + )) (declare put-object) (declare copy-object) -(declare get-object) +(declare get-object-bytes) +(declare get-object-data) (declare get-object-url) (declare del-object-in-bulk) (declare build-s3-client) @@ -87,7 +94,11 @@ (defmethod impl/get-object-data :s3 [backend object] - (get-object backend object)) + (get-object-data backend object)) + +(defmethod impl/get-object-bytes :s3 + [backend object] + (get-object-bytes backend object)) (defmethod impl/get-object-url :s3 [backend object options] @@ -104,19 +115,19 @@ (case region :eu-central-1 Region/EU_CENTRAL_1)) -(defn- build-s3-client +(defn build-s3-client [{:keys [region]}] (.. (S3Client/builder) (region (lookup-region region)) (build))) -(defn- build-s3-presigner +(defn build-s3-presigner [{:keys [region]}] (.. (S3Presigner/builder) (region (lookup-region region)) (build))) -(defn- put-object +(defn put-object [{:keys [client bucket prefix]} {:keys [id] :as object} content] (let [path (str prefix (impl/id->path id)) mdata (meta object) @@ -125,14 +136,15 @@ (bucket bucket) (contentType mtype) (key path) - (build)) - content (RequestBody/fromInputStream (io/input-stream content) - (count content))] - (.putObject ^S3Client client - ^PutObjectRequest request - ^RequestBody content))) + (build))] -(defn- copy-object + (with-open [^InputStream is (io/input-stream content)] + (let [content (RequestBody/fromInputStream is (count content))] + (.putObject ^S3Client client + ^PutObjectRequest request + ^RequestBody content))))) + +(defn copy-object [{:keys [client bucket prefix]} src-object dst-object] (let [source-path (str prefix (impl/id->path (:id src-object))) source-mdata (meta src-object) @@ -146,22 +158,33 @@ (contentType source-mtype) (build))] - (.copyObject ^S3Client client - ^CopyObjectRequest request))) + (.copyObject ^S3Client client ^CopyObjectRequest request))) -(defn- get-object +(defn get-object-data [{:keys [client bucket prefix]} {:keys [id]}] (let [gor (.. (GetObjectRequest/builder) (bucket bucket) (key (str prefix (impl/id->path id))) (build)) - obj (.getObject ^S3Client client ^GetObjectRequest gor)] + obj (.getObject ^S3Client client ^GetObjectRequest gor) + ;; rsp (.response ^ResponseInputStream obj) + ;; len (.contentLength ^GetObjectResponse rsp) + ] (io/input-stream obj))) +(defn get-object-bytes + [{:keys [client bucket prefix]} {:keys [id]}] + (let [gor (.. (GetObjectRequest/builder) + (bucket bucket) + (key (str prefix (impl/id->path id))) + (build)) + obj (.getObjectAsBytes ^S3Client client ^GetObjectRequest gor)] + (.asByteArray ^ResponseBytes obj))) + (def default-max-age (dt/duration {:minutes 10})) -(defn- get-object-url +(defn get-object-url [{:keys [presigner bucket prefix]} {:keys [id]} {:keys [max-age] :or {max-age default-max-age}}] (us/assert dt/duration? max-age) (let [gor (.. (GetObjectRequest/builder) @@ -175,7 +198,7 @@ pgor (.presignGetObject ^S3Presigner presigner ^GetObjectPresignRequest gopr)] (u/uri (str (.url ^PresignedGetObjectRequest pgor))))) -(defn- del-object-in-bulk +(defn del-object-in-bulk [{:keys [bucket client prefix]} ids] (let [oids (map (fn [id] (.. (ObjectIdentifier/builder) diff --git a/backend/src/app/tasks/file_offload.clj b/backend/src/app/tasks/file_offload.clj new file mode 100644 index 0000000000..5c5abd7bfd --- /dev/null +++ b/backend/src/app/tasks/file_offload.clj @@ -0,0 +1,64 @@ +;; 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.tasks.file-offload + "A maintenance task that offloads file data to an external storage (S3)." + (:require + [app.common.spec :as us] + [app.db :as db] + [app.storage :as sto] + [app.storage.impl :as simpl] + [app.util.logging :as l] + [app.util.time :as dt] + [clojure.spec.alpha :as s] + [integrant.core :as ig])) + +(def sql:offload-candidates-chunk + "select f.id, f.data from file as f + where f.data is not null + and f.modified_at < now() - ?::interval + order by f.modified_at + limit 10") + +(defn- retrieve-candidates + [{:keys [conn max-age]}] + (db/exec! conn [sql:offload-candidates-chunk max-age])) + +(defn- offload-candidate + [{:keys [storage conn backend] :as cfg} {:keys [id data] :as file}] + (l/debug :action "offload file data" :id id) + (let [backend (simpl/resolve-backend storage backend)] + (->> (simpl/content data) + (simpl/put-object backend file)) + (db/update! conn :file + {:data nil + :data-backend (name (:id backend))} + {:id id}))) + +;; ---- STATE INIT + +(s/def ::max-age ::dt/duration) +(s/def ::backend ::us/keyword) + + +(defmethod ig/pre-init-spec ::handler [_] + (s/keys :req-un [::db/pool ::max-age ::sto/storage ::backend])) + +(defmethod ig/init-key ::handler + [_ {:keys [pool max-age] :as cfg}] + (fn [_] + (db/with-atomic [conn pool] + (let [max-age (db/interval max-age) + cfg (-> cfg + (assoc :conn conn) + (assoc :max-age max-age))] + (loop [n 0] + (let [candidates (retrieve-candidates cfg)] + (if (seq candidates) + (do + (run! (partial offload-candidate cfg) candidates) + (recur (+ n (count candidates)))) + (l/debug :hint "offload summary" :count n)))))))) diff --git a/backend/test/app/storage_test.clj b/backend/test/app/storage_test.clj index d35dae02fe..05e471854b 100644 --- a/backend/test/app/storage_test.clj +++ b/backend/test/app/storage_test.clj @@ -22,7 +22,6 @@ th/database-reset th/clean-storage)) -;; TODO: add specific tests for DB backend. (t/deftest put-and-retrieve-object (let [storage (:app.storage/storage th/*system*) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 588da49bfc..f4f177274d 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -747,6 +747,7 @@ (st/emit! dm/hide)) do-dismiss #(do (st/emit! ignore-sync) (st/emit! dm/hide))] + (rx/of (dm/info-dialog (tr "workspace.updates.there-are-updates") :inline-actions From ee7248204f67dd7ccc0b2026ae5756b257ebb3eb Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Mon, 14 Jun 2021 20:00:10 +0200 Subject: [PATCH 081/204] :lipstick: Add new actions icon --- frontend/resources/styles/common/framework.scss | 4 ++-- frontend/src/app/main/ui/viewer/header.cljs | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index 629cee8d1a..d294b661d1 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -118,10 +118,10 @@ .btn-icon-basic { @extend %btn; background: transparent; - color: $color-gray-60; + color: $color-gray-20; padding: $x-small; svg { - fill: $color-gray-60; + fill: $color-gray-20; } &:hover { background: transparent; diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 0b82dd4344..ff22757b7b 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -276,11 +276,13 @@ :on-zoom-to-100 (st/emitf dv/reset-zoom) :on-zoom-to-200 (st/emitf dv/zoom-to-200)}] - [:span.btn-icon-dark.btn-small.tooltip.tooltip-bottom-left + [:span.btn-icon-basic.btn-small.tooltip.tooltip-bottom-left {:alt (t locale "viewer.header.fullscreen") :on-click #(if @fullscreen (fullscreen false) (fullscreen true))} (if @fullscreen i/full-screen-off i/full-screen)] - ]])) + + [:span.btn-icon-dark.btn-small + i/actions]]])) From 2293253558d177078516e477076ea061dff598d3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 15 Jun 2021 08:36:04 +0200 Subject: [PATCH 082/204] :tada: Add profiler dev dependency. --- backend/deps.edn | 1 + backend/scripts/repl | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/backend/deps.edn b/backend/deps.edn index 6a3ae16699..0bee0c0c52 100644 --- a/backend/deps.edn +++ b/backend/deps.edn @@ -55,6 +55,7 @@ {com.bhauman/rebel-readline {:mvn/version "RELEASE"} org.clojure/tools.namespace {:mvn/version "RELEASE"} org.clojure/test.check {:mvn/version "RELEASE"} + com.clojure-goes-fast/clj-async-profiler {:mvn/version "0.5.0"} criterium/criterium {:mvn/version "RELEASE"} mockery/mockery {:mvn/version "RELEASE"}} diff --git a/backend/scripts/repl b/backend/scripts/repl index 1f434abcfb..cee9d8c5b6 100755 --- a/backend/scripts/repl +++ b/backend/scripts/repl @@ -2,7 +2,7 @@ export PENPOT_ASSERTS_ENABLED=true -export OPTIONS="-A:jmx-remote:dev -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -J-Dlog4j2.configurationFile=log4j2-devenv.xml -J-XX:+UseZGC -J-XX:ConcGCThreads=1 -J-XX:-OmitStackTraceInFastThrow -J-Xms50m -J-Xmx512m"; +export OPTIONS="-A:jmx-remote:dev -J-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager -J-Dlog4j2.configurationFile=log4j2-devenv.xml -J-Djdk.attach.allowAttachSelf -J-XX:+UseZGC -J-XX:ConcGCThreads=1 -J-XX:-OmitStackTraceInFastThrow -J-Xms50m -J-Xmx512m"; # export OPTIONS="$OPTIONS -J-XX:+UnlockDiagnosticVMOptions"; # export OPTIONS="$OPTIONS -J-XX:-TieredCompilation -J-XX:CompileThreshold=10000"; From 2728fa2b8d5d76d4698a33e0835c487a1955ac53 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 15 Jun 2021 09:14:40 +0200 Subject: [PATCH 083/204] :sparkles: Add proper fdata objects deletion. --- backend/src/app/rpc/mutations/files.clj | 15 +++++++++++---- backend/src/app/storage/db.clj | 7 +++++++ backend/src/app/storage/fs.clj | 8 ++++++++ backend/src/app/storage/impl.clj | 9 ++++++++- backend/src/app/storage/s3.clj | 15 +++++++++++++++ backend/src/app/tasks/file_offload.clj | 1 - backend/src/app/tasks/objects_gc.clj | 19 +++++++++++++++++++ 7 files changed, 68 insertions(+), 6 deletions(-) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index a08da44f8b..0b6da5b516 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -11,15 +11,15 @@ [app.common.pages.migrations :as pmg] [app.common.spec :as us] [app.common.uuid :as uuid] - + [app.config :as cf] [app.db :as db] [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.services :as sv] [app.util.time :as dt] - [clojure.spec.alpha :as s])) (declare create-file) @@ -45,7 +45,6 @@ (proj/check-edition-permissions! conn profile-id project-id) (create-file conn params))) - (defn create-file-role [conn {:keys [file-id profile-id role]}] (let [params {:file-id file-id @@ -112,7 +111,6 @@ {:is-shared is-shared} {:id id})) - ;; --- Mutation: Delete File (declare mark-file-deleted) @@ -288,6 +286,11 @@ (> (inst-ms (dt/diff modified-at (dt/now))) (inst-ms (dt/duration {:hours 3}))))) +(defn- delete-from-storage + [{:keys [storage] :as cfg} file] + (when-let [backend (simpl/resolve-backend storage (cf/get :fdata-storage-backend))] + (simpl/del-object backend file))) + (defn- update-file [{:keys [conn] :as cfg} {:keys [file changes changes-with-metadata session-id profile-id] :as params}] (when (> (:revn params) @@ -334,6 +337,10 @@ :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)}) diff --git a/backend/src/app/storage/db.clj b/backend/src/app/storage/db.clj index e5814c8509..0890f74552 100644 --- a/backend/src/app/storage/db.clj +++ b/backend/src/app/storage/db.clj @@ -55,8 +55,15 @@ [_ _] (throw (UnsupportedOperationException. "not supported"))) +(defmethod impl/del-object :db + [_ _] + ;; NOOP: because deleting the row already deletes the file data from + ;; the database. + nil) + (defmethod impl/del-objects-in-bulk :db [_ _] ;; NOOP: because deleting the row already deletes the file data from ;; the database. nil) + diff --git a/backend/src/app/storage/fs.clj b/backend/src/app/storage/fs.clj index e467bd1681..e15bb7b0e3 100644 --- a/backend/src/app/storage/fs.clj +++ b/backend/src/app/storage/fs.clj @@ -91,6 +91,13 @@ (str existing (impl/id->path id)) (str existing "/" (impl/id->path id)))))) +(defmethod impl/del-object :fs + [backend {:keys [id] :as object}] + (let [base (fs/path (:directory backend)) + path (fs/path (impl/id->path id)) + path (fs/join base path)] + (Files/deleteIfExists ^Path path))) + (defmethod impl/del-objects-in-bulk :fs [backend ids] (let [base (fs/path (:directory backend))] @@ -98,3 +105,4 @@ (let [path (fs/path (impl/id->path id)) path (fs/join base path)] (Files/deleteIfExists ^Path path))))) + diff --git a/backend/src/app/storage/impl.clj b/backend/src/app/storage/impl.clj index e39aaa7f9c..4c3a619009 100644 --- a/backend/src/app/storage/impl.clj +++ b/backend/src/app/storage/impl.clj @@ -62,6 +62,14 @@ :context cfg)) +(defmulti del-object (fn [cfg _] (:type cfg))) + +(defmethod del-object :default + [cfg _] + (ex/raise :type :internal + :code :invalid-storage-backend + :context cfg)) + (defmulti del-objects-in-bulk (fn [cfg _] (:type cfg))) (defmethod del-objects-in-bulk :default @@ -70,7 +78,6 @@ :code :invalid-storage-backend :context cfg)) - ;; --- HELPERS (defn uuid->hex diff --git a/backend/src/app/storage/s3.clj b/backend/src/app/storage/s3.clj index 335b284a7c..2d42277a7c 100644 --- a/backend/src/app/storage/s3.clj +++ b/backend/src/app/storage/s3.clj @@ -29,6 +29,7 @@ software.amazon.awssdk.services.s3.model.CopyObjectRequest software.amazon.awssdk.services.s3.model.DeleteObjectsRequest software.amazon.awssdk.services.s3.model.DeleteObjectsResponse + software.amazon.awssdk.services.s3.model.DeleteObjectRequest software.amazon.awssdk.services.s3.model.GetObjectRequest software.amazon.awssdk.services.s3.model.ObjectIdentifier software.amazon.awssdk.services.s3.model.PutObjectRequest @@ -44,6 +45,7 @@ (declare get-object-bytes) (declare get-object-data) (declare get-object-url) +(declare del-object) (declare del-object-in-bulk) (declare build-s3-client) (declare build-s3-presigner) @@ -104,6 +106,10 @@ [backend object options] (get-object-url backend object options)) +(defmethod impl/del-object :s3 + [backend object] + (del-object backend object)) + (defmethod impl/del-objects-in-bulk :s3 [backend ids] (del-object-in-bulk backend ids)) @@ -198,6 +204,15 @@ pgor (.presignGetObject ^S3Presigner presigner ^GetObjectPresignRequest gopr)] (u/uri (str (.url ^PresignedGetObjectRequest pgor))))) +(defn del-object + [{:keys [bucket client prefix]} {:keys [id] :as obj}] + (let [dor (.. (DeleteObjectRequest/builder) + (bucket bucket) + (key (str prefix (impl/id->path id))) + (build))] + (.deleteObject ^S3Client client + ^DeleteObjectRequest dor))) + (defn del-object-in-bulk [{:keys [bucket client prefix]} ids] (let [oids (map (fn [id] diff --git a/backend/src/app/tasks/file_offload.clj b/backend/src/app/tasks/file_offload.clj index 5c5abd7bfd..ee784d02d2 100644 --- a/backend/src/app/tasks/file_offload.clj +++ b/backend/src/app/tasks/file_offload.clj @@ -43,7 +43,6 @@ (s/def ::max-age ::dt/duration) (s/def ::backend ::us/keyword) - (defmethod ig/pre-init-spec ::handler [_] (s/keys :req-un [::db/pool ::max-age ::sto/storage ::backend])) diff --git a/backend/src/app/tasks/objects_gc.clj b/backend/src/app/tasks/objects_gc.clj index b146c85afa..1125245607 100644 --- a/backend/src/app/tasks/objects_gc.clj +++ b/backend/src/app/tasks/objects_gc.clj @@ -8,8 +8,10 @@ "A maintenance task that performs a general purpose garbage collection of deleted objects." (:require + [app.config :as cf] [app.db :as db] [app.storage :as sto] + [app.storage.impl :as simpl] [app.util.logging :as l] [app.util.time :as dt] [clojure.spec.alpha :as s] @@ -50,6 +52,23 @@ (count result))) + +;; --- IMPL: file deletion + +(defmethod delete-objects "file" + [{:keys [conn max-age table storage] :as cfg}] + (let [sql (str/fmt sql:delete-objects + {:table table :limit 50}) + result (db/exec! conn [sql max-age]) + backend (simpl/resolve-backend storage (cf/get :fdata-storage-backend))] + + (doseq [{:keys [id] :as item} result] + (l/trace :action "delete object" :table table :id id) + (when backend + (simpl/del-object backend item))) + + (count result))) + ;; --- IMPL: team-font-variant deletion (defmethod delete-objects "team_font_variant" From a106c728bacca6315c61e11b6f99903b9fa11d32 Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Tue, 15 Jun 2021 09:55:38 +0200 Subject: [PATCH 084/204] :lipstick: Add new project header --- CHANGES.md | 1 + .../main/partials/dashboard-header.scss | 5 +++ .../styles/main/partials/dashboard.scss | 3 +- frontend/src/app/main/ui/dashboard/files.cljs | 34 ++++++++++++------- .../src/app/main/ui/dashboard/projects.cljs | 25 ++++++++------ frontend/translations/en.po | 3 ++ 6 files changed, 48 insertions(+), 23 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7459428f52..7594df1608 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ - Allow to ungroup assets [Taiga #1719](https://tree.taiga.io/project/penpot/us/1719) - Allow to rename assets groups [Taiga #1721](https://tree.taiga.io/project/penpot/us/1721) - Memorize collapse state of assets in panel [Taiga #1718](https://tree.taiga.io/project/penpot/us/1718) +- Headers button sets and menus review [Taiga #1663](https://tree.taiga.io/project/penpot/us/1663) - Add the ability to offload file data to a cheaper storage when file becomes inactive. diff --git a/frontend/resources/styles/main/partials/dashboard-header.scss b/frontend/resources/styles/main/partials/dashboard-header.scss index c3af04cb22..3c5c7ab400 100644 --- a/frontend/resources/styles/main/partials/dashboard-header.scss +++ b/frontend/resources/styles/main/partials/dashboard-header.scss @@ -104,7 +104,12 @@ } } + .dashboard-header-actions { + display: flex; + } + .pin-icon { + margin: 0 $small 0 $big; svg { fill: $color-gray-20; } diff --git a/frontend/resources/styles/main/partials/dashboard.scss b/frontend/resources/styles/main/partials/dashboard.scss index 780dd8794e..6c90ee4f5d 100644 --- a/frontend/resources/styles/main/partials/dashboard.scss +++ b/frontend/resources/styles/main/partials/dashboard.scss @@ -34,7 +34,7 @@ height: 40px; .btn-secondary { - margin-left: $big; + border: none; height: 32px; } @@ -62,6 +62,7 @@ cursor: pointer; display: flex; align-items: center; + margin-left: 40px; margin-right: 10px; svg { width: 15px; diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs index a430269645..dc7b44e8b4 100644 --- a/frontend/src/app/main/ui/dashboard/files.cljs +++ b/frontend/src/app/main/ui/dashboard/files.cljs @@ -29,7 +29,11 @@ team-id (:id team) on-menu-click - (mf/use-callback #(swap! local assoc :menu-open true)) + (mf/use-callback + (fn [event] + (let [position (dom/get-client-position event)] + (dom/prevent-default event) + (swap! local assoc :menu-open true :menu-pos position)))) on-menu-close (mf/use-callback #(swap! local assoc :menu-open false)) @@ -63,20 +67,26 @@ [:div.dashboard-title [:h1 {:on-double-click on-edit} (:name project)] - [:div.icon {:on-click on-menu-click} - i/actions] [:& project-menu {:project project :show? (:menu-open @local) + :left (- (:x (:menu-pos @local)) 180) + :top (:y (:menu-pos @local)) :on-edit on-edit - :on-menu-close on-menu-close}] - [:div.icon.pin-icon - {:class (when (:is-pinned project) "active") - :on-click toggle-pin} - (if (:is-pinned project) - i/pin-fill - i/pin)]])) - [:a.btn-secondary.btn-small {:on-click on-create-clicked} - (tr "dashboard.new-file")]])) + :on-menu-close on-menu-close}]])) + [:div.dashboard-header-actions + [:a.btn-secondary.btn-small {:on-click on-create-clicked} + (tr "dashboard.new-file")] + + [:div.icon.pin-icon.tooltip.tooltip-bottom + {:class (when (:is-pinned project) "active") + :on-click toggle-pin :alt (tr "dashboard.pin-unpin")} + (if (:is-pinned project) + i/pin-fill + i/pin)] + + [:div.icon.tooltip.tooltip-bottom + {:on-click on-menu-click :alt (tr "dashboard.options")} + i/actions]]])) (mf/defc files-section [{:keys [project team] :as props}] diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 7921624aa7..8e6485b379 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -108,13 +108,6 @@ [:div.dashboard-project-row {:class (when first? "first")} [:div.project - (when-not (:is-default project) - [:span.pin-icon - {:class (when (:is-pinned project) "active") - :on-click toggle-pin} - (if (:is-pinned project) - i/pin-fill - i/pin)]) (if (:edition? @local) [:& inline-edition {:content (:name project) :on-end on-edit}] @@ -141,9 +134,21 @@ #_[:& import-button {:project-id (:id project) :on-finish-import on-finish-import}] - [:a.btn-secondary.btn-small - {:on-click create-file} - (tr "dashboard.new-file")]] + (when-not (:is-default project) + [:span.pin-icon.tooltip.tooltip-bottom + {:class (when (:is-pinned project) "active") + :on-click toggle-pin :alt (tr "dashboard.pin-unpin")} + (if (:is-pinned project) + i/pin-fill + i/pin)]) + + [:a.btn-secondary.btn-small.tooltip.tooltip-bottom + {:on-click create-file :alt (tr "dashboard.new-file")} + i/close] + + [:a.btn-secondary.btn-small.tooltip.tooltip-bottom + {:on-click on-menu-click :alt (tr "dashboard.options")} + i/actions]] [:& line-grid {:project-id (:id project) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a745b6c076..6b383d7392 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2693,3 +2693,6 @@ msgstr "Export file" msgid "dashboard.export-multi" msgstr "Export %s files" + +msgid "dashboard.options" +msgstr "Options" From d6e009ce781337ee9bc0ee6655b93a26e88e3b1d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 14 Jun 2021 16:00:16 +0200 Subject: [PATCH 085/204] :sparkles: Adds flip,proportion and rotation --- common/src/app/common/file_builder.cljc | 12 +- frontend/src/app/libs/file_builder.cljs | 59 +++++++++- frontend/src/app/main/exports.cljs | 4 + frontend/src/app/main/ui/shapes/export.cljs | 38 ++++++- .../src/app/main/ui/workspace/viewport.cljs | 3 + frontend/src/app/util/import/parser.cljc | 107 +++++++++++++----- frontend/src/app/worker/import.cljs | 6 +- 7 files changed, 189 insertions(+), 40 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 64ae5dff46..c2cd12a07b 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -87,15 +87,15 @@ :changes []}))) (defn add-page - [file name] - (let [page-id (uuid/next)] + [file data] + (let [page-id (uuid/next) + page (-> init/empty-page-data + (assoc :id page-id) + (d/deep-merge data))] (-> file (commit-change {:type :add-page - :id page-id - :name name - :page (-> init/empty-page-data - (assoc :name name))}) + :page page}) ;; Current page being edited (assoc :current-page-id page-id) diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 4f1c6a207c..2aa4bff3af 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -7,14 +7,65 @@ (ns app.libs.file-builder (:require [app.common.data :as d] - [app.common.file-builder :as fb])) + [app.common.file-builder :as fb] + [cuerdas.core :as str])) + +(defn parse-data [data] + (as-> data $ + (js->clj $ :keywordize-keys true) + ;; Transforms camelCase to kebab-case + (d/deep-mapm + (fn [[k v]] + [(-> k d/name str/kebab keyword) v]) $))) (deftype File [^:mutable file] Object - (addPage [self name] - (set! file (fb/add-page file name)) - (str (:current-page-id file)))) + (addPage + ([self name] + (addPage self name nil)) + + ([self name options] + (set! file (fb/add-page file {:name name :options options})) + (str (:current-page-id file)))) + + (closePage [self] + (set! file (fb/close-page file))) + + (addArtboard [self data] + (set! file (fb/add-artboard file (parse-data data))) + (str (:last-id file))) + + (closeArtboard [self data] + (set! file (fb/close-artboard file))) + + (addGroup [self data] + (set! file (fb/add-group file (parse-data data))) + (str (:last-id file))) + + (closeGroup [self] + (set! file (fb/close-group file))) + + (createRect [self data] + (set! file (fb/create-rect file (parse-data data)))) + + (createCircle [self data] + (set! file (fb/create-circle file (parse-data data)))) + + (createPath [self data] + (set! file (fb/create-path file (parse-data data)))) + + (createText [self data] + (set! file (fb/create-text file (parse-data data)))) + + (createImage [self data] + (set! file (fb/create-image file (parse-data data)))) + + (createSVG [self data] + (set! file (fb/create-svg-raw file (parse-data data)))) + + (closeSVG [self] + (set! file (fb/close-svg-raw file)))) (defn create-file-export [^string name] (File. (fb/create-file name))) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index 2ee9d6db72..cb5a327d54 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -16,6 +16,7 @@ [app.common.uuid :as uuid] [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.embed :as embed] + [app.main.ui.shapes.export :as use] [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.group :as group] @@ -158,6 +159,9 @@ :style {:width "100%" :height "100%" :background background-color}} + + [:& use/export-page {:options (:options data)}] + (for [item shapes] (let [frame? (= (:type item) :frame)] (cond diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 6cda48b11c..5fcdb91537 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -83,6 +83,37 @@ (cond-> mask? (obj/set! "penpot:masked-group" "true")))))) +(defn prefix-keys [m] + (letfn [(prefix-entry [[k v]] + [(str "penpot:" (d/name k)) v])] + (into {} (map prefix-entry) m))) + +(mf/defc export-grid-data + [{:keys [grids]}] + [:> "penpot:grids" #js {} + (for [{:keys [type display params]} grids] + (let [props (->> (d/without-keys params [:color]) + (prefix-keys) + (clj->js))] + [:> "penpot:grid" + (-> props + (obj/set! "penpot:color" (get-in params [:color :color])) + (obj/set! "penpot:opacity" (get-in params [:color :opacity])) + (obj/set! "penpot:type" (d/name type)) + (cond-> (some? display) + (obj/set! "penpot:display" (str display))))]))]) + +(mf/defc export-page + [{:keys [options]}] + [:> "penpot:page" #js {} + (let [saved-grids (get options :saved-grids)] + (when-not (empty? saved-grids) + (let [parse-grid + (fn [[type params]] + {:type type :params params}) + grids (->> saved-grids (mapv parse-grid))] + [:& export-grid-data {:grids grids}])))]) + (mf/defc export-data [{:keys [shape]}] (let [props (-> (obj/new) @@ -135,5 +166,10 @@ (clj->js))))] [:> "penpot:svg-content" props (for [leaf (->> shape :content :content (filter string?))] - [:> "penpot:svg-child" {} leaf])]))])) + [:> "penpot:svg-child" {} leaf])])) + + + (when (and (= (:type shape) :frame) + (not (empty? (:grids shape)))) + [:& export-grid-data {:grids (:grids shape)}])])) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 8ead981ca2..6a79f9c1e1 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -14,6 +14,7 @@ [app.main.ui.context :as muc] [app.main.ui.measurements :as msr] [app.main.ui.shapes.embed :as embed] + [app.main.ui.shapes.export :as use] [app.main.ui.workspace.shapes :as shapes] [app.main.ui.workspace.shapes.text.editor :as editor] [app.main.ui.workspace.viewport.actions :as actions] @@ -188,6 +189,8 @@ :style {:background-color (get options :background "#E8E9EA") :pointer-events "none"}} + [:& use/export-page {:options options}] + [:& (mf/provider embed/context) {:value true} ;; Render root shape [:& shapes/root-shape {:key page-id diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 97f8113214..8609e3ed11 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -32,7 +32,8 @@ (defn get-data ([node] - (->> node :content (d/seek #(= :penpot:shape (:tag %))))) + (->> node :content (d/seek #(or (= :penpot:shape (:tag %)) + (= :penpot:page (:tag %)))))) ([node tag] (->> (get-data node) :content @@ -98,6 +99,41 @@ m attrs)) +(defn without-penpot-prefix + [m] + (let [no-penpot-prefix? + (fn [[k v]] + (not (str/starts-with? (d/name k) "penpot:")))] + (into {} (filter no-penpot-prefix?) m))) + +(defn remove-penpot-prefix + [m] + (into {} + (map (fn [[k v]] + (if (str/starts-with? (d/name k) "penpot:") + [(-> k d/name (str/replace "penpot:" "") keyword) v] + [k v]))) + m)) + +(defn camelize [[k v]] + [(-> k d/name str/camel keyword) v]) + +(defn camelize-keys + [m] + (assert (map? m) (str m)) + + (into {} (map camelize) m)) + +(defn fix-style-attr + [m] + (let [fix-style + (fn [[k v]] + (if (= k :style) + [k (-> v parse-style camelize-keys)] + [k v]))] + + (d/deep-mapm (comp camelize fix-style) m))) + (def search-data-node? #{:rect :image :path :text :circle}) (defn get-svg-data @@ -372,6 +408,26 @@ :suffix (get-meta node :suffix) :scale (get-meta node :scale d/parse-double)}) + +(defn parse-grid-node [node] + (let [attrs (-> node :attrs remove-penpot-prefix) + color {:color (:color attrs) + :opacity (-> attrs :opacity d/parse-double)} + + params (-> (d/without-keys attrs [:color :opacity :display :type]) + (d/update-when :size d/parse-double) + (d/update-when :item-length d/parse-double) + (d/update-when :gutter d/parse-double) + (d/update-when :margin d/parse-double) + (assoc :color color))] + {:type (-> attrs :type keyword) + :display (-> attrs :display str->bool) + :params params})) + +(defn parse-grids [node] + (let [grid-node (get-data node :penpot:grids)] + (->> grid-node :content (mapv parse-grid-node)))) + (defn extract-from-data ([node tag] (extract-from-data node tag identity)) @@ -477,32 +533,6 @@ props))) -(defn without-penpot-prefix - [m] - (let [no-penpot-prefix? - (fn [[k v]] - (not (str/starts-with? (d/name k) "penpot:")))] - (into {} (filter no-penpot-prefix?) m))) - -(defn camelize [[k v]] - [(-> k d/name str/camel keyword) v]) - -(defn camelize-keys - [m] - (assert (map? m) (str m)) - - (into {} (map camelize) m)) - -(defn fix-style-attr - [m] - (let [fix-style - (fn [[k v]] - (if (= k :style) - [k (-> v parse-style camelize-keys)] - [k v]))] - - (d/deep-mapm (comp camelize fix-style) m))) - (defn add-svg-content [props node] (let [svg-content (get-data node :penpot:svg-content) @@ -522,6 +552,12 @@ :tag tag :content node-content}))) +(defn add-frame-data [props node] + (let [grids (parse-grids node)] + (cond-> props + (not (empty? grids)) + (assoc :grids grids)))) + (defn get-image-name [node] (get-in node [:attrs :penpot:name])) @@ -550,6 +586,9 @@ (cond-> (= :svg-raw type) (add-svg-content node)) + (cond-> (= :frame type) + (add-frame-data node)) + (cond-> (= :group type) (add-group-data node)) @@ -561,3 +600,17 @@ (cond-> (= :text type) (add-text-data node)))))) + +(defn parse-page-data + [node] + (let [style (parse-style (get-in node [:attrs :style])) + background (:background style) + grids (->> (parse-grids node) + (group-by :type) + (d/mapm (fn [k v] (-> v first :params))))] + (cond-> {} + (some? background) + (assoc-in [:options :background] background) + + (not (empty? grids)) + (assoc-in [:options :saved-grids] grids)))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 6a1c338a9c..ac01f53ee3 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -137,11 +137,13 @@ [file [page-name content]] (if (cip/valid? content) (let [nodes (->> content cip/node-seq) - file-id (:id file)] + file-id (:id file) + page-data (-> (cip/parse-page-data content) + (assoc :name page-name))] (->> (rx/from nodes) (rx/filter cip/shape?) (rx/mapcat (partial resolve-images file-id)) - (rx/reduce add-shape-file (fb/add-page file page-name)) + (rx/reduce add-shape-file (fb/add-page file page-data)) (rx/map fb/close-page))) (rx/empty))) From 8952cb4e000d0969047253d270edf312726cf995 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 14 Jun 2021 16:12:51 +0200 Subject: [PATCH 086/204] :sparkles: Adds constraints to export/import --- frontend/src/app/main/ui/shapes/export.cljs | 21 +++++++++++++-------- frontend/src/app/util/import/parser.cljc | 16 ++++++++++++++-- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 5fcdb91537..a74740040c 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -70,6 +70,11 @@ (obj/set! "penpot:center-x" (-> center :x str)) (obj/set! "penpot:center-y" (-> center :y str)) + ;; Constraints + (add! :constraints-h) + (add! :constraints-v) + (add! :fixed-scroll) + (cond-> (and rect? (some? (:r1 shape))) (-> (add! :r1) (add! :r2) @@ -105,14 +110,14 @@ (mf/defc export-page [{:keys [options]}] - [:> "penpot:page" #js {} - (let [saved-grids (get options :saved-grids)] - (when-not (empty? saved-grids) - (let [parse-grid - (fn [[type params]] - {:type type :params params}) - grids (->> saved-grids (mapv parse-grid))] - [:& export-grid-data {:grids grids}])))]) + (let [saved-grids (get options :saved-grids)] + (when-not (empty? saved-grids) + (let [parse-grid + (fn [[type params]] + {:type type :params params}) + grids (->> saved-grids (mapv parse-grid))] + [:> "penpot:page" #js {} + [:& export-grid-data {:grids grids}]])))) (mf/defc export-data [{:keys [shape]}] diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 8609e3ed11..2f3857b6a5 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -267,7 +267,10 @@ flip-y (get-meta node :flip-y str->bool) proportion (get-meta node :proportion d/parse-double) proportion-lock (get-meta node :proportion-lock str->bool) - rotation (get-meta node :rotation d/parse-double)] + rotation (get-meta node :rotation d/parse-double) + constraints-h (get-meta node :constraints-h keyword) + constraints-v (get-meta node :constraints-v keyword) + fixed-scroll (get-meta node :fixed-scroll str->bool)] (-> props (assoc :name name) @@ -279,7 +282,16 @@ (assoc :flip-y flip-y) (assoc :proportion proportion) (assoc :proportion-lock proportion-lock) - (assoc :rotation rotation)))) + (assoc :rotation rotation) + + (cond-> (some? constraints-h) + (assoc :constraints-h constraints-h)) + + (cond-> (some? constraints-v) + (assoc :constraints-v constraints-v)) + + (cond-> (some? fixed-scroll) + (assoc :fixed-scroll fixed-scroll))))) (defn add-position [props type node svg-data] From 9fc633080a97d852b60b5951b2f5d6e632f34dfd Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 14 Jun 2021 17:04:14 +0200 Subject: [PATCH 087/204] :sparkles: Upload fill-image data --- frontend/src/app/util/import/parser.cljc | 80 +++++++++++++++++------- frontend/src/app/worker/import.cljs | 3 +- 2 files changed, 61 insertions(+), 22 deletions(-) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 2f3857b6a5..ab5fc82db3 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -30,14 +30,28 @@ (and (vector? node) (= ::close (first node)))) +(defn find-node + [node tag] + (when (some? node) + (->> node :content (d/seek #(= (:tag %) tag))))) + +(defn find-node-by-id + [id coll] + (->> coll (d/seek #(= id (-> % :attrs :id))))) + +(defn find-all-nodes + [node tag] + (when (some? node) + (->> node :content (filterv #(= (:tag %) :defs))))) + (defn get-data ([node] - (->> node :content (d/seek #(or (= :penpot:shape (:tag %)) - (= :penpot:page (:tag %)))))) - ([node tag] - (->> (get-data node) - :content - (d/seek #(= tag (:tag %)))))) + (or (find-node node :penpot:shape) + (find-node node :penpot:page))) + + ([node tag] + (-> (get-data node) + (find-node tag)))) (defn get-type [node] @@ -184,6 +198,7 @@ selrect (gsh/content->selrect content-tr) points (-> (gsh/rect->points selrect) (gsh/transform-points center transform))] + (-> props (assoc :content content) (assoc :selrect selrect) @@ -202,9 +217,6 @@ (def url-regex #"url\(#([^\)]*)\)") -(defn seek-node - [id coll] - (->> coll (d/seek #(= id (-> % :attrs :id))))) (defn parse-stops [gradient-node] @@ -219,7 +231,7 @@ (defn parse-gradient [node ref-url] (let [[_ url] (re-find url-regex ref-url) - gradient-node (->> node (node-seq) (seek-node url)) + gradient-node (->> node (node-seq) (find-node-by-id url)) stops (parse-stops gradient-node)] (when (contains? (:attrs gradient-node) :penpot:gradient) @@ -276,14 +288,18 @@ (assoc :name name) (assoc :blocked blocked) (assoc :hidden hidden) - (assoc :transform transform) - (assoc :transform-inverse transform-inverse) (assoc :flip-x flip-x) (assoc :flip-y flip-y) (assoc :proportion proportion) (assoc :proportion-lock proportion-lock) (assoc :rotation rotation) + (cond-> (some? transform) + (assoc :transform transform)) + + (cond-> (some? transform-inverse) + (assoc :transform-inverse transform-inverse)) + (cond-> (some? constraints-h) (assoc :constraints-h constraints-h)) @@ -378,12 +394,17 @@ (assoc :rx rx :ry ry)))) (defn add-image-data - [props node] - (-> props - (assoc-in [:metadata :id] (get-meta node :media-id)) - (assoc-in [:metadata :width] (get-meta node :media-width)) - (assoc-in [:metadata :height] (get-meta node :media-height)) - (assoc-in [:metadata :mtype] (get-meta node :media-mtype)))) + [props type node] + (let [metadata {:id (get-meta node :media-id) + :width (get-meta node :media-width) + :height (get-meta node :media-height) + :mtype (get-meta node :media-mtype)}] + (cond-> props + (= type :image) + (assoc :metadata metadata) + + (not= type :image) + (assoc :fill-image metadata)))) (defn add-text-data [props node] @@ -570,13 +591,30 @@ (not (empty? grids)) (assoc :grids grids)))) +(defn has-image? + [type node] + (let [pattern-image + (-> node + (find-node :defs) + (find-node :pattern) + (find-node :image))] + (or (= type :image) + (some? pattern-image)))) + (defn get-image-name [node] (get-in node [:attrs :penpot:name])) (defn get-image-data [node] - (let [svg-data (get-svg-data :image node)] + (let [pattern-data + (-> node + (find-node :defs) + (find-node :pattern) + (find-node :image) + :attrs) + image-data (get-svg-data :image node) + svg-data (or image-data pattern-data)] (:xlink:href svg-data))) (defn parse-data @@ -607,8 +645,8 @@ (cond-> (= :rect type) (add-rect-data node svg-data)) - (cond-> (= :image type) - (add-image-data node)) + (cond-> (some? (get-in node [:attrs :penpot:media-id])) + (add-image-data type node)) (cond-> (= :text type) (add-text-data node)))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index ac01f53ee3..48fe14a153 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -117,7 +117,8 @@ (defn resolve-images [file-id node] - (if (and (cip/shape? node) (= (cip/get-type node) :image) (not (cip/close? node))) + (if (and (not (cip/close? node)) + (cip/has-image? type node)) (let [name (cip/get-image-name node) data-uri (cip/get-image-data node)] (->> (upload-media-files file-id name data-uri) From 76e23097783d29f0c8af61dfe72aaed8010a9527 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 15 Jun 2021 10:39:06 +0200 Subject: [PATCH 088/204] :sparkles: Improve builder library --- frontend/src/app/libs/file_builder.cljs | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 2aa4bff3af..8d64cbb3a8 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -15,19 +15,23 @@ (js->clj $ :keywordize-keys true) ;; Transforms camelCase to kebab-case (d/deep-mapm - (fn [[k v]] - [(-> k d/name str/kebab keyword) v]) $))) + (fn [[key value]] + (let [value (if (= (type value) js/Symbol) + (keyword (js/Symbol.keyFor value)) + value) + key (-> key d/name str/kebab keyword)] + [key value])) $))) (deftype File [^:mutable file] Object - (addPage - ([self name] - (addPage self name nil)) + (addPage [self name] + (set! file (fb/add-page file {:name name})) + (str (:current-page-id file))) - ([self name options] - (set! file (fb/add-page file {:name name :options options})) - (str (:current-page-id file)))) + (addPage [self name options] + (set! file (fb/add-page file {:name name :options options})) + (str (:current-page-id file))) (closePage [self] (set! file (fb/close-page file))) @@ -65,7 +69,10 @@ (set! file (fb/create-svg-raw file (parse-data data)))) (closeSVG [self] - (set! file (fb/close-svg-raw file)))) + (set! file (fb/close-svg-raw file))) + + (asMap [self] + (clj->js file))) (defn create-file-export [^string name] (File. (fb/create-file name))) From 5c9ec92cc5e7e17331ff37474c7cb8e5c415819c Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 14 Jun 2021 21:48:29 +0200 Subject: [PATCH 089/204] :sparkles: UI debug toggle for export/import --- .../app/main/ui/components/file_uploader.cljs | 22 ++++--- .../src/app/main/ui/dashboard/file_menu.cljs | 7 ++- frontend/src/app/main/ui/dashboard/fonts.cljs | 2 +- .../src/app/main/ui/dashboard/import.cljs | 12 ++-- .../app/main/ui/dashboard/project_menu.cljs | 58 +++++++++++++------ .../src/app/main/ui/dashboard/projects.cljs | 27 +++------ frontend/src/app/main/ui/dashboard/team.cljs | 2 +- .../src/app/main/ui/settings/profile.cljs | 2 +- .../app/main/ui/workspace/left_toolbar.cljs | 2 +- .../app/main/ui/workspace/sidebar/assets.cljs | 2 +- frontend/src/app/util/debug.cljs | 2 +- frontend/translations/en.po | 3 + 12 files changed, 81 insertions(+), 60 deletions(-) diff --git a/frontend/src/app/main/ui/components/file_uploader.cljs b/frontend/src/app/main/ui/components/file_uploader.cljs index 54898407a7..1151976143 100644 --- a/frontend/src/app/main/ui/components/file_uploader.cljs +++ b/frontend/src/app/main/ui/components/file_uploader.cljs @@ -12,17 +12,21 @@ [app.util.dom :as dom])) (mf/defc file-uploader - [{:keys [accept multi label-text label-class input-id input-ref on-selected] :as props}] + {::mf/forward-ref true} + [{:keys [accept multi label-text label-class input-id on-selected] :as props} input-ref] (let [opt-pick-one #(if multi % (first %)) - on-files-selected (fn [event] - (let [target (dom/get-target event)] - (st/emit! - (some-> target - (dom/get-files) - (opt-pick-one) - (on-selected))) - (dom/clean-value! target)))] + on-files-selected + (mf/use-callback + (mf/deps opt-pick-one) + (fn [event] + (let [target (dom/get-target event)] + (st/emit! + (some-> target + (dom/get-files) + (opt-pick-one) + (on-selected))) + (dom/clean-value! target))))] [:* (when label-text [:label {:for input-id :class-name label-class} label-text]) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 6dcedd52ab..5de3b72a2e 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -14,6 +14,7 @@ [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.context :as ctx] [app.main.worker :as uw] + [app.util.debug :as d] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] @@ -192,7 +193,8 @@ [[(tr "dashboard.duplicate-multi" file-count) on-duplicate] (when (or (seq current-projects) (seq other-teams)) [(tr "dashboard.move-to-multi" file-count) nil sub-options]) - #_[(tr "dashboard.export-multi" file-count) on-export-files] + (when (d/debug? :export) + [(tr "dashboard.export-multi" file-count) on-export-files]) [:separator] [(tr "labels.delete-multi-files" file-count) on-delete]] @@ -204,7 +206,8 @@ (if (:is-shared file) [(tr "dashboard.remove-shared") on-del-shared] [(tr "dashboard.add-shared") on-add-shared]) - #_[(tr "dashboard.export-single") on-export-files] + (when (d/debug? :export) + [(tr "dashboard.export-single") on-export-files]) [:separator] [(tr "labels.delete") on-delete]])] diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index bda48caadf..9363698bf4 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -144,7 +144,7 @@ [:& file-uploader {:input-id "font-upload" :accept cm/str-font-types :multi true - :input-ref input-ref + :ref input-ref :on-selected on-selected}]]] [:* diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index c30e48f03f..dcec18c73a 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -39,17 +39,15 @@ (log/debug :action "import-end") (when on-finish-import (on-finish-import)))))))))) -(mf/defc import-button - [{:keys [project-id on-finish-import]}] +(mf/defc import-form + {::mf/forward-ref true} + [{:keys [project-id on-finish-import]} external-ref] - (let [file-input (mf/use-ref nil) - on-file-selected (use-import-file project-id on-finish-import)] + (let [on-file-selected (use-import-file project-id on-finish-import)] [:form.import-file - [:button.import-file-btn {:type "button" - :on-click #(dom/click (mf/ref-val file-input))} i/import] [:& file-uploader {:accept "application/zip" :multi true - :input-ref file-input + :ref external-ref :on-selected on-file-selected}]])) diff --git a/frontend/src/app/main/ui/dashboard/project_menu.cljs b/frontend/src/app/main/ui/dashboard/project_menu.cljs index 8ea8b4132b..68c166a326 100644 --- a/frontend/src/app/main/ui/dashboard/project_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/project_menu.cljs @@ -14,6 +14,9 @@ [app.main.store :as st] [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.context :as ctx] + [app.main.ui.dashboard.import :as udi] + [app.util.debug :as d] + [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [beicon.core :as rx] @@ -71,22 +74,43 @@ :title (tr "modals.delete-project-confirm.title") :message (tr "modals.delete-project-confirm.message") :accept-label (tr "modals.delete-project-confirm.accept") - :on-accept delete-fn}))] + :on-accept delete-fn})) - [:& 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 [(when-not (:is-default project) - [(tr "labels.rename") on-edit]) - [(tr "dashboard.duplicate") on-duplicate] - [(tr "dashboard.pin-unpin") toggle-pin] - (when (seq teams) - [(tr "dashboard.move-to") nil - (for [team teams] - [(:name team) (on-move (:id team))])]) - [:separator] - [(tr "labels.delete") on-delete]]}])) + + file-input (mf/use-ref nil) + + on-import-files + (mf/use-callback + (fn [] + (dom/click (mf/ref-val file-input)))) + + on-finish-import + (mf/use-callback + (fn [] + (st/emit! (dd/fetch-recent-files) + (dd/clear-selected-files))))] + + [:* + [:& udi/import-form {:ref file-input + :project-id (:id project) + :on-finish-import on-finish-import}] + [:& 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 [(when-not (:is-default project) + [(tr "labels.rename") on-edit]) + [(tr "dashboard.duplicate") on-duplicate] + [(tr "dashboard.pin-unpin") toggle-pin] + (when (seq teams) + [(tr "dashboard.move-to") nil + (for [team teams] + [(:name team) (on-move (:id team))])]) + (when (d/debug? :import) + [(tr "dashboard.import") on-import-files]) + [:separator] + [(tr "labels.delete") on-delete]]}]])) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 8e6485b379..262fcaac62 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -21,8 +21,7 @@ [app.util.router :as rt] [app.util.time :as dt] [okulary.core :as l] - [rumext.alpha :as mf] - [app.main.ui.dashboard.import :refer [import-button]])) + [rumext.alpha :as mf])) (mf/defc header {::mf/wrap [mf/memo]} @@ -98,13 +97,7 @@ (fn [] (let [mdata {:on-success on-file-created} params {:project-id (:id project)}] - (st/emit! (dd/create-file (with-meta params mdata)))))) - - on-finish-import - (mf/use-callback - (fn [] - (st/emit! (dd/fetch-recent-files) - (dd/clear-selected-files))))] + (st/emit! (dd/create-file (with-meta params mdata))))))] [:div.dashboard-project-row {:class (when first? "first")} [:div.project @@ -117,13 +110,12 @@ (tr "labels.drafts") (:name project))]) - (when (:menu-open @local) - [:& project-menu {:project project - :show? (:menu-open @local) - :left (:x (:menu-pos @local)) - :top (:y (:menu-pos @local)) - :on-edit on-edit-open - :on-menu-close on-menu-close}]) + [:& project-menu {:project project + :show? (:menu-open @local) + :left (:x (:menu-pos @local)) + :top (:y (:menu-pos @local)) + :on-edit on-edit-open + :on-menu-close on-menu-close}] [:span.info (str file-count " files")] (when (> file-count 0) @@ -131,9 +123,6 @@ (dt/timeago {:locale locale}))] [:span.recent-files-row-title-info (str ", " time)])) - #_[:& import-button {:project-id (:id project) - :on-finish-import on-finish-import}] - (when-not (:is-default project) [:span.pin-icon.tooltip.tooltip-bottom {:class (when (:is-pinned project) "active") diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 168a944c25..4eb028314c 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -285,7 +285,7 @@ [:img {:src (cfg/resolve-team-photo-url team)}] [:& file-uploader {:accept "image/jpeg,image/png" :multi false - :input-ref finput + :ref finput :on-selected on-file-selected}]]] [:div.block.owner-block diff --git a/frontend/src/app/main/ui/settings/profile.cljs b/frontend/src/app/main/ui/settings/profile.cljs index 2ed0cb1829..03a6ddda28 100644 --- a/frontend/src/app/main/ui/settings/profile.cljs +++ b/frontend/src/app/main/ui/settings/profile.cljs @@ -96,7 +96,7 @@ [:img {:src photo}] [:& file-uploader {:accept "image/jpeg,image/png" :multi false - :input-ref file-input + :ref file-input :on-selected on-file-selected}]]])) ;; --- Profile Page diff --git a/frontend/src/app/main/ui/workspace/left_toolbar.cljs b/frontend/src/app/main/ui/workspace/left_toolbar.cljs index 1692a392f6..f7ed31957b 100644 --- a/frontend/src/app/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/app/main/ui/workspace/left_toolbar.cljs @@ -52,7 +52,7 @@ [:& file-uploader {:input-id "image-upload" :accept cm/str-image-types :multi true - :input-ref ref + :ref ref :on-selected on-files-selected}]]])) (mf/defc left-toolbar diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 26fd4d30b9..2935531008 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -743,7 +743,7 @@ i/plus [:& file-uploader {:accept cm/str-image-types :multi true - :input-ref input-ref + :ref input-ref :on-selected on-file-selected}]]]) [:& asset-section-block {:role :content} diff --git a/frontend/src/app/util/debug.cljs b/frontend/src/app/util/debug.cljs index d83a3e0543..cfffaf3f33 100644 --- a/frontend/src/app/util/debug.cljs +++ b/frontend/src/app/util/debug.cljs @@ -6,7 +6,7 @@ [app.common.math :as mth] [cljs.pprint :refer [pprint]])) -(def debug-options #{:bounding-boxes :group :events :rotation-handler :resize-handler :selection-center #_:simple-selection}) +(def debug-options #{:bounding-boxes :group :events :rotation-handler :resize-handler :selection-center :export :import #_:simple-selection}) ;; These events are excluded when we activate the :events flag (def debug-exclude-events diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 6b383d7392..46916731f2 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -2694,5 +2694,8 @@ msgstr "Export file" msgid "dashboard.export-multi" msgstr "Export %s files" +msgid "dashboard.import" +msgstr "Import files" + msgid "dashboard.options" msgstr "Options" From 9300adf37434ce36ad05fae6ae2b0fee501d4edd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 15 Jun 2021 13:30:30 +0200 Subject: [PATCH 090/204] :tada: Activate edit file menu in viewer --- .../styles/main/partials/viewer-header.scss | 8 +++ frontend/src/app/main/ui/viewer/header.cljs | 52 ++++++++++++------- frontend/translations/en.po | 4 +- frontend/translations/es.po | 4 +- 4 files changed, 46 insertions(+), 22 deletions(-) diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss index 891f836670..6012f4a954 100644 --- a/frontend/resources/styles/main/partials/viewer-header.scss +++ b/frontend/resources/styles/main/partials/viewer-header.scss @@ -81,6 +81,14 @@ } } + .file-menu { + .dropdown { + min-width: 100px; + right: 0px; + top: 40px; + } + } + .sitemap-zone { align-items: center; cursor: pointer; diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index ff22757b7b..5d6800d4f3 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -108,9 +108,9 @@ [{:keys [state locale] :as props}] (let [imode (:interactions-mode state) - show-dropdown? (mf/use-state false) - show-dropdown (mf/use-fn #(reset! show-dropdown? true)) - hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) + show-dropdown? (mf/use-state false) + toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) + hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) select-mode (mf/use-callback @@ -118,7 +118,7 @@ (st/emit! (dv/set-interactions-mode mode))))] [:div.view-options - [:div.view-options-dropdown {:on-click #(swap! show-dropdown? not)} + [:div.view-options-dropdown {:on-click toggle-dropdown} [:span (t locale "viewer.header.interactions")] i/arrow-down] [:& dropdown {:show @show-dropdown? @@ -139,14 +139,13 @@ [:span.icon i/tick] [:span.label (t locale "viewer.header.show-interactions-on-click")]]]]])) - (mf/defc comments-menu [{:keys [locale] :as props}] (let [{cmode :mode cshow :show} (mf/deref refs/comments-local) - show-dropdown? (mf/use-state false) - show-dropdown (mf/use-fn #(reset! show-dropdown? true)) - hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) + show-dropdown? (mf/use-state false) + toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) + hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) update-mode (mf/use-callback @@ -159,7 +158,7 @@ (st/emit! (dcm/update-filters {:show mode}))))] [:div.view-options - [:div.icon {:on-click #(swap! show-dropdown? not)} i/eye] + [:div.icon {:on-click toggle-dropdown} i/eye] [:& dropdown {:show @show-dropdown? :on-close hide-dropdown} [:ul.dropdown.with-check @@ -180,6 +179,28 @@ [:span.icon i/tick] [:span.label (t locale "labels.hide-resolved-comments")]]]]])) +(mf/defc file-menu + [{:keys [locale project-id file-id page-id] :as props}] + (let [show-dropdown? (mf/use-state false) + toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) + hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) + + on-edit + (mf/use-callback + (mf/deps project-id file-id page-id) + (st/emitf (rt/nav :workspace + {:project-id project-id + :file-id file-id} + {:page-id page-id})))] + [:div.file-menu + [:span.btn-icon-dark.btn-small {:on-click toggle-dropdown} + i/actions + [:& dropdown {:show @show-dropdown? + :on-close hide-dropdown} + [:ul.dropdown + [:li {:on-click on-edit} + [:span.label (t locale "viewer.header.edit-file")]]]]]])) + (mf/defc header [{:keys [data index section state] :as props}] (let [{:keys [project file page frames]} data @@ -209,13 +230,6 @@ (mf/deps project) (st/emitf (dv/go-to-dashboard project))) - on-edit - (mf/use-callback - (mf/deps project-id file-id page-id) - (st/emitf (rt/nav :workspace - {:project-id project-id - :file-id file-id} - {:page-id page-id}))) navigate (mf/use-callback (mf/deps file-id page-id) @@ -283,6 +297,8 @@ i/full-screen-off i/full-screen)] - [:span.btn-icon-dark.btn-small - i/actions]]])) + [:& file-menu {:locale locale + :project-id project-id + :file-id file-id + :page-id page-id}]]])) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index a745b6c076..6b70646a90 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -1454,8 +1454,8 @@ msgid "viewer.header.dont-show-interactions" msgstr "Don't show interactions" #: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Edit page" +msgid "viewer.header.edit-file" +msgstr "Edit file" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index bb520ed395..b668c94284 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1444,8 +1444,8 @@ msgid "viewer.header.dont-show-interactions" msgstr "No mostrar interacciones" #: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Editar página" +msgid "viewer.header.edit-file" +msgstr "Editar archivo" #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.fullscreen" From 2cd7f0f74c0d77f22940ebb672139fd294f20d12 Mon Sep 17 00:00:00 2001 From: Andrew Montoya Date: Mon, 14 Jun 2021 18:08:27 -0600 Subject: [PATCH 091/204] :lipstick: Fix add font button wrap --- .../styles/main/partials/dashboard-fonts.scss | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/frontend/resources/styles/main/partials/dashboard-fonts.scss b/frontend/resources/styles/main/partials/dashboard-fonts.scss index 0f61a12a3a..2370def964 100644 --- a/frontend/resources/styles/main/partials/dashboard-fonts.scss +++ b/frontend/resources/styles/main/partials/dashboard-fonts.scss @@ -121,11 +121,9 @@ fill: $color-gray-30; } } - } } - .filenames { display: flex; flex-direction: column; @@ -153,7 +151,6 @@ transform: rotate(45deg); } } - } } } @@ -164,7 +161,6 @@ display: flex; flex-direction: column; - .upload-button { width: 100px; } @@ -182,7 +178,7 @@ .banner { background-color: unset; - display: flex; + display: flex; .icon { display: flex; @@ -203,6 +199,10 @@ width: 80%; color: $color-gray-40; } + + .btn-primary { + flex-shrink: 0; + } } .fonts-placeholder { @@ -217,7 +217,6 @@ border: 1px dashed $color-gray-20; margin-top: 16px; - .icon { svg { fill: $color-gray-40; From 2711181e19527725e3fc123be06556bf48b00940 Mon Sep 17 00:00:00 2001 From: Eranot Date: Sun, 13 Jun 2021 02:26:51 +0000 Subject: [PATCH 092/204] :globe_with_meridians: Add translations for: Portuguese (Brazil). Currently translated at 46.8% (310 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/pt_BR/ --- frontend/translations/pt_BR.po | 231 ++++++++++++++++++++++++++++++++- 1 file changed, 230 insertions(+), 1 deletion(-) diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index bbd103dcc6..30a8a3aff7 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-05-26 09:23+0000\n" +"PO-Revision-Date: 2021-06-15 18:34+0000\n" "Last-Translator: Eranot \n" "Language-Team: Portuguese (Brazil) \n" @@ -1025,3 +1025,232 @@ msgstr "Excluir membro da equipe" #: 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?" + +#: 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.outer" +msgstr "Fora" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Misturado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Dentro" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Pontilhada" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Tracejada" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +msgid "workspace.options.size-presets" +msgstr "Predefinições de tamanho" + +#: 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/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.offsety" +msgstr "Y" + +#: 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 interior" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Sombra projetada" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Borrar" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Rotação" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.single-corners" +msgstr "Cantos individuais" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Todos cantos" + +msgid "workspace.options.radius" +msgstr "Raio" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Protótipo" + +#: 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/menus/interactions.cljs +msgid "workspace.options.none" +msgstr "Nenhum" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.navigate-to" +msgstr "Navegar para" + +#: 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/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.title" +msgstr "Grades & Layouts" + +#: 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/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.params.width" +msgstr "Largura" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Usar padrão" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Superior" + +#: 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.type.right" +msgstr "Direita" + +#: 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.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Inferior" + +#: 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/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.set-default" +msgstr "Definir como padrão" + +#: 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.margin" +msgstr "Margem" + +#: 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.columns" +msgstr "Colunas" + +#: 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.auto" +msgstr "Automático" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Preencher" + +#: 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 +msgid "workspace.options.export.suffix" +msgstr "Sufixo" + +#: 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" + +#: 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.cljs +msgid "workspace.options.design" +msgstr "Design" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Componente" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Camada" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Fundo" + +msgid "workspace.library.store" +msgstr "Bibliotecas da loja" + +msgid "workspace.library.own" +msgstr "Minhas bibliotecas" + +msgid "workspace.library.libraries" +msgstr "Bibliotecas" + +msgid "workspace.library.all" +msgstr "Todas bibliotecas" From 796141f2b87ef56a50975c5a7956985d21352d19 Mon Sep 17 00:00:00 2001 From: Amine Gdoura Date: Mon, 14 Jun 2021 18:21:21 +0000 Subject: [PATCH 093/204] :globe_with_meridians: Add translations for: Arabic. Currently translated at 23.7% (157 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 184 +++++++++++++++++++++++++++++++++++- 1 file changed, 183 insertions(+), 1 deletion(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 4fe8cbd344..97f5784f9c 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-06-10 15:33+0000\n" +"PO-Revision-Date: 2021-06-15 18:34+0000\n" "Last-Translator: Amine Gdoura \n" "Language-Team: Arabic \n" @@ -495,3 +495,185 @@ msgstr "فتح ملف في علامة تبويب جديدة" #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.num-of-members" msgstr "٪s الأعضاء" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "عرض" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "صلب" + +msgid "handoff.attributes.stroke.style.none" +msgstr "لا أحد" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "مختلط" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "منقط" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "خارج" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "داخل" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "مركز" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "ظل" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +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.rotation" +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.left" +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" +msgstr "تخطيط" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "عرض" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "ارتفاع" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "تحميل صورة المصدر" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.rgba" +msgstr "RGBA" + +#: 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.hex" +msgstr "HEX" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "قيمة" + +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +msgstr "حدث خطأ" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "البريد الإلكتروني" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" +"يرجى وصف سبب بريدك الإلكتروني ، وتحديد ما إذا كانت مشكلة أم فكرة أم شك. سيرد " +"أحد أعضاء فريقنا في أسرع وقت ممكن." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +msgstr "موضوع" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-title" +msgstr "مناقشات الفريق" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle2" +msgstr "" +"يمكنك طرح الأسئلة والإجابة عليها، إجراء محادثات مفتوحة، ومتابعة القرارات " +"التي تؤثر على المشروع." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle1" +msgstr "انضم إلى منتدى التواصل التعاوني لفريق Penpot." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-go-to" +msgstr "اذهب إلى المناقشات" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "وصف" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "ترغب في الكلام؟ تحدث معنا في Gitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-start" +msgstr "انضم إلى الدردشة" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.wrong-old-password" +msgstr "كلمة المرور القديمة غير صحيحة" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "يجب ألا تقل كلمة المرور عن 8 أحرف" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "يجب أن تتطابق كلمة مرور التأكيد" + +msgid "errors.network" +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 +msgid "errors.media-type-not-allowed" +msgstr "يبدو أن هذه ليست صورة صالحة." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "الصورة كبيرة جدا بحيث لا يمكن إدراجها (يجب أن تكون أقل من 5mb)." + +msgid "errors.media-format-unsupported" +msgstr "تنسيق الصورة غير مدعوم (يجب أن يكون svg أو jpg أو png)." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "تم تعطيل مصادقة LDAP." + +#: src/app/main/ui/components/color_input.cljs +msgid "errors.invalid-color" +msgstr "لون غير صالح" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +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" +msgstr "حدث خطأ ما." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "يجب أن يتطابق البريد الإلكتروني للتأكيد" From 370b6bb2f21b211097f63e8c6371b3427638c7d7 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 16 Jun 2021 11:09:47 +0200 Subject: [PATCH 094/204] :bug: Fix problem with odd widh/height and antialias icons --- frontend/resources/styles/common/framework.scss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index d294b661d1..d690ec01dc 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -22,8 +22,8 @@ transition: all .4s; text-decoration: none !important; svg { - height: 15px; - width: 15px; + height: 16px; + width: 16px; } &.btn-large { font-size: $fs14; From cb5e3005343d8c5a6b907197f251e18a8250c832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 15 Jun 2021 16:04:57 +0200 Subject: [PATCH 095/204] :tada: Add full screen to view menu --- frontend/src/app/main/ui/viewer/header.cljs | 30 ++++++++++++++------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index 5d6800d4f3..ed3cc84ede 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -34,7 +34,8 @@ on-decrease on-zoom-to-50 on-zoom-to-100 - on-zoom-to-200] + on-zoom-to-200 + on-fullscreen] :as props}] (let [show-dropdown? (mf/use-state false)] [:div.zoom-widget {:on-click #(reset! show-dropdown? true)} @@ -52,7 +53,10 @@ [:li {:on-click on-zoom-to-100} "Zoom to 100%" [:span (sc/get-tooltip :reset-zoom)]] [:li {:on-click on-zoom-to-200} - "Zoom to 200%" [:span (sc/get-tooltip :zoom-200)]]]]])) + "Zoom to 200%" [:span (sc/get-tooltip :zoom-200)]] + [:li {:on-click on-fullscreen} + "Full screen"]]]])) + ;; "Full screen" [:span (sc/get-tooltip :full-screen)]]]]])) (mf/defc share-link [{:keys [page token] :as props}] @@ -234,7 +238,13 @@ (mf/use-callback (mf/deps file-id page-id) (fn [section] - (st/emit! (dv/go-to-section section))))] + (st/emit! (dv/go-to-section section)))) + + toggle-fullscreen + (mf/use-callback + (mf/deps fullscreen) + (fn [] + (if @fullscreen (fullscreen false) (fullscreen true))))] [:header.viewer-header [:div.main-icon @@ -288,17 +298,19 @@ :on-decrease (st/emitf dv/decrease-zoom) :on-zoom-to-50 (st/emitf dv/zoom-to-50) :on-zoom-to-100 (st/emitf dv/reset-zoom) - :on-zoom-to-200 (st/emitf dv/zoom-to-200)}] + :on-zoom-to-200 (st/emitf dv/zoom-to-200) + :on-fullscreen toggle-fullscreen}] [:span.btn-icon-basic.btn-small.tooltip.tooltip-bottom-left {:alt (t locale "viewer.header.fullscreen") - :on-click #(if @fullscreen (fullscreen false) (fullscreen true))} + :on-click toggle-fullscreen} (if @fullscreen i/full-screen-off i/full-screen)] - [:& file-menu {:locale locale - :project-id project-id - :file-id file-id - :page-id page-id}]]])) + (when has-permission? + [:& file-menu {:locale locale + :project-id project-id + :file-id file-id + :page-id page-id}])]])) From 5572c0798f1193539d88bde3467266fb36f5c684 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 07:58:02 +0200 Subject: [PATCH 096/204] :sparkles: Minor improvement on start-tmux.sh script. --- docker/devenv/files/start-tmux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/devenv/files/start-tmux.sh b/docker/devenv/files/start-tmux.sh index e21875389e..6676425a8f 100755 --- a/docker/devenv/files/start-tmux.sh +++ b/docker/devenv/files/start-tmux.sh @@ -27,7 +27,7 @@ tmux new-window -t penpot:2 -n 'exporter' tmux select-window -t penpot:2 tmux send-keys -t penpot 'cd penpot/exporter' enter C-l tmux send-keys -t penpot 'rm -f target/app.js*' enter C-l -tmux send-keys -t penpot 'npx shadow-cljs watch main' enter +tmux send-keys -t penpot 'clojure -M:dev:shadow-cljs watch main' enter tmux split-window -v tmux send-keys -t penpot 'cd penpot/exporter' enter C-l From ac37f903d404d258ef2ad9a9c9dbc36bd2ee6f2f Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 07:58:21 +0200 Subject: [PATCH 097/204] :arrow_up: Update frontend npm deps. --- frontend/package.json | 12 +++++------ frontend/yarn.lock | 49 +++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 9dd8342283..9474d74375 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -27,19 +27,19 @@ "gulp-sourcemaps": "^3.0.0", "gulp-svg-sprite": "^1.5.0", "map-stream": "0.0.7", - "marked": "^2.0.3", + "marked": "^2.1.1", "mkdirp": "^1.0.4", - "postcss": "^8.2.15", + "postcss": "^8.3.5", "postcss-clean": "^1.2.2", "rimraf": "^3.0.0", - "sass": "^1.32.8", + "sass": "^1.35.1", "shadow-cljs": "^2.14.2" }, "dependencies": { - "date-fns": "^2.21.3", + "date-fns": "^2.22.1", "draft-js": "^0.11.7", - "highlight.js": "^10.6.0", - "js-beautify": "^1.13.5", + "highlight.js": "^11.0.1", + "js-beautify": "^1.14.0", "jszip": "^3.6.0", "luxon": "^1.26.0", "mousetrap": "^1.6.5", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 561957a9d3..ba27ea7440 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1186,10 +1186,10 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-fns@^2.21.3: - version "2.22.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.0.tgz#e25d79dda0639ae9e840d02ab34446613037c392" - integrity sha512-1TMrlJRPYjeR6KS9TgnJz4DX1rHW5NkfgIHpe9NWL6TGTzd6qo8mLo6ibt3p1wvXAu/DOal1Yce5YloFGeexBA== +date-fns@^2.22.1: + version "2.22.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.22.1.tgz#1e5af959831ebb1d82992bf67b765052d8f0efc4" + integrity sha512-yUFPQjrxEmIsMqlHhAhmxkuH769baF21Kk+nZwZGyrMoyLA+LugaQtC0+Tqf9CBUUULWwUJt6Q5ySI3LJDDCGg== dateformat@^3.0.3: version "3.0.3" @@ -2323,10 +2323,10 @@ he@1.1.1: resolved "https://registry.yarnpkg.com/he/-/he-1.1.1.tgz#93410fd21b009735151f8868c2f271f3427e23fd" integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= -highlight.js@^10.6.0: - version "10.7.2" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" - integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg== +highlight.js@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.0.1.tgz#a78bafccd9aa297978799fe5eed9beb7ee1ef887" + integrity sha512-EqYpWyTF2s8nMfttfBA2yLKPNoZCO33pLS4MnbXQ4hECf1TKujCt1Kq7QAdrio7roL4+CqsfjqwYj4tYgq0pJQ== hmac-drbg@^1.0.1: version "1.0.1" @@ -2772,15 +2772,14 @@ js-base64@^2.1.8: resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== -js-beautify@^1.13.5: - version "1.13.13" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.13.13.tgz#756907d1728f329f2b84c42efd56ad17514620bf" - integrity sha512-oH+nc0U5mOAqX8M5JO1J0Pw/7Q35sAdOsM5W3i87pir9Ntx6P/5Gx1xLNoK+MGyvHk4rqqRCE4Oq58H6xl2W7A== +js-beautify@^1.14.0: + version "1.14.0" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.0.tgz#2ce790c555d53ce1e3d7363227acf5dc69024c2d" + integrity sha512-yuck9KirNSCAwyNJbqW+BxJqJ0NLJ4PwBUzQQACl5O3qHMBXVkXb/rD0ilh/Lat/tn88zSZ+CAHOlk0DsY7GuQ== dependencies: config-chain "^1.1.12" editorconfig "^0.15.3" glob "^7.1.3" - mkdirp "^1.0.4" nopt "^5.0.0" "js-tokens@^3.0.0 || ^4.0.0": @@ -3167,10 +3166,10 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" -marked@^2.0.3: - version "2.0.6" - resolved "https://registry.yarnpkg.com/marked/-/marked-2.0.6.tgz#c3ab1403b9b70f26acd92590fc272eb1718e778c" - integrity sha512-S2mYj0FzTQa0dLddssqwRVW4EOJOVJ355Xm2Vcbm+LU7GQRGWvwbO5K87OaPSOux2AwTSgtPPaXmc8sDPrhn2A== +marked@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.1.tgz#b7c27f520fc4de0ddd049d9b4be3b04e06314923" + integrity sha512-5XFS69o9CzDpQDSpUYC+AN2xvq8yl1EGa5SG/GI1hP78/uTeo3PDfiDNmsUyiahpyhToDDJhQk7fNtJsga+KVw== matchdep@^2.0.0: version "2.0.0" @@ -4008,10 +4007,10 @@ postcss@^7.0.16: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^8.2.15: - version "8.3.0" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.0.tgz#b1a713f6172ca427e3f05ef1303de8b65683325f" - integrity sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ== +postcss@^8.3.5: + version "8.3.5" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.5.tgz#982216b113412bc20a86289e91eb994952a5b709" + integrity sha512-NxTuJocUhYGsMiMFHDUkmjSKT3EdH4/WbGF6GCi1NDGk+vbcUTun4fpbOqaPtD8IIsztA2ilZm2DhYCuyN58gA== dependencies: colorette "^1.2.2" nanoid "^3.1.23" @@ -4492,10 +4491,10 @@ sass-graph@2.2.5: scss-tokenizer "^0.2.3" yargs "^13.3.2" -sass@^1.32.8: - version "1.34.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.34.0.tgz#e46d5932d8b0ecc4feb846d861f26a578f7f7172" - integrity sha512-rHEN0BscqjUYuomUEaqq3BMgsXqQfkcMVR7UhscsAVub0/spUrZGBMxQXFS2kfiDsPLZw5yuU9iJEFNC2x38Qw== +sass@^1.35.1: + version "1.35.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.35.1.tgz#90ecf774dfe68f07b6193077e3b42fb154b9e1cd" + integrity sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ== dependencies: chokidar ">=3.0.0 <4.0.0" From 23b315c58f24ab104cb557a039c14fddc2c5b7a3 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 07:58:51 +0200 Subject: [PATCH 098/204] :bug: Fix incorrect lense on dashboard selected files. --- frontend/src/app/main/refs.cljs | 2 +- frontend/src/app/main/ui/dashboard/file_menu.cljs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index db33ad5aa2..bb3f8a5cb0 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -80,7 +80,7 @@ (def dashboard-selected-files (l/derived (fn [state] (let [get-file #(get-in state [:dashboard-files %]) - sim-file #(select-keys % [:id :name :project-id]) + sim-file #(select-keys % [:id :name :project-id :is-shared]) selected (get-in state [:dashboard-local :selected-files]) xform (comp (map get-file) (map sim-file))] diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 5de3b72a2e..27e35dab8c 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -97,7 +97,7 @@ :title (tr "modals.delete-file-multi-confirm.title" file-count) :message (tr "modals.delete-file-multi-confirm.message" file-count) :accept-label (tr "modals.delete-file-multi-confirm.accept" file-count) - :on-accept delete-fn})) + :on-accept delete-fn})) (st/emit! (modal/show {:type :confirm :title (tr "modals.delete-file-confirm.title") From 46e0151c28e4591738752ab588117b85f407ac64 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 08:00:08 +0200 Subject: [PATCH 099/204] :lipstick: Start use nginx (without cache) to serve frontend dev files. Usefull for checking production builds and not depend on the shadow-cljs watch http-dev server running. --- docker/devenv/files/nginx.conf | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/devenv/files/nginx.conf b/docker/devenv/files/nginx.conf index ee7e37bb0b..1b7530aac9 100644 --- a/docker/devenv/files/nginx.conf +++ b/docker/devenv/files/nginx.conf @@ -128,8 +128,10 @@ http { } location / { - add_header Cache-Control "no-cache, max-age=0"; - proxy_pass http://127.0.0.1:8888; + add_header Last-Modified $date_gmt; + add_header Cache-Control 'no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0'; + if_modified_since off; + expires off; } } } From a548bd7ffd471f9f7c29271eed4f3ae4b995c579 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 08:06:38 +0200 Subject: [PATCH 100/204] :lipstick: Fix linter issues on ui/workspace ns. --- frontend/src/app/main/ui/workspace.cljs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 6fc58ebd2c..9b6ea62c6c 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -6,16 +6,11 @@ (ns app.main.ui.workspace (:require - [app.common.geom.point :as gpt] - [app.main.constants :as c] - [app.main.data.history :as udh] [app.main.data.messages :as dm] [app.main.data.workspace :as dw] [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.colorpalette :refer [colorpalette]] [app.main.ui.workspace.colorpicker] @@ -29,10 +24,7 @@ [app.main.ui.workspace.viewport :refer [viewport]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [app.util.keyboard :as kbd] [app.util.object :as obj] - [beicon.core :as rx] - [cuerdas.core :as str] [okulary.core :as l] [rumext.alpha :as mf])) From 1d6905cb25bc56b0ec7d95a9c3363ef686d03691 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 08:07:00 +0200 Subject: [PATCH 101/204] :fire: Remove obsoleted props on colorpalette component. --- frontend/src/app/main/ui/workspace.cljs | 4 +--- frontend/src/app/main/ui/workspace/colorpalette.cljs | 9 ++++----- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 9b6ea62c6c..843f110a26 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -59,10 +59,8 @@ file (obj/get props "file") layout (obj/get props "layout")] [:* - ;; TODO: left-sidebar option is obsolete because left-sidebar now - ;; is always visible. (when (:colorpalette layout) - [:& colorpalette {:left-sidebar? true}]) + [:& colorpalette]) [:section.workspace-content [:section.workspace-viewport diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index d9d4df2304..102beadca0 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -60,7 +60,7 @@ [:& cb/color-name {:color color :size size}]])) (mf/defc palette - [{:keys [left-sidebar? current-colors recent-colors file-colors shared-libs selected size]}] + [{:keys [current-colors recent-colors file-colors shared-libs selected size]}] (let [state (mf/use-state {:show-menu false }) width (:width @state 0) @@ -120,7 +120,7 @@ (fn [] (events/unlistenByKey key1)))) - [:div.color-palette {:class (when left-sidebar? "left-sidebar-open")} + [:div.color-palette.left-sidebar-open [:& dropdown {:show (:show-menu @state) :on-close #(swap! state assoc :show-menu false)} [:ul.workspace-context-menu.palette-menu @@ -192,7 +192,7 @@ (vals)))) (mf/defc colorpalette - [{:keys [left-sidebar?]}] + [] (let [team-id (mf/use-ctx ctx/current-team-id) recent-colors (mf/deref refs/workspace-recent-colors) file-colors (mf/deref refs/workspace-file-colors) @@ -225,8 +225,7 @@ (reset! current-library-colors (into [] (->> (vals file-colors) (sort-by :name))))))) - [:& palette {:left-sidebar? left-sidebar? - :current-colors @current-library-colors + [:& palette {:current-colors @current-library-colors :recent-colors recent-colors :file-colors file-colors :shared-libs shared-libs From a8523f41b3313634c5e330eec953753cfc32f3d9 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 08:11:40 +0200 Subject: [PATCH 102/204] :bug: Remove unnecesary redirect when user goes from dashboard to workspace. And then, clicks the browser back button. --- CHANGES.md | 3 +++ frontend/src/app/main/data/workspace.cljs | 5 +---- frontend/src/app/main/ui/workspace.cljs | 27 +++++++++++------------ 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7594df1608..fc02209f9e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -13,6 +13,9 @@ becomes inactive. ### :bug: Bugs fixed + +- Remove unnecesary redirect from history when user goes to workspace from dashboard [Taiga 1820](https://tree.taiga.io/project/penpot/issue/1820). + ### :arrow_up: Deps updates ### :boom: Breaking changes ### :heart: Community contributions by (Thank you!) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index e973bf1dee..618b656a12 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -214,8 +214,6 @@ (rx/of (dwn/finalize file-id) ::dwp/finalize)))) -(declare go-to-page) - (defn initialize-page [page-id] (us/assert ::us/uuid page-id) @@ -1193,7 +1191,6 @@ (defn go-to-page ([] - (ptk/reify ::go-to-page ptk/WatchEvent (watch [it state stream] @@ -1203,7 +1200,7 @@ pparams {:file-id file-id :project-id project-id} qparams {:page-id page-id}] - (rx/of (rt/nav :workspace pparams qparams)))))) + (rx/of (rt/nav' :workspace pparams qparams)))))) ([page-id] (us/verify ::us/uuid page-id) (ptk/reify ::go-to-page diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 843f110a26..34877fdaa2 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -86,22 +86,21 @@ (mf/defc workspace-page [{:keys [file layout page-id] :as props}] - (let [page (mf/deref trimmed-page-ref)] - (mf/use-layout-effect - (mf/deps page-id) + (mf/use-layout-effect + (mf/deps page-id) + (fn [] + (if (nil? page-id) + (st/emit! (dw/go-to-page)) + (st/emit! (dw/initialize-page page-id))) + (fn [] - (if (nil? page-id) - (st/emit! (dw/go-to-page)) - (st/emit! (dw/initialize-page page-id))) + (when page-id + (st/emit! (dw/finalize-page page-id)))))) - (fn [] - (when page-id - (st/emit! (dw/finalize-page page-id)))))) - - (when page - [:& workspace-content {:key page-id - :file file - :layout layout}]))) + (when-let [page (mf/deref trimmed-page-ref)] + [:& workspace-content {:key page-id + :file file + :layout layout}])) (mf/defc workspace-loader [] From a1ad6ca28972c86009b029166f9cab8c47bb708e Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 08:16:03 +0200 Subject: [PATCH 103/204] :bug: Fix tooltip positioning on view application. --- CHANGES.md | 1 + frontend/resources/styles/common/framework.scss | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fc02209f9e..34b9cd52e9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ ### :bug: Bugs fixed - Remove unnecesary redirect from history when user goes to workspace from dashboard [Taiga 1820](https://tree.taiga.io/project/penpot/issue/1820). +- Fix tooltip position on view application [Taiga 1819](https://tree.taiga.io/project/penpot/issue/1819). ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index d690ec01dc..e6497dacb6 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -968,7 +968,7 @@ input[type=range]:focus::-ms-fill-upper { &.tooltip-bottom { &:hover { &::after { - left: -100%; + left: -20%; top: 130%; } } From 16b5bb595ca8eb1e1d8f37ede08b68bab3a751d5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 08:38:56 +0200 Subject: [PATCH 104/204] :bug: Fix tooltip positioning. --- .../resources/styles/common/framework.scss | 10 +- .../app/main/ui/workspace/sidebar/align.cljs | 100 +++++++++--------- .../workspace/sidebar/options/menus/text.cljs | 10 +- 3 files changed, 55 insertions(+), 65 deletions(-) diff --git a/frontend/resources/styles/common/framework.scss b/frontend/resources/styles/common/framework.scss index e6497dacb6..f74c58e25b 100644 --- a/frontend/resources/styles/common/framework.scss +++ b/frontend/resources/styles/common/framework.scss @@ -965,16 +965,8 @@ input[type=range]:focus::-ms-fill-upper { } } + // the default is the `right` &.tooltip-bottom { - &:hover { - &::after { - left: -20%; - top: 130%; - } - } - } - - &.tooltip-bottom-right { &:hover { &::after { left: 0; diff --git a/frontend/src/app/main/ui/workspace/sidebar/align.cljs b/frontend/src/app/main/ui/workspace/sidebar/align.cljs index 7bf85b1a65..acb3c6a426 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/align.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/align.cljs @@ -6,13 +6,13 @@ (ns app.main.ui.workspace.sidebar.align (:require - [rumext.alpha :as mf] - [app.main.ui.icons :as i] + [app.common.uuid :as uuid] + [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] - [app.main.data.workspace :as dw] - [app.util.i18n :as i18n :refer [t]] - [app.common.uuid :as uuid])) + [app.main.ui.icons :as i] + [app.util.i18n :as i18n :refer [tr]] + [rumext.alpha :as mf])) (mf/defc align-options [] @@ -25,14 +25,12 @@ (empty? selected) true (> (count selected) 1) false :else - (= uuid/zero (:frame-id (get objects (first selected))))) + (= uuid/zero (:frame-id (get objects (first selected))))) disabled-distribute (cond - (empty? selected) true - (< (count selected) 2) true - :else false) - - locale (i18n/use-locale) + (empty? selected) true + (< (count selected) 2) true + :else false) on-align-button-clicked (fn [axis] (when-not disabled (st/emit! (dw/align-objects axis)))) @@ -42,52 +40,52 @@ [:div.align-options [:div.align-group - [:div.align-button.tooltip.tooltip-bottom-right - {:alt (t locale "workspace.align.hleft") - :class (when disabled "disabled") - :on-click #(on-align-button-clicked :hleft)} - i/shape-halign-left] + [:div.align-button.tooltip.tooltip-bottom + {:alt (tr "workspace.align.hleft") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :hleft)} + i/shape-halign-left] - [:div.align-button.tooltip.tooltip-bottom - {:alt (t locale "workspace.align.hcenter") - :class (when disabled "disabled") - :on-click #(on-align-button-clicked :hcenter)} - i/shape-halign-center] + [:div.align-button.tooltip.tooltip-bottom + {:alt (tr "workspace.align.hcenter") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :hcenter)} + i/shape-halign-center] - [:div.align-button.tooltip.tooltip-bottom - {:alt (t locale "workspace.align.hright") - :class (when disabled "disabled") - :on-click #(on-align-button-clicked :hright)} - i/shape-halign-right] + [:div.align-button.tooltip.tooltip-bottom + {:alt (tr "workspace.align.hright") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :hright)} + i/shape-halign-right] - [:div.align-button.tooltip.tooltip-bottom - {:alt (t locale "workspace.align.hdistribute") - :class (when disabled-distribute "disabled") - :on-click #(on-distribute-button-clicked :horizontal)} - i/shape-hdistribute]] + [:div.align-button.tooltip.tooltip-bottom + {:alt (tr "workspace.align.hdistribute") + :class (when disabled-distribute "disabled") + :on-click #(on-distribute-button-clicked :horizontal)} + i/shape-hdistribute]] [:div.align-group - [:div.align-button.tooltip.tooltip-bottom - {:alt (t locale "workspace.align.vtop") - :class (when disabled "disabled") - :on-click #(on-align-button-clicked :vtop)} - i/shape-valign-top] + [:div.align-button.tooltip.tooltip-bottom + {:alt (tr "workspace.align.vtop") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :vtop)} + i/shape-valign-top] - [:div.align-button.tooltip.tooltip-bottom - {:alt (t locale "workspace.align.vcenter") - :class (when disabled "disabled") - :on-click #(on-align-button-clicked :vcenter)} - i/shape-valign-center] + [:div.align-button.tooltip.tooltip-bottom-left + {:alt (tr "workspace.align.vcenter") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :vcenter)} + i/shape-valign-center] - [:div.align-button.tooltip.tooltip-bottom - {:alt (t locale "workspace.align.vbottom") - :class (when disabled "disabled") - :on-click #(on-align-button-clicked :vbottom)} - i/shape-valign-bottom] + [:div.align-button.tooltip.tooltip-bottom-left + {:alt (tr "workspace.align.vbottom") + :class (when disabled "disabled") + :on-click #(on-align-button-clicked :vbottom)} + i/shape-valign-bottom] - [:div.align-button.tooltip.tooltip-bottom-left - {:alt (t locale "workspace.align.vdistribute") - :class (when disabled-distribute "disabled") - :on-click #(on-distribute-button-clicked :vertical)} - i/shape-vdistribute]]])) + [:div.align-button.tooltip.tooltip-bottom-left + {:alt (tr "workspace.align.vdistribute") + :class (when disabled-distribute "disabled") + :on-click #(on-distribute-button-clicked :vertical)} + i/shape-vdistribute]]])) 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 0d6ab25464..3cda61c33a 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 @@ -117,12 +117,12 @@ (on-change {:text-direction val}))] ;; --- Align [:div.align-icons - [:span.tooltip.tooltip-bottom + [:span.tooltip.tooltip-bottom-left {:alt (tr "workspace.options.text-options.direction-ltr") :class (dom/classnames :current (= "ltr" direction)) :on-click #(handle-change % "ltr")} i/text-direction-ltr] - [:span.tooltip.tooltip-bottom + [:span.tooltip.tooltip-bottom-left {:alt (tr "workspace.options.text-options.direction-rtl") :class (dom/classnames :current (= "rtl" direction)) :on-click #(handle-change % "rtl")} @@ -137,17 +137,17 @@ (on-change {:vertical-align new-align}))] [:div.align-icons - [:span.tooltip.tooltip-bottom + [:span.tooltip.tooltip-bottom-left {:alt (tr "workspace.options.text-options.align-top") :class (dom/classnames :current (= "top" vertical-align)) :on-click #(handle-change % "top")} i/align-top] - [:span.tooltip.tooltip-bottom + [:span.tooltip.tooltip-bottom-left {:alt (tr "workspace.options.text-options.align-middle") :class (dom/classnames :current (= "center" vertical-align)) :on-click #(handle-change % "center")} i/align-middle] - [:span.tooltip.tooltip-bottom + [:span.tooltip.tooltip-bottom-left {:alt (tr "workspace.options.text-options.align-bottom") :class (dom/classnames :current (= "bottom" vertical-align)) :on-click #(handle-change % "bottom")} From eacc9452543265b05ccbe8bfaf17579e0f65fc1d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 08:46:47 +0200 Subject: [PATCH 105/204] :bug: Fix wrong styles on viewer comments header menu & icon. And additionally fix some linter issues on the affected namespaces. --- .../styles/main/partials/viewer-header.scss | 8 ++- frontend/src/app/main/ui/viewer/header.cljs | 61 ++++++++----------- 2 files changed, 34 insertions(+), 35 deletions(-) diff --git a/frontend/resources/styles/main/partials/viewer-header.scss b/frontend/resources/styles/main/partials/viewer-header.scss index 6012f4a954..605a08ec3a 100644 --- a/frontend/resources/styles/main/partials/viewer-header.scss +++ b/frontend/resources/styles/main/partials/viewer-header.scss @@ -49,6 +49,12 @@ display: flex; justify-content: center; + svg { + fill: $color-gray-30; + height: 30px; + width: 28px; + } + &:hover { > svg { fill: $color-primary; @@ -61,7 +67,7 @@ left: 0px; top: 40px; } - + .view-options-dropdown { align-items: center; cursor: pointer; diff --git a/frontend/src/app/main/ui/viewer/header.cljs b/frontend/src/app/main/ui/viewer/header.cljs index ed3cc84ede..061afd0f17 100644 --- a/frontend/src/app/main/ui/viewer/header.cljs +++ b/frontend/src/app/main/ui/viewer/header.cljs @@ -10,7 +10,6 @@ [app.common.uuid :as uuid] [app.config :as cfg] [app.main.data.comments :as dcm] - [app.main.data.events :as ev] [app.main.data.messages :as dm] [app.main.data.viewer :as dv] [app.main.data.viewer.shortcuts :as sc] @@ -20,11 +19,9 @@ [app.main.ui.components.fullscreen :as fs] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t]] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.webapi :as wapi] - [cuerdas.core :as str] - [potok.core :as ptk] [rumext.alpha :as mf])) (mf/defc zoom-widget @@ -59,11 +56,9 @@ ;; "Full screen" [:span (sc/get-tooltip :full-screen)]]]]])) (mf/defc share-link - [{:keys [page token] :as props}] + [{:keys [token] :as props}] (let [show-dropdown? (mf/use-state false) dropdown-ref (mf/use-ref) - locale (mf/deref i18n/locale) - create (st/emitf (dv/create-share-link)) delete (st/emitf (dv/delete-share-link)) @@ -76,40 +71,40 @@ link (assoc cfg/public-uri :fragment link) copy-link - (fn [event] + (fn [_] (wapi/write-to-clipboard (str link)) (st/emit! (dm/show {:type :info :content "Link copied successfuly!" :timeout 3000})))] [:* [:span.btn-primary.btn-small - {:alt (t locale "viewer.header.share.title") + {:alt (tr "viewer.header.share.title") :on-click #(swap! show-dropdown? not)} - (t locale "viewer.header.share.title")] + (tr "viewer.header.share.title")] [:& dropdown {:show @show-dropdown? :on-close #(swap! show-dropdown? not) :container dropdown-ref} [:div.dropdown.share-link-dropdown {:ref dropdown-ref} - [:span.share-link-title (t locale "viewer.header.share.title")] + [:span.share-link-title (tr "viewer.header.share.title")] [:div.share-link-input (if (string? token) [:* [:span.link (str link)] [:span.link-button {:on-click copy-link} - (t locale "viewer.header.share.copy-link")]] - [:span.link-placeholder (t locale "viewer.header.share.placeholder")])] + (tr "viewer.header.share.copy-link")]] + [:span.link-placeholder (tr "viewer.header.share.placeholder")])] - [:span.share-link-subtitle (t locale "viewer.header.share.subtitle")] + [:span.share-link-subtitle (tr "viewer.header.share.subtitle")] [:div.share-link-buttons (if (string? token) [:button.btn-warning {:on-click delete} - (t locale "viewer.header.share.remove-link")] + (tr "viewer.header.share.remove-link")] [:button.btn-primary {:on-click create} - (t locale "viewer.header.share.create-link")])]]]])) + (tr "viewer.header.share.create-link")])]]]])) (mf/defc interactions-menu - [{:keys [state locale] :as props}] + [{:keys [state] :as props}] (let [imode (:interactions-mode state) show-dropdown? (mf/use-state false) @@ -123,7 +118,7 @@ [:div.view-options [:div.view-options-dropdown {:on-click toggle-dropdown} - [:span (t locale "viewer.header.interactions")] + [:span (tr "viewer.header.interactions")] i/arrow-down] [:& dropdown {:show @show-dropdown? :on-close hide-dropdown} @@ -131,20 +126,20 @@ [:li {:class (dom/classnames :selected (= imode :hide)) :on-click #(select-mode :hide)} [:span.icon i/tick] - [:span.label (t locale "viewer.header.dont-show-interactions")]] + [:span.label (tr "viewer.header.dont-show-interactions")]] [:li {:class (dom/classnames :selected (= imode :show)) :on-click #(select-mode :show)} [:span.icon i/tick] - [:span.label (t locale "viewer.header.show-interactions")]] + [:span.label (tr "viewer.header.show-interactions")]] [:li {:class (dom/classnames :selected (= imode :show-on-click)) :on-click #(select-mode :show-on-click)} [:span.icon i/tick] - [:span.label (t locale "viewer.header.show-interactions-on-click")]]]]])) + [:span.label (tr "viewer.header.show-interactions-on-click")]]]]])) (mf/defc comments-menu - [{:keys [locale] :as props}] + [] (let [{cmode :mode cshow :show} (mf/deref refs/comments-local) show-dropdown? (mf/use-state false) @@ -169,22 +164,22 @@ [:li {:class (dom/classnames :selected (= :all cmode)) :on-click #(update-mode :all)} [:span.icon i/tick] - [:span.label (t locale "labels.show-all-comments")]] + [:span.label (tr "labels.show-all-comments")]] [:li {:class (dom/classnames :selected (= :yours cmode)) :on-click #(update-mode :yours)} [:span.icon i/tick] - [:span.label (t locale "labels.show-your-comments")]] + [:span.label (tr "labels.show-your-comments")]] [:hr] [:li {:class (dom/classnames :selected (= :pending cshow)) :on-click #(update-show (if (= :pending cshow) :all :pending))} [:span.icon i/tick] - [:span.label (t locale "labels.hide-resolved-comments")]]]]])) + [:span.label (tr "labels.hide-resolved-comments")]]]]])) (mf/defc file-menu - [{:keys [locale project-id file-id page-id] :as props}] + [{:keys [project-id file-id page-id] :as props}] (let [show-dropdown? (mf/use-state false) toggle-dropdown (mf/use-fn #(swap! show-dropdown? not)) hide-dropdown (mf/use-fn #(reset! show-dropdown? false)) @@ -203,7 +198,7 @@ :on-close hide-dropdown} [:ul.dropdown [:li {:on-click on-edit} - [:span.label (t locale "viewer.header.edit-file")]]]]]])) + [:span.label (tr "viewer.header.edit-file")]]]]]])) (mf/defc header [{:keys [data index section state] :as props}] @@ -212,7 +207,6 @@ fullscreen (mf/use-ctx fs/fullscreen-context) total (count frames) - locale (mf/deref i18n/locale) profile (mf/deref refs/profile) teams (mf/deref refs/teams) @@ -252,7 +246,7 @@ ;; If the user doesn't have permission we disable the link :style {:pointer-events (when-not has-permission? "none")}} i/logo-icon]] - [:div.sitemap-zone {:alt (t locale "viewer.header.sitemap") + [:div.sitemap-zone {:alt (tr "viewer.header.sitemap") :on-click on-click} [:span.project-name (:name project)] [:span "/"] @@ -284,8 +278,8 @@ [:div.options-zone (case section - :interactions [:& interactions-menu {:state state :locale locale}] - :comments [:& comments-menu {:locale locale}] + :interactions [:& interactions-menu {:state state}] + :comments [:& comments-menu] nil) (when has-permission? @@ -302,15 +296,14 @@ :on-fullscreen toggle-fullscreen}] [:span.btn-icon-basic.btn-small.tooltip.tooltip-bottom-left - {:alt (t locale "viewer.header.fullscreen") + {:alt (tr "viewer.header.fullscreen") :on-click toggle-fullscreen} (if @fullscreen i/full-screen-off i/full-screen)] (when has-permission? - [:& file-menu {:locale locale - :project-id project-id + [:& file-menu {:project-id project-id :file-id file-id :page-id page-id}])]])) From 231a133f23d42ca57371e5f147daaf26b3de98a5 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 08:58:17 +0200 Subject: [PATCH 106/204] :bug: Fix team modal auto focus handling. --- .../src/app/main/ui/dashboard/team_form.cljs | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/main/ui/dashboard/team_form.cljs b/frontend/src/app/main/ui/dashboard/team_form.cljs index e4426f0d85..758477b702 100644 --- a/frontend/src/app/main/ui/dashboard/team_form.cljs +++ b/frontend/src/app/main/ui/dashboard/team_form.cljs @@ -62,20 +62,19 @@ (st/emit! (dd/update-team (with-meta team mdata)) (modal/hide)))) -(mf/defc team-form-modal - {::mf/register modal/components +(defn- on-submit + [form _] + (let [data (:clean-data @form)] + (if (:id data) + (on-update-submit form) + (on-create-submit form)))) + +(mf/defc team-form-modal {::mf/register modal/components ::mf/register-as :team-form} [{:keys [team] :as props}] - (let [form (fm/use-form :spec ::team-form - :initial (or team {})) - - on-submit - (mf/use-callback - (mf/deps team) - (if team - (partial on-update-submit form) - (partial on-create-submit form)))] - + (let [initial (mf/use-memo (fn [] (or team {}))) + form (fm/use-form :spec ::team-form + :initial initial)] [:div.modal-overlay [:div.modal-container.team-form-modal [:& fm/form {:form form :on-submit on-submit} @@ -91,7 +90,7 @@ [:div.modal-content.generic-form [:& fm/input {:type "text" - :auto-focus true + :auto-focus? true :form form :name :name :label (tr "labels.create-team.placeholder")}]] From 376d0663c23103ba36a119f9ed4d35695103c0ad Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 09:06:07 +0200 Subject: [PATCH 107/204] :bug: Fix navigation on dashboard when file is moved to other team. --- CHANGES.md | 1 + frontend/src/app/main/data/dashboard.cljs | 20 ++++++++++++------- .../src/app/main/ui/dashboard/file_menu.cljs | 20 ++++++++++--------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 34b9cd52e9..92a3e5e5e4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ - Remove unnecesary redirect from history when user goes to workspace from dashboard [Taiga 1820](https://tree.taiga.io/project/penpot/issue/1820). - Fix tooltip position on view application [Taiga 1819](https://tree.taiga.io/project/penpot/issue/1819). +- Fix dashboard navigation on moving file to other team [Taiga 1817](https://tree.taiga.io/project/penpot/issue/1817). ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index eacc98c3ac..8bdc4dafe6 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -688,13 +688,19 @@ (defn go-to-files - [project-id] - (ptk/reify ::go-to-files - ptk/WatchEvent - (watch [_ state stream] - (let [team-id (:current-team-id state)] - (rx/of (rt/nav :dashboard-files {:team-id team-id - :project-id project-id})))))) + ([project-id] + (ptk/reify ::go-to-files + ptk/WatchEvent + (watch [_ state stream] + (let [team-id (:current-team-id state)] + (rx/of (rt/nav :dashboard-files {:team-id team-id + :project-id project-id})))))) + ([team-id project-id] + (ptk/reify ::go-to-files + ptk/WatchEvent + (watch [_ state stream] + (rx/of (rt/nav :dashboard-files {:team-id team-id + :project-id project-id})))))) (defn go-to-search ([] (go-to-search nil)) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 27e35dab8c..1dead8bf46 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -72,19 +72,19 @@ (:projects current-team)) on-new-tab - (fn [event] + (fn [_] (let [pparams {:project-id (:project-id file) :file-id (:id file)} qparams {:page-id (first (get-in file [:data :pages]))}] (st/emit! (rt/nav-new-window :workspace pparams qparams)))) on-duplicate - (fn [event] + (fn [_] (apply st/emit! (map dd/duplicate-file files)) (st/emit! (dm/success (tr "dashboard.success-duplicate-file")))) delete-fn - (fn [event] + (fn [_] (apply st/emit! (map dd/delete-file files)) (st/emit! (dm/success (tr "dashboard.success-delete-file")))) @@ -111,16 +111,18 @@ (st/emit! (dm/success (tr "dashboard.success-move-files"))) (st/emit! (dm/success (tr "dashboard.success-move-file")))) (if (or navigate? (not= team-id current-team-id)) - (st/emit! (dd/go-to-files project-id)) + (st/emit! (dd/go-to-files team-id project-id)) (st/emit! (dd/fetch-recent-files) (dd/clear-selected-files)))) on-move (fn [team-id project-id] - (let [data {:ids (set (map :id files)) - :project-id project-id} - mdata {:on-success #(on-move-success team-id project-id)}] - (st/emitf (dd/move-files (with-meta data mdata))))) + (let [params {:ids (set (map :id files)) + :project-id project-id}] + (fn [] + (st/emit! (dd/move-files + (with-meta params + {:on-success #(on-move-success team-id project-id)})))))) add-shared (st/emitf (dd/set-file-shared (assoc file :is-shared true))) @@ -155,7 +157,7 @@ :on-accept del-shared}))) on-export-files - (fn [event] + (fn [_] (->> (uw/ask-many! {:cmd :export-file :team-id current-team-id From 41d05d6de0b0dd2da80dfad50c76c0302630bc5b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 09:09:28 +0200 Subject: [PATCH 108/204] :bug: Fix invalid link on workspace header (presence component). --- CHANGES.md | 1 + frontend/resources/styles/main/partials/workspace-header.scss | 1 - frontend/src/app/main/ui/workspace/presence.cljs | 3 +-- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 92a3e5e5e4..05883f9470 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ - Remove unnecesary redirect from history when user goes to workspace from dashboard [Taiga 1820](https://tree.taiga.io/project/penpot/issue/1820). - Fix tooltip position on view application [Taiga 1819](https://tree.taiga.io/project/penpot/issue/1819). - Fix dashboard navigation on moving file to other team [Taiga 1817](https://tree.taiga.io/project/penpot/issue/1817). +- Fix workspace header presence styles and invalid link [Taiga 1813](https://tree.taiga.io/project/penpot/issue/1813). ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/resources/styles/main/partials/workspace-header.scss b/frontend/resources/styles/main/partials/workspace-header.scss index e191a725f5..7c588f3587 100644 --- a/frontend/resources/styles/main/partials/workspace-header.scss +++ b/frontend/resources/styles/main/partials/workspace-header.scss @@ -179,7 +179,6 @@ .active-users { align-items: center; - cursor: pointer; display: flex; margin: 0; diff --git a/frontend/src/app/main/ui/workspace/presence.cljs b/frontend/src/app/main/ui/workspace/presence.cljs index e5a7f0e2e7..8947bfb009 100644 --- a/frontend/src/app/main/ui/workspace/presence.cljs +++ b/frontend/src/app/main/ui/workspace/presence.cljs @@ -17,8 +17,7 @@ (mf/defc session-widget [{:keys [session self? profile] :as props}] [:li.tooltip.tooltip-bottom - {:alt (:fullname profile) - :on-click (when self? (st/emitf (rt/navigate :settings/profile)))} + {:alt (:fullname profile)} [:img {:style {:border-color (:color session)} :src (cfg/resolve-profile-photo-url profile)}]]) From 91425050e4d7be034ea9ac0ae47c23116b6df2b2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 09:46:32 +0200 Subject: [PATCH 109/204] :bug: Fix incorrect value handling on color-input component. Related to the bug when the input value of the page color is not refreshed on page change. --- CHANGES.md | 9 +-- .../app/main/ui/components/color_input.cljs | 68 ++++++++++--------- .../main/ui/workspace/sidebar/options.cljs | 2 +- .../ui/workspace/sidebar/options/page.cljs | 43 +++++------- .../sidebar/options/rows/color_row.cljs | 23 +++---- frontend/src/app/util/dom.cljs | 2 +- 6 files changed, 73 insertions(+), 74 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 05883f9470..1ebaa563f7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,10 +14,11 @@ ### :bug: Bugs fixed -- Remove unnecesary redirect from history when user goes to workspace from dashboard [Taiga 1820](https://tree.taiga.io/project/penpot/issue/1820). -- Fix tooltip position on view application [Taiga 1819](https://tree.taiga.io/project/penpot/issue/1819). -- Fix dashboard navigation on moving file to other team [Taiga 1817](https://tree.taiga.io/project/penpot/issue/1817). -- Fix workspace header presence styles and invalid link [Taiga 1813](https://tree.taiga.io/project/penpot/issue/1813). +- Remove unnecesary redirect from history when user goes to workspace from dashboard [Taiga #1820](https://tree.taiga.io/project/penpot/issue/1820). +- Fix tooltip position on view application [Taiga #1819](https://tree.taiga.io/project/penpot/issue/1819). +- Fix dashboard navigation on moving file to other team [Taiga #1817](https://tree.taiga.io/project/penpot/issue/1817). +- Fix workspace header presence styles and invalid link [Taiga #1813](https://tree.taiga.io/project/penpot/issue/1813). +- Fix color-input wrong behavior (on workspace page color) [Taiga #1795](https://tree.taiga.io/project/penpot/issue/1795). ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/src/app/main/ui/components/color_input.cljs b/frontend/src/app/main/ui/components/color_input.cljs index e6019d5b36..8b851e5465 100644 --- a/frontend/src/app/main/ui/components/color_input.cljs +++ b/frontend/src/app/main/ui/components/color_input.cljs @@ -22,46 +22,46 @@ {::mf/wrap-props false ::mf/forward-ref true} [props external-ref] - (let [value (obj/get props "value") + (let [value (obj/get props "value") on-change (obj/get props "onChange") ;; We need a ref pointing to the input dom element, but the user ;; of this component may provide one (that is forwarded here). ;; So we use the external ref if provided, and the local one if not. local-ref (mf/use-ref) - ref (or external-ref local-ref) + ref (or external-ref local-ref) parse-value (mf/use-callback - (mf/deps ref) - (fn [] - (let [input-node (mf/ref-val ref)] - (try - (let [new-value (-> (dom/get-value input-node) - (uc/expand-hex) - (uc/parse-color) - (uc/prepend-hash))] - (dom/set-validity! input-node "") - new-value) - (catch :default _ - (dom/set-validity! input-node (tr "errors.invalid-color")) - nil))))) + (mf/deps ref) + (fn [] + (let [input-node (mf/ref-val ref)] + (try + (let [new-value (-> (dom/get-value input-node) + (uc/expand-hex) + (uc/parse-color) + (uc/prepend-hash))] + (dom/set-validity! input-node "") + new-value) + (catch :default _e + (dom/set-validity! input-node (tr "errors.invalid-color")) + nil))))) update-input (mf/use-callback - (mf/deps ref) - (fn [new-value] - (let [input-node (mf/ref-val ref)] - (dom/set-value! input-node (uc/remove-hash new-value))))) + (mf/deps ref) + (fn [new-value] + (let [input-node (mf/ref-val ref)] + (dom/set-value! input-node (uc/remove-hash new-value))))) apply-value (mf/use-callback - (mf/deps on-change update-input) - (fn [new-value] - (when new-value - (when on-change - (on-change new-value)) - (update-input new-value)))) + (mf/deps on-change update-input) + (fn [new-value] + (when new-value + (when on-change + (on-change new-value)) + (update-input new-value)))) handle-key-down (mf/use-callback @@ -79,12 +79,12 @@ handle-blur (mf/use-callback - (mf/deps parse-value apply-value update-input) - (fn [event] - (let [new-value (parse-value)] - (if new-value - (apply-value new-value) - (update-input value))))) + (mf/deps parse-value apply-value update-input) + (fn [event] + (let [new-value (parse-value)] + (if new-value + (apply-value new-value) + (update-input value))))) ;; list-id (str "colors-" (uuid/next)) @@ -97,6 +97,12 @@ (obj/set! "onKeyDown" handle-key-down) (obj/set! "onBlur" handle-blur))] + (mf/use-effect + (mf/deps value) + (fn [] + (when-let [node (mf/ref-val ref)] + (dom/set-value! node value)))) + [:* [:> :input props] ;; FIXME: this causes some weird interactions because of using apply-value diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index ec9424f50b..8f072fcd9e 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -66,7 +66,7 @@ [:div.element-options [:& align-options] (case (count selected) - 0 [:& page/options {:page-id page-id}] + 0 [:& page/options] 1 [:& shape-options {:shape (first shapes) :page-id page-id :file-id file-id 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 ef96fe0db7..a5859e682f 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/page.cljs @@ -7,46 +7,39 @@ (ns app.main.ui.workspace.sidebar.options.page "Page options menu entries." (:require - [rumext.alpha :as mf] - [okulary.core :as l] + [app.main.data.workspace :as dw] + [app.main.data.workspace.undo :as dwu] [app.main.refs :as refs] [app.main.store :as st] - [app.main.data.workspace :as dw] - [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.undo :as dwu] - [app.util.i18n :as i18n :refer [t]] - [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]])) - -(defn use-change-color [page-id] - (mf/use-callback - (mf/deps page-id) - (fn [value] - (st/emit! (dw/change-canvas-color value))))) + [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] + [app.util.i18n :as i18n :refer [tr]] + [rumext.alpha :as mf])) (mf/defc options - [{:keys [page-id] :as props}] - (let [locale (i18n/use-locale) - options (mf/deref refs/workspace-page-options) - handle-change-color (use-change-color page-id) + {::mf/wrap [mf/memo]} + [] + (let [options (mf/deref refs/workspace-page-options) + + on-change + (fn [value] + (st/emit! (dw/change-canvas-color value))) on-open - (mf/use-callback - (mf/deps page-id) - #(st/emit! (dwu/start-undo-transaction))) + (fn [] + (st/emit! (dwu/start-undo-transaction))) on-close - (mf/use-callback - (mf/deps page-id) - #(st/emit! (dwu/commit-undo-transaction)))] + (fn [] + (st/emit! (dwu/commit-undo-transaction)))] [:div.element-set - [:div.element-set-title (t locale "workspace.options.canvas-background")] + [:div.element-set-title (tr "workspace.options.canvas-background")] [:div.element-set-content [:& color-row {:disable-gradient true :disable-opacity true :color {:color (get options :background "#E8E9EA") :opacity 1} - :on-change handle-change-color + :on-change on-change :on-open on-open :on-close on-close}]]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index be8a067efd..f7f050e22c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -6,23 +6,22 @@ (ns app.main.ui.workspace.sidebar.options.rows.color-row (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] + [app.common.data :as d] [app.common.math :as math] [app.common.pages :as cp] - [app.common.data :as d] - [app.util.dom :as dom] - [app.util.data :refer [classnames]] - [app.util.i18n :as i18n :refer [tr]] - [app.util.color :as uc] - [app.main.refs :as refs] [app.main.data.modal :as modal] - [app.main.ui.hooks :as h] - [app.main.ui.icons :as i] - [app.main.ui.context :as ctx] + [app.main.refs :as refs] [app.main.ui.components.color-bullet :as cb] [app.main.ui.components.color-input :refer [color-input]] - [app.main.ui.components.numeric-input :refer [numeric-input]])) + [app.main.ui.components.numeric-input :refer [numeric-input]] + [app.main.ui.context :as ctx] + [app.main.ui.hooks :as h] + [app.main.ui.icons :as i] + [app.util.color :as uc] + [app.util.data :refer [classnames]] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [rumext.alpha :as mf])) (defn color-picker-callback [color disable-gradient disable-opacity handle-change-color handle-open handle-close] diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index b5acc982a0..13de17231b 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -128,7 +128,7 @@ (defn set-value! [node value] - (set! (.-value node) value)) + (set! (.-value ^js node) value)) (defn select-text! [node] From ef1c1d8cedbae6897d74e0c4c2f24909293d190d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 09:51:27 +0200 Subject: [PATCH 110/204] :lipstick: Fix linter issues on settings/feedback ns. --- frontend/src/app/main/ui/settings/feedback.cljs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frontend/src/app/main/ui/settings/feedback.cljs b/frontend/src/app/main/ui/settings/feedback.cljs index f8f8b63f10..a8ea4bd220 100644 --- a/frontend/src/app/main/ui/settings/feedback.cljs +++ b/frontend/src/app/main/ui/settings/feedback.cljs @@ -9,15 +9,13 @@ (:require [app.common.spec :as us] [app.main.data.messages :as dm] - [app.main.data.users :as du] [app.main.refs :as refs] + [app.main.repo :as rp] [app.main.store :as st] [app.main.ui.components.forms :as fm] - [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [beicon.core :as rx] - [app.main.repo :as rp] [cljs.spec.alpha :as s] [rumext.alpha :as mf])) @@ -37,7 +35,7 @@ on-succes (mf/use-callback (mf/deps profile) - (fn [event] + (fn [_] (reset! loading false) (st/emit! (dm/success (tr "labels.feedback-sent"))) (swap! form assoc :data {} :touched {} :errors {}))) @@ -54,7 +52,7 @@ on-submit (mf/use-callback (mf/deps profile) - (fn [form event] + (fn [form _] (reset! loading true) (let [data (:clean-data @form)] (->> (rp/mutation! :send-feedback data) From 7b4603e33ef2e482cbc0cb260dc338aba258527d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 17 Jun 2021 10:59:25 +0200 Subject: [PATCH 111/204] :sparkles: Change to penpot file format and fixes --- .../src/app/main/ui/dashboard/file_menu.cljs | 10 ++--- .../src/app/main/ui/dashboard/import.cljs | 2 +- frontend/src/app/main/ui/shapes/svg_raw.cljs | 2 + frontend/src/app/util/dom.cljs | 33 ++++++++------ frontend/src/app/util/import/parser.cljc | 5 ++- frontend/src/app/worker/export.cljs | 43 +++++++++++++------ frontend/src/app/worker/import.cljs | 33 +++++++------- 7 files changed, 77 insertions(+), 51 deletions(-) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 1dead8bf46..8da556dbc3 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -161,15 +161,15 @@ (->> (uw/ask-many! {:cmd :export-file :team-id current-team-id - :files files}) + :files (->> files (mapv :id))}) (rx/subs - (fn [{:keys [type data] :as msg}] - (case type + (fn [msg] + (case (:type msg) :progress - (prn "[Progress]" data) + (prn "[Progress]" (:data msg)) :finish - (dom/save-as data "export" "application/zip" "Export package (*.zip)"))))))] + (dom/trigger-download-uri (:filename msg) (:mtype msg) (:uri msg)))))))] (mf/use-effect (fn [] diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index dcec18c73a..c540a744aa 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -45,7 +45,7 @@ (let [on-file-selected (use-import-file project-id on-finish-import)] [:form.import-file - [:& file-uploader {:accept "application/zip" + [:& file-uploader {:accept ".penpot" :multi true :ref external-ref :on-selected on-file-selected}]])) diff --git a/frontend/src/app/main/ui/shapes/svg_raw.cljs b/frontend/src/app/main/ui/shapes/svg_raw.cljs index 7038c91280..01c9841041 100644 --- a/frontend/src/app/main/ui/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/shapes/svg_raw.cljs @@ -27,6 +27,8 @@ (defn set-styles [attrs shape] (let [custom-attrs (-> (usa/extract-style-attrs shape) (obj/without ["transform"])) + + attrs (or attrs {}) attrs (cond-> attrs (string? (:style attrs)) usvg/clean-attrs) style (obj/merge! (clj->js (:style attrs {})) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 13de17231b..66a5f53f20 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -284,14 +284,15 @@ (defn mtype->extension [mtype] ;; https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/MIME_types (case mtype - "image/apng" "apng" - "image/avif" "avif" - "image/gif" "gif" - "image/jpeg" "jpg" - "image/png" "png" - "image/svg+xml" "svg" - "image/webp" "webp" - "application/zip" "zip" + "image/apng" "apng" + "image/avif" "avif" + "image/gif" "gif" + "image/jpeg" "jpg" + "image/png" "png" + "image/svg+xml" "svg" + "image/webp" "webp" + "application/zip" "zip" + "application/penpot" "penpot" nil)) (defn set-attribute [^js node ^string attr value] @@ -346,11 +347,15 @@ :types [{:description description :accept { mtype [(str "." extension)]}}]}] - (p/let [file-system (.showSaveFilePicker globals/window (clj->js opts)) - writable (.createWritable file-system) - response (js/fetch uri) - blob (.blob response) - _ (.write writable blob)] - (.close writable))) + (-> (p/let [file-system (.showSaveFilePicker globals/window (clj->js opts)) + writable (.createWritable file-system) + response (js/fetch uri) + blob (.blob response) + _ (.write writable blob)] + (.close writable)) + (p/catch + #(when-not (and (= (type %) js/DOMException) + (= (.-name %) "AbortError")) + (trigger-download-uri filename mtype uri))))) (trigger-download-uri filename mtype uri))) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index ab5fc82db3..83ace5ca2f 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -592,8 +592,9 @@ (assoc :grids grids)))) (defn has-image? - [type node] - (let [pattern-image + [node] + (let [type (get-type node) + pattern-image (-> node (find-node :defs) (find-node :pattern) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 26ae4a89f6..5c2d485a00 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -57,14 +57,12 @@ [{:keys [id file-id markup] :as page}] [(str file-id "/" id ".svg") markup]) -(defmethod impl/handler :export-file - [{:keys [team-id project-id files] :as message}] - (let [files-ids (->> files (mapv :id)) +(defn export-file + [team-id file-id] - files-stream - (->> (rx/from files-ids) - (rx/merge-map #(rp/query :file {:id %})) + (let [files-stream + (->> (rp/query :file {:id file-id}) (rx/reduce #(assoc %1 (:id %2) %2) {}) (rx/share)) @@ -87,11 +85,30 @@ (rx/merge (->> render-stream - (rx/map #(hash-map :type :progress - :data (str "Render " (:file-name %) " - " (:name %))))) - (->> (rx/merge pages-stream - manifest-stream) + (rx/map #(hash-map + :type :progress + :file file-id + :data (str "Render " (:file-name %) " - " (:name %))))) + + (->> (rx/merge manifest-stream pages-stream) (rx/reduce conj []) - (rx/flat-map uz/compress-files) - (rx/map #(hash-map :type :finish - :data (dom/create-uri %))))))) + (rx/with-latest-from files-stream) + (rx/flat-map (fn [[data files]] + (->> (uz/compress-files data) + (rx/map #(vector (get files file-id) %))))))))) + +(defmethod impl/handler :export-file + [{:keys [team-id project-id files] :as message}] + + (->> (rx/from files) + (rx/mapcat #(export-file team-id %)) + (rx/map + (fn [value] + (if (contains? value :type) + value + (let [[file export-blob] value] + {:type :finish + :filename (:name file) + :mtype "application/penpot" + :description "Penpot export (*.penpot)" + :uri (dom/create-uri export-blob)})))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 48fe14a153..f9ee680fa3 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -118,7 +118,7 @@ (defn resolve-images [file-id node] (if (and (not (cip/close? node)) - (cip/has-image? type node)) + (cip/has-image? node)) (let [name (cip/get-image-name node) data-uri (cip/get-image-data node)] (->> (upload-media-files file-id name data-uri) @@ -162,25 +162,26 @@ pages (->> (:pages file-desc) (mapv #(vector % (get-in index [(keyword %) :name]))))] (->> (rx/from pages) - (rx/flat-map #(process-page file-id zip %)) + (rx/mapcat #(process-page file-id zip %)) (merge-reduce import-page file) (rx/flat-map send-changes) (rx/ignore)))) +(defn process-package + [project-id zip-file] + (->> (uz/get-file zip-file "manifest.json") + (rx/flat-map (comp :files json/decode :content)) + (rx/flat-map + (fn [[file-id file-desc]] + (->> (create-file project-id (:name file-desc)) + (rx/flat-map #(process-file % file-id file-desc zip-file))))))) + (defmethod impl/handler :import-file [{:keys [project-id files]}] - (let [zip-str (->> (rx/from files) - (rx/flat-map uz/load-from-url) - (rx/share))] - - (->> zip-str - (rx/flat-map #(uz/get-file % "manifest.json")) - (rx/flat-map (comp :files json/decode :content)) - (rx/with-latest-from zip-str) - (rx/flat-map - (fn [[[file-id file-desc] zip]] - (->> (create-file project-id (:name file-desc)) - (rx/flat-map #(process-file % file-id file-desc zip)) - (rx/catch (fn [err] - (.error js/console "ERROR" err (clj->js (.-data err))))))))))) + (->> (rx/from files) + (rx/flat-map uz/load-from-url) + (rx/flat-map (partial process-package project-id)) + (rx/catch + (fn [err] + (.error js/console "ERROR" err (clj->js (.-data err))))))) From c82d936e9651ad064b3a626a4b1201377abe9294 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 17 Jun 2021 12:34:33 +0200 Subject: [PATCH 112/204] :sparkles: Improves selrect calculation --- common/src/app/common/file_builder.cljc | 99 ++++++++++++++++++++---- frontend/src/app/libs/file_builder.cljs | 18 +++-- frontend/src/app/util/import/parser.cljc | 30 +------ 3 files changed, 100 insertions(+), 47 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index c2cd12a07b..17d2fef26b 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -8,6 +8,7 @@ "A version parsing helper." (:require [app.common.data :as d] + [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] [app.common.pages.changes :as ch] [app.common.pages.init :as init] @@ -52,7 +53,49 @@ :parent-id parent-id :obj obj})))) -(defn generate-name +(defn setup-rect-selrect [obj] + (let [rect (select-keys obj [:x :y :width :height]) + center (gsh/center-rect rect) + transform (:transform obj (gmt/matrix)) + selrect (gsh/rect->selrect rect) + + points (-> (gsh/rect->points rect) + (gsh/transform-points center transform))] + + (assoc obj + :selrect selrect + :points points))) + +(defn- setup-path-selrect + [obj] + (let [content (:content obj) + center (:center obj) + + transform-inverse + (->> (:transform-inverse obj (gmt/matrix)) + (gmt/transform-in center)) + + transform + (->> (:transform obj (gmt/matrix)) + (gmt/transform-in center)) + + content' (gsh/transform-content content transform-inverse) + selrect (gsh/content->selrect content') + points (-> (gsh/rect->points selrect) + (gsh/transform-points transform))] + + (-> obj + (dissoc :center) + (assoc :selrect selrect) + (assoc :points points)))) + +(defn- setup-selrect + [obj] + (if (= (:type obj) :path) + (setup-path-selrect obj) + (setup-rect-selrect obj))) + +(defn- generate-name [type data] (if (= type :svg-raw) (let [tag (get-in data [:content :tag])] @@ -62,7 +105,7 @@ :else (str tag)))) (str/capital (d/name type)))) -(defn check-name +(defn- check-name "Given a tag returns its layer name" [data file type] @@ -123,6 +166,7 @@ (let [obj (-> (init/make-minimal-shape :frame) (merge data) (check-name file :frame) + (setup-selrect) (d/without-nils))] (-> file (commit-shape obj) @@ -153,19 +197,46 @@ (defn close-group [file] (let [group-id (-> file :parent-stack peek) group (lookup-shape file group-id) - shapes (->> group :shapes (mapv #(lookup-shape file %))) - selrect (gsh/selection-rect shapes) - points (gsh/rect->points selrect)] + children (->> group :shapes (mapv #(lookup-shape file %))) + + file + (cond + (empty? children) + (commit-change + file + {:type :del-obj + :page-id (:current-page-id file) + :id group-id}) + + (:masked-group? group) + (let [mask (first children)] + (commit-change + file + {:type :mod-obj + :page-id (:current-page-id file) + :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)}]})) + + :else + (let [group' (gsh/update-group-selrect group children)] + (commit-change + file + {:type :mod-obj + :page-id (:current-page-id file) + :id group-id + :operations + [{:type :set :attr :selrect :val (:selrect group')} + {:type :set :attr :points :val (:points group')}]})))] (-> file - (cond-> (not (empty? shapes)) - (commit-change - {:type :mod-obj - :page-id (:current-page-id file) - :id group-id - :operations - [{:type :set :attr :selrect :val selrect} - {:type :set :attr :points :val points}]})) (update :parent-stack pop)))) (defn create-shape [file type data] @@ -175,6 +246,7 @@ obj (-> (init/make-minimal-shape type) (merge data) (check-name file :type) + (setup-selrect) (d/without-nils)) obj (cond-> obj frame (gsh/translate-from-frame frame))] @@ -219,7 +291,6 @@ (-> file (update :parent-stack pop))) - (defn generate-changes [file] (:changes file)) diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 8d64cbb3a8..0560b919a6 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -51,22 +51,28 @@ (set! file (fb/close-group file))) (createRect [self data] - (set! file (fb/create-rect file (parse-data data)))) + (set! file (fb/create-rect file (parse-data data))) + (str (:last-id file))) (createCircle [self data] - (set! file (fb/create-circle file (parse-data data)))) + (set! file (fb/create-circle file (parse-data data))) + (str (:last-id file))) (createPath [self data] - (set! file (fb/create-path file (parse-data data)))) + (set! file (fb/create-path file (parse-data data))) + (str (:last-id file))) (createText [self data] - (set! file (fb/create-text file (parse-data data)))) + (set! file (fb/create-text file (parse-data data))) + (str (:last-id file))) (createImage [self data] - (set! file (fb/create-image file (parse-data data)))) + (set! file (fb/create-image file (parse-data data))) + (str (:last-id file))) (createSVG [self data] - (set! file (fb/create-svg-raw file (parse-data data)))) + (set! file (fb/create-svg-raw file (parse-data data))) + (str (:last-id file))) (closeSVG [self] (set! file (fb/close-svg-raw file))) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 83ace5ca2f..7855017a1b 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -189,31 +189,10 @@ (defn parse-path [props center svg-data] - (let [transform-inverse (:transform-inverse props (gmt/matrix)) - transform (:transform props (gmt/matrix)) - content (upp/parse-path (:d svg-data)) - content-tr (gsh/transform-content - content - (gmt/transform-in center transform-inverse)) - selrect (gsh/content->selrect content-tr) - points (-> (gsh/rect->points selrect) - (gsh/transform-points center transform))] - + (let [content (upp/parse-path (:d svg-data))] (-> props (assoc :content content) - (assoc :selrect selrect) - (assoc :points points)))) - -(defn setup-selrect [props] - (let [data (select-keys props [:x :y :width :height]) - transform (:transform props (gmt/matrix)) - selrect (gsh/rect->selrect data) - points (gsh/rect->points data) - center (gsh/center-rect data)] - - (assoc props - :selrect selrect - :points (gsh/transform-points points center transform)))) + (assoc :center center)))) (def url-regex #"url\(#([^\)]*)\)") @@ -325,10 +304,7 @@ (parse-circle svg-data) (= type :path) - (parse-path center svg-data) - - (or (has-position? type) (= type :svg-raw) (= type :circle)) - (setup-selrect)))) + (parse-path center svg-data)))) (defn add-fill [props node svg-data] From 9e3ba85b72e283d4026b68bd622e711eb34bd102 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 15 Jun 2021 17:24:00 +0200 Subject: [PATCH 113/204] :recycle: Refactor profile registration flow. --- backend/src/app/http/oauth.clj | 78 ++-- backend/src/app/main.clj | 1 + backend/src/app/rpc/mutations/ldap.clj | 76 ++-- backend/src/app/rpc/mutations/profile.clj | 348 +++++++++--------- backend/src/app/rpc/queries/profile.clj | 3 +- backend/test/app/services_profile_test.clj | 173 ++++----- docker/images/files/config.js | 1 + docker/images/files/nginx-entrypoint.sh | 10 +- .../resources/styles/main/layouts/login.scss | 58 ++- .../resources/styles/main/partials/forms.scss | 24 -- frontend/src/app/config.cljs | 7 + frontend/src/app/main/data/users.cljs | 1 + frontend/src/app/main/ui.cljs | 3 + frontend/src/app/main/ui/auth.cljs | 10 +- frontend/src/app/main/ui/auth/login.cljs | 28 +- .../app/main/ui/auth/recovery_request.cljs | 2 +- frontend/src/app/main/ui/auth/register.cljs | 230 ++++++++---- frontend/translations/ar.po | 7 +- frontend/translations/ca.po | 7 +- frontend/translations/da.po | 7 +- frontend/translations/de.po | 15 +- frontend/translations/el.po | 7 +- frontend/translations/en.po | 80 ++-- frontend/translations/es.po | 56 ++- frontend/translations/fr.po | 15 +- frontend/translations/pt_BR.po | 7 +- frontend/translations/ro.po | 15 +- frontend/translations/ru.po | 15 +- frontend/translations/tr.po | 7 +- frontend/translations/zh_CN.po | 7 +- 30 files changed, 717 insertions(+), 581 deletions(-) diff --git a/backend/src/app/http/oauth.clj b/backend/src/app/http/oauth.clj index ea8f10f4bf..72a2d11412 100644 --- a/backend/src/app/http/oauth.clj +++ b/backend/src/app/http/oauth.clj @@ -6,10 +6,13 @@ (ns app.http.oauth (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.common.spec :as us] [app.common.uri :as u] [app.config :as cf] + [app.db :as db] + [app.rpc.queries.profile :as profile] [app.util.http :as http] [app.util.logging :as l] [app.util.time :as dt] @@ -25,11 +28,12 @@ :headers {"location" (str uri)} :body ""}) -(defn generate-error-redirect-uri - [cfg] - (-> (u/uri (:public-uri cfg)) - (assoc :path "/#/auth/login") - (assoc :query (u/map->query-string {:error "unable-to-auth"})))) +(defn generate-error-redirect + [cfg error] + (let [uri (-> (u/uri (:public-uri cfg)) + (assoc :path "/#/auth/login") + (assoc :query (u/map->query-string {:error "unable-to-auth" :hint (ex-message error)})))] + (redirect-response uri))) (defn register-profile [{:keys [rpc] :as cfg} info] @@ -39,15 +43,33 @@ (some? (:invitation-token info)) (assoc :invitation-token (:invitation-token info))))) -(defn generate-redirect-uri - [{:keys [tokens] :as cfg} profile] - (let [token (or (:invitation-token profile) - (tokens :generate {:iss :auth - :exp (dt/in-future "15m") - :profile-id (:id profile)}))] - (-> (u/uri (:public-uri cfg)) - (assoc :path "/#/auth/verify-token") - (assoc :query (u/map->query-string {:token token}))))) +(defn generate-redirect + [{:keys [tokens session] :as cfg} request info profile] + (if profile + (let [sxf ((:create session) (:id profile)) + token (or (:invitation-token info) + (tokens :generate {:iss :auth + :exp (dt/in-future "15m") + :profile-id (:id profile)})) + params {:token token} + + uri (-> (u/uri (:public-uri cfg)) + (assoc :path "/#/auth/verify-token") + (assoc :query (u/map->query-string params)))] + (->> (redirect-response uri) + (sxf request))) + (let [info (assoc info + :iss :prepared-register + :exp (dt/in-future {:hours 48})) + token (tokens :generate info) + params (d/without-nils + {:token token + :fullname (:fullname info)}) + uri (-> (u/uri (:public-uri cfg)) + (assoc :path "/#/auth/register/validate") + (assoc :query (u/map->query-string params)))] + (redirect-response uri)))) + (defn- build-redirect-uri [{:keys [provider] :as cfg}] @@ -146,6 +168,7 @@ (string? roles) (into #{} (str/words roles)) (vector? roles) (into #{} roles) :else #{}))] + ;; check if profile has a configured set of roles (when-not (set/subset? provider-roles profile-roles) (ex/raise :type :internal @@ -188,18 +211,23 @@ {:status 200 :body {:redirect-uri uri}})) +(defn- retrieve-profile + [{:keys [pool] :as cfg} info] + (with-open [conn (db/open pool)] + (some->> (:email info) + (profile/retrieve-profile-data-by-email conn) + (profile/populate-additional-data conn)))) + (defn- callback-handler - [{:keys [session] :as cfg} request] + [cfg request] (try - (let [info (retrieve-info cfg request) - profile (register-profile cfg info) - uri (generate-redirect-uri cfg profile) - sxf ((:create session) (:id profile))] - (->> (redirect-response uri) - (sxf request))) - (catch Exception _e - (-> (generate-error-redirect-uri cfg) - (redirect-response))))) + (let [info (retrieve-info cfg request) + profile (retrieve-profile cfg info)] + (generate-redirect cfg request info profile)) + (catch Exception e + (l/warn :hint "error on oauth process" + :cause e) + (generate-error-redirect cfg e)))) ;; --- INIT @@ -211,7 +239,7 @@ (s/def ::rpc map?) (defmethod ig/pre-init-spec :app.http.oauth/handlers [_] - (s/keys :req-un [::public-uri ::session ::tokens ::rpc])) + (s/keys :req-un [::public-uri ::session ::tokens ::rpc ::db/pool])) (defn wrap-handler [cfg handler] diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index a7593ee4d1..2125f087d9 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -110,6 +110,7 @@ :app.http.oauth/handlers {:rpc (ig/ref :app.rpc/rpc) :session (ig/ref :app.http.session/session) + :pool (ig/ref :app.db/pool) :tokens (ig/ref :app.tokens/tokens) :public-uri (cf/get :public-uri)} diff --git a/backend/src/app/rpc/mutations/ldap.clj b/backend/src/app/rpc/mutations/ldap.clj index 8b5f93ff04..ec9c7f0c51 100644 --- a/backend/src/app/rpc/mutations/ldap.clj +++ b/backend/src/app/rpc/mutations/ldap.clj @@ -9,7 +9,10 @@ [app.common.exceptions :as ex] [app.common.spec :as us] [app.config :as cfg] - [app.rpc.mutations.profile :refer [login-or-register]] + [app.db :as db] + [app.loggers.audit :as audit] + [app.rpc.mutations.profile :as profile-m] + [app.rpc.queries.profile :as profile-q] [app.util.services :as sv] [clj-ldap.client :as ldap] [clojure.spec.alpha :as s] @@ -34,6 +37,7 @@ ;; --- Mutation: login-with-ldap (declare authenticate) +(declare login-or-register) (s/def ::email ::us/email) (s/def ::password ::us/string) @@ -45,30 +49,36 @@ (sv/defmethod ::login-with-ldap {:auth false :rlimit :password} [{:keys [pool session tokens] :as cfg} {:keys [email password invitation-token] :as params}] - (let [info (authenticate params) - cfg (assoc cfg :conn pool)] - (when-not info - (ex/raise :type :validation - :code :wrong-credentials)) - (let [profile (login-or-register cfg {:email (:email info) - :backend (:backend info) - :fullname (:fullname info)})] - (if-let [token (:invitation-token params)] - ;; If invitation token comes in params, this is because the - ;; user comes from team-invitation process; in this case, - ;; regenerate token and send back to the user a new invitation - ;; token (and mark current session as logged). - (let [claims (tokens :verify {:token token :iss :team-invitation}) - claims (assoc claims - :member-id (:id profile) - :member-email (:email profile)) - token (tokens :generate claims)] - (with-meta - {:invitation-token token} - {:transform-response ((:create session) (:id profile))})) + (db/with-atomic [conn pool] + (let [info (authenticate params) + cfg (assoc cfg :conn conn)] - (with-meta profile - {:transform-response ((:create session) (:id profile))}))))) + (when-not info + (ex/raise :type :validation + :code :wrong-credentials)) + + (let [profile (login-or-register cfg {:email (:email info) + :backend (:backend info) + :fullname (:fullname info)})] + (if-let [token (:invitation-token params)] + ;; If invitation token comes in params, this is because the + ;; user comes from team-invitation process; in this case, + ;; regenerate token and send back to the user a new invitation + ;; token (and mark current session as logged). + (let [claims (tokens :verify {:token token :iss :team-invitation}) + claims (assoc claims + :member-id (:id profile) + :member-email (:email profile)) + token (tokens :generate claims)] + (with-meta {:invitation-token token} + {:transform-response ((:create session) (:id profile)) + ::audit/props (:props profile) + ::audit/profile-id (:id profile)})) + + (with-meta profile + {:transform-response ((:create session) (:id profile)) + ::audit/props (:props profile) + ::audit/profile-id (:id profile)})))))) (defn- replace-several [s & {:as replacements}] (reduce-kv clojure.string/replace s replacements)) @@ -88,11 +98,25 @@ (first (ldap/search cpool base-dn params)))) (defn- authenticate - [{:keys [password] :as params}] + [{:keys [password email] :as params}] (with-open [conn (connect)] (when-let [{:keys [dn] :as luser} (get-ldap-user conn params)] (when (ldap/bind? conn dn password) {:photo (get luser (keyword (cfg/get :ldap-attrs-photo))) :fullname (get luser (keyword (cfg/get :ldap-attrs-fullname))) - :email (get luser (keyword (cfg/get :ldap-attrs-email))) + :email email :backend "ldap"})))) + +(defn- login-or-register + [{:keys [conn] :as cfg} info] + (or (some->> (:email info) + (profile-q/retrieve-profile-data-by-email conn) + (profile-q/populate-additional-data conn) + (profile-q/decode-profile-row)) + (let [params (-> info + (assoc :is-active true) + (assoc :is-demo false))] + (->> params + (profile-m/create-profile conn) + (profile-m/create-profile-relations conn) + (profile-q/strip-private-attrs))))) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 0cef24832b..1aa3a8ae47 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -36,106 +36,14 @@ (s/def ::password ::us/not-empty-string) (s/def ::old-password ::us/not-empty-string) (s/def ::theme ::us/string) - -;; --- Mutation: Register Profile +(s/def ::invitation-token ::us/not-empty-string) (declare annotate-profile-register) (declare check-profile-existence!) (declare create-profile) (declare create-profile-relations) -(declare email-domain-in-whitelist?) (declare register-profile) -(s/def ::invitation-token ::us/not-empty-string) -(s/def ::terms-privacy ::us/boolean) - -(s/def ::register-profile - (s/keys :req-un [::email ::password ::fullname ::terms-privacy] - :opt-un [::invitation-token])) - -(sv/defmethod ::register-profile {:auth false :rlimit :password} - [{:keys [pool tokens session] :as cfg} params] - (when-not (cfg/get :registration-enabled) - (ex/raise :type :restriction - :code :registration-disabled)) - - (when-let [domains (cfg/get :registration-domain-whitelist)] - (when-not (email-domain-in-whitelist? domains (:email params)) - (ex/raise :type :validation - :code :email-domain-is-not-allowed))) - - (when-not (:terms-privacy params) - (ex/raise :type :validation - :code :invalid-terms-and-privacy)) - - (db/with-atomic [conn pool] - (let [cfg (assoc cfg :conn conn)] - (register-profile cfg params)))) - -(defn- annotate-profile-register - "A helper for properly increase the profile-register metric once the - transaction is completed." - [metrics profile] - (fn [] - (when (::created profile) - ((get-in metrics [:definitions :profile-register]) :inc)))) - -(defn- register-profile - [{:keys [conn tokens session metrics] :as cfg} params] - (check-profile-existence! conn params) - (let [profile (->> (create-profile conn params) - (create-profile-relations conn)) - profile (assoc profile ::created true)] - - (sid/load-initial-project! conn profile) - - (if-let [token (:invitation-token params)] - ;; If invitation token comes in params, this is because the - ;; user comes from team-invitation process; in this case, - ;; regenerate token and send back to the user a new invitation - ;; token (and mark current session as logged). - (let [claims (tokens :verify {:token token :iss :team-invitation}) - claims (assoc claims - :member-id (:id profile) - :member-email (:email profile)) - token (tokens :generate claims) - resp {:invitation-token token}] - (with-meta resp - {:transform-response ((:create session) (:id profile)) - :before-complete (annotate-profile-register metrics profile) - ::audit/props (:props profile) - ::audit/profile-id (:id profile)})) - - ;; If no token is provided, send a verification email - (let [vtoken (tokens :generate - {:iss :verify-email - :exp (dt/in-future "48h") - :profile-id (:id profile) - :email (:email profile)}) - ptoken (tokens :generate-predefined - {:iss :profile-identity - :profile-id (:id profile)})] - - ;; Don't allow proceed in register page if the email is - ;; already reported as permanent bounced - (when (eml/has-bounce-reports? conn (:email profile)) - (ex/raise :type :validation - :code :email-has-permanent-bounces - :hint "looks like the email has one or many bounces reported")) - - (eml/send! {::eml/conn conn - ::eml/factory eml/register - :public-uri (:public-uri cfg) - :to (:email profile) - :name (:fullname profile) - :token vtoken - :extra-data ptoken}) - - (with-meta profile - {:before-complete (annotate-profile-register metrics profile) - ::audit/props (:props profile) - ::audit/profile-id (:id profile)}))))) - (defn email-domain-in-whitelist? "Returns true if email's domain is in the given whitelist or if given whitelist is an empty string." @@ -177,28 +85,171 @@ {:update false :valid false}))) + +;; --- MUTATION: Prepare Register + +(s/def ::prepare-register-profile + (s/keys :req-un [::email ::password] + :opt-un [::invitation-token])) + +(sv/defmethod ::prepare-register-profile {:auth false} + [{:keys [pool tokens] :as cfg} params] + (when-not (cfg/get :registration-enabled) + (ex/raise :type :restriction + :code :registration-disabled)) + + (when-let [domains (cfg/get :registration-domain-whitelist)] + (when-not (email-domain-in-whitelist? domains (:email params)) + (ex/raise :type :validation + :code :email-domain-is-not-allowed))) + + ;; Don't allow proceed in preparing registration if the profile is + ;; already reported as spamer. + (when (eml/has-bounce-reports? pool (:email params)) + (ex/raise :type :validation + :code :email-has-permanent-bounces + :hint "looks like the email has one or many bounces reported")) + + (check-profile-existence! pool params) + + (let [params (assoc params + :backend "penpot" + :iss :prepared-register + :exp (dt/in-future "48h")) + token (tokens :generate params)] + {:token token})) + +;; --- MUTATION: Register Profile + +(s/def ::accept-terms-and-privacy ::us/boolean) +(s/def ::accept-newsletter-subscription ::us/boolean) +(s/def ::token ::us/not-empty-string) + +(s/def ::register-profile + (s/keys :req-un [::token ::fullname + ::accept-terms-and-privacy] + :opt-un [::accept-newsletter-subscription])) + +(sv/defmethod ::register-profile {:auth false :rlimit :password} + [{:keys [pool tokens session] :as cfg} params] + + (when-not (:accept-terms-and-privacy params) + (ex/raise :type :validation + :code :invalid-terms-and-privacy)) + + (db/with-atomic [conn pool] + (let [cfg (assoc cfg :conn conn)] + (register-profile cfg params)))) + +(defn- annotate-profile-register + "A helper for properly increase the profile-register metric once the + transaction is completed." + [metrics] + (fn [] + ((get-in metrics [:definitions :profile-register]) :inc))) + +(defn register-profile + [{:keys [conn tokens session metrics] :as cfg} {:keys [token] :as params}] + (let [claims (tokens :verify {:token token :iss :prepared-register}) + params (merge params claims)] + (check-profile-existence! conn params) + (let [profile (->> params + (create-profile conn) + (create-profile-relations conn))] + + (sid/load-initial-project! conn profile) + + (cond + ;; If invitation token comes in params, this is because the + ;; user comes from team-invitation process; in this case, + ;; regenerate token and send back to the user a new invitation + ;; token (and mark current session as logged). + (some? (:invitation-token params)) + (let [token (:invitation-token params) + claims (tokens :verify {:token token :iss :team-invitation}) + claims (assoc claims + :member-id (:id profile) + :member-email (:email profile)) + token (tokens :generate claims) + resp {:invitation-token token}] + (with-meta resp + {:transform-response ((:create session) (:id profile)) + :before-complete (annotate-profile-register metrics) + ::audit/props (:props profile) + ::audit/profile-id (:id profile)})) + + ;; If auth backend is different from "penpot" means user is + ;; registring 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) + {:transform-response ((:create session) (:id profile)) + :before-complete (annotate-profile-register metrics) + ::audit/props (:props profile) + ::audit/profile-id (:id profile)}) + + ;; In all other cases, send a verification email. + :else + (let [vtoken (tokens :generate + {:iss :verify-email + :exp (dt/in-future "48h") + :profile-id (:id profile) + :email (:email profile)}) + ptoken (tokens :generate-predefined + {:iss :profile-identity + :profile-id (:id profile)})] + + (eml/send! {::eml/conn conn + ::eml/factory eml/register + :public-uri (:public-uri cfg) + :to (:email profile) + :name (:fullname profile) + :token vtoken + :extra-data ptoken}) + + (with-meta profile + {:before-complete (annotate-profile-register metrics) + ::audit/props (:props profile) + ::audit/profile-id (:id profile)})))))) + (defn create-profile "Create the profile entry on the database with limited input filling all the other fields with defaults." - [conn {:keys [id fullname email password is-active is-muted is-demo opts deleted-at] - :or {is-active false is-muted false is-demo false} - :as params}] - (let [id (or id (uuid/next)) - is-active (if is-demo true is-active) - props (-> params extract-props db/tjson) - password (derive-password password) + [conn params] + (let [id (or (:id params) (uuid/next)) + + props (-> (extract-props params) + (merge (:props params)) + (assoc :accept-terms-and-privacy (:accept-terms-and-privacy params true)) + (assoc :accept-newsletter-subscription (:accept-newsletter-subscription params false)) + (db/tjson)) + + password (if-let [password (:password params)] + (derive-password password) + "!") + + locale (as-> (:locale params) locale + (and (string? locale) (not (str/blank? locale)) locale)) + + backend (:backend params "penpot") + is-demo (:is-demo params false) + is-muted (:is-muted params false) + is-active (:is-active params (or (not= "penpot" backend) is-demo)) + email (str/lower (:email params)) + params {:id id - :fullname fullname - :email (str/lower email) - :auth-backend "penpot" + :fullname (:fullname params) + :email email + :auth-backend backend + :lang locale :password password - :deleted-at deleted-at + :deleted-at (:deleted-at params) :props props :is-active is-active :is-muted is-muted :is-demo is-demo}] (try - (-> (db/insert! conn :profile params opts) + (-> (db/insert! conn :profile params) (update :props db/decode-transit-pgobject)) (catch org.postgresql.util.PSQLException e (let [state (.getSQLState e)] @@ -231,7 +282,7 @@ (assoc :default-team-id (:id team)) (assoc :default-project-id (:id project))))) -;; --- Mutation: Login +;; --- MUTATION: Login (s/def ::email ::us/email) (s/def ::scope ::us/string) @@ -286,7 +337,7 @@ {:transform-response ((:create session) (:id profile)) ::audit/profile-id (:id profile)})))))) -;; --- Mutation: Logout +;; --- MUTATION: Logout (s/def ::logout (s/keys :req-un [::profile-id])) @@ -296,74 +347,7 @@ (with-meta {} {:transform-response (:delete session)})) - -;; --- Mutation: Register if not exists - -(declare login-or-register) - -(s/def ::backend ::us/string) -(s/def ::login-or-register - (s/keys :req-un [::email ::fullname ::backend])) - -(sv/defmethod ::login-or-register {:auth false} - [{:keys [pool metrics] :as cfg} params] - (db/with-atomic [conn pool] - (let [profile (-> (assoc cfg :conn conn) - (login-or-register params)) - props (merge - (select-keys profile [:backend :fullname :email]) - (:props profile))] - (with-meta profile - {:before-complete (annotate-profile-register metrics profile) - ::audit/name (if (::created profile) "register" "login") - ::audit/props props - ::audit/profile-id (:id profile)})))) - -(defn login-or-register - [{:keys [conn] :as cfg} {:keys [email] :as params}] - (letfn [(info->lang [{:keys [locale] :as info}] - (when (and (string? locale) - (not (str/blank? locale))) - locale)) - - (create-profile [conn {:keys [fullname backend email props] :as info}] - (let [params {:id (uuid/next) - :fullname fullname - :email (str/lower email) - :lang (info->lang props) - :auth-backend backend - :is-active true - :password "!" - :props (db/tjson props) - :is-demo false}] - (-> (db/insert! conn :profile params) - (update :props db/decode-transit-pgobject)))) - - (update-profile [conn info profile] - (let [props (merge (:props profile) - (:props info))] - (db/update! conn :profile - {:props (db/tjson props) - :modified-at (dt/now)} - {:id (:id profile)}) - (assoc profile :props props))) - - (register-profile [conn params] - (let [profile (->> (create-profile conn params) - (create-profile-relations conn))] - (sid/load-initial-project! conn profile) - (assoc profile ::created true)))] - - (let [profile (profile/retrieve-profile-data-by-email conn email) - profile (if profile - (->> profile - (update-profile conn params) - (profile/populate-additional-data conn)) - (register-profile conn params))] - (profile/strip-private-attrs profile)))) - - -;; --- Mutation: Update Profile (own) +;; --- MUTATION: Update Profile (own) (defn- update-profile [conn {:keys [id fullname lang theme] :as params}] @@ -383,7 +367,7 @@ (update-profile conn params) nil)) -;; --- Mutation: Update Password +;; --- MUTATION: Update Password (declare validate-password!) (declare update-profile-password!) @@ -412,7 +396,7 @@ {:password (derive-password password)} {:id id})) -;; --- Mutation: Update Photo +;; --- MUTATION: Update Photo (declare update-profile-photo) @@ -447,7 +431,7 @@ nil) -;; --- Mutation: Request Email Change +;; --- MUTATION: Request Email Change (declare request-email-change) (declare change-email-inmediatelly) @@ -515,7 +499,7 @@ [conn id] (db/get-by-id conn :profile id {:for-update true})) -;; --- Mutation: Request Profile Recovery +;; --- MUTATION: Request Profile Recovery (s/def ::request-profile-recovery (s/keys :req-un [::email])) @@ -564,7 +548,7 @@ (send-email-notification conn)))))) -;; --- Mutation: Recover Profile +;; --- MUTATION: Recover Profile (s/def ::token ::us/not-empty-string) (s/def ::recover-profile @@ -585,7 +569,7 @@ (update-password conn)) nil))) -;; --- Mutation: Update Profile Props +;; --- MUTATION: Update Profile Props (s/def ::props map?) (s/def ::update-profile-props @@ -607,7 +591,7 @@ nil))) -;; --- Mutation: Delete Profile +;; --- MUTATION: Delete Profile (declare check-can-delete-profile!) (declare mark-profile-as-deleted!) diff --git a/backend/src/app/rpc/queries/profile.clj b/backend/src/app/rpc/queries/profile.clj index 4508b740eb..da36bb6647 100644 --- a/backend/src/app/rpc/queries/profile.clj +++ b/backend/src/app/rpc/queries/profile.clj @@ -73,7 +73,8 @@ (defn decode-profile-row [{:keys [props] :as row}] (cond-> row - (db/pgobject? props) (assoc :props (db/decode-transit-pgobject props)))) + (db/pgobject? props "jsonb") + (assoc :props (db/decode-transit-pgobject props)))) (defn retrieve-profile-data [conn id] diff --git a/backend/test/app/services_profile_test.clj b/backend/test/app/services_profile_test.clj index 0f64da41dc..d5e6dac437 100644 --- a/backend/test/app/services_profile_test.clj +++ b/backend/test/app/services_profile_test.clj @@ -168,126 +168,95 @@ (t/testing "not allowed email domain" (t/is (false? (profile/email-domain-in-whitelist? whitelist "username@somedomain.com")))))) -(t/deftest test-register-with-no-terms-and-privacy - (let [data {::th/type :register-profile +(t/deftest prepare-register-and-register-profile + (let [data {::th/type :prepare-register-profile :email "user@example.com" - :password "foobar" - :fullname "foobar" - :terms-privacy nil} + :password "foobar"} out (th/mutation! data) - error (:error out) - edata (ex-data error)] - (t/is (th/ex-info? error)) - (t/is (= (:type edata) :validation)) - (t/is (= (:code edata) :spec-validation)))) + token (get-in out [:result :token])] + (t/is (string? token)) -(t/deftest test-register-with-bad-terms-and-privacy - (let [data {::th/type :register-profile - :email "user@example.com" - :password "foobar" - :fullname "foobar" - :terms-privacy false} - out (th/mutation! data) - error (:error out) - edata (ex-data error)] - (t/is (th/ex-info? error)) - (t/is (= (:type edata) :validation)) - (t/is (= (:code edata) :invalid-terms-and-privacy)))) -(t/deftest test-register-when-registration-disabled + ;; try register without accepting terms + (let [data {::th/type :register-profile + :token token + :fullname "foobar" + :accept-terms-and-privacy false} + out (th/mutation! data)] + (let [error (:error out)] + (t/is (th/ex-info? error)) + (t/is (th/ex-of-type? error :validation)) + (t/is (th/ex-of-code? error :invalid-terms-and-privacy)))) + + ;; try register without token + (let [data {::th/type :register-profile + :fullname "foobar" + :accept-terms-and-privacy true} + out (th/mutation! data)] + (let [error (:error out)] + (t/is (th/ex-info? error)) + (t/is (th/ex-of-type? error :validation)) + (t/is (th/ex-of-code? error :spec-validation)))) + + ;; try correct register + (let [data {::th/type :register-profile + :token token + :fullname "foobar" + :accept-terms-and-privacy true + :accept-newsletter-subscription true}] + (let [{:keys [result error]} (th/mutation! data)] + (t/is (nil? error)) + (t/is (true? (get-in result [:props :accept-newsletter-subscription]))) + (t/is (true? (get-in result [:props :accept-terms-and-privacy]))))) + )) + +(t/deftest prepare-register-with-registration-disabled (with-mocks [mock {:target 'app.config/get :return (th/mock-config-get-with {:registration-enabled false})}] - (let [data {::th/type :register-profile - :email "user@example.com" - :password "foobar" - :fullname "foobar" - :terms-privacy true} - out (th/mutation! data) - error (:error out) - edata (ex-data error)] - (t/is (th/ex-info? error)) - (t/is (= (:type edata) :restriction)) - (t/is (= (:code edata) :registration-disabled))))) -(t/deftest test-register-existing-profile + (let [data {::th/type :prepare-register-profile + :email "user@example.com" + :password "foobar"}] + (let [{:keys [result error] :as out} (th/mutation! data)] + (t/is (th/ex-info? error)) + (t/is (th/ex-of-type? error :restriction)) + (t/is (th/ex-of-code? error :registration-disabled)))))) + +(t/deftest prepare-register-with-existing-user (let [profile (th/create-profile* 1) - data {::th/type :register-profile + data {::th/type :prepare-register-profile :email (:email profile) - :password "foobar" - :fullname "foobar" - :terms-privacy true} - out (th/mutation! data) - error (:error out) - edata (ex-data error)] - (t/is (th/ex-info? error)) - (t/is (= (:type edata) :validation)) - (t/is (= (:code edata) :email-already-exists)))) - -(t/deftest test-register-profile - (with-mocks [mock {:target 'app.emails/send! - :return nil}] - (let [pool (:app.db/pool th/*system*) - data {::th/type :register-profile - :email "user@example.com" - :password "foobar" - :fullname "foobar" - :terms-privacy true} - out (th/mutation! data)] + :password "foobar"}] + (let [{:keys [result error] :as out} (th/mutation! data)] ;; (th/print-result! out) - (let [mock (deref mock) - [params] (:call-args mock)] - ;; (clojure.pprint/pprint params) - (t/is (:called? mock)) - (t/is (= (:email data) (:to params))) - (t/is (contains? params :extra-data)) - (t/is (contains? params :token))) - - (let [result (:result out)] - (t/is (false? (:is-demo result))) - (t/is (= (:email data) (:email result))) - (t/is (= "penpot" (:auth-backend result))) - (t/is (= "foobar" (:fullname result))) - (t/is (not (contains? result :password))))))) + (t/is (th/ex-info? error)) + (t/is (th/ex-of-type? error :validation)) + (t/is (th/ex-of-code? error :email-already-exists))))) (t/deftest test-register-profile-with-bounced-email - (with-mocks [mock {:target 'app.emails/send! - :return nil}] - (let [pool (:app.db/pool th/*system*) - data {::th/type :register-profile - :email "user@example.com" - :password "foobar" - :fullname "foobar" - :terms-privacy true} - _ (th/create-global-complaint-for pool {:type :bounce :email "user@example.com"}) - out (th/mutation! data)] - ;; (th/print-result! out) + (let [pool (:app.db/pool th/*system*) + data {::th/type :prepare-register-profile + :email "user@example.com" + :password "foobar"}] - (let [mock (deref mock)] - (t/is (false? (:called? mock)))) + (th/create-global-complaint-for pool {:type :bounce :email "user@example.com"}) - (let [error (:error out) - edata (ex-data error)] - (t/is (th/ex-info? error)) - (t/is (= (:type edata) :validation)) - (t/is (= (:code edata) :email-has-permanent-bounces)))))) + (let [{:keys [result error] :as out} (th/mutation! data)] + (t/is (th/ex-info? error)) + (t/is (th/ex-of-type? error :validation)) + (t/is (th/ex-of-code? error :email-has-permanent-bounces))))) (t/deftest test-register-profile-with-complained-email - (with-mocks [mock {:target 'app.emails/send! :return nil}] - (let [pool (:app.db/pool th/*system*) - data {::th/type :register-profile - :email "user@example.com" - :password "foobar" - :fullname "foobar" - :terms-privacy true} - _ (th/create-global-complaint-for pool {:type :complaint :email "user@example.com"}) - out (th/mutation! data)] + (let [pool (:app.db/pool th/*system*) + data {::th/type :prepare-register-profile + :email "user@example.com" + :password "foobar"}] - (let [mock (deref mock)] - (t/is (true? (:called? mock)))) - - (let [result (:result out)] - (t/is (= (:email data) (:email result))))))) + (th/create-global-complaint-for pool {:type :complaint :email "user@example.com"}) + (let [{:keys [result error] :as out} (th/mutation! data)] + (t/is (nil? error)) + (t/is (string? (:token result)))))) (t/deftest test-email-change-request (with-mocks [email-send-mock {:target 'app.emails/send! :return nil} diff --git a/docker/images/files/config.js b/docker/images/files/config.js index 8af727193e..ef86caa0e7 100644 --- a/docker/images/files/config.js +++ b/docker/images/files/config.js @@ -10,3 +10,4 @@ //var penpotLoginWithLDAP = ; //var penpotRegistrationEnabled = ; //var penpotAnalyticsEnabled = ; +//var penpotFlags = ""; diff --git a/docker/images/files/nginx-entrypoint.sh b/docker/images/files/nginx-entrypoint.sh index 7543ab0e1b..cf292de0a9 100644 --- a/docker/images/files/nginx-entrypoint.sh +++ b/docker/images/files/nginx-entrypoint.sh @@ -105,6 +105,14 @@ update_analytics_enabled() { fi } +update_flags() { + if [ -n "$PENPOT_FLAGS" ]; then + sed -i \ + -e "s|^//var penpotFlags = .*;|var penpotFlags = \"$PENPOT_FLAGS\";|g" \ + "$1" + fi +} + update_public_uri /var/www/app/js/config.js update_demo_warning /var/www/app/js/config.js update_allow_demo_users /var/www/app/js/config.js @@ -115,5 +123,5 @@ update_oidc_client_id /var/www/app/js/config.js update_login_with_ldap /var/www/app/js/config.js update_registration_enabled /var/www/app/js/config.js update_analytics_enabled /var/www/app/js/config.js - +update_flags /var/www/app/js/config.js exec "$@"; diff --git a/frontend/resources/styles/main/layouts/login.scss b/frontend/resources/styles/main/layouts/login.scss index 13be87951c..49aee2b0f0 100644 --- a/frontend/resources/styles/main/layouts/login.scss +++ b/frontend/resources/styles/main/layouts/login.scss @@ -54,6 +54,20 @@ justify-content: center; position: relative; + input { + margin-bottom: 0px; + } + + .buttons-stack { + display: flex; + flex-direction: column; + width: 100%; + + *:not(:last-child) { + margin-bottom: $medium; + } + } + .form-container { width: 412px; @@ -83,15 +97,47 @@ } .btn-github-auth { - margin-bottom: $medium; - text-decoration: none; + margin-bottom: $medium; + text-decoration: none; - .logo { - width: 20px; - height: 20px; - margin-right: 1rem; + .logo { + width: 20px; + height: 20px; + margin-right: 1rem; + } + } + + .separator { + display: flex; + justify-content: center; + width: 100%; + text-transform: uppercase; + } + + .links { + display: flex; + font-size: $fs14; + flex-direction: column; + justify-content: space-between; + margin-top: $medium; + margin-bottom: $medium; + + + &.demo { + justify-content: center; + margin-top: $big; + } + + .link-entry { + font-size: $fs14; + color: $color-gray-40; + margin-bottom: 10px; + a { + font-size: $fs14; + color: $color-primary-dark; } } + } } .terms-login { diff --git a/frontend/resources/styles/main/partials/forms.scss b/frontend/resources/styles/main/partials/forms.scss index 9ba80498e2..2c780c91c3 100644 --- a/frontend/resources/styles/main/partials/forms.scss +++ b/frontend/resources/styles/main/partials/forms.scss @@ -109,30 +109,6 @@ textarea { hr { border-color: $color-gray-20; } - - .links { - display: flex; - font-size: $fs14; - flex-direction: column; - justify-content: space-between; - margin-bottom: $medium; - - &.demo { - justify-content: center; - margin-top: $big; - } - } - - .link-entry { - font-size: $fs14; - color: $color-gray-40; - margin-bottom: 10px; - } - - .link-entry a { - font-size: $fs14; - color: $color-primary-dark; - } } .custom-input { diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 87dcf44315..42b90c26ca 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -54,6 +54,11 @@ :browser :webworker)) +(defn- parse-flags + [global] + (let [flags (obj/get global "penpotFlags" "")] + (into #{} (map keyword) (str/words flags)))) + (defn- parse-version [global] (-> (obj/get global "penpotVersion") @@ -78,6 +83,8 @@ (def themes (obj/get global "penpotThemes")) (def analytics (obj/get global "penpotAnalyticsEnabled" false)) +(def flags (delay (parse-flags global))) + (def version (delay (parse-version global))) (def target (delay (parse-target global))) (def browser (delay (parse-browser))) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 906df6b8d2..57c4e14678 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -221,6 +221,7 @@ ;; --- EVENT: register +;; TODO: remove (s/def ::invitation-token ::us/not-empty-string) (s/def ::register diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 601b89ecc1..3ea44d9ea9 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -62,6 +62,8 @@ ["/login" :auth-login] (when cfg/registration-enabled ["/register" :auth-register]) + (when cfg/registration-enabled + ["/register/validate" :auth-register-validate]) (when cfg/registration-enabled ["/register/success" :auth-register-success]) ["/recovery/request" :auth-recovery-request] @@ -112,6 +114,7 @@ (case (:name data) (:auth-login :auth-register + :auth-register-validate :auth-register-success :auth-recovery-request :auth-recovery) diff --git a/frontend/src/app/main/ui/auth.cljs b/frontend/src/app/main/ui/auth.cljs index 06b56dca4d..96996970e5 100644 --- a/frontend/src/app/main/ui/auth.cljs +++ b/frontend/src/app/main/ui/auth.cljs @@ -14,7 +14,7 @@ [app.main.ui.auth.login :refer [login-page]] [app.main.ui.auth.recovery :refer [recovery-page]] [app.main.ui.auth.recovery-request :refer [recovery-request-page]] - [app.main.ui.auth.register :refer [register-page register-success-page]] + [app.main.ui.auth.register :refer [register-page register-success-page register-validate-page]] [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.forms :as fm] @@ -36,13 +36,16 @@ [:div.auth [:section.auth-sidebar - [:a.logo {:href "https://penpot.app"} i/logo] + [:a.logo {:href "#/"} i/logo] [:span.tagline (t locale "auth.sidebar-tagline")]] [:section.auth-content (case section :auth-register - [:& register-page {:locale locale :params params}] + [:& register-page {:params params}] + + :auth-register-validate + [:& register-validate-page {:params params}] :auth-register-success [:& register-success-page {:params params}] @@ -55,6 +58,7 @@ :auth-recovery [:& recovery-page {:locale locale :params params}]) + [:div.terms-login [:a {:href "https://penpot.app/terms.html" :target "_blank"} "Terms of service"] [:span "and"] diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index 3b809e986d..c095f3a034 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -23,6 +23,12 @@ [cljs.spec.alpha :as s] [rumext.alpha :as mf])) +(def show-alt-login-buttons? + (or cfg/google-client-id + cfg/gitlab-client-id + cfg/github-client-id + cfg/oidc-client-id)) + (s/def ::email ::us/email) (s/def ::password ::us/not-empty-string) @@ -103,13 +109,15 @@ :tab-index "3" :help-icon i/eye :label (tr "auth.password")}]] - [:& fm/submit-button - {:label (tr "auth.login-submit")}] - (when cfg/login-with-ldap - [:& fm/submit-button - {:label (tr "auth.login-with-ldap-submit") - :on-click on-submit-ldap}])]])) + [:div.buttons-stack + [:& fm/submit-button + {:label (tr "auth.login-submit")}] + + (when cfg/login-with-ldap + [:& fm/submit-button + {:label (tr "auth.login-with-ldap-submit") + :on-click on-submit-ldap}])]]])) (mf/defc login-buttons [{:keys [params] :as props}] @@ -147,6 +155,13 @@ [:& login-form {:params params}] + (when show-alt-login-buttons? + [:* + [:span.separator (tr "labels.or")] + + [:div.buttons + [:& login-buttons {:params params}]]]) + [:div.links [:div.link-entry [:a {:on-click #(st/emit! (rt/nav :auth-recovery-request))} @@ -158,7 +173,6 @@ [:a {:on-click #(st/emit! (rt/nav :auth-register {} params))} (tr "auth.register-submit")]])] - [:& login-buttons {:params params}] (when cfg/allow-demo-users [:div.links.demo diff --git a/frontend/src/app/main/ui/auth/recovery_request.cljs b/frontend/src/app/main/ui/auth/recovery_request.cljs index 637bbc852f..5c7cb5c2d1 100644 --- a/frontend/src/app/main/ui/auth/recovery_request.cljs +++ b/frontend/src/app/main/ui/auth/recovery_request.cljs @@ -86,4 +86,4 @@ [:div.links [:div.link-entry [:a {:on-click #(st/emit! (rt/nav :auth-login))} - (tr "auth.go-back-to-login")]]]]]) + (tr "labels.go-back")]]]]]) diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 97c1aec2eb..4e968b4fe3 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -7,10 +7,11 @@ (ns app.main.ui.auth.register (:require [app.common.spec :as us] - [app.config :as cfg] + [app.config :as cf] [app.main.data.users :as du] [app.main.data.messages :as dm] [app.main.store :as st] + [app.main.repo :as rp] [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] @@ -30,6 +31,8 @@ {:type :warning :content (tr "auth.demo-warning")}]) +;; --- PAGE: Register + (defn- validate [data] (let [password (:password data) @@ -48,9 +51,29 @@ (s/def ::terms-privacy ::us/boolean) (s/def ::register-form - (s/keys :req-un [::password ::fullname ::email ::terms-privacy] + (s/keys :req-un [::password ::email] :opt-un [::invitation-token])) +(defn- handle-prepare-register-error + [form error] + (case (:code error) + :registration-disabled + (st/emit! (dm/error (tr "errors.registration-disabled"))) + + :email-has-permanent-bounces + (let [email (get @form [:data :email])] + (st/emit! (dm/error (tr "errors.email-has-permanent-bounces" email)))) + + :email-already-exists + (swap! form assoc-in [:errors :email] + {:message "errors.email-already-exists"}) + + (st/emit! (dm/error (tr "errors.generic"))))) + +(defn- handle-prepare-register-success + [form {:keys [token] :as result}] + (st/emit! (rt/nav :auth-register-validate {} {:token token}))) + (mf/defc register-form [{:keys [params] :as props}] (let [initial (mf/use-memo (mf/deps params) (constantly params)) @@ -59,49 +82,20 @@ :initial initial) submitted? (mf/use-state false) - on-error - (mf/use-callback - (fn [form error] - (reset! submitted? false) - (case (:code error) - :registration-disabled - (rx/of (dm/error (tr "errors.registration-disabled"))) - - :email-has-permanent-bounces - (let [email (get @form [:data :email])] - (rx/of (dm/error (tr "errors.email-has-permanent-bounces" email)))) - - :email-already-exists - (swap! form assoc-in [:errors :email] - {:message "errors.email-already-exists"}) - - (rx/throw error)))) - - on-success - (mf/use-callback - (fn [form data] - (reset! submitted? false) - (if-let [token (:invitation-token data)] - (st/emit! (rt/nav :auth-verify-token {} {:token token})) - (st/emit! (rt/nav :auth-register-success {} {:email (:email data)}))))) - on-submit (mf/use-callback (fn [form event] (reset! submitted? true) - (let [data (with-meta (:clean-data @form) - {:on-error (partial on-error form) - :on-success (partial on-success form)})] - (st/emit! (du/register data)))))] + (let [params (:clean-data @form)] + (->> (rp/mutation :prepare-register-profile params) + (rx/finalize #(reset! submitted? false)) + (rx/subs (partial handle-prepare-register-success form) + (partial handle-prepare-register-error form)))))) + ] [:& fm/form {:on-submit on-submit :form form} - [:div.fields-row - [:& fm/input {:name :fullname - :tab-index "1" - :label (tr "auth.fullname") - :type "text"}]] [:div.fields-row [:& fm/input {:type "email" :name :email @@ -115,18 +109,145 @@ :label (tr "auth.password") :type "password"}]] + [:& fm/submit-button + {:label (tr "auth.register-submit") + :disabled @submitted?}]])) + +(mf/defc register-page + [{:keys [params] :as props}] + [:div.form-container + [:h1 (tr "auth.register-title")] + [:div.subtitle (tr "auth.register-subtitle")] + + (when cf/demo-warning + [:& demo-warning]) + + [:& register-form {:params params}] + + (when login/show-alt-login-buttons? + [:* + [:span.separator (tr "labels.or")] + + [:div.buttons + [:& login/login-buttons {:params params}]]]) + + [:div.links + [:div.link-entry + [:span (tr "auth.already-have-account") " "] + [:a {:on-click #(st/emit! (rt/nav :auth-login {} params)) + :tab-index "4"} + (tr "auth.login-here")]] + + (when cf/allow-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")]])]]) + +;; --- PAGE: register validation + +(defn- handle-register-error + [form error] + (case (:code error) + :registration-disabled + (st/emit! (dm/error (tr "errors.registration-disabled"))) + + :email-has-permanent-bounces + (let [email (get @form [:data :email])] + (st/emit! (dm/error (tr "errors.email-has-permanent-bounces" email)))) + + :email-already-exists + (swap! form assoc-in [:errors :email] + {:message "errors.email-already-exists"}) + + (do + (println (:explain error)) + (st/emit! (dm/error (tr "errors.generic")))))) + +(defn- handle-register-success + [form data] + (cond + (some? (:invitation-token data)) + (let [token (:invitation-token data)] + (st/emit! (rt/nav :auth-verify-token {} {:token token}))) + + + (not= "penpot" (:auth-backend data)) + (st/emit! + (du/fetch-profile) + (rt/nav :dashboard-projects {:team-id (:default-team-id data)})) + + :else + (st/emit! (rt/nav :auth-register-success {} {:email (:email data)})))) + +(s/def ::accept-terms-and-privacy ::us/boolean) +(s/def ::accept-newsletter-subscription ::us/boolean) + +(s/def ::register-validate-form + (s/keys :req-un [::token ::fullname ::accept-terms-and-privacy] + :opt-un [::accept-newsletter-subscription])) + +(mf/defc register-validate-form + [{:keys [params] :as props}] + (let [initial (mf/use-memo + (mf/deps params) + (fn [] + (assoc params :accept-newsletter-subscription false))) + form (fm/use-form :spec ::register-validate-form + :initial initial) + submitted? (mf/use-state false) + + on-submit + (mf/use-callback + (fn [form event] + (reset! submitted? true) + (let [params (:clean-data @form)] + (->> (rp/mutation :register-profile params) + (rx/finalize #(reset! submitted? false)) + (rx/subs (partial handle-register-success form) + (partial handle-register-error form)))))) + ] + + [:& fm/form {:on-submit on-submit + :form form} [:div.fields-row - [:& fm/input {:name :terms-privacy + [:& fm/input {:name :fullname + :tab-index "1" + :label (tr "auth.fullname") + :type "text"}]] + [:div.fields-row + [:& fm/input {:name :accept-terms-and-privacy :class "check-primary" - :tab-index "4" :label (tr "auth.terms-privacy-agreement") :type "checkbox"}]] + (when (contains? @cf/flags :show-newsletter-check-on-register-validation) + [:div.fields-row + [:& fm/input {:name :accept-newsletter-subscription + :class "check-primary" + :label (tr "auth.terms-privacy-agreement") + :type "checkbox"}]]) + [:& fm/submit-button {:label (tr "auth.register-submit") :disabled @submitted?}]])) -;; --- Register Page + +(mf/defc register-validate-page + [{:keys [params] :as props}] + (prn "register-validate-page" params) + [:div.form-container + [:h1 (tr "auth.register-title")] + [:div.subtitle (tr "auth.register-subtitle")] + + [:& register-validate-form {:params params}] + + [:div.links + [:div.link-entry + [:a {:on-click #(st/emit! (rt/nav :auth-register {} {})) + :tab-index "4"} + (tr "labels.go-back")]]]]) (mf/defc register-success-page [{:keys [params] :as props}] @@ -136,32 +257,3 @@ [:div.notification-text-email (:email params "")] [:div.notification-text (tr "auth.check-your-email")]]) -(mf/defc register-page - [{:keys [params] :as props}] - [:div.form-container - [:h1 (tr "auth.register-title")] - [:div.subtitle (tr "auth.register-subtitle")] - - (when cfg/demo-warning - [:& demo-warning]) - - [:& register-form {:params params}] - - [:div.links - [:div.link-entry - [:span (tr "auth.already-have-account") " "] - [:a {:on-click #(st/emit! (rt/nav :auth-login {} params)) - :tab-index "4"} - (tr "auth.login-here")]] - - (when cfg/allow-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")]]) - - [:& login/login-buttons {:params params}]]]) - - - diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 86bac1358e..972b4464bc 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -48,10 +48,6 @@ msgstr "هل نسيت كلمة السر؟" msgid "auth.fullname" msgstr "الاسم بالكامل" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "الرجوع للخلف!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "تسجيل الدخول هنا" @@ -190,6 +186,9 @@ msgstr "نمط" msgid "labels.fonts" msgstr "الخطوط" +msgid "labels.go-back" +msgstr "الرجوع للخلف" + msgid "labels.images" msgstr "الصور" diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 0944d45743..5c9ab6c800 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -46,10 +46,6 @@ msgstr "Has oblidat la contrasenya?" msgid "auth.fullname" msgstr "Nom complet" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Tornar" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Inicia sessió aquí" @@ -467,6 +463,9 @@ msgstr "S'ha produït un error" msgid "labels.accept" msgstr "Acceptar" +msgid "labels.go-back" +msgstr "Tornar" + msgid "labels.recent" msgstr "Recent" diff --git a/frontend/translations/da.po b/frontend/translations/da.po index 2847eee0e6..1f15d4514c 100644 --- a/frontend/translations/da.po +++ b/frontend/translations/da.po @@ -51,10 +51,6 @@ msgstr "Glemt adgangskode?" msgid "auth.fullname" msgstr "Fulde Navn" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Gå tilbage!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Log på her" @@ -452,6 +448,9 @@ msgstr "Stil" msgid "labels.fonts" msgstr "Skrifttyper" +msgid "labels.go-back" +msgstr "Gå tilbage!" + msgid "labels.installed-fonts" msgstr "Installeret skrifttyper" diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 0fbb95166f..84ff7a0213 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -51,10 +51,6 @@ msgstr "Passwort vergessen?" msgid "auth.fullname" msgstr "Vollständiger Name" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Zurück!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Hier einloggen" @@ -823,6 +819,9 @@ msgstr "Feedback gesendet" msgid "labels.give-feedback" msgstr "Feedback geben" +msgid "labels.go-back" +msgstr "Zurück!" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" msgstr "Erledigte Kommentare ausblenden" @@ -1306,6 +1305,10 @@ msgstr "Seite bearbeiten" msgid "viewer.header.fullscreen" msgstr "Vollbildmodus" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interaktionen" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.copy-link" msgstr "Link kopieren" @@ -1330,10 +1333,6 @@ msgstr "Jeder mit dem Link hat Zugriff" msgid "viewer.header.share.title" msgstr "Prototyp teilen" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.interactions" -msgstr "Interaktionen" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Interaktionen anzeigen" diff --git a/frontend/translations/el.po b/frontend/translations/el.po index d5b7822500..b8a58c0116 100644 --- a/frontend/translations/el.po +++ b/frontend/translations/el.po @@ -46,10 +46,6 @@ msgstr "Ξεχάσατε τον κωδικό;" msgid "auth.fullname" msgstr "Πλήρες όνομα" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Πίσω" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Συνδεθείτε εδώ" @@ -823,6 +819,9 @@ msgstr "Εστάλη γνώμη" msgid "labels.give-feedback" msgstr "Δώστε μας τη γνώμη σας" +msgid "labels.go-back" +msgstr "Πίσω" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" msgstr "Απόκρυψη επιλυμένων σχολίων" diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 08bb955865..b873e8b916 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -49,10 +49,6 @@ msgstr "Forgot password?" msgid "auth.fullname" msgstr "Full Name" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Go back!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Login here" @@ -206,6 +202,12 @@ msgstr "Duplicate %s files" msgid "dashboard.empty-files" msgstr "You still have no files here" +msgid "dashboard.export-multi" +msgstr "Export %s files" + +msgid "dashboard.export-single" +msgstr "Export file" + msgid "dashboard.fonts.deleted-placeholder" msgstr "Font deleted" @@ -229,6 +231,9 @@ msgstr "" "Service](https://penpot.app/terms.html). You also might want to read about " "[font licensing](https://www.typography.com/faq)." +msgid "dashboard.import" +msgstr "Import files" + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Invite to team" @@ -304,6 +309,9 @@ msgstr "%s members" msgid "dashboard.open-in-new-tab" msgstr "Open file in a new tab" +msgid "dashboard.options" +msgstr "Options" + #: src/app/main/ui/settings/password.cljs msgid "dashboard.password-change" msgstr "Change password" @@ -913,6 +921,9 @@ msgstr "Fonts" msgid "labels.give-feedback" msgstr "Give feedback" +msgid "labels.go-back" +msgstr "Go back" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" msgstr "Hide resolved comments" @@ -1000,6 +1011,9 @@ msgstr "Old password" msgid "labels.only-yours" msgstr "Only yours" +msgid "labels.or" +msgstr "or" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.owner" msgstr "Owner" @@ -1461,6 +1475,10 @@ msgstr "Edit file" msgid "viewer.header.fullscreen" msgstr "Full Screen" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interactions" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.copy-link" msgstr "Copy link" @@ -1485,10 +1503,6 @@ msgstr "Anyone with the link will have access" msgid "viewer.header.share.title" msgstr "Share prototype" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.interactions" -msgstr "Interactions" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Show interactions" @@ -1889,20 +1903,28 @@ msgid "workspace.options.constraints" msgstr "Constraints" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.left" -msgstr "Left" +msgid "workspace.options.constraints.bottom" +msgstr "Bottom" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.right" -msgstr "Right" +msgid "workspace.options.constraints.center" +msgstr "Center" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.fix-when-scrolling" +msgstr "Fix when scrolling" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.left" +msgstr "Left" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.leftright" msgstr "Left & Right" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.center" -msgstr "Center" +msgid "workspace.options.constraints.right" +msgstr "Right" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.scale" @@ -1912,26 +1934,10 @@ msgstr "Scale" msgid "workspace.options.constraints.top" msgstr "Top" -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.bottom" -msgstr "Bottom" - #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.topbottom" msgstr "Top & Bottom" -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.center" -msgstr "Center" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.scale" -msgstr "Scale" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.fix-when-scrolling" -msgstr "Fix when scrolling" - #: src/app/main/ui/workspace/sidebar/options.cljs msgid "workspace.options.design" msgstr "Design" @@ -2686,16 +2692,4 @@ msgid "workspace.updates.update" msgstr "Update" msgid "workspace.viewport.click-to-close-path" -msgstr "Click to close the path" - -msgid "dashboard.export-single" -msgstr "Export file" - -msgid "dashboard.export-multi" -msgstr "Export %s files" - -msgid "dashboard.import" -msgstr "Import files" - -msgid "dashboard.options" -msgstr "Options" +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 b668c94284..14ea29d177 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -51,10 +51,6 @@ msgstr "¿Olvidaste tu contraseña?" msgid "auth.fullname" msgstr "Nombre completo" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Volver" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Entra aquí" @@ -915,6 +911,9 @@ msgstr "Fuentes" msgid "labels.give-feedback" msgstr "Danos tu opinión" +msgid "labels.go-back" +msgstr "Volver" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" msgstr "Ocultar comentarios resueltos" @@ -1002,6 +1001,9 @@ msgstr "Contraseña anterior" msgid "labels.only-yours" msgstr "Sólo los tuyos" +msgid "labels.or" +msgstr "o" + #: src/app/main/ui/dashboard/team.cljs, src/app/main/ui/dashboard/team.cljs msgid "labels.owner" msgstr "Dueño" @@ -1451,6 +1453,10 @@ msgstr "Editar archivo" msgid "viewer.header.fullscreen" msgstr "Pantalla completa" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interacciones" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.copy-link" msgstr "Copiar enlace" @@ -1475,10 +1481,6 @@ msgstr "Cualquiera con el enlace podrá acceder" msgid "viewer.header.share.title" msgstr "Compartir prototipo" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.interactions" -msgstr "Interacciones" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Mostrar interacciones" @@ -1881,20 +1883,28 @@ msgid "workspace.options.constraints" msgstr "Restricciones" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.left" -msgstr "Izquierda" +msgid "workspace.options.constraints.bottom" +msgstr "Abajo" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.right" -msgstr "Derecha" +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 "Fijo al desplazar" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.constraints.left" +msgstr "Izquierda" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.leftright" msgstr "Izq. y Der." #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.center" -msgstr "Centro" +msgid "workspace.options.constraints.right" +msgstr "Derecha" #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.scale" @@ -1904,26 +1914,10 @@ msgstr "Escalar" msgid "workspace.options.constraints.top" msgstr "Arriba" -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.bottom" -msgstr "Abajo" - #: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs msgid "workspace.options.constraints.topbottom" msgstr "Arriba y Abajo" -#: 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.scale" -msgstr "Escalar" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.constraints.fix-when-scrolling" -msgstr "Fijo al desplazar" - #: src/app/main/ui/workspace/sidebar/options.cljs msgid "workspace.options.design" msgstr "Diseño" @@ -2680,4 +2674,4 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" +msgstr "Pulsar para cerrar la ruta" \ No newline at end of file diff --git a/frontend/translations/fr.po b/frontend/translations/fr.po index 313aa753d5..b43da10802 100644 --- a/frontend/translations/fr.po +++ b/frontend/translations/fr.po @@ -51,10 +51,6 @@ msgstr "Mot de passe oublié ?" msgid "auth.fullname" msgstr "Nom complet" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Retour !" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Se connecter ici" @@ -737,6 +733,9 @@ msgstr "Adresse e‑mail" msgid "labels.give-feedback" msgstr "Donnez votre avis" +msgid "labels.go-back" +msgstr "Retour" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" msgstr "Masquer les commentaires résolus" @@ -1202,6 +1201,10 @@ msgstr "Modifier la page" msgid "viewer.header.fullscreen" msgstr "Plein écran" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interactions" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.copy-link" msgstr "Copier le lien" @@ -1226,10 +1229,6 @@ msgstr "Toute personne disposant du lien aura accès" msgid "viewer.header.share.title" msgstr "Partager le prototype" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.interactions" -msgstr "Interactions" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Afficher les interactions" diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index a00125a3d2..bb98380421 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -51,10 +51,6 @@ msgstr "Esqueceu a senha?" msgid "auth.fullname" msgstr "Nome completo" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Voltar!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Entrar aqui" @@ -697,6 +693,9 @@ msgstr "Fontes" msgid "labels.give-feedback" msgstr "Enviar feedback" +msgid "labels.go-back" +msgstr "Voltar" + msgid "labels.icons" msgstr "Ícones" diff --git a/frontend/translations/ro.po b/frontend/translations/ro.po index 3a0e4402b5..f12369e267 100644 --- a/frontend/translations/ro.po +++ b/frontend/translations/ro.po @@ -52,10 +52,6 @@ msgstr "Ai uitat parola?" msgid "auth.fullname" msgstr "Numele complet" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Întoarce-te!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Conectează-te" @@ -907,6 +903,9 @@ msgstr "Fonturi" msgid "labels.give-feedback" msgstr "Lasă un feedback" +msgid "labels.go-back" +msgstr "Întoarce-te" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" msgstr "Ascunde comentariile rezolvate" @@ -1441,6 +1440,10 @@ msgstr "Editează pagina" msgid "viewer.header.fullscreen" msgstr "Ecran complet" +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.interactions" +msgstr "Interacţiunile" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.share.copy-link" msgstr "Copiază link" @@ -1469,10 +1472,6 @@ msgstr "Distribuie link" msgid "viewer.header.show-interactions" msgstr "Afişează interacţiunile" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.interactions" -msgstr "Interacţiunile" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions-on-click" msgstr "Afişează interacţiunile la click" diff --git a/frontend/translations/ru.po b/frontend/translations/ru.po index b56f9f0789..4d681bf2b1 100644 --- a/frontend/translations/ru.po +++ b/frontend/translations/ru.po @@ -40,10 +40,6 @@ msgstr "Забыли пароль?" msgid "auth.fullname" msgstr "Полное имя" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Назад!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Войти здесь" @@ -359,6 +355,9 @@ msgstr "Email" msgid "labels.give-feedback" msgstr "Дать обратную связь" +msgid "labels.go-back" +msgstr "Назад" + msgid "labels.icons" msgstr "Иконки" @@ -563,6 +562,10 @@ msgstr "На странице не найдено ни одного кадра" msgid "viewer.frame-not-found" msgstr "Кадры не найдены." +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header-interactions" +msgstr "взаимодействия" + #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.dont-show-interactions" msgstr "Не показывать взаимодействия" @@ -599,10 +602,6 @@ msgstr "Любой, у кого есть ссылка будет иметь до msgid "viewer.header.share.title" msgstr "Поделиться ссылкой" -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header-interactions" -msgstr "взаимодействия" - #: src/app/main/ui/viewer/header.cljs msgid "viewer.header.show-interactions" msgstr "Показывать взаимодействия" diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index a947e22230..12cfa3a4b4 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -51,10 +51,6 @@ msgstr "Parolanı mı unuttun?" msgid "auth.fullname" msgstr "Tam Adın" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "Geri dön!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "Buradan giriş yap" @@ -739,6 +735,9 @@ msgstr "Fontlar" msgid "labels.give-feedback" msgstr "Geri bildirimde bulun" +msgid "labels.go-back" +msgstr "Geri dön" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" msgstr "Çözülmüş yorumları gizle" diff --git a/frontend/translations/zh_CN.po b/frontend/translations/zh_CN.po index 481883c522..37b1d70051 100644 --- a/frontend/translations/zh_CN.po +++ b/frontend/translations/zh_CN.po @@ -42,10 +42,6 @@ msgstr "忘记密码?" msgid "auth.fullname" msgstr "全名" -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.go-back-to-login" -msgstr "返回!" - #: src/app/main/ui/auth/register.cljs msgid "auth.login-here" msgstr "在这里登录" @@ -751,6 +747,9 @@ msgstr "反馈已发出" msgid "labels.give-feedback" msgstr "提交反馈" +msgid "labels.go-back" +msgstr "返回" + #: src/app/main/ui/workspace/comments.cljs, src/app/main/ui/viewer/header.cljs msgid "labels.hide-resolved-comments" msgstr "隐藏已决定的评论" From c21ad48370dad76045e9a07cc8a8b86f4bdc025f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 18 Jun 2021 10:36:04 +0200 Subject: [PATCH 114/204] :bug: Fix problem with order in color palette --- frontend/src/app/main/ui/workspace/colorpalette.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index 102beadca0..f4495ccb3c 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -210,7 +210,7 @@ (cond (= selected :recent) (reverse recent-colors) (= selected :file) (->> (vals file-colors) (sort-by :name)) - :else (library->colors shared-libs selected)))))) + :else (->> (library->colors shared-libs selected) (sort-by :name))))))) (mf/use-effect (mf/deps recent-colors) From 09314c892624d4e4bf838685dd967de7a856bd6b Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Jun 2021 11:18:05 +0200 Subject: [PATCH 115/204] :paperclip: Sort & validate translation files. --- frontend/translations/ar.po | 900 +++++----- frontend/translations/ca.po | 679 ++++---- frontend/translations/de.po | 61 +- frontend/translations/id.po | 323 ++-- frontend/translations/pt_BR.po | 572 +++---- frontend/translations/tr.po | 2871 ++++++++++++++++---------------- 6 files changed, 2704 insertions(+), 2702 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 4783704d6b..fd849b184a 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-06-15 18:34+0000\n" "Last-Translator: Amine Gdoura \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" @@ -96,6 +96,14 @@ msgstr "رمز الاسترداد غير صالح." msgid "auth.notifications.password-changed-succesfully" 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 msgid "auth.notifications.team-invitation-accepted" msgstr "تم الانضمام إلى الفريق بنجاح" @@ -153,6 +161,45 @@ msgstr "عند إنشاء حساب جديد ، فإنك توافق على شرو msgid "auth.verification-email-sent" msgstr "لقد أرسلنا رسالة تحقق إلى بريدك الالكتروني" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "أضف كمكتبة مشتركة" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +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 "+ إنشاء فريق جديد" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Penpot الخاص بك" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +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/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "تكرير ٪s الملفات" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "لا يزال لديك 0 ملفات هنا" + #, fuzzy, markdown msgid "dashboard.fonts.hero-text1" msgstr "" @@ -169,6 +216,406 @@ msgstr "" "(https://penpot.app/terms.html). قد ترغب أيضًا في القراءة عن [ترخيص الخطوط] " "(2)." +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "قم بدعوة فريق" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +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 "تحميل ملفاتك …" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +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/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ ملف جديد" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "ملف جديد" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ مشروع جديد" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "مشروع جديد" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "لم يتم العثور على مطابقات ل \"٪s\"" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "ستظهر المشاريع المثبتة هنا" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "تم تحديث عنوان بريدك الإلكتروني بنجاح" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "تم التحقق من عنوان بريدك الإلكتروني بنجاح" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "تم حفظ كلمة المرور بنجاح!" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "٪s الأعضاء" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "فتح ملف في علامة تبويب جديدة" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +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/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "الترقية إلى مالك" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.remove-account" +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.searching-for" +msgstr "البحث عن \"٪s\"…" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "حدد لغة واجهة المستخدم" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "اختر نمطا" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "إظهار كافة الملفات" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "تم حذف ملفك بنجاح" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "تم حذف مشروعك بنجاح" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "تم تكرار ملفك بنجاح" + +#: src/app/main/ui/dashboard/project_menu.cljs +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 +msgid "dashboard.success-move-file" +msgstr "تم نقل ملفك بنجاح" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "تم نقل الملفات بنجاح" + +#: src/app/main/ui/dashboard/project_menu.cljs +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 "معلومات الفريق" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "أعضاء الفريق" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "مشاريع الفريق" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "نتائج البحث" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +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 "تحديث الإعدادات" + +#: src/app/main/ui/settings.cljs +#, fuzzy +msgid "dashboard.your-account-title" +msgstr "حسابك الخاص" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "البريد الالكتروني" + +#: src/app/main/ui/settings/profile.cljs +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 +msgid "dashboard.your-penpot" +msgstr "Penpot الخاص بك" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +msgstr "إلغاء الأمر" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "حسنا" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "هل أنت متأكد؟" + +#: src/app/main/ui/dashboard/grid.cljs +#, fuzzy +msgid "ds.updated-at" +msgstr "محدث: ٪s" + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "لا يمكن للمتصفح إجراء هذه العملية" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "البريد الإلكتروني مستخدم بالفعل" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "تم التحقق من صحة البريد الإلكتروني." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +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" +msgstr "حدث خطأ ما." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +msgstr "المصادقة مع جوجل تعطلت في الخلفية" + +#: src/app/main/ui/components/color_input.cljs +msgid "errors.invalid-color" +msgstr "لون غير صالح" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "تم تعطيل مصادقة LDAP." + +msgid "errors.media-format-unsupported" +msgstr "تنسيق الصورة غير مدعوم (يجب أن يكون svg أو jpg أو png)." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "الصورة كبيرة جدا بحيث لا يمكن إدراجها (يجب أن تكون أقل من 5mb)." + +#: 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 "يبدو أن هذه ليست صورة صالحة." + +msgid "errors.network" +msgstr "تعذر الاتصال بخادم الواجهة الخلفية." + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "يجب أن تتطابق كلمة مرور التأكيد" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "يجب ألا تقل كلمة المرور عن 8 أحرف" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.wrong-old-password" +msgstr "كلمة المرور القديمة غير صحيحة" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-start" +msgstr "انضم إلى الدردشة" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "ترغب في الكلام؟ تحدث معنا في Gitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "وصف" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-go-to" +msgstr "اذهب إلى المناقشات" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle1" +msgstr "انضم إلى منتدى التواصل التعاوني لفريق Penpot." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle2" +msgstr "" +"يمكنك طرح الأسئلة والإجابة عليها، إجراء محادثات مفتوحة، ومتابعة القرارات " +"التي تؤثر على المشروع." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-title" +msgstr "مناقشات الفريق" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +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/password.cljs +msgid "generic.error" +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/image.cljs +msgid "handoff.attributes.image.download" +msgstr "تحميل صورة المصدر" + +#: src/app/main/ui/handoff/attributes/image.cljs +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 "دوران" + +#: 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/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +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 "عرض" + msgid "labels.custom-fonts" msgstr "خطوط مخصصة" @@ -228,451 +675,4 @@ msgid "title.dashboard.fonts" msgstr "الخطوط -٪ s - Penpot" msgid "workspace.viewport.click-to-close-path" -msgstr "انقر لإغلاق المسار" - -#: src/app/main/ui/settings/password.cljs -msgid "dashboard.notifications.password-saved" -msgstr "تم حفظ كلمة المرور بنجاح!" - -#: src/app/main/ui/auth/verify_token.cljs -msgid "dashboard.notifications.email-verified-successfully" -msgstr "تم التحقق من عنوان بريدك الإلكتروني بنجاح" - -#: src/app/main/ui/auth/verify_token.cljs -msgid "dashboard.notifications.email-changed-successfully" -msgstr "تم تحديث عنوان بريدك الإلكتروني بنجاح" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.no-projects-placeholder" -msgstr "ستظهر المشاريع المثبتة هنا" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.no-matches-for" -msgstr "لم يتم العثور على مطابقات ل \"٪s\"" - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-project-prefix" -msgstr "مشروع جديد" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dashboard.new-project" -msgstr "+ مشروع جديد" - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-file-prefix" -msgstr "ملف جديد" - -#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs -msgid "dashboard.new-file" -msgstr "+ ملف جديد" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to-other-team" -msgstr "الانتقال إلى فريق آخر" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to-multi" -msgstr "أنقل ٪s الملفات إلى" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to" -msgstr "الانتقال إلى" - -#: src/app/main/ui/dashboard/grid.cljs -msgid "dashboard.loading-files" -msgstr "تحميل ملفاتك …" - -#: src/app/main/ui/dashboard/libraries.cljs -msgid "dashboard.libraries-title" -msgstr "المكتبات المشتركة" - -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.leave-team" -msgstr "ترك الفريق" - -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.invite-profile" -msgstr "قم بدعوة فريق" - -#: src/app/main/ui/dashboard/grid.cljs -msgid "dashboard.empty-files" -msgstr "لا يزال لديك 0 ملفات هنا" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate-multi" -msgstr "تكرير ٪s الملفات" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate" -msgstr "تكرير" - -msgid "dashboard.draft-title" -msgstr "مسودة" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.delete-team" -msgstr "حذف الفريق" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.default-team-name" -msgstr "Penpot الخاص بك" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.create-new-team" -msgstr "+ إنشاء فريق جديد" - -#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs -msgid "dashboard.copy-suffix" -msgstr "(نسخة)" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.change-email" -msgstr "تغيير البريد الإلكتروني" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.add-shared" -msgstr "أضف كمكتبة مشتركة" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.notifications.recovery-token-sent" -msgstr "تم إرسال رابط استعادة كلمة المرور إلى صندوق البريد الخاص بك." - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.notifications.profile-not-verified" -msgstr "لم يتم التعرف على الحساب الشخصي ، يرجى التحقق قبل المتابعة." - -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.email-already-validated" -msgstr "تم التحقق من صحة البريد الإلكتروني." - -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs -msgid "errors.email-already-exists" -msgstr "البريد الإلكتروني مستخدم بالفعل" - -#: src/app/main/data/workspace.cljs -msgid "errors.clipboard-not-implemented" -msgstr "لا يمكن للمتصفح إجراء هذه العملية" - -#: src/app/main/ui/dashboard/grid.cljs -#, fuzzy -msgid "ds.updated-at" -msgstr "محدث: ٪s" - -#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs -msgid "ds.confirm-title" -msgstr "هل أنت متأكد؟" - -#: src/app/main/ui/confirm.cljs -msgid "ds.confirm-ok" -msgstr "حسنا" - -#: src/app/main/ui/confirm.cljs -msgid "ds.confirm-cancel" -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 -msgid "dashboard.your-penpot" -msgstr "Penpot الخاص بك" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.your-name" -msgstr "اسمك" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.your-email" -msgstr "البريد الالكتروني" - -#: src/app/main/ui/settings.cljs -#, fuzzy -msgid "dashboard.your-account-title" -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 "تحديث الإعدادات" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.type-something" -msgstr "اكتب لإظهار نتائج البحث" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.title-search" -msgstr "نتائج البحث" - -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.team-projects" -msgstr "مشاريع الفريق" - -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.team-members" -msgstr "أعضاء الفريق" - -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.team-info" -msgstr "معلومات الفريق" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.switch-team" -msgstr "تبديل الفريق" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-move-project" -msgstr "تم نقل مشروعك بنجاح" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-move-files" -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 "تم نقل ملفك بنجاح" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-duplicate-project" -msgstr "تم نسخ مشروعك بنجاح" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-duplicate-file" -msgstr "تم تكرار ملفك بنجاح" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-delete-project" -msgstr "تم حذف مشروعك بنجاح" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-delete-file" -msgstr "تم حذف ملفك بنجاح" - -#: src/app/main/ui/dashboard/grid.cljs -msgid "dashboard.show-all-files" -msgstr "إظهار كافة الملفات" - -#: src/app/main/ui/settings/options.cljs -msgid "dashboard.select-ui-theme" -msgstr "اختر نمطا" - -#: src/app/main/ui/settings/options.cljs -msgid "dashboard.select-ui-language" -msgstr "حدد لغة واجهة المستخدم" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.searching-for" -msgstr "البحث عن \"٪s\"…" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.search-placeholder" -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/settings/profile.cljs -msgid "dashboard.remove-account" -msgstr "هل تريد إزالة حسابك؟" - -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "الترقية إلى مالك" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dashboard.projects-title" -msgstr "المشاريع" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.pin-unpin" -msgstr "تثبيت / إلغاء التثبيت" - -#: src/app/main/ui/settings/password.cljs -msgid "dashboard.password-change" -msgstr "تغيير كلمة المرور" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.open-in-new-tab" -msgstr "فتح ملف في علامة تبويب جديدة" - -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.num-of-members" -msgstr "٪s الأعضاء" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke.width" -msgstr "عرض" - -msgid "handoff.attributes.stroke.style.solid" -msgstr "صلب" - -msgid "handoff.attributes.stroke.style.none" -msgstr "لا أحد" - -msgid "handoff.attributes.stroke.style.mixed" -msgstr "مختلط" - -msgid "handoff.attributes.stroke.style.dotted" -msgstr "منقط" - -#, permanent -msgid "handoff.attributes.stroke.alignment.outer" -msgstr "خارج" - -#, permanent -msgid "handoff.attributes.stroke.alignment.inner" -msgstr "داخل" - -#, permanent -msgid "handoff.attributes.stroke.alignment.center" -msgstr "مركز" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow" -msgstr "ظل" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.width" -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.rotation" -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.left" -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" -msgstr "تخطيط" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.width" -msgstr "عرض" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.height" -msgstr "ارتفاع" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.download" -msgstr "تحميل صورة المصدر" - -#: src/app/main/ui/handoff/attributes/common.cljs -msgid "handoff.attributes.color.rgba" -msgstr "RGBA" - -#: 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.hex" -msgstr "HEX" - -#: src/app/main/ui/handoff/attributes/blur.cljs -msgid "handoff.attributes.blur.value" -msgstr "قيمة" - -#: src/app/main/ui/settings/password.cljs -msgid "generic.error" -msgstr "حدث خطأ" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.title" -msgstr "البريد الإلكتروني" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.subtitle" -msgstr "" -"يرجى وصف سبب بريدك الإلكتروني ، وتحديد ما إذا كانت مشكلة أم فكرة أم شك. سيرد " -"أحد أعضاء فريقنا في أسرع وقت ممكن." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.subject" -msgstr "موضوع" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-title" -msgstr "مناقشات الفريق" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-subtitle2" -msgstr "" -"يمكنك طرح الأسئلة والإجابة عليها، إجراء محادثات مفتوحة، ومتابعة القرارات " -"التي تؤثر على المشروع." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-subtitle1" -msgstr "انضم إلى منتدى التواصل التعاوني لفريق Penpot." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-go-to" -msgstr "اذهب إلى المناقشات" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.description" -msgstr "وصف" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.chat-subtitle" -msgstr "ترغب في الكلام؟ تحدث معنا في Gitter" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.chat-start" -msgstr "انضم إلى الدردشة" - -#: src/app/main/ui/settings/password.cljs -msgid "errors.wrong-old-password" -msgstr "كلمة المرور القديمة غير صحيحة" - -#: src/app/main/ui/settings/password.cljs -msgid "errors.password-too-short" -msgstr "يجب ألا تقل كلمة المرور عن 8 أحرف" - -#: src/app/main/ui/settings/password.cljs -msgid "errors.password-invalid-confirmation" -msgstr "يجب أن تتطابق كلمة مرور التأكيد" - -msgid "errors.network" -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 -msgid "errors.media-type-not-allowed" -msgstr "يبدو أن هذه ليست صورة صالحة." - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.media-too-large" -msgstr "الصورة كبيرة جدا بحيث لا يمكن إدراجها (يجب أن تكون أقل من 5mb)." - -msgid "errors.media-format-unsupported" -msgstr "تنسيق الصورة غير مدعوم (يجب أن يكون svg أو jpg أو png)." - -#: src/app/main/ui/auth/login.cljs -msgid "errors.ldap-disabled" -msgstr "تم تعطيل مصادقة LDAP." - -#: src/app/main/ui/components/color_input.cljs -msgid "errors.invalid-color" -msgstr "لون غير صالح" - -#: src/app/main/ui/auth/login.cljs -msgid "errors.google-auth-not-enabled" -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" -msgstr "حدث خطأ ما." - -#: src/app/main/ui/settings/change_email.cljs -msgid "errors.email-invalid-confirmation" -msgstr "يجب أن يتطابق البريد الإلكتروني للتأكيد" +msgstr "انقر لإغلاق المسار" \ No newline at end of file diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index 44ac3aa5d3..f2f53221be 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-06-01 00:38+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" @@ -18,8 +18,8 @@ msgstr "Ja teniu un compte?" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" msgstr "" -"Reviseu el correu i cliqueu l'enllaç per verificar i començar a utilitzar el " -"Penpot." +"Reviseu el correu i cliqueu l'enllaç per verificar i començar a utilitzar " +"el Penpot." #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" @@ -75,10 +75,18 @@ msgstr "Entreu amb GitHub" msgid "auth.login-with-gitlab-submit" msgstr "Entreu amb GitLab" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Entreu amb Google" + #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" msgstr "Identifiqueu-vos amb LDAP" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Entreu amb OpenID (SSO)" + #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" msgstr "Escriviu la nova contrasenya" @@ -93,8 +101,7 @@ msgstr "La contrasenya s'ha canviat correctament" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.profile-not-verified" -msgstr "" -"El perfil encara no ha estat verificat. Verifiqueu-lo abans de continuar." +msgstr "El perfil encara no ha estat verificat. Verifiqueu-lo abans de continuar." #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.recovery-token-sent" @@ -148,6 +155,12 @@ msgstr "Creeu un compte" msgid "auth.sidebar-tagline" msgstr "La solució de codi obert per dissenyar i prototipar." +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"En crear un nou compte, accepteu les nostres condicions del servei i la " +"política de privacitat." + #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "S'ha enviat un correu de verificació a" @@ -160,6 +173,10 @@ msgstr "Afegeix una biblioteca compartida" msgid "dashboard.change-email" msgstr "Canvia el correu" +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(còpia)" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.create-new-team" msgstr "+ Crea un nou equip" @@ -175,10 +192,36 @@ msgstr "Suprimeix l'equip" msgid "dashboard.draft-title" msgstr "Esborrany" +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplica" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Duplica %s fitxers" + #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.empty-files" msgstr "Encara no teniu cap arxiu aquí" +#, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"Els tipus de lletra web que pengeu aquí s'afegiran a la llista de famílies " +"tipogràfiques disponibles a les propietats del text dels fitxers d'aquest " +"equip. Els tipus de lletra amb el mateix nom de família s'agruparan en " +"**una sola família tipogràfica**. Podeu pujar tipus de lletra amb aquests " +"formats: **TTF, OTF i WOFF** (només és necessari un)." + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"Només podeu pujar tipus de lletra de la vostra propietat o dels que tingueu " +"una llicència que us permeti utilitzar-los al Penpot. Teniu més informació " +"a la secció de drets de contingut de les [Condicions del servei del " +"Penpot](https://penpot.app/terms.html). També podeu llegir sobre " +"[llicenciament de tipus de lletra](https://www.typography.com/faq)." + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Convida a l'equip" @@ -195,14 +238,34 @@ msgstr "Biblioteques compartides" msgid "dashboard.loading-files" msgstr "S'estan carregant els fitxers…" +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Mou a" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Mou %s fitxers a" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Mou a un altre equip" + #: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs msgid "dashboard.new-file" msgstr "+ Fitxer nou" +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "Fitxer nou" + #: src/app/main/ui/dashboard/projects.cljs msgid "dashboard.new-project" msgstr "+ Projecte nou" +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Projecte nou" + #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" msgstr "No s'ha trobat cap coincidència amb “%s“" @@ -227,10 +290,18 @@ msgstr "La contrasenya s'ha desat correctament" msgid "dashboard.num-of-members" msgstr "%s membres" +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "Obre el fitxer en una pestanya nova" + #: src/app/main/ui/settings/password.cljs msgid "dashboard.password-change" msgstr "Canvia la contrasenya" +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Fixa/Deixa de fixar" + #: src/app/main/ui/dashboard/projects.cljs msgid "dashboard.projects-title" msgstr "Projectes" @@ -267,6 +338,34 @@ msgstr "Selecciona un tema" msgid "dashboard.show-all-files" msgstr "Mostra tots els fitxers" +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "S'ha eliminat el fitxer" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "S'ha eliminat el projecte" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "S'ha duplicat el fitxer" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "S'ha eliminat el projecte" + +#: 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 "S'ha mogut el fitxer" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "S'han mogut els fitxers" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "S'ha mogut el projecte" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.switch-team" msgstr "Canvia d'equip" @@ -359,6 +458,14 @@ msgstr "Alguna cosa ha anat malament" msgid "errors.google-auth-not-enabled" msgstr "L'autenticació amb Google està desactivada en aquest servidor" +#: src/app/main/ui/components/color_input.cljs +msgid "errors.invalid-color" +msgstr "El color no és vàlid" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "L'autenticació LDAP està inhabilitada." + msgid "errors.media-format-unsupported" msgstr "El format d'imatge no està suportat (ha de ser SVG, JPG o PNG)." @@ -369,7 +476,8 @@ msgstr "La imatge és massa gran (ha de ser inferior a 5 MB)." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" msgstr "" -"Sembla que el contingut de la imatge no coincideix amb l'extensió del fitxer." +"Sembla que el contingut de la imatge no coincideix amb l'extensió del " +"fitxer." #: 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" @@ -402,6 +510,15 @@ msgstr "" msgid "errors.registration-disabled" msgstr "El registre està desactivat" +msgid "errors.terms-privacy-agreement-invalid" +msgstr "" +"Heu d'acceptar les nostres condicions del servei i la política de " +"privacitat." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.token-expired" +msgstr "El codi ha caducat" + #: 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 "S'ha produït un error inesperat." @@ -467,9 +584,224 @@ msgstr "Correu electrònic" msgid "generic.error" msgstr "S'ha produït un error" +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Difuminat" + +#: 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 "Emplenat" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "Baixa la imatge original" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "Alçada" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Amplada" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout" +msgstr "Disposició" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Alçada" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Esquerra" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Radi" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Rotació" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Superior" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Amplada" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Ombra" + +#: 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ç" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Centre" + +#, 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 "Puntejat" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Mesclat" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Cap" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Sòlid" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Amplada" + +#: 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 tipogràfica" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Mida de la lletra" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Estil de la lletra" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Espaiat de la lletra" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Alçada de la línia" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Decoració del text" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Cap" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Barrat" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Subratllat" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Transformació del text" + +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "Minúscules" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Cap" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Inicials en majúscules" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "Majúscules" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Codi" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Cercle" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Corba" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Taula de treball" + +msgid "handoff.tabs.code.selected.group" +msgstr "Grup" + +msgid "handoff.tabs.code.selected.image" +msgstr "Imatge" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s seleccionats" + +msgid "handoff.tabs.code.selected.path" +msgstr "Camí" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Rectangle" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.text" +msgstr "Text" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Informació" + +msgid "history.alert-message" +msgstr "Esteu veient la versió %s" + msgid "labels.accept" msgstr "Acceptar" +#: 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 "Administració" + msgid "labels.go-back" msgstr "Enrere" @@ -485,335 +817,4 @@ msgstr "Projectes - %s - Penpot" #: src/app/main/ui/dashboard/search.cljs msgid "title.dashboard.search" -msgstr "Cerca - %s - Penpot" - -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.token-expired" -msgstr "El codi ha caducat" - -msgid "errors.terms-privacy-agreement-invalid" -msgstr "" -"Heu d'acceptar les nostres condicions del servei i la política de privacitat." - -#: src/app/main/ui/auth/login.cljs -msgid "errors.ldap-disabled" -msgstr "L'autenticació LDAP està inhabilitada." - -#: src/app/main/ui/components/color_input.cljs -msgid "errors.invalid-color" -msgstr "El color no és vàlid" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-move-project" -msgstr "S'ha mogut el projecte" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-move-files" -msgstr "S'han mogut els fitxers" - -#: 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 "S'ha mogut el fitxer" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-duplicate-project" -msgstr "S'ha eliminat el projecte" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-duplicate-file" -msgstr "S'ha duplicat el fitxer" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-delete-project" -msgstr "S'ha eliminat el projecte" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-delete-file" -msgstr "S'ha eliminat el fitxer" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.pin-unpin" -msgstr "Fixa/Deixa de fixar" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.open-in-new-tab" -msgstr "Obre el fitxer en una pestanya nova" - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-project-prefix" -msgstr "Projecte nou" - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-file-prefix" -msgstr "Fitxer nou" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to-other-team" -msgstr "Mou a un altre equip" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to-multi" -msgstr "Mou %s fitxers a" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to" -msgstr "Mou a" - -#, markdown -msgid "dashboard.fonts.hero-text2" -msgstr "" -"Només podeu pujar tipus de lletra de la vostra propietat o dels que tingueu " -"una llicència que us permeti utilitzar-los al Penpot. Teniu més informació a " -"la secció de drets de contingut de les [Condicions del servei del " -"Penpot](https://penpot.app/terms.html). També podeu llegir sobre [" -"llicenciament de tipus de lletra](https://www.typography.com/faq)." - -#, markdown -msgid "dashboard.fonts.hero-text1" -msgstr "" -"Els tipus de lletra web que pengeu aquí s'afegiran a la llista de famílies " -"tipogràfiques disponibles a les propietats del text dels fitxers d'aquest " -"equip. Els tipus de lletra amb el mateix nom de família s'agruparan en **una " -"sola família tipogràfica**. Podeu pujar tipus de lletra amb aquests formats: " -"**TTF, OTF i WOFF** (només és necessari un)." - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate-multi" -msgstr "Duplica %s fitxers" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate" -msgstr "Duplica" - -#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs -msgid "dashboard.copy-suffix" -msgstr "(còpia)" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.terms-privacy-agreement" -msgstr "" -"En crear un nou compte, accepteu les nostres condicions del servei i la " -"política de privacitat." - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-oidc-submit" -msgstr "Entreu amb OpenID (SSO)" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-google-submit" -msgstr "Entreu amb Google" - -#: 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 "Administració" - -msgid "history.alert-message" -msgstr "Esteu veient la versió %s" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.info" -msgstr "Informació" - -msgid "handoff.tabs.code.selected.text" -msgstr "Text" - -msgid "handoff.tabs.code.selected.svg-raw" -msgstr "SVG" - -msgid "handoff.tabs.code.selected.rect" -msgstr "Rectangle" - -msgid "handoff.tabs.code.selected.path" -msgstr "Camí" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code.selected.multiple" -msgstr "%s seleccionats" - -msgid "handoff.tabs.code.selected.image" -msgstr "Imatge" - -msgid "handoff.tabs.code.selected.group" -msgstr "Grup" - -msgid "handoff.tabs.code.selected.frame" -msgstr "Taula de treball" - -msgid "handoff.tabs.code.selected.curve" -msgstr "Corba" - -msgid "handoff.tabs.code.selected.circle" -msgstr "Cercle" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.letter-spacing" -msgstr "Espaiat de la lletra" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code" -msgstr "Codi" - -msgid "handoff.attributes.typography.text-transform.uppercase" -msgstr "Majúscules" - -msgid "handoff.attributes.typography.text-transform.titlecase" -msgstr "Inicials en majúscules" - -msgid "handoff.attributes.typography.text-transform.none" -msgstr "Cap" - -msgid "handoff.attributes.typography.text-transform.lowercase" -msgstr "Minúscules" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-transform" -msgstr "Transformació del text" - -msgid "handoff.attributes.typography.text-decoration.underline" -msgstr "Subratllat" - -msgid "handoff.attributes.typography.text-decoration.strikethrough" -msgstr "Barrat" - -msgid "handoff.attributes.typography.text-decoration.none" -msgstr "Cap" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-decoration" -msgstr "Decoració del text" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.line-height" -msgstr "Alçada de la línia" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-style" -msgstr "Estil de la lletra" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-size" -msgstr "Mida de la lletra" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-family" -msgstr "Família tipogràfica" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography" -msgstr "Tipografia" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke.width" -msgstr "Amplada" - -msgid "handoff.attributes.stroke.style.solid" -msgstr "Sòlid" - -msgid "handoff.attributes.stroke.style.none" -msgstr "Cap" - -msgid "handoff.attributes.stroke.style.mixed" -msgstr "Mesclat" - -msgid "handoff.attributes.stroke.style.dotted" -msgstr "Puntejat" - -#, permanent -msgid "handoff.attributes.stroke.alignment.outer" -msgstr "Exterior" - -#, permanent -msgid "handoff.attributes.stroke.alignment.inner" -msgstr "Interior" - -#, permanent -msgid "handoff.attributes.stroke.alignment.center" -msgstr "Centre" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke" -msgstr "Traç" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow.shorthand.spread" -msgstr "S" - -#: 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.offset-x" -msgstr "X" - -#: 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" -msgstr "Ombra" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.width" -msgstr "Amplada" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.top" -msgstr "Superior" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.rotation" -msgstr "Rotació" - -#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.radius" -msgstr "Radi" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.left" -msgstr "Esquerra" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.height" -msgstr "Alçada" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout" -msgstr "Disposició" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.width" -msgstr "Amplada" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.height" -msgstr "Alçada" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.download" -msgstr "Baixa la imatge original" - -#: src/app/main/ui/handoff/attributes/fill.cljs -msgid "handoff.attributes.fill" -msgstr "Emplenat" - -#: src/app/main/ui/handoff/attributes/common.cljs -msgid "handoff.attributes.color.rgba" -msgstr "RGBA" - -#: 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.hex" -msgstr "HEX" - -#: src/app/main/ui/handoff/attributes/blur.cljs -msgid "handoff.attributes.blur.value" -msgstr "Valor" - -#: src/app/main/ui/handoff/attributes/blur.cljs -msgid "handoff.attributes.blur" -msgstr "Difuminat" +msgstr "Cerca - %s - Penpot" \ No newline at end of file diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 4763752014..0d14a32660 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-05-25 12:31+0000\n" "Last-Translator: Yannik Rödel \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" @@ -75,10 +75,18 @@ msgstr "Einloggen mit Github" msgid "auth.login-with-gitlab-submit" msgstr "Einloggen mit Gitlab" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Einloggen mit Google" + #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" msgstr "Anmelden mit LDAP" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Einloggen mit OpenID (SSO)" + #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" msgstr "Geben Sie ein neues Passwort ein" @@ -149,6 +157,12 @@ msgstr "Konto erstellen" msgid "auth.sidebar-tagline" msgstr "Die Open-Source-Lösung für Design und Prototyping." +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Wenn Sie ein neues Konto erstellen, stimmen Sie unseren Nutzungsbedingungen " +"und Datenschutzrichtlinien zu." + #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "Wir haben eine Bestätigungs-E-Mail gesendet an" @@ -184,10 +198,24 @@ msgstr "Entwurf" msgid "dashboard.duplicate" msgstr "Duplizieren" +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "%s Dateien duplizieren" + #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.empty-files" msgstr "Sie haben hier noch keine Dateien" +#, markdown +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)." + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Zum Team einladen" @@ -2412,31 +2440,4 @@ msgid "workspace.updates.update" msgstr "Aktualisieren" msgid "workspace.viewport.click-to-close-path" -msgstr "Klicken Sie, um den Pfad zu schließen" - -#, markdown -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)." - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate-multi" -msgstr "%s Dateien duplizieren" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.terms-privacy-agreement" -msgstr "" -"Wenn Sie ein neues Konto erstellen, stimmen Sie unseren Nutzungsbedingungen " -"und Datenschutzrichtlinien zu." - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-oidc-submit" -msgstr "Einloggen mit OpenID (SSO)" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-google-submit" -msgstr "Einloggen mit Google" +msgstr "Klicken Sie, um den Pfad zu schließen" \ No newline at end of file diff --git a/frontend/translations/id.po b/frontend/translations/id.po index 5210b494db..800120d39f 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-05-23 21:33+0000\n" "Last-Translator: luthfi azhari \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" @@ -11,84 +11,9 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.7-dev\n" -#: src/app/main/ui/dashboard/grid.cljs -msgid "dashboard.empty-files" -msgstr "Anda belum memiliki file disini" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate-multi" -msgstr "Duplikasi % file" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate" -msgstr "Duplikat" - -msgid "dashboard.draft-title" -msgstr "Konsep" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.delete-team" -msgstr "Hapus tim" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.default-team-name" -msgstr "Penpot anda" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.create-new-team" -msgstr "+ Buat tim baru" - -#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs -msgid "dashboard.copy-suffix" -msgstr "(salin)" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.change-email" -msgstr "Ubah email" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.add-shared" -msgstr "Tambahkan sebagai pustaka bersama" - #: src/app/main/ui/auth/register.cljs -msgid "auth.verification-email-sent" -msgstr "Kami mengirim verifikasi ke surel anda" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.register-title" -msgstr "Buat akun baru" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.register-subtitle" -msgstr "Ini gratis, Open Source" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.register-submit" -msgstr "Buat akun baru" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.register" -msgstr "Tidak ada akun?" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.recovery-submit" -msgstr "Ubah kata sandi anda" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.recovery-request-title" -msgstr "Lupa kata sandi?" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.recovery-request-subtitle" -msgstr "Kami akan mengirimi anda surel dengan intruksi" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.recovery-request-submit" -msgstr "Pemulihan Kata sandi" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.password-length-hint" -msgstr "Paling tidak 8 karakter" +msgid "auth.already-have-account" +msgstr "Sudah memiliki akun?" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" @@ -100,72 +25,13 @@ msgstr "" msgid "auth.confirm-password" msgstr "Konfirmasi kata sandi" -#: src/app/main/ui/auth/register.cljs -msgid "auth.terms-privacy-agreement" -msgstr "" -"Ketika membuat akun baru, anda menyetujui persyaratan layanan dan kebijakan " -"privasi kami." - -#: src/app/main/ui/auth.cljs -msgid "auth.sidebar-tagline" -msgstr "Solusi open-source untuk desain dan pembuatan prototype." +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Buat akun demo" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.password" -msgstr "Kata sandi" - -#: src/app/main/ui/auth/verify_token.cljs -msgid "auth.notifications.team-invitation-accepted" -msgstr "Berhasil bergabung dengan tim" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.notifications.recovery-token-sent" -msgstr "Link pemulihan kata sandi berhasil dikirim ke kotak masuk anda." - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.notifications.profile-not-verified" -msgstr "" -"Akun belum terverifikasi, harap verifikasi profile anda sebelum melanjutkan." - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.password-changed-succesfully" -msgstr "Kata sandi berhasil diubah" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.invalid-token-error" -msgstr "Token pemulihan tidak valid." - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.new-password" -msgstr "Ketikkan kata sandi baru" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-oidc-submit" -msgstr "Masuk dengan OpenID (SSO)" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-ldap-submit" -msgstr "Masuk dengan LDAP" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-google-submit" -msgstr "Masuk dengan Google" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-gitlab-submit" -msgstr "Masuk dengan Gitlab" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-github-submit" -msgstr "Masuk dengan Github" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-title" -msgstr "Senang bertemu denganmu lagi!" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Masukkan detail anda di bawah ini" +msgid "auth.create-demo-profile" +msgstr "Ingin mencobanya?" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" @@ -173,38 +39,171 @@ msgstr "" "Ini layanan DEMO, JANGAN GUNAKAN untuk pekerjaan nyata, project ini akan di " "hapus secara berkala." +#: 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 "Surel" + #: src/app/main/ui/auth/login.cljs -msgid "auth.login-submit" -msgstr "Masuk" +msgid "auth.forgot-password" +msgstr "Lupa kata sandi?" #: src/app/main/ui/auth/register.cljs -msgid "auth.login-here" -msgstr "Masuk disini" +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.fullname" -msgstr "Nama Lengkap" +msgid "auth.login-here" +msgstr "Masuk disini" #: src/app/main/ui/auth/login.cljs -msgid "auth.forgot-password" -msgstr "Lupa kata sandi?" +msgid "auth.login-submit" +msgstr "Masuk" -#: 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 "Surel" +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-subtitle" +msgstr "Masukkan detail anda di bawah ini" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Senang bertemu denganmu lagi!" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Masuk dengan Github" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "Masuk dengan Gitlab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Masuk dengan Google" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "Masuk dengan LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Masuk dengan OpenID (SSO)" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Ketikkan kata sandi baru" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "Token pemulihan tidak valid." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-succesfully" +msgstr "Kata sandi berhasil diubah" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "Akun belum terverifikasi, harap verifikasi profile anda sebelum melanjutkan." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Link pemulihan kata sandi berhasil dikirim ke kotak masuk anda." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Berhasil bergabung dengan tim" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.create-demo-profile" -msgstr "Ingin mencobanya?" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.create-demo-account" -msgstr "Buat akun demo" +msgid "auth.password" +msgstr "Kata sandi" #: src/app/main/ui/auth/register.cljs -msgid "auth.already-have-account" -msgstr "Sudah memiliki akun?" +msgid "auth.password-length-hint" +msgstr "Paling tidak 8 karakter" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Pemulihan Kata sandi" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Kami akan mengirimi anda surel dengan intruksi" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Lupa kata sandi?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Ubah kata sandi anda" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Tidak ada akun?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Buat akun baru" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "Ini gratis, Open Source" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Buat akun baru" + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "Solusi open-source untuk desain dan pembuatan prototype." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Ketika membuat akun baru, anda menyetujui persyaratan layanan dan kebijakan " +"privasi kami." + +#: src/app/main/ui/auth/register.cljs +msgid "auth.verification-email-sent" +msgstr "Kami mengirim verifikasi ke surel anda" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Tambahkan sebagai pustaka bersama" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Ubah email" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(salin)" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Buat tim baru" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Penpot anda" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Hapus tim" + +msgid "dashboard.draft-title" +msgstr "Konsep" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplikat" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Duplikasi % file" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Anda belum memiliki file disini" \ No newline at end of file diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index ae9e3d988b..5541abdb76 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-06-15 18:34+0000\n" "Last-Translator: Eranot \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" @@ -849,6 +849,14 @@ msgstr "" msgid "modals.delete-font.title" msgstr "Excluindo fonte" +#: 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?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Excluir membro da equipe" + #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-member-confirm.accept" msgstr "Enviar convite" @@ -862,6 +870,10 @@ msgstr "" "Você não pode deixar a equipe se não houver outro membro para promover a " "proprietário. Você pode excluir a equipe." +#: src/app/main/ui/dashboard/sidebar.cljs +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" @@ -906,6 +918,13 @@ msgstr "Promover a proprietário" msgid "modals.remove-shared-confirm.accept" msgstr "Remover como Biblioteca Compartilhada" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Depois de removida como Biblioteca Compartilhada, os Componentes deste " +"arquivo deixarão de estar disponível para serem usados com o resto de seus " +"arquivos." + #: 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" @@ -918,6 +937,46 @@ msgstr "Atualizar componente" 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 "" +"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" + +#: 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 "" +"Você não pode deletar seu perfil. Designe um novo proprietário para suas " +"equipes antes de continuar." + +#: 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. Verifique seu e-mail!" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Ir para a página de 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 "Misto" + +#: 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" @@ -926,330 +985,271 @@ msgstr "Provedores de fonte - %s - Penpot" msgid "title.dashboard.fonts" msgstr "Fontes - %s - Penpot" -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-settings" -msgstr "Configurações - %s - Penpot" - -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-members" -msgstr "Membros - %s - Penpot" - -#: src/app/main/ui/settings/profile.cljs -msgid "title.settings.profile" -msgstr "Perfil - Penpot" - -#: src/app/main/ui/settings/password.cljs -msgid "title.settings.password" -msgstr "Senha - Penpot" - -#: src/app/main/ui/settings/options.cljs -msgid "title.settings.options" -msgstr "Configurações - Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "title.settings.feedback" -msgstr "Dê sua opinião - Penpot" - -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs -msgid "title.default" -msgstr "Penpot - Liberdade de design para equipes" - -#: src/app/main/ui/dashboard/libraries.cljs -msgid "title.dashboard.shared-libraries" -msgstr "Bibliotecas Compartilhadas - %s - 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 "Pesquisar - %s - Penpot" -#: src/app/main/ui/dashboard/projects.cljs -msgid "title.dashboard.projects" -msgstr "Projetos - %s - Penpot" +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Bibliotecas Compartilhadas - %s - Penpot" -#: src/app/main/ui/dashboard/files.cljs -msgid "title.dashboard.files" -msgstr "%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 equipes" -#: 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 "Misto" +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Dê sua opinião - Penpot" -#: src/app/main/ui/auth/recovery.cljs -msgid "profile.recovery.go-to-login" -msgstr "Ir para a página de login" +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Configurações - Penpot" -#: src/app/main/ui/settings/change_email.cljs -msgid "notifications.validation-email-sent" -msgstr "E-mail de verificação enviado para %s. Verifique seu e-mail!" +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Senha - Penpot" -#: 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/delete_account.cljs -msgid "notifications.profile-deletion-not-allowed" -msgstr "" -"Você não pode deletar seu perfil. Designe um novo proprietário para suas " -"equipes antes de continuar." +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Perfil - Penpot" #: src/app/main/ui/dashboard/team.cljs -msgid "notifications.invitation-email-sent" -msgstr "Convite enviado com sucesso" - -#: 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" - -#: 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." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.remove-shared-confirm.hint" -msgstr "" -"Depois de removida como Biblioteca Compartilhada, os Componentes deste " -"arquivo deixarão de estar disponível para serem usados com o resto de seus " -"arquivos." - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint1" -msgstr "Você é o proprietário de %s." +msgid "title.team-members" +msgstr "Membros - %s - Penpot" #: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.title" -msgstr "Excluir membro da equipe" +msgid "title.team-settings" +msgstr "Configurações - %s - Penpot" -#: 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?" +msgid "workspace.library.all" +msgstr "Todas bibliotecas" -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.solid" -msgstr "Sólido" +msgid "workspace.library.libraries" +msgstr "Bibliotecas" -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.outer" -msgstr "Fora" +msgid "workspace.library.own" +msgstr "Minhas bibliotecas" -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.mixed" -msgstr "Misturado" +msgid "workspace.library.store" +msgstr "Bibliotecas da loja" -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.inner" -msgstr "Dentro" +msgid "workspace.options.blur-options.background-blur" +msgstr "Fundo" -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dotted" -msgstr "Pontilhada" +msgid "workspace.options.blur-options.layer-blur" +msgstr "Camada" -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dashed" -msgstr "Tracejada" +#: 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/stroke.cljs -msgid "workspace.options.stroke.center" -msgstr "Centro" - -#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs -msgid "workspace.options.size-presets" -msgstr "Predefinições de tamanho" - -#: 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/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.offsety" -msgstr "Y" - -#: 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 interior" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.drop-shadow" -msgstr "Sombra projetada" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.blur" -msgstr "Borrar" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.rotation" -msgstr "Rotação" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.radius.single-corners" -msgstr "Cantos individuais" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.radius.all-corners" -msgstr "Todos cantos" - -msgid "workspace.options.radius" -msgstr "Raio" +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Componente" #: src/app/main/ui/workspace/sidebar/options.cljs -msgid "workspace.options.prototype" -msgstr "Protótipo" - -#: 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/menus/interactions.cljs -msgid "workspace.options.none" -msgstr "Nenhum" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.navigate-to" -msgstr "Navegar para" - -#: 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/layer.cljs -msgid "workspace.options.layer-options.blend-mode.color" -msgstr "Cor" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.title" -msgstr "Grades & Layouts" - -#: 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/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.params.width" -msgstr "Largura" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.use-default" -msgstr "Usar padrão" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.top" -msgstr "Superior" - -#: 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.type.right" -msgstr "Direita" - -#: 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.center" -msgstr "Centro" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.bottom" -msgstr "Inferior" - -#: 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/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.set-default" -msgstr "Definir como padrão" - -#: 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.margin" -msgstr "Margem" - -#: 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.columns" -msgstr "Colunas" - -#: 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.auto" -msgstr "Automático" - -#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs -msgid "workspace.options.fill" -msgstr "Preencher" +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.exporting-object" -msgstr "Exportando…" +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-object" +msgstr "Exportar forma" #: 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 -msgid "workspace.options.export-object" -msgstr "Exportar forma" +msgid "workspace.options.exporting-object" +msgstr "Exportando…" -#: 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/fill.cljs +msgid "workspace.options.fill" +msgstr "Preencher" + +#: 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.params.columns" +msgstr "Colunas" + +#: 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.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/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Inferior" + +#: 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" +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.type.top" +msgstr "Superior" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Usar padrão" + +#: 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/frame_grid.cljs +msgid "workspace.options.grid.title" +msgstr "Grades & Layouts" + +#: 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.title.multiple" +msgstr "Camadas selecionadas" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.navigate-to" +msgstr "Navegar para" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.none" +msgstr "Nenhum" + +#: 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.design" -msgstr "Design" +msgid "workspace.options.prototype" +msgstr "Protótipo" -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs -msgid "workspace.options.component" -msgstr "Componente" +msgid "workspace.options.radius" +msgstr "Raio" -#: 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/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Todos cantos" -msgid "workspace.options.blur-options.layer-blur" -msgstr "Camada" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.single-corners" +msgstr "Cantos individuais" -msgid "workspace.options.blur-options.background-blur" -msgstr "Fundo" +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Rotação" -msgid "workspace.library.store" -msgstr "Bibliotecas da loja" +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Borrar" -msgid "workspace.library.own" -msgstr "Minhas bibliotecas" +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Sombra projetada" -msgid "workspace.library.libraries" -msgstr "Bibliotecas" +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "Sombra interior" -msgid "workspace.library.all" -msgstr "Todas bibliotecas" +#: 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.title" +msgstr "Sombra" + +#: 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 "Predefinições de tamanho" + +#: 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 "Tracejada" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Pontilhada" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Dentro" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Misturado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.outer" +msgstr "Fora" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.solid" +msgstr "Sólido" \ No newline at end of file diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 55e5587d08..176064b54a 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-06-01 00:38+0000\n" "Last-Translator: Çağlar Yeşilyurt \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" @@ -209,9 +209,10 @@ 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ı] içindeki " -"İçerik hakları bölümünden detaylı bilgi alabilirsiniz (https://penpot.app/" -"terms.html). Ayrıca [yazı tipi lisanslama](https://www.typography.com/faq) " -"hakkında daha fazla bilgi almak isteyebilirsiniz." +"İçerik hakları bölümünden detaylı bilgi alabilirsiniz " +"(https://penpot.app/terms.html). Ayrıca [yazı tipi " +"lisanslama](https://www.typography.com/faq) hakkında daha fazla bilgi almak " +"isteyebilirsiniz." #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" @@ -538,6 +539,12 @@ msgstr "Açıklama" msgid "feedback.discussions-go-to" msgstr "Tartışmalar bölümüne git" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle2" +msgstr "" +"Soru sorabilir ve soruları cevaplayabilir, açık uçlu tartışmalar yapabilir " +"ve projeyi etkileyen kararları takip edebilirsin." + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-title" msgstr "Takım tartışmaları" @@ -546,6 +553,12 @@ msgstr "Takım tartışmaları" msgid "feedback.subject" msgstr "Konu" +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" +"Lütfen bir sorun, fikir ya da kuşkunuzu açıklayarak e-postanızın nedenini " +"belirtin. Ekibimizin bir üyesi en kısa sürede yanıt verecektir." + #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" msgstr "E-posta" @@ -590,24 +603,144 @@ msgstr "Yükseklik" msgid "handoff.attributes.image.width" msgstr "Genişlik" +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Yükseklik" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Sol" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Yarı Çap" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Döndür" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Üst" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Genişlik" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Gölge" + +#: 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 "Y" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Çerçeve" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Merkezi" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "İçinde" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Dışarıda" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Noktalı" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Karışık" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Hiçbiri" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Düz" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Genişlik" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografi" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-family" +msgstr "Font Ailesi" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Font Boyutu" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Font Stili" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Harf Aralığı" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Satır Yüksekliği" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Metin Süsleme" + msgid "handoff.attributes.typography.text-decoration.none" msgstr "Hiçbiri" +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Üstü Çizili" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Altı Çizili" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Metin Dönüşümü" + msgid "handoff.attributes.typography.text-transform.lowercase" msgstr "Küçük Harf" msgid "handoff.attributes.typography.text-transform.none" msgstr "Hiçbiri" +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "İlk Harfleri Büyük" + msgid "handoff.attributes.typography.text-transform.uppercase" msgstr "Büyük Harf" +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Kod" + msgid "handoff.tabs.code.selected.circle" msgstr "Daire" msgid "handoff.tabs.code.selected.curve" msgstr "Eğri" +msgid "handoff.tabs.code.selected.frame" +msgstr "Çalışma yüzeyi" + msgid "handoff.tabs.code.selected.group" msgstr "Grup" @@ -648,6 +781,16 @@ msgstr "Yönetici" msgid "labels.all" msgstr "Hepsi" +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.desc-message" +msgstr "" +"Görünüşe göre biraz beklemen ve yeniden denemen gerekiyor; sunucularımızda " +"küçük bir bakım yapıyoruz." + +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.main-message" +msgstr "Hatalı Ağ Geçidi" + #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.cancel" msgstr "İptal" @@ -678,6 +821,9 @@ msgstr "Yeni takım oluştur" msgid "labels.create-team.placeholder" msgstr "Yeni takım adı gir" +msgid "labels.custom-fonts" +msgstr "Özel Fontlar" + #: src/app/main/ui/settings/sidebar.cljs msgid "labels.dashboard" msgstr "Kontrol paneli" @@ -725,6 +871,9 @@ msgstr "Geri bildirim gönderildi" msgid "labels.font-family" msgstr "Font Ailesi" +msgid "labels.font-providers" +msgstr "Font sağlayıcısı" + msgid "labels.font-variant" msgstr "Stil" @@ -742,6 +891,9 @@ msgstr "Geri dön" msgid "labels.hide-resolved-comments" msgstr "Çözülmüş yorumları gizle" +msgid "labels.icons" +msgstr "Simgeler" + msgid "labels.images" msgstr "Görseller" @@ -754,6 +906,10 @@ msgstr "" "Kötü bir şey oldu. Lütfen işlemi yeniden deneyin ve sorun devam ederse " "destek ile iletişime geçin." +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.main-message" +msgstr "İç Hata" + #: src/app/main/ui/settings/options.cljs msgid "labels.language" msgstr "Dil" @@ -762,6 +918,9 @@ msgstr "Dil" msgid "labels.logout" msgstr "Çıkış Yap" +msgid "labels.manage-fonts" +msgstr "Fontları yönet" + #: 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 "Üyeler" @@ -903,1297 +1062,74 @@ msgstr "Çıkış yap" msgid "labels.update" msgstr "Güncelle" +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Takımı güncelle" + msgid "labels.upload" msgstr "Yükle" +msgid "labels.upload-custom-fonts" +msgstr "Özel yazı tipi yükle" + msgid "labels.uploading" msgstr "Yükleniyor…" -msgid "modals.delete-font.message" -msgstr "" -"Bu fontu silmek istediğine emin misin? Bir dosyada kullanılıyorsa " -"yüklenmeyecektir." +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Görüntüler" -msgid "modals.delete-font.title" -msgstr "Fontu sil" +#: src/app/main/ui/comments.cljs +msgid "labels.write-new-comment" +msgstr "Yeni yorum yaz" -#: src/app/main/ui/dashboard/fonts.cljs -msgid "title.dashboard.fonts" -msgstr "Fontlar - %s - Penpot" - -msgid "labels.manage-fonts" -msgstr "Fontları yönet" - -#: src/app/main/ui/static.cljs -msgid "labels.internal-error.main-message" -msgstr "İç Hata" - -msgid "labels.font-providers" -msgstr "Font sağlayıcısı" - -msgid "labels.custom-fonts" -msgstr "Özel Fontlar" - -#: src/app/main/ui/static.cljs -msgid "labels.bad-gateway.main-message" -msgstr "Hatalı Ağ Geçidi" - -#: src/app/main/ui/static.cljs -msgid "labels.bad-gateway.desc-message" -msgstr "" -"Görünüşe göre biraz beklemen ve yeniden denemen gerekiyor; sunucularımızda " -"küçük bir bakım yapıyoruz." - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code" -msgstr "Kod" - -msgid "handoff.attributes.typography.text-decoration.underline" -msgstr "Altı Çizili" - -msgid "handoff.attributes.typography.text-decoration.strikethrough" -msgstr "Üstü Çizili" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.line-height" -msgstr "Satır Yüksekliği" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.letter-spacing" -msgstr "Harf Aralığı" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-style" -msgstr "Font Stili" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-size" -msgstr "Font Boyutu" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-family" -msgstr "Font Ailesi" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography" -msgstr "Tipografi" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke.width" -msgstr "Genişlik" - -msgid "handoff.attributes.stroke.style.solid" -msgstr "Düz" - -msgid "handoff.attributes.stroke.style.none" -msgstr "Hiçbiri" - -msgid "handoff.attributes.stroke.style.mixed" -msgstr "Karışık" - -msgid "handoff.attributes.stroke.style.dotted" -msgstr "Noktalı" - -#, permanent -msgid "handoff.attributes.stroke.alignment.center" -msgstr "Merkezi" - -#, permanent -msgid "handoff.attributes.stroke.alignment.outer" -msgstr "Dışarıda" - -#, permanent -msgid "handoff.attributes.stroke.alignment.inner" -msgstr "İçinde" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke" -msgstr "Çerçeve" - -#: 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.offset-x" -msgstr "X" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow" -msgstr "Gölge" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.width" -msgstr "Genişlik" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.top" -msgstr "Üst" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.rotation" -msgstr "Döndür" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.left" -msgstr "Sol" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.height" -msgstr "Yükseklik" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.subtitle" -msgstr "" -"Lütfen bir sorun, fikir ya da kuşkunuzu açıklayarak e-postanızın nedenini " -"belirtin. Ekibimizin bir üyesi en kısa sürede yanıt verecektir." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-subtitle2" -msgstr "" -"Soru sorabilir ve soruları cevaplayabilir, açık uçlu tartışmalar yapabilir " -"ve projeyi etkileyen kararları takip edebilirsin." - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.typography" -msgstr "%s tipografi" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.libraries.text.multiple-typography-tooltip" -msgstr "Tüm tipografileri ayır" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.libraries.text.multiple-typography" -msgstr "Çoklu tipografiler" - -#: src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.small-thumbnails" -msgstr "Küçük önizlemeler" - -#: src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.big-thumbnails" -msgstr "Büyük önizlemeler" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-variant-id" -msgstr "Çeşit" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.typography" -msgstr "Tipografiler" - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "title.viewer" -msgstr "%s - Görünüm modu - Penpot" - -#: 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 "" -"Paylaşılmış bir kütüphanedeki bileşeni güncellemek üzeresin. Onu kullanan " -"diğer dosyalar etkilenebilir." - -#: 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 "Bileşeni güncelle" - -#: 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.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." - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.direction-rtl" -msgstr "Sağdan sola" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.direction-ltr" -msgstr "Soldan sağa" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.titlecase" -msgstr "İlk Harfi Büyük" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.flip-vertical" -msgstr "Dikey ters çevir" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.flip-horizontal" -msgstr "Yatay ters çevir" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.go-to-edit" -msgstr "Düzenlemek için biçim kütüphane dosyasına gidin" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.hcenter" -msgstr "Yatay olarak ortaya hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.stretch" -msgstr "Ger" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.right" -msgstr "Sağ" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.left" -msgstr "Sol" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.center" -msgstr "Orta" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.bottom" -msgstr "Alt" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type" -msgstr "Tür" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.size" -msgstr "Boyut" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.set-default" -msgstr "Varsayılan olarak belirle" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.rows" -msgstr "Satırlar" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.margin" -msgstr "Kenar Boşluğu" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.height" -msgstr "Yükseklik" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.columns" -msgstr "Sütunlar" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.column" -msgstr "Sütunlar" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.auto" -msgstr "Otomatik" - -#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs -msgid "workspace.options.fill" -msgstr "Doldur" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.exporting-object" -msgstr "Dışarı aktarılıyor…" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs -msgid "workspace.options.export.suffix" -msgstr "Son ek" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.export-object" -msgstr "Şekli dışarı aktar" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.export" -msgstr "Dışarı Aktar" - -#: src/app/main/ui/workspace/sidebar/options.cljs -msgid "workspace.options.design" -msgstr "Tasarım" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs -msgid "workspace.options.component" -msgstr "Bileşen" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-snap-grid" -msgstr "Izgaraya tutturmayı kapat" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-scale-text" -msgstr "Metin ölçeklendirmeyi kapat" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-dynamic-alignment" -msgstr "Dinamik hizalamayı kapat" - -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs -msgid "workspace.gradients.radial" -msgstr "Dairesel degrade" - -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs -msgid "workspace.gradients.linear" -msgstr "Doğrusal degrade" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.create-group" -msgstr "Grup oluştur" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.components" -msgstr "Bileşenler" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.colors" -msgstr "Renkler" - -msgid "workspace.assets.box-filter-graphics" -msgstr "Grafikler" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.box-filter-all" -msgstr "Tüm varlıklar" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.assets" -msgstr "Varlıklar" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.vtop" -msgstr "Üste hizala" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.vdistribute" -msgstr "Dikeyde dağıt" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.hdistribute" -msgstr "Yatayda dağıt" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Sayfayı düzenle" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.dont-show-interactions" -msgstr "Etkileşimleri gösterme" - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "viewer.frame-not-found" -msgstr "Çerçeve bulunmadı." - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "viewer.empty-state" -msgstr "Sayfada çerçeve bulunmuyor." - -#: src/app/main/ui/workspace.cljs -msgid "title.workspace" -msgstr "%s - Penpot" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "title.dashboard.font-providers" -msgstr "Yazıtipi Sağlayıcıları - %s - Penpot" - -#: 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 "İptal" +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "Resim yükleniyor…" #: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.remove-shared-confirm.accept" -msgstr "Paylaşılmış Kütüphane olarak kaldır" +msgid "modals.add-shared-confirm.accept" +msgstr "Paylaşılmış Kütüphane olarak Ekle" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.title" -msgstr "Sahip olarak terfi et" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.message" -msgstr "Bu kullanıcıyı sahip olarak terfi etmek istediğinden emin misin?" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-confirm.title" -msgstr "Takımdan ayrılmak" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "Ayrılmadan önce terfi etmek için başka bir üye seçin" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.mixed" -msgstr "Karışık" - -#: 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.saturation" -msgstr "Doygunluk" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.overlay" -msgstr "Üst katman" - -#: 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.color-burn" -msgstr "Renk yanması" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.curve" -msgstr "Eğri (%s)" - -msgid "workspace.path.actions.snap-nodes" -msgstr "Düğümleri tuttur (%s)" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.strikethrough" -msgstr "Üstü çizili" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.line-height" -msgstr "Satır yüksekliği" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.letter-spacing" -msgstr "Harf Aralıkları" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.grow-fixed" -msgstr "Sabit" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.grow-auto-width" -msgstr "Otomatik genişlik" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.grow-auto-height" -msgstr "Otomatik yükseklik" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.google" -msgstr "Google" - -msgid "workspace.options.text-options.decoration" -msgstr "Süsleme" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-top" -msgstr "Üste hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-right" -msgstr "Sağa hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-middle" -msgstr "Merkeze hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-left" -msgstr "Sola hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-justify" -msgstr "İki yana yasla" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-center" -msgstr "Ortaya hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-bottom" -msgstr "Alta hizala" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.frame" -msgstr "Çalışma Yüzeyi (%s)" - -msgid "workspace.undo.entry.single.frame" -msgstr "çalışma yüzeyi" - -msgid "workspace.undo.entry.multiple.frame" -msgstr "çalışma yüzeyi" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.select-artboard" -msgstr "Çalışma yüzeyi seç" - -msgid "modals.leave-and-reassign.forbiden" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.hint" msgstr "" -"Birisini takımın sahibi yapmadan takımı bırakamazsın. Takımı silmek " -"isteyebilirsin." +"Paylaşılmış Kütüphane olarak eklenince, bu dosya kütüphanesindeki varlıklar " +"diğer dosyalarınızdan da ulaşılabilecek." -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.title" -msgstr "Takım üyesini sil" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.message" -msgstr "Bu üyeyi takımdan silmek istediğinden emin misin?" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.accept" -msgstr "Üyeyi sil" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.title" -msgstr "Takımın silinmesi" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.message" -msgstr "" -"Bu takımı silmek istediğinden emin misin? Takımla ilişkili dosyalar ve " -"projeler kalıcı olarak silinecektir." - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.accept" -msgstr "Takımı sil" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.title" -msgstr "Projeyi sil" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.message" -msgstr "Bu projeyi silmek istediğinden emin misin?" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.accept" -msgstr "Projeyi sil" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "modals.delete-page.title" -msgstr "Sayfayı sil" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "modals.delete-page.body" -msgstr "Bu sayfayı silmek istediğinden emin misin?" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.title" -msgstr "%s dosyanın silinmesi" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.message" -msgstr "%s dosyayı silmek istediğinden emin misin?" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.accept" -msgstr "Dosyalar sil" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-confirm.title" -msgstr "Dosya siliniyor" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-confirm.message" -msgstr "Bu dosyayı silmek istediğinden emin misin?" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-confirm.accept" -msgstr "Dosyayı sil" - -#: src/app/main/ui/comments.cljs -msgid "modals.delete-comment-thread.title" -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." - -#: src/app/main/ui/comments.cljs -msgid "modals.delete-comment-thread.accept" -msgstr "Konuşmayı sil" - -msgid "handoff.tabs.code.selected.frame" -msgstr "Çalışma yüzeyi" - -msgid "handoff.attributes.typography.text-transform.titlecase" -msgstr "İlk Harfleri Büyük" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-transform" -msgstr "Metin Dönüşümü" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-decoration" -msgstr "Metin Süsleme" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow.shorthand.spread" -msgstr "Y" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.vcenter" -msgstr "Dikey olarak ortaya hizala" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.vbottom" -msgstr "Alta hizala" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.hright" -msgstr "Sağa hizala" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.hleft" -msgstr "Sola hizala" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.sitemap" -msgstr "Site haritası" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.show-interactions-on-click" -msgstr "Tıklamada etkileşimleri göster" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.show-interactions" -msgstr "Etkileşimleri göster" - -#: 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 "Bağlantıyı paylaş" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.subtitle" -msgstr "Bağlantıya sahip herkes erişebilecek" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.remove-link" -msgstr "Bağlantıyı kaldır" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.placeholder" -msgstr "Paylaşım adresi burada görünecek" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.create-link" -msgstr "Bağlantı oluştur" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.copy-link" -msgstr "Bağlantıyı kopyala" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.fullscreen" -msgstr "Tam Ekran" - -#: src/app/main/ui/dashboard/files.cljs -msgid "title.dashboard.files" -msgstr "%s - 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 "Karışık" - -#: src/app/main/ui/auth/recovery.cljs -msgid "profile.recovery.go-to-login" -msgstr "Giriş yap" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.message" +msgstr "Paylaşılmış Kütüphane olarak “%s” Ekle" #: src/app/main/ui/settings/change_email.cljs -msgid "notifications.validation-email-sent" -msgstr "" -"%s adresine doğrulama e-postası gönderildi. E-postalarınızı kontrol edin!" +msgid "modals.change-email.confirm-email" +msgstr "Yeni e-postayı doğrulayın" -#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs -msgid "notifications.profile-saved" -msgstr "Profil başarıyla kaydedildi!" +#: 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." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Yeni e-posta" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "E-postayı değiştir" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "E-postanızı değiştirin" #: src/app/main/ui/settings/delete_account.cljs -msgid "notifications.profile-deletion-not-allowed" -msgstr "Profilini silemezsin. Önce takımlarını birine atamalsın." +msgid "modals.delete-account.cancel" +msgstr "İptal et ve hesabımı koru" -#: src/app/main/ui/dashboard/team.cljs -msgid "notifications.invitation-email-sent" -msgstr "Davet başarıyla iletildi" - -#: 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 "Paylaşılmış bir kütüphanede bir bileşen güncelle" - -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-settings" -msgstr "Ayarlar * %s - Penpot" - -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-members" -msgstr "Üyeler - %s - Penpot" - -#: src/app/main/ui/settings/profile.cljs -msgid "title.settings.profile" -msgstr "Profil - Penpot" - -#: src/app/main/ui/settings/password.cljs -msgid "title.settings.password" -msgstr "Parola - Penpot" - -#: src/app/main/ui/settings/options.cljs -msgid "title.settings.options" -msgstr "Ayarlar - Penpot" - -#: src/app/main/ui/settings/feedback.cljs -msgid "title.settings.feedback" -msgstr "Geri bildirimde bulun - Penpot" - -#: src/app/main/ui/dashboard/search.cljs -msgid "title.dashboard.search" -msgstr "Ara - %s - Penpot" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-libraries-need-sync" -msgstr "Güncelleme gerektiren Paylaşılmış Kütüphane bulunmuyor" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title.group" -msgstr "Gölge grubu" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title" -msgstr "Gölge" - -#: 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/component.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.go-main" -msgstr "Ana bileşen dosyasına git" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.front" -msgstr "En öne getir" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.forward" -msgstr "Öne getir" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.edit" -msgstr "Düzenle" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.duplicate" -msgstr "Çoğalt" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.delete" -msgstr "Sil" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.cut" -msgstr "Kes" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.create-component" -msgstr "Bileşen oluştur" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.copy" -msgstr "Kopyala" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.backward" -msgstr "En arkaya gönder" - -msgid "workspace.path.actions.move-nodes" -msgstr "Düğümleri taşı (%s)" - -msgid "workspace.path.actions.merge-nodes" -msgstr "Düğümleri birleştir (%s)" - -msgid "workspace.path.actions.make-curve" -msgstr "Eğriye (%s)" - -msgid "workspace.path.actions.make-corner" -msgstr "Köşeye (%s)" - -msgid "workspace.path.actions.join-nodes" -msgstr "Düğümleri birleştir (%s)" - -msgid "workspace.path.actions.draw-nodes" -msgstr "Düğüm çiz (%s)" - -msgid "workspace.path.actions.delete-node" -msgstr "Düğüm sil (%s)" - -msgid "workspace.options.text-options.vertical-align" -msgstr "Düşey hizalama" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.uppercase" -msgstr "Büyük Harf" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.underline" -msgstr "Altı Çizili" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -#, fuzzy -msgid "workspace.options.text-options.title-selection" -msgstr "Metin seçimi" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.title-group" -msgstr "Grup metni" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.title" -msgstr "Metin" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.lowercase" -msgstr "Küçük harf" - -msgid "workspace.undo.entry.multiple.group" -msgstr "gruplar" - -msgid "workspace.undo.entry.multiple.curve" -msgstr "eğriler" - -msgid "workspace.undo.entry.multiple.component" -msgstr "bileşenler" - -msgid "workspace.undo.entry.multiple.color" -msgstr "renk varlıkları" - -msgid "workspace.undo.entry.multiple.circle" -msgstr "daireler" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.move" -msgstr "Nesneler taşındı" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.modify" -msgstr "%s düzenlendi" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.delete" -msgstr "%s silindi" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.empty" -msgstr "Şu ana kadar değişim geçmişi yok" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.text" -msgstr "Metin (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.move" -msgstr "Taşı" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.image" -msgstr "Resim (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.ellipse" -msgstr "Elips (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.comments" -msgstr "Yorumlar (%s)" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.assets" -msgstr "Varlıklar(%s)" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "workspace.sidebar.sitemap" -msgstr "Sayfalar" - -#: 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 Öznitelikleri İçeri Aktarıldı" - -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.sidebar.layers" -msgstr "Katmanlar (%s)" - -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.there-are-updates" -msgstr "Paylaşılmış kütüphanelerde güncellemeler mevcut" - -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.dismiss" -msgstr "Gözardı et" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.unknown" -msgstr "%s üstündeki işlem" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.title" -msgstr "Geçmiş" - -msgid "workspace.undo.entry.single.text" -msgstr "metin" - -msgid "workspace.undo.entry.single.shape" -msgstr "şekil" - -msgid "workspace.undo.entry.single.rect" -msgstr "dikdörtgen" - -msgid "workspace.undo.entry.single.page" -msgstr "sayfa" - -msgid "workspace.undo.entry.single.multiple" -msgstr "nesne" - -msgid "workspace.undo.entry.single.media" -msgstr "grafik varlığı" - -msgid "workspace.undo.entry.single.image" -msgstr "resim" - -msgid "workspace.undo.entry.single.group" -msgstr "grup" - -msgid "workspace.undo.entry.single.curve" -msgstr "eğri" - -msgid "workspace.undo.entry.single.component" -msgstr "bileşen" - -msgid "workspace.undo.entry.single.color" -msgstr "renk varlığı" - -msgid "workspace.undo.entry.single.circle" -msgstr "daire" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.new" -msgstr "Yeni %s" - -msgid "workspace.undo.entry.multiple.text" -msgstr "metinler" - -msgid "workspace.undo.entry.multiple.shape" -msgstr "şekiller" - -msgid "workspace.undo.entry.multiple.rect" -msgstr "dikdörtgenler" - -msgid "workspace.undo.entry.multiple.page" -msgstr "sayfalar" - -msgid "workspace.undo.entry.multiple.multiple" -msgstr "nesneler" - -msgid "workspace.undo.entry.multiple.media" -msgstr "grafik varlığı" - -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.update" -msgstr "Güncelle" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dotted" -msgstr "Noktalı" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dashed" -msgstr "Çizgili" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.center" -msgstr "Merkez" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.color" -msgstr "Renk" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.square" -msgstr "Kare" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.row" -msgstr "Satırlar" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.width" -msgstr "Genişlik" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.use-default" -msgstr "Varsayılanı kullan" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.top" -msgstr "Üst" - -#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs -msgid "workspace.options.blur-options.title" -msgstr "Bulanıklık" - -msgid "workspace.options.blur-options.layer-blur" -msgstr "Katman" - -msgid "workspace.options.blur-options.background-blur" -msgstr "Arkaplan" - -msgid "workspace.library.own" -msgstr "Kütüphanelerim" - -msgid "workspace.library.libraries" -msgstr "Kütüphaneler" - -msgid "workspace.library.all" -msgstr "Tüm kütüphaneler" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.updates" -msgstr "GÜNCELLEMELER" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.update" -msgstr "Güncelle" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.search-shared-libraries" -msgstr "Paylaşılmış kütüphane ara" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.shared-libraries" -msgstr "PAYLAŞILMIŞ KÜTÜPHANELER" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-shared-libraries-available" -msgstr "Paylaşılmış Kütüphane bulunmuyor" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-matches-for" -msgstr "“%s“ için eşleşme bulunmadı" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.components" -msgstr "%s bileşen" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.library" -msgstr "KÜTÜPHANE" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.libraries" -msgstr "KÜTÜPHANELER" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.in-this-file" -msgstr "BU DOSYADAKİ KÜTÜPHANELER" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.graphics" -msgstr "%s grafik" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.file-library" -msgstr "Dosya kütüphanesi" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.save-color" -msgstr "Renk biçimini kaydet" - -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.recent-colors" -msgstr "Son renkler" - -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.file-library" -msgstr "Dosya kütüphanesi" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.colors" -msgstr "%s renk" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.add" -msgstr "Ekle" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.viewer" -msgstr "Görünüm modu (%s)" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.unsaved" -msgstr "Kaydedilmemiş değişiklikler" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.saving" -msgstr "Kaydediliyor" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.saved" -msgstr "Kaydedildi" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.save-error" -msgstr "Kaydetmede hata" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-rules" -msgstr "Cetveli göster" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-palette" -msgstr "Renk paletini 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-grid" -msgstr "Izgarayı 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.select-all" -msgstr "Tümünü seç" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-rules" -msgstr "Cetveli gizle" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-palette" -msgstr "Renk paletini 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-grid" -msgstr "Izgaraları gizle" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-assets" -msgstr "Varlıkları gizle" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.text-transform" -msgstr "Metin Dönüşümü" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.line-height" -msgstr "Satır Yüksekliği" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.letter-spacing" -msgstr "Harf Boşluğu" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-size" -msgstr "Boyut" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-id" -msgstr "Yazı tipi" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.shared" -msgstr "PAYLAŞILDI" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.selected-count" -msgid_plural "workspace.assets.selected-count" -msgstr[0] "Tek öge seçildi" -msgstr[1] "%s öge seçildi" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.search" -msgstr "Varlık ara" - -#: 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 "Yeniden adlandır" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.not-found" -msgstr "Varlık bulunmadı" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.libraries" -msgstr "Kütüphaneler" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.group-name" -msgstr "Grup adı" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.group" -msgstr "Grup" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.graphics" -msgstr "Grafikler" - -#: 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.edit" -msgstr "Düzenle" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.duplicate" -msgstr "Çoğalt" - -#: 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 "Sil" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.accept" -msgstr "Terfi et" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-confirm.message" -msgstr "Bu takımdan ayrılmak istediğinden emin misin?" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-confirm.accept" -msgstr "Takımdan ayrıl" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.title" -msgstr "Terfi etmek için bir üye seçin" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.select-memeber-to-promote" -msgstr "Terfi etmek için bir üye seçin" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.promote-and-leave" -msgstr "Terfi et ve ayrıl" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint1" -msgstr "%s sahibisiniz." - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-member.title" -msgstr "Takıma katılma daveti gönder" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-member-confirm.accept" -msgstr "Davet gönder" +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.confirm" +msgstr "Evet, hesabımı sil" #: src/app/main/ui/settings/delete_account.cljs msgid "modals.delete-account.info" @@ -2203,229 +1139,1294 @@ msgstr "Hesabını silerek tüm projelerini ve arşivlerini kaybedeceksin." msgid "modals.delete-account.title" msgstr "Hesabını silmek istediğinden emin misin?" -#: src/app/main/ui/settings/delete_account.cljs -msgid "modals.delete-account.confirm" -msgstr "Evet, hesabımı sil" - -#: src/app/main/ui/settings/delete_account.cljs -msgid "modals.delete-account.cancel" -msgstr "İptal et ve hesabımı koru" - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.title" -msgstr "E-postanızı değiştirin" - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.submit" -msgstr "E-postayı değiştir" - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.new-email" -msgstr "Yeni e-posta" - -#: 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." - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.confirm-email" -msgstr "Yeni e-postayı doğrulayın" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.add-shared-confirm.message" -msgstr "Paylaşılmış Kütüphane olarak “%s” Ekle" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.add-shared-confirm.hint" -msgstr "" -"Paylaşılmış Kütüphane olarak eklenince, bu dosya kütüphanesindeki varlıklar " -"diğer dosyalarınızdan da ulaşılabilecek." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.add-shared-confirm.accept" -msgstr "Paylaşılmış Kütüphane olarak Ekle" - -#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs -msgid "media.loading" -msgstr "Resim yükleniyor…" +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Konuşmayı sil" #: src/app/main/ui/comments.cljs -msgid "labels.write-new-comment" -msgstr "Yeni yorum yaz" +msgid "modals.delete-comment-thread.message" +msgstr "" +"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" +msgstr "Konuşmayı sil" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Dosyayı sil" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "Bu dosyayı silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Dosya siliniyor" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.accept" +msgstr "Dosyalar sil" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "%s dosyayı silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "%s dosyanın silinmesi" + +msgid "modals.delete-font.message" +msgstr "" +"Bu fontu silmek istediğine emin misin? Bir dosyada kullanılıyorsa " +"yüklenmeyecektir." + +msgid "modals.delete-font.title" +msgstr "Fontu sil" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Bu sayfayı silmek istediğinden emin misin?" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Sayfayı sil" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Projeyi sil" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Bu projeyi silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Projeyi sil" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Takımı sil" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Bu takımı silmek istediğinden emin misin? Takımla ilişkili dosyalar ve " +"projeler kalıcı olarak silinecektir." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Takımın silinmesi" #: src/app/main/ui/dashboard/team.cljs -msgid "labels.viewer" -msgstr "Görüntüler" +msgid "modals.delete-team-member-confirm.accept" +msgstr "Üyeyi sil" -msgid "labels.upload-custom-fonts" -msgstr "Özel yazı tipi yükle" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Bu üyeyi takımdan silmek istediğinden emin misin?" -#: src/app/main/ui/dashboard/team_form.cljs -msgid "labels.update-team" -msgstr "Takımı güncelle" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Takım üyesini sil" -msgid "labels.icons" -msgstr "Simgeler" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Davet gönder" -#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.radius" -msgstr "Yarı Çap" +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member.title" +msgstr "Takıma katılma daveti gönder" -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs -msgid "title.default" -msgstr "Penpot * Takımlar için Özgür Tasarım" +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"Birisini takımın sahibi yapmadan takımı bırakamazsın. Takımı silmek " +"isteyebilirsin." -#: src/app/main/ui/dashboard/libraries.cljs -msgid "title.dashboard.shared-libraries" -msgstr "Paylaşılmış Kütüphaneler - %s - Penpot" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "%s sahibisiniz." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint2" +msgstr "Ayrılmadan önce terfi etmek 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 et ve ayrıl" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.select-memeber-to-promote" +msgstr "Terfi etmek için bir üye seçin" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.title" +msgstr "Terfi etmek için bir üye seçin" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.accept" +msgstr "Takımdan ayrıl" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.message" +msgstr "Bu takımdan ayrılmak istediğinden emin misin?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.title" +msgstr "Takımdan ayrılmak" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.accept" +msgstr "Terfi et" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.message" +msgstr "Bu kullanıcıyı sahip olarak terfi etmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.title" +msgstr "Sahip olarak terfi et" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.accept" +msgstr "Paylaşılmış Kütüphane olarak 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.accept" +msgstr "Bileşeni güncelle" + +#: 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 "İptal" + +#: 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 "" +"Paylaşılmış bir kütüphanedeki bileşeni güncellemek üzeresin. Onu kullanan " +"diğer dosyalar etkilenebilir." + +#: 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 "Paylaşılmış bir kütüphanede bir bileşen güncelle" + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Davet başarıyla iletildi" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "Profilini silemezsin. Önce takımlarını birine atamalsın." + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Profil başarıyla kaydedildi!" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "%s adresine doğrulama e-postası gönderildi. E-postalarınızı kontrol edin!" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Giriş yap" + +#: 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 "Karışık" + +#: 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 "Yazıtipi Sağlayıcıları - %s - Penpot" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.fonts" +msgstr "Fontlar - %s - Penpot" #: src/app/main/ui/dashboard/projects.cljs msgid "title.dashboard.projects" msgstr "Projeler - %s - Penpot" +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Ara - %s - Penpot" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Paylaşılmış Kütüphaneler - %s - Penpot" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot * Takımlar için Özgür Tasarım" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Geri bildirimde bulun - Penpot" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Ayarlar - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Parola - 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-members" +msgstr "Üyeler - %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Ayarlar * %s - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Görünüm modu - Penpot" + +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "Sayfada çerçeve bulunmuyor." + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.frame-not-found" +msgstr "Çerçeve bulunmadı." + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "Etkileşimleri gösterme" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.edit-page" +msgstr "Sayfayı düzenle" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "Tam Ekran" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "Bağlantıyı kopyala" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Bağlantı oluştur" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "Paylaşım adresi burada görünecek" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Bağlantıyı kaldır" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Bağlantıya sahip herkes erişebilecek" + +#: 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 "Bağlantıyı paylaş" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "Etkileşimleri göster" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions-on-click" +msgstr "Tıklamada etkileşimleri göster" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Site haritası" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hcenter" +msgstr "Yatay olarak ortaya hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "Yatayda dağıt" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hleft" +msgstr "Sola hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hright" +msgstr "Sağa hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vbottom" +msgstr "Alta hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vcenter" +msgstr "Dikey olarak ortaya hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vdistribute" +msgstr "Dikeyde dağıt" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vtop" +msgstr "Üste hizala" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Varlıklar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Tüm varlıklar" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Grafikler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Renkler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Bileşenler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "Grup oluştur" + +#: 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 "Sil" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Çoğalt" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +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" +msgstr "Grafikler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Grup" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group-name" +msgstr "Grup adı" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Kütüphaneler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "Varlık bulunmadı" + +#: 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 "Yeniden adlandır" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Varlık ara" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.selected-count" +msgid_plural "workspace.assets.selected-count" +msgstr[0] "Tek öge seçildi" +msgstr[1] "%s öge seçildi" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "PAYLAŞILDI" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografiler" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Yazı tipi" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Boyut" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Çeşit" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Düzenlemek için biçim kütüphane dosyasına gidin" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Harf Boşluğu" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Satır Yüksekliği" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +msgstr "Metin Dönüşümü" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Doğrusal degrade" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Dairesel degrade" + #: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-snap-grid" -msgstr "Izgaraya tuttur" +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Dinamik hizalamayı kapat" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-scale-text" +msgstr "Metin ölçeklendirmeyi kapat" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Izgaraya tutturmayı kapat" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Dinamik hizalamayı etkinleştir" #: src/app/main/ui/workspace/header.cljs msgid "workspace.header.menu.enable-scale-text" msgstr "Metin ölçeklendirmeyi etkinleştir" #: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-dynamic-alignment" -msgstr "Dinamik hizalamayı etkinleştir" +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Izgaraya tuttur" -#: 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 "Boyut" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-assets" +msgstr "Varlıkları gizle" -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title.multiple" -msgstr "Gölge seçimi" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-grid" +msgstr "Izgaraları gizle" -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.spread" -msgstr "Yayılma" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-layers" +msgstr "Katmanları gizle" -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.offsety" -msgstr "Y" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Renk paletini gizle" -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.inner-shadow" -msgstr "İç gölge" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Cetveli gizle" -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.drop-shadow" -msgstr "Kabartı gölgesi" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.select-all" +msgstr "Tümünü seç" -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.rotation" -msgstr "Döndür" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-assets" +msgstr "Varlıkları göster" -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.radius.single-corners" -msgstr "Tek köşe" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-grid" +msgstr "Izgarayı göster" -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.radius.all-corners" -msgstr "Tüm köşeler" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-layers" +msgstr "Katmanları göster" -msgid "workspace.options.radius" -msgstr "Yarı çap" +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Renk paletini göster" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-rules" +msgstr "Cetveli göster" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Kaydetmede hata" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Kaydedildi" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Kaydediliyor" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Kaydedilmemiş değişiklikler" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Görünüm modu (%s)" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Ekle" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s renk" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Büyük önizlemeler" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Dosya kütüphanesi" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Son renkler" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Renk biçimini kaydet" + +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Küçük önizlemeler" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s bileşen" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "Dosya kütüphanesi" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s grafik" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "BU DOSYADAKİ KÜTÜPHANELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "KÜTÜPHANELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "KÜTÜPHANE" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "Güncelleme gerektiren Paylaşılmış Kütüphane bulunmuyor" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "“%s“ için eşleşme bulunmadı" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "Paylaşılmış Kütüphane bulunmuyor" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Paylaşılmış kütüphane ara" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "PAYLAŞILMIŞ KÜTÜPHANELER" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography" +msgstr "Çoklu tipografiler" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Tüm tipografileri ayır" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s tipografi" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Güncelle" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "GÜNCELLEMELER" + +msgid "workspace.library.all" +msgstr "Tüm kütüphaneler" + +msgid "workspace.library.libraries" +msgstr "Kütüphaneler" + +msgid "workspace.library.own" +msgstr "Kütüphanelerim" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Arkaplan" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Katman" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Bulanıklık" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Bileşen" #: src/app/main/ui/workspace/sidebar/options.cljs -msgid "workspace.options.prototype" -msgstr "Prototip" +msgid "workspace.options.design" +msgstr "Tasarım" -#: 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 "Konum" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export" +msgstr "Dışarı Aktar" -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.none" -msgstr "Hiç biri" +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgstr "Şekli dışarı aktar" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +msgid "workspace.options.export.suffix" +msgstr "Son ek" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.exporting-object" +msgstr "Dışarı aktarılıyor…" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Doldur" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Otomatik" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Sütunlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Sütunlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.height" +msgstr "Yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Kenar Boşluğu" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.rows" +msgstr "Satırlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.set-default" +msgstr "Varsayılan olarak belirle" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.size" +msgstr "Boyut" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type" +msgstr "Tür" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Alt" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Orta" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Sol" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Sağ" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.stretch" +msgstr "Ger" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Üst" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Varsayılanı kullan" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.width" +msgstr "Genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.row" +msgstr "Satırlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.square" +msgstr "Kare" #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.title.multiple" -msgstr "Seçili katmanlar" +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Renk" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color-burn" +msgstr "Renk yanması" + +#: 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 "Üst katman" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.saturation" +msgstr "Doygunluk" + +#: 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.title" +msgstr "Katman" #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs msgid "workspace.options.layer-options.title.group" msgstr "Katman grubu" #: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.title" -msgstr "Katman" +msgid "workspace.options.layer-options.title.multiple" +msgstr "Seçili katmanlar" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.sidebar.history" -msgstr "Geçmiş (%s)" +#: 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/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 "Ana bileşeni güncelle" +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.none" +msgstr "Hiç biri" + +#: 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 "Konum" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Prototip" + +msgid "workspace.options.radius" +msgstr "Yarı çap" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Tüm köşeler" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.single-corners" +msgstr "Tek köşe" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Döndür" + +#: 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." + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-artboard" +msgstr "Çalışma yüzeyi seç" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Kabartı gölgesi" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.inner-shadow" +msgstr "İç gölge" + +#: 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 "Yayılma" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Gölge" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.group" +msgstr "Gölge grubu" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Gölge seçimi" + +#: 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 "Boyut" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Merkez" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Çizgili" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Noktalı" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Karışık" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Alta hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-center" +msgstr "Ortaya hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-justify" +msgstr "İki yana yasla" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-left" +msgstr "Sola hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-middle" +msgstr "Merkeze hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-right" +msgstr "Sağa hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-top" +msgstr "Üste hizala" + +msgid "workspace.options.text-options.decoration" +msgstr "Süsleme" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-ltr" +msgstr "Soldan sağa" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.direction-rtl" +msgstr "Sağdan sola" + +#: 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 "Otomatik yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Otomatik genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Sabit" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.letter-spacing" +msgstr "Harf Aralıkları" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Satır yüksekliği" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Küçük harf" + +#: 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 "Hiçbiri" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Üstü çizili" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Metin" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Grup metni" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +#, fuzzy +msgid "workspace.options.text-options.title-selection" +msgstr "Metin seçimi" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.titlecase" +msgstr "İlk Harfi Büyük" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Altı Çizili" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Büyük Harf" + +msgid "workspace.options.text-options.vertical-align" +msgstr "Düşey 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." + +msgid "workspace.path.actions.add-node" +msgstr "Düğüm ekle (%s)" + +msgid "workspace.path.actions.delete-node" +msgstr "Düğüm sil (%s)" + +msgid "workspace.path.actions.draw-nodes" +msgstr "Düğüm çiz (%s)" + +msgid "workspace.path.actions.join-nodes" +msgstr "Düğümleri birleştir (%s)" + +msgid "workspace.path.actions.make-corner" +msgstr "Köşeye (%s)" + +msgid "workspace.path.actions.make-curve" +msgstr "Eğriye (%s)" + +msgid "workspace.path.actions.merge-nodes" +msgstr "Düğümleri birleştir (%s)" + +msgid "workspace.path.actions.move-nodes" +msgstr "Düğümleri taşı (%s)" + +msgid "workspace.path.actions.separate-nodes" +msgstr "Düğümleri ayır (%s)" + +msgid "workspace.path.actions.snap-nodes" +msgstr "Düğümleri tuttur (%s)" #: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.unlock" -msgstr "Çöz" +msgid "workspace.shape.menu.back" +msgstr "Arkaya gönder" #: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.ungroup" -msgstr "Grubu dağıt" +msgid "workspace.shape.menu.backward" +msgstr "En arkaya gönder" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Kopyala" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Bileşen oluştur" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.cut" +msgstr "Kes" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.delete" +msgstr "Sil" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.duplicate" +msgstr "Çoğalt" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.edit" +msgstr "Düzenle" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Yatay ters çevir" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.flip-vertical" +msgstr "Dikey ters çevir" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.forward" +msgstr "Öne getir" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.front" +msgstr "En öne getir" #: 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 "Ana bileşeni göster" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.show" -msgstr "Göster" - -#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.paste" -msgstr "Yapıştır" - -#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.mask" -msgstr "Maskele" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.lock" -msgstr "Kilitle" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.hide" -msgstr "Gizle" +msgid "workspace.shape.menu.go-main" +msgstr "Ana bileşen dosyasına git" #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.group" msgstr "Grup" #: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.back" -msgstr "Arkaya gönder" +msgid "workspace.shape.menu.hide" +msgstr "Gizle" -msgid "workspace.path.actions.separate-nodes" -msgstr "Düğümleri ayır (%s)" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Kilitle" -msgid "workspace.path.actions.add-node" -msgstr "Düğüm ekle (%s)" +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Maskele" -#: 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." +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Yapıştır" -#: 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 "Hiçbiri" +#: src/app/main/ui/workspace/context_menu.cljs +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-main" +msgstr "Ana bileşeni göster" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Grubu dağıt" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unlock" +msgstr "Çöz" + +#: 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 "Ana bileşeni güncelle" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "Geçmiş (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.layers" +msgstr "Katmanlar (%s)" + +#: 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 Öznitelikleri İçeri Aktarıldı" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "workspace.sidebar.sitemap" +msgstr "Sayfalar" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Varlıklar(%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.color-palette" +msgstr "Renk Paketi (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Yorumlar (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.curve" +msgstr "Eğri (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "Elips (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Çalışma Yüzeyi (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Resim (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Taşı" #: src/app/main/ui/workspace/left_toolbar.cljs msgid "workspace.toolbar.rect" msgstr "Dikdörtgen (%s)" #: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.color-palette" -msgstr "Renk Paketi (%s)" +msgid "workspace.toolbar.text" +msgstr "Metin (%s)" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.empty" +msgstr "Şu ana kadar değişim geçmişi yok" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.delete" +msgstr "%s silindi" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.modify" +msgstr "%s düzenlendi" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.move" +msgstr "Nesneler taşındı" + +msgid "workspace.undo.entry.multiple.circle" +msgstr "daireler" + +msgid "workspace.undo.entry.multiple.color" +msgstr "renk varlıkları" + +msgid "workspace.undo.entry.multiple.component" +msgstr "bileşenler" + +msgid "workspace.undo.entry.multiple.curve" +msgstr "eğriler" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "çalışma yüzeyi" + +msgid "workspace.undo.entry.multiple.group" +msgstr "gruplar" + +msgid "workspace.undo.entry.multiple.media" +msgstr "grafik varlığı" + +msgid "workspace.undo.entry.multiple.multiple" +msgstr "nesneler" + +msgid "workspace.undo.entry.multiple.page" +msgstr "sayfalar" + +msgid "workspace.undo.entry.multiple.rect" +msgstr "dikdörtgenler" + +msgid "workspace.undo.entry.multiple.shape" +msgstr "şekiller" + +msgid "workspace.undo.entry.multiple.text" +msgstr "metinler" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.new" +msgstr "Yeni %s" + +msgid "workspace.undo.entry.single.circle" +msgstr "daire" + +msgid "workspace.undo.entry.single.color" +msgstr "renk varlığı" + +msgid "workspace.undo.entry.single.component" +msgstr "bileşen" + +msgid "workspace.undo.entry.single.curve" +msgstr "eğri" + +msgid "workspace.undo.entry.single.frame" +msgstr "çalışma yüzeyi" + +msgid "workspace.undo.entry.single.group" +msgstr "grup" + +msgid "workspace.undo.entry.single.image" +msgstr "resim" + +msgid "workspace.undo.entry.single.media" +msgstr "grafik varlığı" + +msgid "workspace.undo.entry.single.multiple" +msgstr "nesne" + +msgid "workspace.undo.entry.single.page" +msgstr "sayfa" + +msgid "workspace.undo.entry.single.rect" +msgstr "dikdörtgen" + +msgid "workspace.undo.entry.single.shape" +msgstr "şekil" + +msgid "workspace.undo.entry.single.text" +msgstr "metin" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.unknown" +msgstr "%s üstündeki işlem" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.title" +msgstr "Geçmiş" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Gözardı et" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "Paylaşılmış kütüphanelerde güncellemeler mevcut" + +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.update" +msgstr "Güncelle" \ No newline at end of file From 80371233c9123bf71016ba42a90a56b56c68a7ff Mon Sep 17 00:00:00 2001 From: andy Date: Thu, 17 Jun 2021 13:50:41 +0000 Subject: [PATCH 116/204] :globe_with_meridians: Add translations for: Spanish. Currently translated at 99.5% (659 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/es/ --- frontend/translations/es.po | 70 ++++++++++++++++++++++--------------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/frontend/translations/es.po b/frontend/translations/es.po index eb9ae497ce..3eba470f26 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -1,15 +1,15 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-04-14 13:44+0000\n" -"Last-Translator: Andrés Moya \n" -"Language-Team: Spanish " -"\n" +"PO-Revision-Date: 2021-06-18 09:19+0000\n" +"Last-Translator: andy \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.7-dev\n" +"X-Generator: Weblate 4.7\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -69,7 +69,7 @@ msgstr "Introduce tus datos aquí" #: src/app/main/ui/auth/login.cljs msgid "auth.login-title" -msgstr "Encantados de volverte a ver" +msgstr "¡Encantados de verte de nuevo!" #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-github-submit" @@ -213,19 +213,19 @@ msgstr "Todavía no hay ningún archivo aquí" #, markdown msgid "dashboard.fonts.hero-text1" msgstr "" -"Any web font you upload here will be added to the font family list " -"available at the text properties of the files of this team. Fonts with the " -"same font family name will be grouped as a **single font family**. You can " -"upload fonts with the following formats: **TTF, OTF and WOFF** (only one " -"will be needed)." +"Cualquier fuente personalizada añadida aquí aparecerá en la lista de " +"familias de fuentes disponible en las propiedades de texto de los archivos " +"de este equipo. Las fuentes con el mismo nombre de familia serán agrupadas " +"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 "" -"You should only upload fonts you own or have license to use in Penpot. Find " -"out more in the Content rights section of [Penpot's Terms of " -"Service](https://penpot.app/terms.html). You also might want to read about " -"[font licensing](2)." +"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](1). También te " +"puede interesar leer más sobre licencias tipográficas: [font licensing](2)." #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" @@ -341,7 +341,7 @@ msgstr "Selecciona un tema" #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.show-all-files" -msgstr "Ver todos los ficheros" +msgstr "Ver todos los archivos" #: src/app/main/ui/dashboard/file_menu.cljs msgid "dashboard.success-delete-file" @@ -465,7 +465,7 @@ msgstr "Autenticación con google esta dehabilitada en el servidor" #: src/app/main/ui/components/color_input.cljs msgid "errors.invalid-color" -msgstr "Color inválido" +msgstr "Color no válido" #: src/app/main/ui/auth/login.cljs msgid "errors.ldap-disabled" @@ -561,10 +561,12 @@ 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 "" +msgstr "Debates de equipo" #: src/app/main/ui/settings/feedback.cljs msgid "feedback.subject" @@ -573,6 +575,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." #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" @@ -1022,7 +1027,7 @@ msgstr "Renombrar" #: src/app/main/ui/dashboard/team_form.cljs msgid "labels.rename-team" -msgstr "Renomba el equipo" +msgstr "Renombra el equipo" #: src/app/main/ui/static.cljs, src/app/main/ui/static.cljs, src/app/main/ui/static.cljs msgid "labels.retry" @@ -1044,7 +1049,7 @@ msgstr "Enviar" #: src/app/main/ui/settings/feedback.cljs msgid "labels.sending" -msgstr "Enviando..." +msgstr "Enviando…" #: src/app/main/ui/static.cljs msgid "labels.service-unavailable.desc-message" @@ -1083,13 +1088,13 @@ msgid "labels.update-team" msgstr "Actualiza el equipo" msgid "labels.upload" -msgstr "Subir" +msgstr "Cargar" msgid "labels.upload-custom-fonts" -msgstr "Subir fuente" +msgstr "Cargar fuente" msgid "labels.uploading" -msgstr "Subiendo..." +msgstr "Subiendo…" #: src/app/main/ui/dashboard/team.cljs msgid "labels.viewer" @@ -1123,7 +1128,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" @@ -1193,8 +1199,8 @@ msgstr "Eliminando %s archivos" msgid "modals.delete-font.message" msgstr "" -"Are you sure you want to delete this font? It will not load if is used in a " -"file." +"¿Seguro que quieres eliminar esta fuente? Si está siendo usada en algún " +"fichero no se cargará." msgid "modals.delete-font.title" msgstr "Eliminando fuente" @@ -1715,7 +1721,7 @@ msgstr "Añadir" #: src/app/main/ui/workspace/libraries.cljs msgid "workspace.libraries.colors" -msgstr "%s colors" +msgstr "%s colores" #: src/app/main/ui/workspace/colorpalette.cljs msgid "workspace.libraries.colors.big-thumbnails" @@ -2595,4 +2601,12 @@ 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/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Ir al login" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.create-team.placeholder" +msgstr "Introduce un nuevo nombre de equipo" From 4a82c148082248977cac41e16defb01a54bf5222 Mon Sep 17 00:00:00 2001 From: Amine Gdoura Date: Thu, 17 Jun 2021 14:43:17 +0000 Subject: [PATCH 117/204] :globe_with_meridians: Add translations for: Arabic. Currently translated at 27.3% (181 of 662 strings) Translation: Penpot/frontend Translate-URL: https://hosted.weblate.org/projects/penpot/frontend/ar/ --- frontend/translations/ar.po | 86 +++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index 97f5784f9c..e490c31e30 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -1,6 +1,6 @@ msgid "" msgstr "" -"PO-Revision-Date: 2021-06-15 18:34+0000\n" +"PO-Revision-Date: 2021-06-18 09:19+0000\n" "Last-Translator: Amine Gdoura \n" "Language-Team: Arabic \n" @@ -10,7 +10,7 @@ msgstr "" "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.7-dev\n" +"X-Generator: Weblate 4.7\n" #: src/app/main/ui/auth/register.cljs msgid "auth.already-have-account" @@ -137,7 +137,6 @@ msgid "auth.register-submit" msgstr "إنشاء حساب" #: src/app/main/ui/auth/register.cljs -#, fuzzy msgid "auth.register-subtitle" msgstr "إنه مجاني ، مفتوح المصدر" @@ -340,7 +339,7 @@ msgstr "تم إرسال رابط استعادة كلمة المرور إلى ص #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.profile-not-verified" -msgstr "لم يتم التعرف على الحساب الشخصي ، يرجى التحقق قبل المتابعة." +msgstr "لم يتم التعرف على الحساب الشخصي ، يرجى التحقق قبل المتابعة." #: src/app/main/ui/auth/verify_token.cljs msgid "errors.email-already-validated" @@ -677,3 +676,82 @@ msgstr "حدث خطأ ما." #: src/app/main/ui/settings/change_email.cljs msgid "errors.email-invalid-confirmation" 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.accept" +msgstr "إقبل" + +msgid "history.alert-message" +msgstr "أنت ترى الإصدار٪ s" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "معلومات" + +msgid "handoff.tabs.code.selected.text" +msgstr "نص" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.rect" +msgstr "رباعي" + +msgid "handoff.tabs.code.selected.path" +msgstr "مسار" + +msgid "handoff.tabs.code.selected.image" +msgstr "صورة" + +msgid "handoff.tabs.code.selected.curve" +msgstr "منحنى" + +msgid "handoff.tabs.code.selected.circle" +msgstr "دائرة" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "شفرة" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "الأحرف الكبيرة" + +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "أحرف صغيرة" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "تغيير النص" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "مسطر" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "يتوسطه خط" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "زخرفة النص" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "ارتفاع الخط" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "تباعد الحروف" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "نوع الخط" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.spread" +msgstr "S" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.offset-y" +msgstr "Y" From e90185b5533644369f623a340aad0c122a6e6ade Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 13:26:38 +0200 Subject: [PATCH 118/204] :sparkles: Fix linter issues on frontend (part 1). --- .clj-kondo/config.edn | 22 +- .clj-kondo/hooks/export.clj | 10 + frontend/src/app/config.cljs | 3 +- frontend/src/app/main.cljs | 7 +- frontend/src/app/main/data/comments.cljs | 41 +-- frontend/src/app/main/data/dashboard.cljs | 105 ++++--- frontend/src/app/main/data/events.cljs | 12 +- frontend/src/app/main/data/fonts.cljs | 20 +- frontend/src/app/main/data/history.cljs | 270 ------------------ frontend/src/app/main/data/media.cljs | 12 +- frontend/src/app/main/data/messages.cljs | 11 +- frontend/src/app/main/data/modal.cljs | 8 +- frontend/src/app/main/data/shortcuts.cljs | 7 +- frontend/src/app/main/data/users.cljs | 58 ++-- frontend/src/app/main/data/viewer.cljs | 43 ++- .../src/app/main/data/viewer/shortcuts.cljs | 8 +- frontend/src/app/main/data/workspace.cljs | 135 ++++----- .../app/main/data/workspace/persistence.cljs | 2 +- .../main/ui/workspace/sidebar/history.cljs | 1 - frontend/src/app/util/avatars.cljs | 3 +- frontend/src/app/util/code_gen.cljs | 2 +- frontend/src/app/util/color.cljs | 13 +- frontend/src/app/util/data.cljs | 2 +- frontend/src/app/util/debug.cljs | 4 +- frontend/src/app/util/dom/dnd.cljs | 7 +- frontend/src/app/util/forms.cljs | 19 +- frontend/src/app/util/geom/grid.cljs | 10 +- frontend/src/app/util/geom/snap_points.cljs | 6 +- frontend/src/app/util/http.cljs | 7 +- frontend/src/app/util/i18n.cljs | 3 +- frontend/src/app/util/logging.clj | 5 +- frontend/src/app/util/logging.cljs | 10 +- frontend/src/app/util/object.cljs | 5 +- frontend/src/app/util/path/commands.cljs | 11 +- frontend/src/app/util/path/format.cljs | 6 +- frontend/src/app/util/path/geom.cljs | 5 - frontend/src/app/util/path/parser.cljs | 9 +- .../src/app/util/path/shapes_to_path.cljs | 9 +- .../src/app/util/path/simplify_curve.cljs | 9 +- frontend/src/app/util/path/tools.cljs | 16 +- frontend/src/app/util/router.cljs | 8 +- frontend/src/app/util/simple_math.cljs | 14 +- frontend/src/app/util/storage.cljs | 12 +- frontend/src/app/util/svg.cljs | 27 +- frontend/src/app/util/text_editor.cljs | 9 +- frontend/src/app/util/theme.cljs | 8 +- frontend/src/app/util/time.cljs | 18 +- frontend/src/app/util/uri.cljs | 4 +- frontend/src/app/util/webapi.cljs | 5 +- frontend/src/app/util/websockets.cljs | 5 +- frontend/src/app/util/worker.cljs | 2 +- frontend/src/app/util/zip.cljs | 5 +- frontend/src/app/worker.cljs | 19 +- 53 files changed, 324 insertions(+), 748 deletions(-) delete mode 100644 frontend/src/app/main/data/history.cljs diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 7f0a4e788c..4713efa815 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -1,14 +1,22 @@ -{:lint-as {potok.core/reify clojure.core/reify - promesa.core/let clojure.core/let - rumext.alpha/defc clojure.core/defn - app.common.data/export clojure.core/def - app.db/with-atomic clojure.core/with-open} +{:lint-as + {promesa.core/let clojure.core/let + rumext.alpha/defc clojure.core/defn + app.common.data/export clojure.core/def + app.db/with-atomic clojure.core/with-open} :hooks - {:analyze-call {app.common.data/export hooks.export/export}} + {:analyze-call {app.common.data/export hooks.export/export + potok.core/reify hooks.export/potok-reify}} :output - {:exclude-files ["data_readers.clj"]} + {:exclude-files + ["data_readers.clj" + "app/util/perf.cljs" + "app/util/import/.*" + "app/worker/.*" + "app/main/ui.*" + "app/libs/.*" + ]} :linters {:unsorted-required-namespaces diff --git a/.clj-kondo/hooks/export.clj b/.clj-kondo/hooks/export.clj index bac6996ca6..5cdb5e4d52 100644 --- a/.clj-kondo/hooks/export.clj +++ b/.clj-kondo/hooks/export.clj @@ -9,3 +9,13 @@ (api/token-node (symbol (name (:value sname)))) sname])] {:node result})) + +(defn potok-reify + [{:keys [:node]}] + (let [[rnode rtype & other] (:children node) + result (api/list-node + (into [(api/token-node (symbol "deftype")) + (api/token-node (gensym (name (:k rtype)))) + (api/vector-node [])] + other))] + {:node result})) diff --git a/frontend/src/app/config.cljs b/frontend/src/app/config.cljs index 42b90c26ca..0cc0a34273 100644 --- a/frontend/src/app/config.cljs +++ b/frontend/src/app/config.cljs @@ -6,9 +6,8 @@ (ns app.config (:require - [app.common.data :as d] - [app.common.uri :as u] [app.common.spec :as us] + [app.common.uri :as u] [app.common.version :as v] [app.util.avatars :as avatars] [app.util.dom :as dom] diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs index dc2a58f961..ce862ff133 100644 --- a/frontend/src/app/main.cljs +++ b/frontend/src/app/main.cljs @@ -12,7 +12,6 @@ [app.main.data.events :as ev] [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 :as ui] [app.main.ui.confirm] @@ -21,11 +20,9 @@ [app.util.dom :as dom] [app.util.i18n :as i18n] [app.util.logging :as log] - [app.util.object :as obj] [app.util.router :as rt] [app.util.storage :refer [storage]] [app.util.theme :as theme] - [app.util.timers :as ts] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk] @@ -81,7 +78,7 @@ (defn initialize [] - (letfn [(on-profile [profile] + (letfn [(on-profile [_profile] (rx/of (rt/initialize-router ui/routes) (rt/initialize-history on-navigate)))] (ptk/reify ::initialize @@ -90,7 +87,7 @@ (assoc state :session-id (uuid/next))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (rx/merge (rx/of (ptk/event ::ev/initialize) diff --git a/frontend/src/app/main/data/comments.cljs b/frontend/src/app/main/data/comments.cljs index 034921fda3..5250b7202d 100644 --- a/frontend/src/app/main/data/comments.cljs +++ b/frontend/src/app/main/data/comments.cljs @@ -6,28 +6,11 @@ (ns app.main.data.comments (:require - [cuerdas.core :as str] [app.common.data :as d] - [app.common.exceptions :as ex] - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] - [app.common.geom.shapes :as geom] - [app.common.math :as mth] - [app.common.pages :as cp] [app.common.spec :as us] - [app.common.uuid :as uuid] - [app.config :as cfg] - [app.main.constants :as c] [app.main.repo :as rp] - [app.main.store :as st] - [app.main.streams :as ms] - [app.main.worker :as uw] - [app.util.router :as rt] - [app.util.timers :as ts] - [app.util.webapi :as wapi] [beicon.core :as rx] [cljs.spec.alpha :as s] - [clojure.set :as set] [potok.core :as ptk])) (s/def ::content ::us/string) @@ -91,7 +74,7 @@ (ptk/reify ::create-thread ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/mutation :create-comment-thread params) (rx/mapcat #(rp/query :comment-thread {:file-id (:file-id %) :id (:id %)})) (rx/map #(partial created %))))))) @@ -101,7 +84,7 @@ (us/assert ::comment-thread thread) (ptk/reify ::update-comment-thread-status ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [done #(d/update-in-when % [:comment-threads id] assoc :count-unread-comments 0)] (->> (rp/mutation :update-comment-thread-status {:id id}) (rx/map (constantly done))))))) @@ -117,7 +100,7 @@ (d/update-in-when state [:comment-threads id] assoc :is-resolved is-resolved)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/mutation :update-comment-thread {:id id :is-resolved is-resolved}) (rx/ignore))))) @@ -130,7 +113,7 @@ (update-in state [:comments (:id thread)] assoc (:id comment) comment))] (ptk/reify ::create-comment ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (rx/concat (->> (rp/mutation :add-comment {:thread-id (:id thread) :content content}) (rx/map #(partial created %))) @@ -145,7 +128,7 @@ (d/update-in-when state [:comments thread-id id] assoc :content content)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/mutation :update-comment {:id id :content content}) (rx/ignore))))) @@ -160,7 +143,7 @@ (update :comment-threads dissoc id))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/mutation :delete-comment-thread {:id id}) (rx/ignore))))) @@ -173,7 +156,7 @@ (d/update-in-when state [:comments thread-id] dissoc id)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/mutation :delete-comment {:id id}) (rx/ignore))))) @@ -184,7 +167,7 @@ (assoc-in state [:comment-threads id] thread))] (ptk/reify ::refresh-comment-thread ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query :comment-thread {:file-id file-id :id id}) (rx/map #(partial fetched %))))))) @@ -195,7 +178,7 @@ (assoc state :comment-threads (d/index-by :id data)))] (ptk/reify ::retrieve-comment-threads ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query :comment-threads {:file-id file-id}) (rx/map #(partial fetched %))))))) @@ -206,7 +189,7 @@ (update state :comments assoc thread-id (d/index-by :id comments)))] (ptk/reify ::retrieve-comments ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query :comments {:thread-id thread-id}) (rx/map #(partial fetched %))))))) @@ -216,7 +199,7 @@ (us/assert ::us/uuid team-id) (ptk/reify ::retrieve-unread-comment-threads ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [fetched #(assoc %2 :comment-threads (d/index-by :id %1))] (->> (rp/query :unread-comment-threads {:team-id team-id}) (rx/map #(partial fetched %))))))) @@ -320,7 +303,7 @@ (defn apply-filters [cstate profile threads] - (let [{:keys [show mode open]} cstate] + (let [{:keys [show mode]} cstate] (cond->> threads (= :pending show) (filter (comp not :is-resolved)) diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 8bdc4dafe6..b55b5cab0d 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -7,23 +7,16 @@ (ns app.main.data.dashboard (:require [app.common.data :as d] - [app.common.pages :as cp] [app.common.spec :as us] [app.common.uuid :as uuid] - [app.main.repo :as rp] - [app.main.data.events :as ev] - [app.main.data.users :as du] [app.main.data.fonts :as df] + [app.main.data.media :as di] + [app.main.data.users :as du] + [app.main.repo :as rp] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] - [app.util.time :as dt] - [app.util.timers :as ts] - [app.util.avatars :as avatars] - [app.main.data.media :as di] - [app.main.data.messages :as dm] [beicon.core :as rx] [cljs.spec.alpha :as s] - [cuerdas.core :as str] [potok.core :as ptk])) ;; --- Specs @@ -44,13 +37,13 @@ ::modified-at])) (s/def ::project - (s/keys ::req-un [::id - ::name - ::team-id - ::profile-id - ::created-at - ::modified-at - ::is-pinned])) + (s/keys :req-un [::id + ::name + ::team-id + ::profile-id + ::created-at + ::modified-at + ::is-pinned])) (s/def ::file (s/keys :req-un [::id @@ -109,7 +102,7 @@ [] (ptk/reify ::fetch-team-members ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (->> (rp/query :team-members {:team-id team-id}) (rx/map team-members-fetched)))))) @@ -127,7 +120,7 @@ [] (ptk/reify ::fetch-team-stats ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (->> (rp/query :team-stats {:team-id team-id}) (rx/map team-stats-fetched)))))) @@ -146,7 +139,7 @@ [] (ptk/reify ::fetch-projects ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (->> (rp/query :projects {:team-id team-id}) (rx/map projects-fetched)))))) @@ -173,7 +166,7 @@ (dissoc state :dashboard-search-result)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state) params (assoc params :team-id team-id)] (->> (rp/query :search-files params) @@ -202,7 +195,7 @@ (us/assert ::us/uuid project-id) (ptk/reify ::fetch-files ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query :project-files {:project-id project-id}) (rx/map #(files-fetched project-id %)))))) @@ -219,7 +212,7 @@ [] (ptk/reify ::fetch-shared-files ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (->> (rp/query :team-shared-files {:team-id team-id}) (rx/map shared-files-fetched)))))) @@ -240,7 +233,7 @@ [] (ptk/reify ::fetch-recent-files ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (->> (rp/query :team-recent-files {:team-id team-id}) (rx/map recent-files-fetched)))))) @@ -293,7 +286,7 @@ (us/assert string? name) (ptk/reify ::create-team ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] @@ -313,7 +306,7 @@ (assoc-in state [:teams id :name] name)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/mutation! :update-team params) (rx/ignore))))) @@ -322,7 +315,7 @@ (us/assert ::di/file file) (ptk/reify ::update-team-photo ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [on-success di/notify-finished-loading on-error #(do (di/notify-finished-loading) (di/process-error %)) @@ -344,7 +337,7 @@ (us/assert ::us/keyword role) (ptk/reify ::update-team-member-role ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state) params (assoc params :team-id team-id)] (->> (rp/mutation! :update-team-member-role params) @@ -357,7 +350,7 @@ (us/assert ::us/uuid member-id) (ptk/reify ::delete-team-member ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state) params (assoc params :team-id team-id)] (->> (rp/mutation! :delete-team-member params) @@ -370,7 +363,7 @@ (us/assert (s/nilable ::us/uuid) reassign-to) (ptk/reify ::leave-team ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) @@ -391,7 +384,7 @@ (us/assert ::us/keyword role) (ptk/reify ::invite-team-member ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) @@ -408,7 +401,7 @@ (us/assert ::team params) (ptk/reify ::delete-team ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] @@ -434,7 +427,7 @@ [] (ptk/reify ::create-project ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [name (name (gensym (str (tr "dashboard.new-project-prefix") " "))) team-id (:current-team-id state) params {:name name @@ -461,7 +454,7 @@ (us/assert ::us/uuid id) (ptk/reify ::duplicate-project ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) @@ -480,7 +473,7 @@ (us/assert ::us/uuid team-id) (ptk/reify ::move-project ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] @@ -491,7 +484,7 @@ (rx/catch on-error)))))) (defn toggle-project-pin - [{:keys [id is-pinned team-id] :as project}] + [{:keys [id is-pinned] :as project}] (us/assert ::project project) (ptk/reify ::toggle-project-pin ptk/UpdateEvent @@ -499,7 +492,7 @@ (assoc-in state [:dashboard-projects id :is-pinned] (not is-pinned))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [project (get-in state [:dashboard-projects id]) params (select-keys project [:id :is-pinned :team-id])] (->> (rp/mutation :update-project-pin params) @@ -508,7 +501,7 @@ ;; --- EVENT: rename-project (defn rename-project - [{:keys [id name team-id] :as params}] + [{:keys [id name] :as params}] (us/assert ::project params) (ptk/reify ::rename-project ptk/UpdateEvent @@ -518,7 +511,7 @@ (update :dashboard-local dissoc :project-for-edit))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [params {:id id :name name}] (->> (rp/mutation :rename-project params) (rx/ignore)))))) @@ -526,7 +519,7 @@ ;; --- EVENT: delete-project (defn delete-project - [{:keys [id team-id] :as params}] + [{:keys [id] :as params}] (us/assert ::project params) (ptk/reify ::delete-project ptk/UpdateEvent @@ -534,14 +527,14 @@ (update state :dashboard-projects dissoc id)) ptk/WatchEvent - (watch [_ state s] + (watch [_ _ _] (->> (rp/mutation :delete-project {:id id}) (rx/ignore))))) ;; --- EVENT: delete-file (defn file-deleted - [team-id project-id] + [_team-id project-id] (ptk/reify ::file-deleted ptk/UpdateEvent (update [_ state] @@ -558,7 +551,7 @@ (d/update-when :dashboard-recent-files dissoc id))) ptk/WatchEvent - (watch [_ state s] + (watch [_ state _] (let [team-id (uuid/uuid (get-in state [:route :path-params :team-id]))] (->> (rp/mutation :delete-file {:id id}) (rx/map #(file-deleted team-id project-id))))))) @@ -576,7 +569,7 @@ (d/update-in-when [:dashboard-recent-files id :name] (constantly name)))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [params (select-keys params [:id :name])] (->> (rp/mutation :rename-file params) (rx/ignore)))))) @@ -594,7 +587,7 @@ (d/update-in-when [:dashboard-recent-files id :is-shared] (constantly is-shared)))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [params {:id id :is-shared is-shared}] (->> (rp/mutation :set-file-shared params) (rx/ignore)))))) @@ -621,7 +614,7 @@ (us/assert ::us/uuid project-id) (ptk/reify ::create-file ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) @@ -642,7 +635,7 @@ (us/assert ::name name) (ptk/reify ::duplicate-file ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params) @@ -663,7 +656,7 @@ (us/assert ::us/uuid project-id) (ptk/reify ::move-files ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] @@ -682,7 +675,7 @@ (us/assert ::file file) (ptk/reify ::go-to-workspace ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [pparams {:project-id project-id :file-id id}] (rx/of (rt/nav :workspace pparams)))))) @@ -691,14 +684,14 @@ ([project-id] (ptk/reify ::go-to-files ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (rx/of (rt/nav :dashboard-files {:team-id team-id :project-id project-id})))))) ([team-id project-id] (ptk/reify ::go-to-files ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (rx/of (rt/nav :dashboard-files {:team-id team-id :project-id project-id})))))) @@ -707,7 +700,7 @@ ([term] (ptk/reify ::go-to-search ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (if (empty? term) (rx/of (rt/nav :dashboard-search @@ -720,13 +713,13 @@ ([] (ptk/reify ::go-to-projects ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (rx/of (rt/nav :dashboard-projects {:team-id team-id})))))) ([team-id] (ptk/reify ::go-to-projects ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (du/set-current-team! team-id) (rx/of (rt/nav :dashboard-projects {:team-id team-id})))))) @@ -734,7 +727,7 @@ [] (ptk/reify ::go-to-team-members ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (rx/of (rt/nav :dashboard-team-members {:team-id team-id})))))) @@ -742,6 +735,6 @@ [] (ptk/reify ::go-to-team-settings ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (rx/of (rt/nav :dashboard-team-settings {:team-id team-id})))))) diff --git a/frontend/src/app/main/data/events.cljs b/frontend/src/app/main/data/events.cljs index ca4b99a619..83c2a835f5 100644 --- a/frontend/src/app/main/data/events.cljs +++ b/frontend/src/app/main/data/events.cljs @@ -8,14 +8,14 @@ (:require ["ua-parser-js" :as UAParser] [app.common.data :as d] - [app.main.repo :as rp] [app.config :as cf] + [app.main.repo :as rp] [app.util.globals :as g] [app.util.http :as http] + [app.util.i18n :as i18n] [app.util.object :as obj] [app.util.storage :refer [storage]] [app.util.time :as dt] - [app.util.i18n :as i18n] [beicon.core :as rx] [lambdaisland.uri :as u] [potok.core :as ptk])) @@ -139,7 +139,7 @@ :project-id (:project-id data)}})) (defn- event->generic-action - [event name] + [_ name] {:type "action" :name name :props {}}) @@ -176,7 +176,7 @@ [_ {:keys [buffer] :as params}] (ptk/reify ::persistence ptk/EffectEvent - (effect [_ state stream] + (effect [_ state _] (let [profile-id (:profile-id state) events (into [] (take max-buffer-size) @buffer)] (when (seq events) @@ -191,7 +191,7 @@ (let [buffer (atom #queue [])] (ptk/reify ::initialize ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (->> (rx/merge (->> (rx/from-atom buffer) (rx/filter #(pos? (count %))) @@ -202,7 +202,7 @@ (rx/map #(ptk/event ::persistence {:buffer buffer})))) ptk/EffectEvent - (effect [_ state stream] + (effect [_ _ stream] (let [events (methods process-event) session (atom nil) diff --git a/frontend/src/app/main/data/fonts.cljs b/frontend/src/app/main/data/fonts.cljs index 41ca36c9ac..74872995b0 100644 --- a/frontend/src/app/main/data/fonts.cljs +++ b/frontend/src/app/main/data/fonts.cljs @@ -8,16 +8,14 @@ (:require ["opentype.js" :as ot] [app.common.data :as d] - [app.common.spec :as us] [app.common.media :as cm] + [app.common.spec :as us] [app.common.uuid :as uuid] [app.main.fonts :as fonts] [app.main.repo :as rp] - [app.util.i18n :as i18n :refer [tr]] [app.util.logging :as log] - [beicon.core :as rx] - [cljs.spec.alpha :as s] [app.util.webapi :as wa] + [beicon.core :as rx] [cuerdas.core :as str] [potok.core :as ptk])) @@ -62,7 +60,7 @@ (assoc state :dashboard-fonts (d/index-by :id fonts))) ptk/EffectEvent - (effect [_ state stream] + (effect [_ _ _] (let [fonts (->> fonts (map adapt-font-id) (group-by :font-id) @@ -73,7 +71,7 @@ [team-id] (ptk/reify ::load-team-fonts ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query :font-variants {:team-id team-id}) (rx/map fonts-fetched))))) @@ -121,7 +119,7 @@ (parse-font [{:keys [data] :as params}] (try (assoc params :font (ot/parse data)) - (catch :default e + (catch :default _e (log/warn :msg (str/fmt "skiping file %s, unsupported format" (:name params))) nil))) @@ -204,7 +202,7 @@ fonts)))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (->> (rp/mutation! :update-font {:id id :name name :team-id team-id}) (rx/ignore)))))) @@ -218,10 +216,10 @@ (update [_ state] (update state :dashboard-fonts (fn [variants] - (d/removem (fn [[id variant]] + (d/removem (fn [[_id variant]] (= (:font-id variant) font-id)) variants)))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (->> (rp/mutation! :delete-font {:id font-id :team-id team-id}) (rx/ignore)))))) @@ -238,7 +236,7 @@ (= (:id variant) id)) variants)))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (->> (rp/mutation! :delete-font-variant {:id id :team-id team-id}) (rx/ignore)))))) diff --git a/frontend/src/app/main/data/history.cljs b/frontend/src/app/main/data/history.cljs deleted file mode 100644 index 8da91d3874..0000000000 --- a/frontend/src/app/main/data/history.cljs +++ /dev/null @@ -1,270 +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.main.data.history - (:require - [beicon.core :as rx] - [cljs.spec.alpha :as s] - [potok.core :as ptk] - [app.common.spec :as us] - [app.common.pages :as cp] - [app.main.repo :as rp] - [app.util.data :refer [replace-by-id index-by]])) - -;; --- Schema - -(s/def ::pinned boolean?) -(s/def ::id uuid?) -(s/def ::label string?) -(s/def ::project uuid?) -(s/def ::created-at inst?) -(s/def ::modified-at inst?) -(s/def ::version number?) -(s/def ::user uuid?) - -(s/def ::shapes - (s/every ::cp/minimal-shape :kind vector?)) - -(s/def ::data - (s/keys :req-un [::shapes])) - -(s/def ::history-entry - (s/keys :req-un [::id - ::pinned - ::label - ::project - ::created-at - ::modified-at - ::version - ::user - ::data])) - -(s/def ::history-entries - (s/every ::history-entry)) - -;; --- Initialize History State - -(declare fetch-history) -(declare fetch-pinned-history) - -(defn initialize - [id] - (us/verify ::us/uuid id) - (ptk/reify ::initialize - ptk/UpdateEvent - (update [_ state] - (update-in state [:workspace id] - assoc :history {:selected nil - :pinned #{} - :items #{} - :byver {}})) - - ptk/WatchEvent - (watch [_ state stream] - (rx/of (fetch-history id) - (fetch-pinned-history id))))) - -;; --- Watch Page Changes - -(defn watch-page-changes - [id] - (us/verify ::us/uuid id) - (reify - ptk/WatchEvent - (watch [_ state stream] - #_(let [stopper (rx/filter #(= % ::stop-page-watcher) stream)] - (->> stream - (rx/filter dp/page-persisted?) - (rx/debounce 1000) - (rx/flat-map #(rx/of (fetch-history id) - (fetch-pinned-history id))) - (rx/take-until stopper)))))) - -;; --- Pinned Page History Fetched - -(defn pinned-history-fetched - [items] - (us/verify ::history-entries items) - (ptk/reify ::pinned-history-fetched - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace :current]) - items-map (index-by :version items) - items-set (into #{} items)] - (update-in state [:workspace pid :history] - (fn [history] - (-> history - (assoc :pinned items-set) - (update :byver merge items-map)))))))) - -;; --- Fetch Pinned Page History - -(defn fetch-pinned-history - [id] - (us/verify ::us/uuid id) - (ptk/reify ::fetch-pinned-history - ptk/WatchEvent - (watch [_ state s] - (let [params {:page id :pinned true}] - #_(->> (rp/req :fetch/page-history params) - (rx/map :payload) - (rx/map pinned-history-fetched)))))) - -;; --- Page History Fetched - -(defn history-fetched - [items] - (us/verify ::history-entries items) - (ptk/reify ::history-fetched - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace :current]) - versions (into #{} (map :version) items) - items-map (index-by :version items) - min-version (apply min versions) - max-version (apply max versions)] - (update-in state [:workspace pid :history] - (fn [history] - (-> history - (assoc :min-version min-version) - (assoc :max-version max-version) - (update :byver merge items-map) - (update :items #(reduce conj % items))))))))) - -;; --- Fetch Page History - -(defn fetch-history - ([id] - (fetch-history id nil)) - ([id {:keys [since max]}] - (us/verify ::us/uuid id) - (ptk/reify ::fetch-history - ptk/WatchEvent - (watch [_ state s] - (let [params (merge {:page id - :max (or max 20)} - (when since - {:since since}))] - #_(->> (rp/req :fetch/page-history params) - (rx/map :payload) - (rx/map history-fetched))))))) - -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Context Aware Events -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; - -;; --- Select Section - -(deftype SelectSection [section] - ptk/UpdateEvent - (update [_ state] - (assoc-in state [:workspace :history :section] section))) - -(defn select-section - [section] - {:pre [(keyword? section)]} - (SelectSection. section)) - -;; --- Load More - -(def load-more - (ptk/reify ::load-more - ptk/WatchEvent - (watch [_ state stream] - (let [pid (get-in state [:workspace :current]) - since (get-in state [:workspace pid :history :min-version])] - (rx/of (fetch-history pid {:since since})))))) - -;; --- Select Page History - -(defn select - [version] - (us/verify int? version) - (ptk/reify ::select - ptk/UpdateEvent - (update [_ state] - #_(let [pid (get-in state [:workspace :current]) - item (get-in state [:workspace pid :history :byver version]) - page (-> (get-in state [:pages pid]) - (assoc :history true - :data (:data item)))] - (-> state - (dp/unpack-page page) - (assoc-in [:workspace pid :history :selected] version)))))) - -;; --- Apply Selected History - -(def apply-selected - (ptk/reify ::apply-selected - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace :current])] - (-> state - (update-in [:pages pid] dissoc :history) - (assoc-in [:workspace pid :history :selected] nil)))) - - ptk/WatchEvent - (watch [_ state s] - #_(let [pid (get-in state [:workspace :current])] - (rx/of (dp/persist-page pid)))))) - -;; --- Deselect Page History - -(def deselect - (ptk/reify ::deselect - ptk/UpdateEvent - (update [_ state] - #_(let [pid (get-in state [:workspace :current]) - packed (get-in state [:packed-pages pid])] - (-> (dp/unpack-page state packed) - (assoc-in [:workspace pid :history :selected] nil)))))) - - ;; --- Refresh Page History - -(def refres-history - (ptk/reify ::refresh-history - ptk/WatchEvent - (watch [_ state stream] - (let [pid (get-in state [:workspace :current]) - history (get-in state [:workspace pid :history]) - maxitems (count (:items history))] - (rx/of (fetch-history pid {:max maxitems}) - (fetch-pinned-history pid)))))) - -;; --- History Item Updated - -(defn history-updated - [item] - (us/verify ::history-entry item) - (ptk/reify ::history-item-updated - ptk/UpdateEvent - (update [_ state] - (let [pid (get-in state [:workspace :current])] - (update-in state [:workspace pid :history] - (fn [history] - (-> history - (update :items #(into #{} (replace-by-id item) %)) - (update :pinned #(into #{} (replace-by-id item) %)) - (assoc-in [:byver (:version item)] item)))))))) - -(defn history-updated? - [v] - (= ::history-item-updated (ptk/type v))) - -;; --- Update History Item - -(defn update-history-item - [item] - (ptk/reify ::update-history-item - ptk/WatchEvent - (watch [_ state stream] - (rx/concat - #_(->> (rp/req :update/page-history item) - (rx/map :payload) - (rx/map history-updated)) - (->> (rx/filter history-updated? stream) - (rx/take 1) - (rx/map (constantly refres-history))))))) diff --git a/frontend/src/app/main/data/media.cljs b/frontend/src/app/main/data/media.cljs index 695807cc28..ba9ad47df4 100644 --- a/frontend/src/app/main/data/media.cljs +++ b/frontend/src/app/main/data/media.cljs @@ -6,22 +6,14 @@ (ns app.main.data.media (:require - [app.common.data :as d] - [app.common.media :as cm] - [app.common.spec :as us] - [app.common.uuid :as uuid] [app.common.exceptions :as ex] + [app.common.media :as cm] [app.main.data.messages :as dm] - [app.main.repo :as rp] [app.main.store :as st] [app.util.i18n :refer [tr]] - [app.util.router :as r] - [app.util.router :as rt] - [app.util.time :as ts] [beicon.core :as rx] [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [potok.core :as ptk])) + [cuerdas.core :as str])) ;; --- Predicates diff --git a/frontend/src/app/main/data/messages.cljs b/frontend/src/app/main/data/messages.cljs index 41b8f6db93..1a834fa3c6 100644 --- a/frontend/src/app/main/data/messages.cljs +++ b/frontend/src/app/main/data/messages.cljs @@ -7,10 +7,7 @@ (ns app.main.data.messages (:require [app.common.data :as d] - [app.common.exceptions :as ex] - [app.common.pages :as cp] [app.common.spec :as us] - [app.config :as cfg] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk])) @@ -54,7 +51,7 @@ (assoc state :message message))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (when (:timeout data) (let [stoper (rx/filter (ptk/type? ::show) stream)] (->> (rx/of hide) @@ -68,7 +65,7 @@ (d/update-when state :message assoc :status :hide)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (let [stoper (rx/filter (ptk/type? ::show) stream)] (->> (rx/of #(dissoc % :message)) (rx/delay default-animation-timeout) @@ -78,7 +75,7 @@ [tag] (ptk/reify ::hide-tag ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [message (get state :message)] (when (= (:tag message) tag) (rx/of hide)))))) @@ -127,7 +124,7 @@ :tag tag}))) (defn assign-exception - [{:keys [type] :as error}] + [error] (ptk/reify ::assign-exception ptk/UpdateEvent (update [_ state] diff --git a/frontend/src/app/main/data/modal.cljs b/frontend/src/app/main/data/modal.cljs index b77f4ec159..daf3bb1b45 100644 --- a/frontend/src/app/main/data/modal.cljs +++ b/frontend/src/app/main/data/modal.cljs @@ -7,10 +7,10 @@ (ns app.main.data.modal (:refer-clojure :exclude [update]) (:require - [potok.core :as ptk] - [app.main.store :as st] [app.common.uuid :as uuid] - [cljs.core :as c])) + [app.main.store :as st] + [cljs.core :as c] + [potok.core :as ptk])) (defonce components (atom {})) @@ -29,7 +29,7 @@ :allow-click-outside false}))))) (defn update-props - ([type props] + ([_type props] (ptk/reify ::update-modal-props ptk/UpdateEvent (update [_ state] diff --git a/frontend/src/app/main/data/shortcuts.cljs b/frontend/src/app/main/data/shortcuts.cljs index e3eb7810d0..69c9596bce 100644 --- a/frontend/src/app/main/data/shortcuts.cljs +++ b/frontend/src/app/main/data/shortcuts.cljs @@ -8,7 +8,6 @@ (:refer-clojure :exclude [meta reset!]) (:require ["mousetrap" :as mousetrap] - [app.common.data :as d] [app.common.spec :as us] [app.config :as cfg] [app.util.logging :as log] @@ -164,8 +163,8 @@ (update :shortcuts (fnil conj '()) [key shortcuts]))) ptk/EffectEvent - (effect [_ state stream] - (let [[key shortcuts] (peek (:shortcuts state))] + (effect [_ state _] + (let [[_key shortcuts] (peek (:shortcuts state))] (reset! shortcuts))))) (defn pop-shortcuts @@ -179,7 +178,7 @@ (pop shortcuts) shortcuts))))) ptk/EffectEvent - (effect [_ state stream] + (effect [_ state _] (let [[key* shortcuts] (peek (:shortcuts state))] (when (not= key key*) (reset! shortcuts)))))) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 57c4e14678..32a81d39db 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -6,24 +6,20 @@ (ns app.main.data.users (:require - [app.config :as cf] [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.media :as di] [app.main.data.modal :as modal] - [app.main.data.messages :as dm] [app.main.repo :as rp] - [app.main.store :as st] - [app.util.avatars :as avatars] - [app.util.i18n :as i18n :refer [tr]] + [app.util.i18n :as i18n] [app.util.router :as rt] [app.util.storage :refer [storage]] [app.util.theme :as theme] [beicon.core :as rx] [cljs.spec.alpha :as s] - [cuerdas.core :as str] [potok.core :as ptk])) ;; --- COMMON SPECS @@ -75,7 +71,7 @@ [] (ptk/reify ::fetch-teams ptk/WatchEvent - (watch [_ state s] + (watch [_ _ _] (->> (rp/query! :teams) (rx/map teams-fetched))))) @@ -95,7 +91,7 @@ (assoc :profile profile))) ptk/EffectEvent - (effect [_ state stream] + (effect [_ state _] (let [profile (:profile state)] (when (not= uuid/zero (:id profile)) (swap! storage assoc :profile profile) @@ -107,7 +103,7 @@ [] (ptk/reify ::fetch-profile ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query! :profile) (rx/map profile-fetched))))) @@ -120,7 +116,7 @@ [] (ptk/reify ::initialize-profile ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (rx/merge (rx/of (fetch-profile)) (->> stream @@ -141,7 +137,7 @@ (-deref [_] profile) ptk/WatchEvent - (watch [this state stream] + (watch [_ _ _] (let [team-id (get-current-team-id profile) profile (with-meta profile {::ev/source "login"})] @@ -166,7 +162,7 @@ (us/verify ::login-params data) (ptk/reify ::login ptk/WatchEvent - (watch [this state s] + (watch [_ _ _] (let [{:keys [on-error on-success] :or {on-error rx/throw on-success identity}} (meta data) @@ -186,7 +182,7 @@ [{:keys [profile] :as tdata}] (ptk/reify ::login-from-token ptk/WatchEvent - (watch [this state s] + (watch [_ _ _] (rx/of (logged-in (with-meta profile {::ev/source "login-with-token"})))))) @@ -201,11 +197,11 @@ (select-keys state [:route :router :session-id :history])) ptk/WatchEvent - (watch [_ state s] + (watch [_ _ _] (rx/of (rt/nav :auth-login))) ptk/EffectEvent - (effect [_ state s] + (effect [_ _ _] (reset! storage {}) (i18n/reset-locale)))) @@ -213,7 +209,7 @@ [] (ptk/reify ::logout ptk/WatchEvent - (watch [_ state s] + (watch [_ _ _] (->> (rp/mutation :logout) (rx/delay-at-least 300) (rx/catch (constantly (rx/of 1))) @@ -234,7 +230,7 @@ (s/assert ::register data) (ptk/reify ::register ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-error on-success] :or {on-error identity on-success identity}} (meta data)] @@ -249,7 +245,7 @@ (us/assert ::profile data) (ptk/reify ::update-profile ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (let [mdata (meta data) on-success (:on-success mdata identity) on-error (:on-error mdata #(rx/throw %))] @@ -273,7 +269,7 @@ (us/assert ::us/email email) (ptk/reify ::request-email-change ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-error on-success] :or {on-error identity on-success identity}} (meta data)] @@ -286,7 +282,7 @@ (def cancel-email-change (ptk/reify ::cancel-email-change ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/mutation :cancel-email-change {}) (rx/map (constantly (fetch-profile))))))) @@ -302,7 +298,7 @@ (us/verify ::update-password data) (ptk/reify ::update-password ptk/WatchEvent - (watch [_ state s] + (watch [_ _ _] (let [{:keys [on-error on-success] :or {on-error identity on-success identity}} (meta data) @@ -321,7 +317,7 @@ ([{:keys [version]}] (ptk/reify ::mark-oboarding-as-viewed ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [version (or version (:main @cf/version)) props (-> (get-in state [:profile :props]) (assoc :onboarding-viewed true) @@ -336,7 +332,7 @@ (us/verify ::di/blob file) (ptk/reify ::update-photo ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [on-success di/notify-finished-loading on-error #(do (di/notify-finished-loading) (di/process-error %)) @@ -364,7 +360,7 @@ (assoc state :users)))] (ptk/reify ::fetch-team-users ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query :team-users {:team-id team-id}) (rx/map #(partial fetched %))))))) @@ -374,7 +370,7 @@ [params] (ptk/reify ::request-account-deletion ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-error on-success] :or {on-error rx/throw on-success identity}} (meta params)] @@ -395,7 +391,7 @@ (us/verify ::request-profile-recovery data) (ptk/reify ::request-profile-recovery ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-error on-success] :or {on-error rx/throw on-success identity}} (meta data)] @@ -411,19 +407,17 @@ (s/keys :req-un [::password ::token])) (defn recover-profile - [{:keys [token password] :as data}] + [data] (us/verify ::recover-profile data) (ptk/reify ::recover-profile ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [{:keys [on-error on-success] :or {on-error rx/throw on-success identity}} (meta data)] (->> (rp/mutation :recover-profile data) (rx/tap on-success) - (rx/catch (fn [err] - (on-error) - (rx/empty)))))))) + (rx/catch on-error)))))) ;; --- EVENT: crete-demo-profile @@ -431,7 +425,7 @@ [] (ptk/reify ::create-demo-profile ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/mutation :create-demo-profile {}) (rx/map login))))) diff --git a/frontend/src/app/main/data/viewer.cljs b/frontend/src/app/main/data/viewer.cljs index c4eb652924..c6dc46a37d 100644 --- a/frontend/src/app/main/data/viewer.cljs +++ b/frontend/src/app/main/data/viewer.cljs @@ -7,7 +7,6 @@ (ns app.main.data.viewer (:require [app.common.data :as d] - [app.common.exceptions :as ex] [app.common.pages :as cp] [app.common.spec :as us] [app.common.uuid :as uuid] @@ -15,8 +14,6 @@ [app.main.data.comments :as dcm] [app.main.data.fonts :as df] [app.main.repo :as rp] - [app.main.store :as st] - [app.util.avatars :as avatars] [app.util.router :as rt] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -27,7 +24,7 @@ (s/def ::id ::us/uuid) (s/def ::name ::us/string) -(s/def ::project (s/keys ::req-un [::id ::name])) +(s/def ::project (s/keys :req-un [::id ::name])) (s/def ::file (s/keys :req-un [::id ::name])) (s/def ::page ::cp/page) @@ -60,10 +57,10 @@ (s/def ::initialize-params (s/keys :req-un [::page-id ::file-id] - :opt-in [::token])) + :opt-un [::token])) (defn initialize - [{:keys [page-id file-id token] :as params}] + [{:keys [page-id file-id] :as params}] (us/assert ::initialize-params params) (ptk/reify ::initialize ptk/UpdateEvent @@ -78,7 +75,7 @@ lstate))))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (rx/of (fetch-bundle params) (fetch-comment-threads params))))) @@ -86,14 +83,14 @@ (s/def ::fetch-bundle-params (s/keys :req-un [::page-id ::file-id] - :opt-in [::token])) + :opt-un [::token])) (defn fetch-bundle [{:keys [page-id file-id token] :as params}] (us/assert ::fetch-bundle-params params) (ptk/reify ::fetch-file ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [params (cond-> {:page-id page-id :file-id file-id} (string? token) (assoc :token token))] @@ -145,7 +142,7 @@ (ptk/reify ::fetch-comment-threads ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query :comment-threads {:file-id file-id}) (rx/map #(partial fetched %)) (rx/catch on-error)))))) @@ -156,7 +153,7 @@ (assoc-in state [:comment-threads id] thread))] (ptk/reify ::refresh-comment-thread ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query :comment-thread {:file-id file-id :id id}) (rx/map #(partial fetched %))))))) @@ -167,7 +164,7 @@ (update state :comments assoc thread-id (d/index-by :id comments)))] (ptk/reify ::retrieve-comments ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/query :comments {:thread-id thread-id}) (rx/map #(partial fetched %))))))) @@ -175,7 +172,7 @@ [] (ptk/reify ::create-share-link ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [file-id (:current-file-id state) page-id (:current-page-id state)] (->> (rp/mutation! :create-file-share-token {:file-id file-id @@ -187,7 +184,7 @@ [] (ptk/reify ::delete-share-link ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [file-id (:current-file-id state) page-id (:current-page-id state) token (get-in state [:viewer-data :token]) @@ -246,7 +243,7 @@ (def select-prev-frame (ptk/reify ::select-prev-frame ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [route (:route state) screen (-> route :data :name keyword) qparams (:query-params route) @@ -260,7 +257,7 @@ (def select-next-frame (ptk/reify ::select-prev-frame ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [route (:route state) screen (-> route :data :name keyword) qparams (:query-params route) @@ -296,7 +293,7 @@ (assoc-in state [:viewer-local :interactions-show?] true)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (let [stopper (rx/filter (ptk/type? ::flash-interactions) stream)] (->> (rx/of flash-done) (rx/delay 500) @@ -314,7 +311,7 @@ [index] (ptk/reify ::go-to-frame ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [route (:route state) screen (-> route :data :name keyword) qparams (:query-params route) @@ -326,7 +323,7 @@ (us/verify ::us/uuid frame-id) (ptk/reify ::go-to-frame ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [frames (get-in state [:viewer-data :frames]) index (d/index-of-pred frames #(= (:id %) frame-id))] (when index @@ -337,13 +334,11 @@ [section] (ptk/reify ::go-to-section ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [route (:route state) - screen (-> route :data :name keyword) pparams (:path-params route) qparams (:query-params route)] - (rx/of - (rt/nav :viewer pparams (assoc qparams :section section))))))) + (rx/of (rt/nav :viewer pparams (assoc qparams :section section))))))) (defn set-current-frame [frame-id] @@ -422,6 +417,6 @@ ([{:keys [team-id]}] (ptk/reify ::go-to-dashboard ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [team-id (or team-id (get-in state [:viewer-data :project :team-id]))] (rx/of (rt/nav :dashboard-projects {:team-id team-id}))))))) diff --git a/frontend/src/app/main/data/viewer/shortcuts.cljs b/frontend/src/app/main/data/viewer/shortcuts.cljs index 91ea3b3008..86f1ad8800 100644 --- a/frontend/src/app/main/data/viewer/shortcuts.cljs +++ b/frontend/src/app/main/data/viewer/shortcuts.cljs @@ -6,15 +6,9 @@ (ns app.main.data.viewer.shortcuts (:require - [app.config :as cfg] - [app.main.data.workspace.colors :as mdc] [app.main.data.shortcuts :as ds] - [app.main.data.shortcuts :refer [c-mod]] [app.main.data.viewer :as dv] - [app.main.store :as st] - [app.util.dom :as dom] - [beicon.core :as rx] - [potok.core :as ptk])) + [app.main.store :as st])) (def shortcuts {:increase-zoom {:tooltip "+" diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 618b656a12..aca9860a61 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -38,7 +38,6 @@ [app.main.worker :as uw] [app.util.http :as http] [app.util.i18n :as i18n] - [app.util.logging :as log] [app.util.router :as rt] [app.util.webapi :as wapi] [beicon.core :as rx] @@ -134,7 +133,7 @@ (or layout default-layout)))) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (if (and layout-name (contains? layout-names layout-name)) (rx/of (ensure-layout layout-name)) (rx/of (ensure-layout :layers)))))) @@ -153,7 +152,7 @@ :workspace-presence {})) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ stream] (rx/merge (rx/of (dwp/fetch-bundle project-id file-id)) @@ -162,7 +161,7 @@ (rx/filter (ptk/type? ::dwp/bundle-fetched)) (rx/take 1) (rx/map deref) - (rx/mapcat (fn [{:keys [project] :as bundle}] + (rx/mapcat (fn [bundle] (rx/merge (rx/of (dwn/initialize file-id) (dwp/initialize-file-persistence file-id) @@ -188,7 +187,7 @@ :workspace-libraries (d/index-by :id libraries))) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (let [file-id (:id file) ignore-until (:ignore-sync-until file) needs-update? (some #(and (> (:modified-at %) (:synced-at %)) @@ -199,7 +198,7 @@ (rx/of (dwl/notify-sync-file file-id))))))) (defn finalize-file - [project-id file-id] + [_project-id file-id] (ptk/reify ::finalize ptk/UpdateEvent (update [_ state] @@ -210,7 +209,7 @@ :workspace-persistence)) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (rx/of (dwn/finalize file-id) ::dwp/finalize)))) @@ -262,7 +261,7 @@ {:id id :file-id file-id}) ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [pages (get-in state [:workspace-data :pages-index]) unames (dwc/retrieve-used-names pages) name (dwc/generate-unique-name unames "Page") @@ -280,7 +279,7 @@ [page-id] (ptk/reify ::duplicate-page ptk/WatchEvent - (watch [this state stream] + (watch [this state _] (let [id (uuid/next) pages (get-in state [:workspace-data :pages-index]) unames (dwc/retrieve-used-names pages) @@ -306,7 +305,7 @@ (us/verify string? name) (ptk/reify ::rename-page ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page (get-in state [:workspace-data :pages-index id]) rchg {:type :mod-page :id id @@ -327,7 +326,7 @@ [id] (ptk/reify ::delete-page ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page (get-in state [:workspace-data :pages-index id]) rchg {:type :del-page :id id} @@ -353,7 +352,7 @@ (assoc-in state [:workspace-file :name] name)) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (let [params {:id id :name name}] (->> (rp/mutation :rename-file params) (rx/ignore)))))) @@ -368,7 +367,7 @@ (defn initialize-viewport [{:keys [width height] :as size}] - (letfn [(update* [{:keys [vbox vport] :as local}] + (letfn [(update* [{:keys [vport] :as local}] (let [wprop (/ (:width vport) width) hprop (/ (:height vport) height)] (-> local @@ -433,7 +432,7 @@ ptk/UpdateEvent (update [_ state] (update state :workspace-local - (fn [{:keys [vbox vport left-sidebar? zoom] :as local}] + (fn [{:keys [vport left-sidebar? zoom] :as local}] (if (or (mth/almost-zero? width) (mth/almost-zero? height)) ;; If we have a resize to zero just keep the old value local @@ -452,7 +451,7 @@ (defn start-panning [] (ptk/reify ::start-panning ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [stopper (->> stream (rx/filter (ptk/type? ::finish-panning))) zoom (-> (get-in state [:workspace-local :zoom]) gpt/point)] (when-not (get-in state [:workspace-local :panning]) @@ -580,7 +579,7 @@ (mth/nan? (:height srect))) state (update state :workspace-local - (fn [{:keys [vbox vport] :as local}] + (fn [{:keys [vport] :as local}] (let [srect (gal/adjust-to-viewport vport srect {:padding 40}) zoom (/ (:width vport) (:width srect))] (-> local @@ -600,7 +599,7 @@ (map #(get objects %)) (gsh/selection-rect))] (update state :workspace-local - (fn [{:keys [vbox vport] :as local}] + (fn [{:keys [vport] :as local}] (let [srect (gal/adjust-to-viewport vport srect {:padding 40}) zoom (/ (:width vport) (:width srect))] (-> local @@ -615,7 +614,7 @@ (us/verify ::shape-attrs attrs) (ptk/reify ::update-shape ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (rx/of (dch/update-shapes [id] #(merge % attrs)))))) (defn start-rename-shape @@ -631,7 +630,7 @@ (ptk/reify ::end-rename-shape ptk/UpdateEvent (update [_ state] - (update-in state [:workspace-local] dissoc :shape-for-rename)))) + (update state :workspace-local dissoc :shape-for-rename)))) ;; --- Update Selected Shapes attrs @@ -640,45 +639,17 @@ (us/verify ::shape-attrs attrs) (ptk/reify ::update-selected-shapes ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [selected (wsh/lookup-selected state)] (rx/from (map #(update-shape % attrs) selected)))))) -;; --- Shape Movement (using keyboard shorcuts) - -(declare initial-selection-align) - -(defn- get-displacement-with-grid - "Retrieve the correct displacement delta point for the - provided direction speed and distances thresholds." - [shape direction options] - (let [grid-x (:grid-x options 10) - grid-y (:grid-y options 10) - x-mod (mod (:x shape) grid-x) - y-mod (mod (:y shape) grid-y)] - (case direction - :up (gpt/point 0 (- (if (zero? y-mod) grid-y y-mod))) - :down (gpt/point 0 (- grid-y y-mod)) - :left (gpt/point (- (if (zero? x-mod) grid-x x-mod)) 0) - :right (gpt/point (- grid-x x-mod) 0)))) - -(defn- get-displacement - "Retrieve the correct displacement delta point for the - provided direction speed and distances thresholds." - [shape direction] - (case direction - :up (gpt/point 0 (- 1)) - :down (gpt/point 0 1) - :left (gpt/point (- 1) 0) - :right (gpt/point 1 0))) - ;; --- Delete Selected (def delete-selected "Deselect all and remove all selected shapes." (ptk/reify ::delete-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [selected (wsh/lookup-selected state)] (rx/of (dwc/delete-shapes selected) (dws/deselect-all)))))) @@ -692,7 +663,7 @@ (us/verify ::loc loc) (ptk/reify ::vertical-order-selected ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state) @@ -901,7 +872,7 @@ (ptk/reify ::relocate-shapes ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -1012,7 +983,7 @@ [parent-id to-index] (ptk/reify ::relocate-selected-shapes ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [selected (wsh/lookup-selected state)] (rx/of (relocate-shapes selected parent-id to-index)))))) @@ -1021,7 +992,7 @@ [] (ptk/reify ::start-editing-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [selected (wsh/lookup-selected state)] (if-not (= 1 (count selected)) (rx/empty) @@ -1049,7 +1020,7 @@ [id index] (ptk/reify ::relocate-pages ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [cidx (-> (get-in state [:workspace-data :pages]) (d/index-of id)) rchg {:type :mov-page @@ -1072,7 +1043,7 @@ (us/verify ::gal/align-axis axis) (ptk/reify :align-objects ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state) @@ -1103,7 +1074,7 @@ (us/verify ::gal/dist-axis axis) (ptk/reify :align-objects ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state) @@ -1121,7 +1092,7 @@ [id lock] (ptk/reify ::set-shape-proportion-lock ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (letfn [(assign-proportions [shape] (if-not lock (assoc shape :proportion-lock false) @@ -1142,7 +1113,7 @@ (us/verify ::position position) (ptk/reify ::update-position ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) shape (get objects id) @@ -1164,7 +1135,7 @@ (s/assert ::shape-attrs flags) (ptk/reify ::update-shape-flags ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [update-fn (fn [obj] (cond-> obj @@ -1184,7 +1155,7 @@ [project-id] (ptk/reify ::navigate-to-project ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [page-ids (get-in state [:projects project-id :pages]) params {:project project-id :page (first page-ids)}] (rx/of (rt/nav :workspace/page params)))))) @@ -1193,7 +1164,7 @@ ([] (ptk/reify ::go-to-page ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [project-id (:current-project-id state) file-id (:current-file-id state) page-id (get-in state [:workspace-data :pages 0]) @@ -1205,7 +1176,7 @@ (us/verify ::us/uuid page-id) (ptk/reify ::go-to-page ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [project-id (:current-project-id state) file-id (:current-file-id state) pparams {:file-id file-id :project-id project-id} @@ -1217,10 +1188,10 @@ (us/verify ::layout-flag layout) (ptk/reify ::go-to-layout ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [project-id (get-in state [:workspace-project :id]) file-id (get-in state [:workspace-file :id]) - page-id (get-in state [:current-page-id]) + page-id (get state :current-page-id) pparams {:file-id file-id :project-id project-id} qparams {:page-id page-id :layout (name layout)}] (rx/of (rt/nav :workspace pparams qparams)))))) @@ -1228,7 +1199,7 @@ (def go-to-file (ptk/reify ::go-to-file ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [{:keys [id project-id data] :as file} (:workspace-file state) page-id (get-in data [:pages 0]) pparams {:project-id project-id :file-id id} @@ -1240,7 +1211,7 @@ ([{:keys [file-id page-id]}] (ptk/reify ::go-to-viewer ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [{:keys [current-file-id current-page-id]} state params {:file-id (or file-id current-file-id) :page-id (or page-id current-page-id)}] @@ -1252,7 +1223,7 @@ ([{:keys [team-id]}] (ptk/reify ::go-to-dashboard ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (when-let [team-id (or team-id (:current-team-id state))] (rx/of ::dwp/force-persist (rt/nav :dashboard-projects {:team-id team-id}))))))) @@ -1261,7 +1232,7 @@ [] (ptk/reify ::go-to-dashboard ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [team-id (:current-team-id state)] (rx/of ::dwp/force-persist (rt/nav :dashboard-fonts {:team-id team-id})))))) @@ -1291,7 +1262,7 @@ (us/verify ::cp/minimal-shape shape) (ptk/reify ::show-shape-context-menu ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [selected (wsh/lookup-selected state)] (rx/concat (when-not (selected (:id shape)) @@ -1383,7 +1354,7 @@ (ptk/reify ::copy-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [objects (wsh/lookup-page-objects state) selected (->> (wsh/lookup-selected state) (cp/clean-loops objects)) @@ -1410,7 +1381,7 @@ (def paste (ptk/reify ::paste ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (try (let [clipboard-str (wapi/read-from-clipboard) @@ -1449,7 +1420,7 @@ [event in-viewport?] (ptk/reify ::paste-from-event ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (try (let [objects (wsh/lookup-page-objects state) paste-data (wapi/read-from-paste-event event) @@ -1487,8 +1458,8 @@ (defn selected-frame? [state] (let [selected (wsh/lookup-selected state) objects (wsh/lookup-page-objects state)] - (and (and (= 1 (count selected)) - (= :frame (get-in objects [(first selected) :type])))))) + (and (= 1 (count selected)) + (= :frame (get-in objects [(first selected) :type]))))) (defn- paste-shape [{:keys [selected objects images] :as data} in-viewport?] @@ -1569,7 +1540,6 @@ ;; Procceed with the standard shape paste procediment. (do-paste [it state mouse-pos media] (let [media-idx (d/index-by :prev-id media) - page-id (:current-page-id state) ;; Calculate position for the pasted elements [frame-id parent-id delta index] (calculate-paste-position state mouse-pos in-viewport?) @@ -1619,7 +1589,7 @@ (dwc/select-shapes selected))))] (ptk/reify ::paste-shape ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [file-id (:current-file-id state) mouse-pos (deref ms/mouse-position)] (if (= file-id (:file-id data)) @@ -1643,7 +1613,7 @@ (s/assert string? text) (ptk/reify ::paste-text ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [id (uuid/next) {:keys [x y]} @ms/mouse-position width (max 8 (min (* 7 (count text)) 700)) @@ -1672,7 +1642,7 @@ (s/assert string? text) (ptk/reify ::paste-svg ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [position (deref ms/mouse-position) file-id (:current-file-id state)] (->> (dwp/parse-svg ["svg" text]) @@ -1682,7 +1652,7 @@ [image] (ptk/reify ::paste-bin-impl ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [file-id (get-in state [:workspace-file :id]) params {:file-id file-id :blobs [image] @@ -1707,7 +1677,7 @@ [] (ptk/reify ::start-create-interaction ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [initial-pos @ms/mouse-position selected (wsh/lookup-selected state) stopper (rx/filter ms/mouse-up? stream)] @@ -1744,7 +1714,7 @@ (assoc-in [:workspace-local :draw-interaction-to-frame] nil))) ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [position @ms/mouse-position page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -1772,7 +1742,7 @@ [color] (ptk/reify ::change-canvas-color ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (get state :current-page-id) options (wsh/lookup-page-options state page-id) previus-color (:background options)] @@ -1787,7 +1757,6 @@ :value previus-color}] :origin it})))))) - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Exports ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 2f668f7e15..59af222a08 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -255,7 +255,7 @@ (declare fetch-libraries-content) (declare bundle-fetched) -(defn- fetch-bundle +(defn fetch-bundle [project-id file-id] (ptk/reify ::fetch-bundle ptk/WatchEvent diff --git a/frontend/src/app/main/ui/workspace/sidebar/history.cljs b/frontend/src/app/main/ui/workspace/sidebar/history.cljs index f188172a23..4e44f0e319 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/history.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/history.cljs @@ -10,7 +10,6 @@ [cuerdas.core :as str] [app.common.data :as d] [app.main.ui.icons :as i] - [app.main.data.history :as udh] [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] diff --git a/frontend/src/app/util/avatars.cljs b/frontend/src/app/util/avatars.cljs index ff5afc9e16..acca7decaa 100644 --- a/frontend/src/app/util/avatars.cljs +++ b/frontend/src/app/util/avatars.cljs @@ -6,9 +6,8 @@ (ns app.util.avatars (:require - [cuerdas.core :as str] [app.util.object :as obj] - ["randomcolor" :as rdcolor])) + [cuerdas.core :as str])) (defn generate* [{:keys [name color size] diff --git a/frontend/src/app/util/code_gen.cljs b/frontend/src/app/util/code_gen.cljs index ae07021839..a4069406c3 100644 --- a/frontend/src/app/util/code_gen.cljs +++ b/frontend/src/app/util/code_gen.cljs @@ -33,7 +33,7 @@ color {:color (:stroke-color shape) :opacity (:stroke-opacity shape) :gradient (:stroke-color-gradient shape)}] - (if-not (= :none (:stroke-style shape)) + (when-not (= :none (:stroke-style shape)) (str/format "%spx %s %s" width style (uc/color->background color))))) (def styles-data diff --git a/frontend/src/app/util/color.cljs b/frontend/src/app/util/color.cljs index 8899e6f23b..3c540dc3e8 100644 --- a/frontend/src/app/util/color.cljs +++ b/frontend/src/app/util/color.cljs @@ -7,9 +7,8 @@ (ns app.util.color "Color conversion utils." (:require - [cuerdas.core :as str] - [app.common.math :as math] [app.util.object :as obj] + [cuerdas.core :as str] [goog.color :as gcolor])) (defn rgb->str @@ -31,7 +30,7 @@ [v] (try (into [] (gcolor/hexToRgb v)) - (catch :default e [0 0 0]))) + (catch :default _e [0 0 0]))) (defn rgb->hex [[r g b]] @@ -49,7 +48,7 @@ (defn hex->hsl [hex] (try (into [] (gcolor/hexToHsl hex)) - (catch :default e [0 0 0]))) + (catch :default _e [0 0 0]))) (defn hex->hsla [^string data ^number opacity] @@ -96,7 +95,7 @@ c (nth v 2)] (str a a b b c c)) - :default + :else v)) (defn prepend-hash @@ -135,7 +134,7 @@ :else "transparent"))) -(defn multiple? [{:keys [id file-id value color gradient opacity]}] +(defn multiple? [{:keys [id file-id value color gradient]}] (or (= value :multiple) (= color :multiple) (= gradient :multiple) @@ -144,7 +143,7 @@ (defn color? [^string color-str] (and (not (nil? color-str)) - (not (empty? color-str)) + (seq color-str) (gcolor/isValidColor color-str))) (defn parse-color [^string color-str] diff --git a/frontend/src/app/util/data.cljs b/frontend/src/app/util/data.cljs index c070e31c66..fb0007bb35 100644 --- a/frontend/src/app/util/data.cljs +++ b/frontend/src/app/util/data.cljs @@ -39,7 +39,7 @@ (reduce #(dissoc! %1 %2) (transient data) keys))) (defn dissoc-in - [m [k & ks :as keys]] + [m [k & ks :as _keys]] (if ks (if-let [nextmap (get m k)] (let [newmap (dissoc-in nextmap ks)] diff --git a/frontend/src/app/util/debug.cljs b/frontend/src/app/util/debug.cljs index cfffaf3f33..c3ee88d9f9 100644 --- a/frontend/src/app/util/debug.cljs +++ b/frontend/src/app/util/debug.cljs @@ -1,9 +1,9 @@ (ns app.util.debug "Debugging utils" (:require - [app.util.timers :as timers] - [app.util.object :as obj] [app.common.math :as mth] + [app.util.object :as obj] + [app.util.timers :as timers] [cljs.pprint :refer [pprint]])) (def debug-options #{:bounding-boxes :group :events :rotation-handler :resize-handler :selection-center :export :import #_:simple-selection}) diff --git a/frontend/src/app/util/dom/dnd.cljs b/frontend/src/app/util/dom/dnd.cljs index 3f681b99b4..03bb612008 100644 --- a/frontend/src/app/util/dom/dnd.cljs +++ b/frontend/src/app/util/dom/dnd.cljs @@ -7,9 +7,8 @@ (ns app.util.dom.dnd "Drag & Drop interop helpers." (:require - [app.common.transit :as t] - [app.util.data :refer (read-string)] - [cuerdas.core :as str])) + [app.common.transit :as t] + [cuerdas.core :as str])) ;; This is the official documentation for the dnd API: ;; https://developer.mozilla.org/en-US/docs/Web/API/HTML_Drag_and_Drop_API @@ -40,7 +39,7 @@ (defn trace ;; This function is useful to debug the dnd interface behaviour when something weird occurs. [event data label] - (let [currentTarget (.-currentTarget event) + (let [;;currentTarget (.-currentTarget event) relatedTarget (.-relatedTarget event)] (js/console.log label diff --git a/frontend/src/app/util/forms.cljs b/frontend/src/app/util/forms.cljs index 9db9ca89ad..d2b5d5ce99 100644 --- a/frontend/src/app/util/forms.cljs +++ b/frontend/src/app/util/forms.cljs @@ -10,17 +10,14 @@ [app.common.spec :as us] [app.util.dom :as dom] [app.util.i18n :refer [tr]] - [app.util.timers :as tm] - [beicon.core :as rx] [cljs.spec.alpha :as s] [cuerdas.core :as str] - [potok.core :as ptk] [rumext.alpha :as mf])) ;; --- Handlers Helpers (defn- interpret-problem - [acc {:keys [path pred val via in] :as problem}] + [acc {:keys [path pred via] :as problem}] (cond (and (empty? path) (list? pred) @@ -28,8 +25,8 @@ (let [path (conj path (last (last pred)))] (assoc-in acc path {:code ::missing :type :builtin})) - (and (not (empty? path)) - (not (empty? via))) + (and (seq path) + (seq via)) (assoc-in acc path {:code (last via) :type :builtin}) :else acc)) @@ -37,7 +34,7 @@ (declare create-form-mutator) (defn use-form - [& {:keys [spec validators initial] :as opts}] + [& {:keys [initial] :as opts}] (let [state (mf/useState 0) render (aget state 1) state-ref (mf/use-ref {:data (if (fn? initial) (initial) initial) @@ -126,9 +123,8 @@ (defn on-input-blur [form field] - (fn [event] - (let [target (dom/get-target event) - touched (get @form :touched)] + (fn [_] + (let [touched (get @form :touched)] (when-not (get touched field) (swap! form assoc-in [:touched field] true))))) @@ -136,9 +132,8 @@ (mf/defc field-error [{:keys [form field type] - :or {only (constantly true)} :as props}] - (let [{:keys [code message] :as error} (get-in form [:errors field]) + (let [{:keys [message] :as error} (get-in form [:errors field]) touched? (get-in form [:touched field]) show? (and touched? error message (cond diff --git a/frontend/src/app/util/geom/grid.cljs b/frontend/src/app/util/geom/grid.cljs index bd8f58bd19..97496d18f6 100644 --- a/frontend/src/app/util/geom/grid.cljs +++ b/frontend/src/app/util/geom/grid.cljs @@ -6,8 +6,8 @@ (ns app.util.geom.grid (:require - [app.common.math :as mth] - [app.common.geom.point :as gpt])) + [app.common.geom.point :as gpt] + [app.common.math :as mth])) (def ^:private default-items 12) @@ -35,7 +35,7 @@ margin) gutter (if (= :stretch type) (/ (- width (* item-width size) (* margin 2)) (dec size)) gutter) next-x (fn [cur-val] (+ initial-offset x (* (+ item-width gutter) cur-val))) - next-y (fn [cur-val] y)] + next-y (fn [_] y)] [size item-width item-height next-x next-y])) (defn- calculate-row-grid @@ -49,7 +49,7 @@ :center (/ (- height (* item-height size) (* gutter (dec size))) 2) margin) gutter (if (= :stretch type) (/ (- height (* item-height size) (* margin 2)) (dec size)) gutter) - next-x (fn [cur-val] x) + next-x (fn [_] x) next-y (fn [cur-val] (+ initial-offset y (* (+ item-height gutter) cur-val)))] [size item-width item-height next-x next-y])) @@ -91,7 +91,7 @@ ([shape coord] (mapcat #(grid-snap-points shape % coord) (:grids shape))) - ([shape {:keys [type display params] :as grid} coord] + ([shape {:keys [type params] :as grid} coord] (when (:display grid) (case type :square diff --git a/frontend/src/app/util/geom/snap_points.cljs b/frontend/src/app/util/geom/snap_points.cljs index b9525be109..669a291130 100644 --- a/frontend/src/app/util/geom/snap_points.cljs +++ b/frontend/src/app/util/geom/snap_points.cljs @@ -6,10 +6,8 @@ (ns app.util.geom.snap-points (:require - [cljs.spec.alpha :as s] - [clojure.set :as set] - [app.common.geom.shapes :as gsh] - [app.common.geom.point :as gpt])) + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh])) (defn- selrect-snap-points [{:keys [x y width height]}] #{(gpt/point x y) diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index b1213bd1c0..8c613bb33c 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -13,7 +13,6 @@ [app.config :as cfg] [app.util.cache :as c] [app.util.globals :as globals] - [app.util.object :as obj] [app.util.time :as dt] [app.util.webapi :as wapi] [beicon.core :as rx] @@ -55,8 +54,8 @@ {"x-frontend-version" (:full @cfg/version)}) (defn fetch - [{:keys [method uri query headers body timeout mode omit-default-headers] - :or {timeout 10000 mode :cors headers {}}}] + [{:keys [method uri query headers body mode omit-default-headers] + :or {mode :cors headers {}}}] (rx/Observable.create (fn [subscriber] (let [controller (js/AbortController.) @@ -134,7 +133,7 @@ (assoc headers "content-type" "application/transit+json")))) (defn conditional-decode-transit - [{:keys [body headers status] :as response}] + [{:keys [body headers] :as response}] (let [contentype (get headers "content-type")] (if (and (str/starts-with? contentype "application/transit+json") (pos? (count body))) diff --git a/frontend/src/app/util/i18n.cljs b/frontend/src/app/util/i18n.cljs index 870467abdd..1eaf407e32 100644 --- a/frontend/src/app/util/i18n.cljs +++ b/frontend/src/app/util/i18n.cljs @@ -11,7 +11,6 @@ [app.util.globals :as globals] [app.util.object :as obj] [app.util.storage :refer [storage]] - [beicon.core :as rx] [cuerdas.core :as str] [goog.object :as gobj] [okulary.core :as l] @@ -91,7 +90,7 @@ (deftype C [val] IDeref - (-deref [o] val)) + (-deref [_] val)) (defn ^boolean c? [r] diff --git a/frontend/src/app/util/logging.clj b/frontend/src/app/util/logging.clj index 421a9d16e4..f888f1bc8a 100644 --- a/frontend/src/app/util/logging.clj +++ b/frontend/src/app/util/logging.clj @@ -6,9 +6,10 @@ (ns app.util.logging) -(defn- log-expr [form level keyvals] +(defn- log-expr [_form level keyvals] (let [keyvals-map (apply array-map keyvals) - formatter (::formatter keyvals-map 'identity)] + ;;formatter (::formatter keyvals-map 'identity) + ] `(log ~(::logger keyvals-map (str *ns*)) ~level ~(-> keyvals-map diff --git a/frontend/src/app/util/logging.cljs b/frontend/src/app/util/logging.cljs index 559c911b00..b2bfaaff99 100644 --- a/frontend/src/app/util/logging.cljs +++ b/frontend/src/app/util/logging.cljs @@ -11,11 +11,9 @@ (ns app.util.logging (:require - [goog.log :as glog] - [goog.debug.Console :as Console] + [app.common.exceptions :as ex] [cuerdas.core :as str] - [goog.object :as gobj]) - (:import [goog.debug Console]) + [goog.log :as glog]) (:require-macros [app.util.logging])) (defn- logger-name @@ -158,7 +156,7 @@ specials)))))) (defn default-handler - [{:keys [message exception level logger-name]}] + [{:keys [message level logger-name]}] (let [header-styles (str "font-weight: 600; color: " (level->color level)) normal-styles (str "font-weight: 300; color: " (get colors :gray6)) level-name (level->short-name level) @@ -174,7 +172,7 @@ (doseq [[type n v] specials] (case type :js (js/console.log n v) - :error (if (instance? cljs.core.ExceptionInfo v) + :error (if (ex/ex-info? v) (js/console.error (pr-str v)) (js/console.error v)))) (js/console.groupEnd message)) diff --git a/frontend/src/app/util/object.cljs b/frontend/src/app/util/object.cljs index 12aabcb609..0abdd9c90d 100644 --- a/frontend/src/app/util/object.cljs +++ b/frontend/src/app/util/object.cljs @@ -8,9 +8,8 @@ "A collection of helpers for work with javascript objects." (:refer-clojure :exclude [set! get get-in merge clone contains?]) (:require - [cuerdas.core :as str] - [goog.object :as gobj] - ["lodash/omit" :as omit])) + ["lodash/omit" :as omit] + [cuerdas.core :as str])) (defn new [] #js {}) diff --git a/frontend/src/app/util/path/commands.cljs b/frontend/src/app/util/path/commands.cljs index f284a457c5..84a7725ef8 100644 --- a/frontend/src/app/util/path/commands.cljs +++ b/frontend/src/app/util/path/commands.cljs @@ -7,12 +7,7 @@ (ns app.util.path.commands (:require [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.common.geom.shapes.path :as gshp] - [app.util.svg :as usvg] - [cuerdas.core :as str] - [clojure.set :as set] - [app.common.math :as mth])) + [app.common.geom.point :as gpt])) (defn command->point ([prev-pos {:keys [relative params] :as command}] @@ -179,7 +174,7 @@ "Returns the commands involving a point with its indices" [content point] (->> (d/enumerate content) - (filterv (fn [[idx cmd]] (= (command->point cmd) point))))) + (filterv (fn [[_ cmd]] (= (command->point cmd) point))))) (defn prefix->coords [prefix] @@ -192,7 +187,7 @@ (when (and (some? index) (some? prefix) (contains? content index)) - (let [[cx cy :as coords] (prefix->coords prefix)] + (let [[cx cy] (prefix->coords prefix)] (if (= :curve-to (get-in content [index :command])) (gpt/point (get-in content [index :params cx]) (get-in content [index :params cy])) diff --git a/frontend/src/app/util/path/format.cljs b/frontend/src/app/util/path/format.cljs index a79a5a7e50..4b0640f4eb 100644 --- a/frontend/src/app/util/path/format.cljs +++ b/frontend/src/app/util/path/format.cljs @@ -6,10 +6,8 @@ (ns app.util.path.format (:require - [app.common.data :as d] [app.util.path.commands :as upc] - [cuerdas.core :as str] - [app.util.path.subpaths :as ups])) + [cuerdas.core :as str])) (defn command->param-list [command] (let [params (:params command)] @@ -47,7 +45,7 @@ (:x params) "," (:y params))))) -(defn command->string [{:keys [command relative params] :as entry}] +(defn command->string [{:keys [command relative] :as entry}] (let [command-str (case command :move-to "M" :close-path "Z" diff --git a/frontend/src/app/util/path/geom.cljs b/frontend/src/app/util/path/geom.cljs index af99972ee2..0478fff8c7 100644 --- a/frontend/src/app/util/path/geom.cljs +++ b/frontend/src/app/util/path/geom.cljs @@ -6,13 +6,8 @@ (ns app.util.path.geom (:require - [app.common.data :as d] [app.common.geom.point :as gpt] [app.common.geom.shapes.path :as gshp] - [app.util.svg :as usvg] - [cuerdas.core :as str] - [clojure.set :as set] - [app.common.math :as mth] [app.util.path.commands :as upc])) (defn calculate-opposite-handler diff --git a/frontend/src/app/util/path/parser.cljs b/frontend/src/app/util/path/parser.cljs index fc23adc619..7b68caf646 100644 --- a/frontend/src/app/util/path/parser.cljs +++ b/frontend/src/app/util/path/parser.cljs @@ -8,13 +8,10 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.common.geom.shapes.path :as gshp] - [app.common.math :as mth] [app.util.path.arc-to-curve :refer [a2c]] [app.util.path.commands :as upc] [app.util.path.geom :as upg] [app.util.svg :as usvg] - [clojure.set :as set] [cuerdas.core :as str])) ;; @@ -52,7 +49,7 @@ current remain)) (cond-> result - (not (empty? current)) (conj current)))))) + (seq current) (conj current)))))) ;; Path specification ;; https://www.w3.org/TR/SVG11/paths.html @@ -72,7 +69,7 @@ :relative relative :params params})))) -(defmethod parse-command "Z" [cmd] +(defmethod parse-command "Z" [_] [{:command :close-path}]) (defmethod parse-command "L" [cmd] @@ -204,7 +201,7 @@ ;; prev-start : previous move-to necesary for Z commands ;; prev-cc : previous command control point for cubic beziers ;; prev-qc : previous command control point for quadratic curves - (fn [[result prev-pos prev-start prev-cc prev-qc] [command prev]] + (fn [[result prev-pos prev-start prev-cc prev-qc] [command _prev]] (let [command (assoc command :prev-pos prev-pos) command diff --git a/frontend/src/app/util/path/shapes_to_path.cljs b/frontend/src/app/util/path/shapes_to_path.cljs index a1d42ba47d..0fb979bb16 100644 --- a/frontend/src/app/util/path/shapes_to_path.cljs +++ b/frontend/src/app/util/path/shapes_to_path.cljs @@ -40,14 +40,14 @@ height (* radius 2) c bezier-circle-c - c1x (+ x (* (/ width 2) (- 1 c))) + c1x (+ x (* (/ width 2) (- 1 c))) c2x (+ x (* (/ width 2) (+ 1 c))) c1y (+ y (* (/ height 2) (- 1 c))) c2y (+ y (* (/ height 2) (+ 1 c))) h1 (case corner :top-left (assoc from :y c1y) - :top-right (assoc from :x c2x) + :top-right (assoc from :x c2x) :bottom-right (assoc from :y c2y) :bottom-left (assoc from :x c1x)) @@ -67,14 +67,13 @@ ex (+ x width) ey (+ y height) - pc (gpt/point mx my) p1 (gpt/point mx y) p2 (gpt/point ex my) p3 (gpt/point mx ey) p4 (gpt/point x my) c bezier-circle-c - c1x (+ x (* (/ width 2) (- 1 c))) + c1x (+ x (* (/ width 2) (- 1 c))) c2x (+ x (* (/ width 2) (+ 1 c))) c1y (+ y (* (/ height 2) (- 1 c))) c2y (+ y (* (/ height 2) (+ 1 c)))] @@ -90,7 +89,7 @@ [x y width height r1 r2 r3 r4] (let [p1 (gpt/point x (+ y r1)) p2 (gpt/point (+ x r1) y) - + p3 (gpt/point (+ width x (- r2)) y) p4 (gpt/point (+ width x) (+ y r2)) diff --git a/frontend/src/app/util/path/simplify_curve.cljs b/frontend/src/app/util/path/simplify_curve.cljs index c3a400a118..900d66e784 100644 --- a/frontend/src/app/util/path/simplify_curve.cljs +++ b/frontend/src/app/util/path/simplify_curve.cljs @@ -6,14 +6,7 @@ (ns app.util.path.simplify-curve (:require - [app.common.data :as d] - [app.common.geom.point :as gpt] - [app.common.geom.shapes.path :as gshp] - [app.util.path.path-impl-simplify :as impl-simplify] - [app.util.svg :as usvg] - [cuerdas.core :as str] - [clojure.set :as set] - [app.common.math :as mth])) + [app.util.path.path-impl-simplify :as impl-simplify])) (defn simplify "Simplifies a drawing done with the pen tool" diff --git a/frontend/src/app/util/path/tools.cljs b/frontend/src/app/util/path/tools.cljs index 870ddab857..f6409f2216 100644 --- a/frontend/src/app/util/path/tools.cljs +++ b/frontend/src/app/util/path/tools.cljs @@ -80,13 +80,7 @@ in the same vector that results from te previous->next points but with fixed length." [content point] - (let [make-curve-cmd (fn [cmd h1 h2] - (-> cmd - (update :params assoc - :c1x (:x h1) :c1y (:y h1) - :c2x (:x h2) :c2y (:y h2)))) - - indices (upc/point-indices content point) + (let [indices (upc/point-indices content point) vectors (->> indices (mapv (fn [index] (let [cmd (nth content index) prev-i (dec index) @@ -151,10 +145,10 @@ (let [add-curve (fn [content {:keys [index command prev-p next-c next-i]}] (cond-> content - (and (= :line-to (:command command))) + (= :line-to (:command command)) (update index #(line->curve prev-p %)) - (and (= :line-to (:command next-c))) + (= :line-to (:command next-c)) (update next-i #(line->curve point %))))] (->> vectors (reduce add-curve content)))))) @@ -292,7 +286,7 @@ ;; If have a curve the first handler will be relative to the previous ;; point. We change the handler to the new previous point - (and curve? (not (empty? subpath)) (not= old-prev-point new-prev-point)) + (and curve? (seq subpath) (not= old-prev-point new-prev-point)) (update :params merge last-handler)) head-idx (dec (count result)) @@ -394,7 +388,7 @@ result (cond-> result (and (nil? set-a) (nil? set-b)) (conj #{point-a point-b}) - + (and (some? set-a) (nil? set-b)) (add-to-set set-a point-b) diff --git a/frontend/src/app/util/router.cljs b/frontend/src/app/util/router.cljs index d7945580d8..dd3078c75e 100644 --- a/frontend/src/app/util/router.cljs +++ b/frontend/src/app/util/router.cljs @@ -7,13 +7,11 @@ (ns app.util.router (:refer-clojure :exclude [resolve]) (:require - [app.common.data :as d] - [app.config :as cfg] [app.common.uri :as u] + [app.config :as cfg] [app.util.browser-history :as bhistory] [app.util.timers :as ts] [beicon.core :as rx] - [cuerdas.core :as str] [goog.events :as e] [potok.core :as ptk] [reitit.core :as r])) @@ -83,7 +81,7 @@ (dissoc state :exception)) ptk/EffectEvent - (effect [_ state stream] + (effect [_ state _] (ts/asap #(let [router (:router state) history (:history state) @@ -106,7 +104,7 @@ (deftype NavigateNewWindow [id params qparams] ptk/EffectEvent - (effect [_ state stream] + (effect [_ state _] (let [router (:router state) path (resolve router id params qparams) uri (-> (u/uri cfg/public-uri) diff --git a/frontend/src/app/util/simple_math.cljs b/frontend/src/app/util/simple_math.cljs index c92e8d6de2..34d476d455 100644 --- a/frontend/src/app/util/simple_math.cljs +++ b/frontend/src/app/util/simple_math.cljs @@ -6,15 +6,15 @@ (ns app.util.simple-math (:require - [cljs.spec.alpha :as s] - [clojure.string :refer [index-of]] - [cuerdas.core :as str] - [instaparse.core :as insta] - [app.common.data :as d] - [app.common.exceptions :as ex])) + [app.common.data :as d] + [app.common.exceptions :as ex] + [cljs.spec.alpha :as s] + [clojure.string :refer [index-of]] + [cuerdas.core :as str] + [instaparse.core :as insta])) (def parser - (insta/parser + (insta/parser "opt-expr = '' | expr expr = term ( ('+'|'-') expr)* | ('+'|'-'|'*'|'/') factor diff --git a/frontend/src/app/util/storage.cljs b/frontend/src/app/util/storage.cljs index 2f2e5e9cba..d7e150c61a 100644 --- a/frontend/src/app/util/storage.cljs +++ b/frontend/src/app/util/storage.cljs @@ -6,20 +6,10 @@ (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])) -(defn- ^boolean is-worker? - [] - (or (= *target* "nodejs") - (not (exists? js/window)))) - -(defn- decode - [v] - (ex/ignoring (t/decode-str v))) - (defn- persist [storage prev curr] (run! (fn [key] @@ -43,7 +33,7 @@ val (.getItem ^js storage key)] (try (assoc res (t/decode-str key) (t/decode-str val)) - (catch :default e + (catch :default _e res)))) {} (range len))))) diff --git a/frontend/src/app/util/svg.cljs b/frontend/src/app/util/svg.cljs index 12c9e0d97c..b0f407f041 100644 --- a/frontend/src/app/util/svg.cljs +++ b/frontend/src/app/util/svg.cljs @@ -6,12 +6,12 @@ (ns app.util.svg (:require - [app.common.uuid :as uuid] [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] + [app.common.uuid :as uuid] [cuerdas.core :as str])) ;; Regex for XML ids per Spec @@ -539,11 +539,6 @@ (str/camel) (keyword)))) - (lowercase-key [key] - (-> (d/name key) - (str/lower) - (keyword))) - (format-styles [style-str] (->> (str/split style-str ";") (map str/trim) @@ -593,7 +588,7 @@ (defn replace-attrs-ids "Replaces the ids inside a property" [attrs ids-mapping] - (if (and ids-mapping (not (empty? ids-mapping))) + (if (and ids-mapping (seq ids-mapping)) (update-attr-ids attrs (fn [id] (get ids-mapping id id))) ;; Ids-mapping is null attrs)) @@ -606,7 +601,7 @@ (reduce visit-node result (:content node))))] (visit-node {} content))) -(defn extract-defs [{:keys [tag attrs content] :as node}] +(defn extract-defs [{:keys [attrs] :as node}] (if-not (map? node) [{} node] @@ -646,7 +641,7 @@ (cond (nil? to-check) result - + (checked? to-check) (recur result checked? @@ -672,7 +667,7 @@ scale-x (/ width svg-width) scale-y (/ height svg-height)] - + (gmt/multiply (gmt/matrix) @@ -735,7 +730,7 @@ (let [process-matrix (fn [[_ type params]] (let [params (->> (re-seq number-regex params) - (filter #(-> % first empty? not)) + (filter #(-> % first seq)) (map (comp d/parse-double first)))] {:type type :params params})) @@ -761,7 +756,7 @@ (str (format-move head) (->> other (map format-line) (str/join " "))))) -(defn polyline->path [{:keys [attrs tag] :as node}] +(defn polyline->path [{:keys [attrs] :as node}] (let [tag :path attrs (-> attrs (dissoc :points) @@ -769,14 +764,14 @@ (assoc node :attrs attrs :tag tag))) -(defn polygon->path [{:keys [attrs tag] :as node}] +(defn polygon->path [{:keys [attrs] :as node}] (let [tag :path attrs (-> attrs (dissoc :points) (assoc :d (str (points->path (:points attrs)) "Z")))] (assoc node :attrs attrs :tag tag))) -(defn line->path [{:keys [attrs tag] :as node}] +(defn line->path [{:keys [attrs] :as node}] (let [tag :path {:keys [x1 y1 x2 y2]} attrs attrs (-> attrs @@ -868,7 +863,7 @@ :ratio (calculate-ratio (:width svg-data) (:height svg-data))}] (letfn [(fix-length [prop-length val] (* (get viewbox prop-length) (/ val 100.))) - + (fix-coord [prop-coord prop-length val] (+ (get viewbox prop-coord) (fix-length prop-length val))) @@ -896,7 +891,7 @@ (fix-percent-attrs-viewbox [attrs] (d/mapm fix-percent-attr-viewbox attrs)) - (fix-percent-attr-numeric [attr-key attr-val] + (fix-percent-attr-numeric [_ attr-val] (let [is-percent? (str/ends-with? attr-val "%")] (if is-percent? (str (let [attr-num (d/parse-double attr-val)] diff --git a/frontend/src/app/util/text_editor.cljs b/frontend/src/app/util/text_editor.cljs index ccd7fd8dcc..370f17f2de 100644 --- a/frontend/src/app/util/text_editor.cljs +++ b/frontend/src/app/util/text_editor.cljs @@ -9,14 +9,7 @@ (:require ["./text_editor_impl.js" :as impl] ["draft-js" :as draft] - [app.common.attrs :as attrs] - [app.common.data :as d] - [app.common.text :as txt] - [app.common.uuid :as uuid] - [app.util.array :as arr] - [app.util.object :as obj] - [clojure.walk :as walk] - [cuerdas.core :as str])) + [app.common.text :as txt])) ;; --- CONVERSION diff --git a/frontend/src/app/util/theme.cljs b/frontend/src/app/util/theme.cljs index ae446f5bf6..1030c5eea0 100644 --- a/frontend/src/app/util/theme.cljs +++ b/frontend/src/app/util/theme.cljs @@ -8,13 +8,11 @@ (ns app.util.theme "A theme manager." (:require - [cuerdas.core :as str] - [rumext.alpha :as mf] - [beicon.core :as rx] - [goog.object :as gobj] [app.config :as cfg] [app.util.dom :as dom] - [app.util.storage :refer [storage]])) + [app.util.storage :refer [storage]] + [beicon.core :as rx] + [rumext.alpha :as mf])) (defonce theme (get @storage ::theme cfg/default-theme)) (defonce theme-sub (rx/subject)) diff --git a/frontend/src/app/util/time.cljs b/frontend/src/app/util/time.cljs index 317ff866e7..a32a0e1a4e 100644 --- a/frontend/src/app/util/time.cljs +++ b/frontend/src/app/util/time.cljs @@ -6,21 +6,21 @@ (ns app.util.time (:require - [cuerdas.core :as str] - ["luxon" :as lxn] ["date-fns/formatDistanceToNowStrict" :default dateFnsFormatDistanceToNowStrict] - ["date-fns/locale/el" :default dateFnsLocalesEl] - ["date-fns/locale/fr" :default dateFnsLocalesFr] ["date-fns/locale/ca" :default dateFnsLocalesCa] ["date-fns/locale/de" :default dateFnsLocalesDe] - ["date-fns/locale/ro" :default dateFnsLocalesRo] - ["date-fns/locale/pt-BR" :default dateFnsLocalesPtBr] + ["date-fns/locale/el" :default dateFnsLocalesEl] ["date-fns/locale/en-US" :default dateFnsLocalesEnUs] - ["date-fns/locale/zh-CN" :default dateFnsLocalesZhCn] ["date-fns/locale/es" :default dateFnsLocalesEs] - ["date-fns/locale/tr" :default dateFnsLocalesTr] + ["date-fns/locale/fr" :default dateFnsLocalesFr] + ["date-fns/locale/pt-BR" :default dateFnsLocalesPtBr] + ["date-fns/locale/ro" :default dateFnsLocalesRo] ["date-fns/locale/ru" :default dateFnsLocalesRu] - [app.util.object :as obj])) + ["date-fns/locale/tr" :default dateFnsLocalesTr] + ["date-fns/locale/zh-CN" :default dateFnsLocalesZhCn] + ["luxon" :as lxn] + [app.util.object :as obj] + [cuerdas.core :as str])) (def DateTime lxn/DateTime) (def Duration lxn/Duration) diff --git a/frontend/src/app/util/uri.cljs b/frontend/src/app/util/uri.cljs index fe01825439..3f6d6c1bc7 100644 --- a/frontend/src/app/util/uri.cljs +++ b/frontend/src/app/util/uri.cljs @@ -6,8 +6,8 @@ (ns app.util.uri (:require - [cuerdas.core :as str] - [app.util.object :as obj])) + [app.util.object :as obj] + [cuerdas.core :as str])) (defn uri-name [url] (let [query-idx (str/last-index-of url "?") diff --git a/frontend/src/app/util/webapi.cljs b/frontend/src/app/util/webapi.cljs index ef77aaafc2..e07949567e 100644 --- a/frontend/src/app/util/webapi.cljs +++ b/frontend/src/app/util/webapi.cljs @@ -11,8 +11,7 @@ [app.common.exceptions :as ex] [app.util.object :as obj] [beicon.core :as rx] - [cuerdas.core :as str] - [promesa.core :as p])) + [cuerdas.core :as str])) (defn- file-reader [f] @@ -136,7 +135,7 @@ (rx/create (fn [subs] (let [obs (js/ResizeObserver. - (fn [entries x] + (fn [entries _] (rx/push! subs entries)))] (.observe ^js obs node) (fn [] diff --git a/frontend/src/app/util/websockets.cljs b/frontend/src/app/util/websockets.cljs index 9690110200..8bac581684 100644 --- a/frontend/src/app/util/websockets.cljs +++ b/frontend/src/app/util/websockets.cljs @@ -8,11 +8,8 @@ "A interface to webworkers exposed functionality." (:require [app.common.transit :as t] - [app.common.uri :as u] - [app.config :as cfg] [beicon.core :as rx] - [goog.events :as ev] - [potok.core :as ptk]) + [goog.events :as ev]) (:import goog.net.WebSocket goog.net.WebSocket.EventType)) diff --git a/frontend/src/app/util/worker.cljs b/frontend/src/app/util/worker.cljs index 19d4b3df4e..810d32e294 100644 --- a/frontend/src/app/util/worker.cljs +++ b/frontend/src/app/util/worker.cljs @@ -82,7 +82,7 @@ worker)) (defn- handle-response - [{:keys [payload error dropped] :as response}] + [{:keys [payload error dropped]}] (when-not dropped (if-let [{:keys [data message]} error] (throw (ex-info message data)) diff --git a/frontend/src/app/util/zip.cljs b/frontend/src/app/util/zip.cljs index 84a3759d27..47f3544f65 100644 --- a/frontend/src/app/util/zip.cljs +++ b/frontend/src/app/util/zip.cljs @@ -8,10 +8,9 @@ "Helpers for make zip file (using jszip)." (:require ["jszip" :as zip] - [app.common.data :as d] + [app.util.http :as http] [beicon.core :as rx] - [promesa.core :as p] - [app.util.http :as http])) + [promesa.core :as p])) (defn compress-files [files] diff --git a/frontend/src/app/worker.cljs b/frontend/src/app/worker.cljs index 44cd628f6d..59470139e8 100644 --- a/frontend/src/app/worker.cljs +++ b/frontend/src/app/worker.cljs @@ -6,23 +6,16 @@ (ns app.worker (:require - - [app.common.exceptions :as ex] [app.common.spec :as us] [app.common.transit :as t] - [app.common.uuid :as uuid] - [app.util.object :as obj] - [app.util.worker :as w] - [app.worker.impl :as impl] - [app.worker.selection] - - [app.worker.import] [app.worker.export] + [app.worker.impl :as impl] + [app.worker.import] + [app.worker.selection] [app.worker.snaps] [app.worker.thumbnails] [beicon.core :as rx] [cljs.spec.alpha :as s] - [cuerdas.core :as str] [promesa.core :as p])) ;; --- Messages Handling @@ -38,7 +31,7 @@ (s/def ::message (s/keys - :req-opt [::buffer?] + :opt-un [::buffer?] :req-un [::payload ::sender-id])) (def buffer (rx/subject)) @@ -86,7 +79,7 @@ (defn- drop-message "Sends to the client a notifiction that its messages have been dropped" - [{:keys [sender-id payload] :as message}] + [{:keys [sender-id] :as message}] (us/assert ::message message) (.postMessage js/self (t/encode-str {:reply-to sender-id :dropped true}))) @@ -105,7 +98,7 @@ ;; we also store the last message processed in order to detect ;; posible infinite loops (rx/scan - (fn [[messages dropped last] message] + (fn [[messages dropped _last] message] (let [cmd (get-in message [:payload :cmd]) ;; The previous message is dropped From 3e4e54870bb3c1dc6b80f1ecc4deb17ebc9288ee Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 15:24:42 +0200 Subject: [PATCH 119/204] :sparkles: Fix linter issues on frontend (part 2). --- .clj-kondo/config.edn | 3 + .../src/app/main/data/workspace/changes.cljs | 21 ++++--- .../src/app/main/data/workspace/colors.cljs | 58 +++++++------------ .../src/app/main/data/workspace/comments.cljs | 24 +++----- .../src/app/main/data/workspace/common.cljs | 39 ++++++------- .../src/app/main/data/workspace/drawing.cljs | 29 +++++----- .../app/main/data/workspace/drawing/box.cljs | 6 +- .../main/data/workspace/drawing/common.cljs | 10 ++-- .../main/data/workspace/drawing/curve.cljs | 8 +-- .../src/app/main/data/workspace/grid.cljs | 14 ++--- .../src/app/main/data/workspace/groups.cljs | 16 +++-- .../app/main/data/workspace/libraries.cljs | 54 ++++++++--------- .../data/workspace/libraries_helpers.cljs | 43 +++----------- .../main/data/workspace/notifications.cljs | 17 ++---- .../app/main/data/workspace/path/changes.cljs | 2 +- .../app/main/data/workspace/path/common.cljs | 3 +- .../app/main/data/workspace/path/drawing.cljs | 31 ++++------ .../app/main/data/workspace/path/edition.cljs | 18 +++--- .../app/main/data/workspace/path/helpers.cljs | 7 +-- .../main/data/workspace/path/shortcuts.cljs | 2 +- .../app/main/data/workspace/path/spec.cljs | 4 +- .../app/main/data/workspace/path/streams.cljs | 17 +++--- .../app/main/data/workspace/path/tools.cljs | 6 +- .../app/main/data/workspace/path/undo.cljs | 13 ++--- .../app/main/data/workspace/persistence.cljs | 33 +++++------ .../app/main/data/workspace/selection.cljs | 45 +++++++------- .../app/main/data/workspace/shortcuts.cljs | 5 +- .../app/main/data/workspace/svg_upload.cljs | 11 ++-- .../src/app/main/data/workspace/texts.cljs | 22 +++---- .../app/main/data/workspace/transforms.cljs | 4 -- .../src/app/main/data/workspace/undo.cljs | 24 +------- frontend/src/app/main/exports.cljs | 1 - frontend/src/app/main/fonts.clj | 4 +- frontend/src/app/main/fonts.cljs | 11 ++-- frontend/src/app/main/refs.cljs | 3 - frontend/src/app/main/render.cljs | 18 +++--- frontend/src/app/main/repo.cljs | 10 ++-- frontend/src/app/main/snap.cljs | 19 +++--- frontend/src/app/main/store.cljs | 14 ++--- frontend/src/app/main/streams.cljs | 6 +- frontend/src/app/main/worker.cljs | 2 - 41 files changed, 276 insertions(+), 401 deletions(-) diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 4713efa815..50e8689fda 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -1,6 +1,7 @@ {:lint-as {promesa.core/let clojure.core/let rumext.alpha/defc clojure.core/defn + rumext.alpha/fnc clojure.core/fn app.common.data/export clojure.core/def app.db/with-atomic clojure.core/with-open} @@ -16,6 +17,8 @@ "app/worker/.*" "app/main/ui.*" "app/libs/.*" + "app/main/data/workspace/path/selection.cljs" + "app/main/data/workspace/transforms.cljs" ]} :linters diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index b3ffc2d76d..f18a871049 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -10,14 +10,13 @@ [app.common.pages :as cp] [app.common.pages.spec :as spec] [app.common.spec :as us] - [app.main.data.workspace.undo :as dwu] [app.main.data.workspace.state-helpers :as wsh] - [app.main.worker :as uw] + [app.main.data.workspace.undo :as dwu] [app.main.store :as st] + [app.main.worker :as uw] [app.util.logging :as log] [beicon.core :as rx] [cljs.spec.alpha :as s] - [clojure.set :as set] [potok.core :as ptk])) ;; Change this to :info :debug or :trace to debug this module @@ -59,29 +58,29 @@ attrs) uops (cond-> uops - (not (empty? uops)) + (seq uops) (conj {:type :set-touched :touched (:touched old-obj)})) change {:type :mod-obj :page-id page-id :id id}] (cond-> changes - (not (empty? rops)) + (seq rops) (update :redo-changes conj (assoc change :operations rops)) - (not (empty? uops)) + (seq uops) (update :undo-changes conj (assoc change :operations uops))))) (defn update-shapes ([ids f] (update-shapes ids f nil)) ([ids f {:keys [reg-objects? save-undo? keys] - :or {reg-objects? false save-undo? true attrs nil}}] + :or {reg-objects? false save-undo? true}}] (us/assert ::coll-of-uuid ids) (us/assert fn? f) (ptk/reify ::update-shapes ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state) changes {:redo-changes [] @@ -107,7 +106,7 @@ [page-id changes] (ptk/reify ::update-indices ptk/EffectEvent - (effect [_ state stream] + (effect [_ _ _] (uw/ask! {:cmd :update-page-indices :page-id page-id :changes changes})))) @@ -147,7 +146,7 @@ state)))) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (when-not @error (let [;; adds page-id to page changes (that have the `id` field instead) add-page-id @@ -163,7 +162,7 @@ (group-by :page-id)) process-page-changes - (fn [[page-id changes]] + (fn [[page-id _changes]] (update-indices page-id redo-changes))] (rx/concat (rx/from (map process-page-changes changes-by-pages)) diff --git a/frontend/src/app/main/data/workspace/colors.cljs b/frontend/src/app/main/data/workspace/colors.cljs index 2e40b5b98b..1813fa7120 100644 --- a/frontend/src/app/main/data/workspace/colors.cljs +++ b/frontend/src/app/main/data/workspace/colors.cljs @@ -7,23 +7,13 @@ (ns app.main.data.workspace.colors (:require [app.common.data :as d] - [app.common.pages :as cp] - [app.common.spec :as us] - [app.common.uuid :as uuid] [app.main.data.modal :as md] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.texts :as dwt] [app.main.repo :as rp] - [app.main.store :as st] - [app.main.streams :as ms] - [app.util.color :as color] - [app.util.i18n :refer [tr]] - [app.util.router :as rt] - [app.util.time :as dt] [beicon.core :as rx] [cljs.spec.alpha :as s] - [clojure.set :as set] [potok.core :as ptk])) (def clear-color-for-rename @@ -38,17 +28,16 @@ [file-id color-id name] (ptk/reify ::rename-color ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (->> (rp/mutation! :rename-color {:id color-id :name name}) (rx/map (partial rename-color-result file-id)))))) (defn rename-color-result - [file-id color] + [_file-id color] (ptk/reify ::rename-color-result ptk/UpdateEvent (update [_ state] - (-> state - (update-in [:workspace-file :colors] #(d/replace-by-id % color)))))) + (update-in state [:workspace-file :colors] #(d/replace-by-id % color))))) (defn change-palette-size [size] @@ -125,7 +114,7 @@ [ids color] (ptk/reify ::change-fill ptk/WatchEvent - (watch [_ state s] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -157,32 +146,29 @@ [ids color] (ptk/reify ::change-stroke ptk/WatchEvent - (watch [_ state s] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) + (watch [_ _ _] + (let [attrs (cond-> {} + (contains? color :color) + (assoc :stroke-color (:color color)) - attrs (cond-> {} - (contains? color :color) - (assoc :stroke-color (:color color)) + (contains? color :id) + (assoc :stroke-color-ref-id (:id color)) - (contains? color :id) - (assoc :stroke-color-ref-id (:id color)) + (contains? color :file-id) + (assoc :stroke-color-ref-file (:file-id color)) - (contains? color :file-id) - (assoc :stroke-color-ref-file (:file-id color)) + (contains? color :gradient) + (assoc :stroke-color-gradient (:gradient color)) - (contains? color :gradient) - (assoc :stroke-color-gradient (:gradient color)) + (contains? color :opacity) + (assoc :stroke-opacity (:opacity color)))] - (contains? color :opacity) - (assoc :stroke-opacity (:opacity color)))] - - (rx/of (dch/update-shapes ids (fn [shape] - (cond-> (d/merge shape attrs) - (= (:stroke-style shape) :none) - (assoc :stroke-style :solid - :stroke-width 1 - :stroke-opacity 1))))))))) + (rx/of (dch/update-shapes ids (fn [shape] + (cond-> (d/merge shape attrs) + (= (:stroke-style shape) :none) + (assoc :stroke-style :solid + :stroke-width 1 + :stroke-opacity 1))))))))) (defn picker-for-selected-shape diff --git a/frontend/src/app/main/data/workspace/comments.cljs b/frontend/src/app/main/data/workspace/comments.cljs index 08f70ed2fa..d83b51455f 100644 --- a/frontend/src/app/main/data/workspace/comments.cljs +++ b/frontend/src/app/main/data/workspace/comments.cljs @@ -6,19 +6,14 @@ (ns app.main.data.workspace.comments (:require - [app.common.data :as d] - [app.common.exceptions :as ex] [app.common.math :as mth] [app.common.spec :as us] - [app.main.constants :as c] + [app.main.data.comments :as dcm] [app.main.data.workspace :as dw] [app.main.data.workspace.common :as dwc] - [app.main.data.comments :as dcm] - [app.main.store :as st] [app.main.streams :as ms] [app.util.router :as rt] [beicon.core :as rx] - [cljs.spec.alpha :as s] [potok.core :as ptk])) (declare handle-interrupt) @@ -29,7 +24,7 @@ (us/assert ::us/uuid file-id) (ptk/reify ::initialize-comments ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (let [stoper (rx/filter #(= ::finalize %) stream)] (rx/merge (rx/of (dcm/retrieve-comment-threads file-id)) @@ -47,8 +42,8 @@ [] (ptk/reify ::handle-interrupt ptk/WatchEvent - (watch [_ state stream] - (let [local (:comments-local state)] + (watch [_ state _] + (let [local (:comments-local state)] (cond (:draft local) (rx/of (dcm/close-thread)) (:open local) (rx/of (dcm/close-thread)) @@ -62,7 +57,7 @@ [position] (ptk/reify ::handle-comment-layer-click ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [local (:comments-local state)] (if (some? (:open local)) (rx/of (dcm/close-thread)) @@ -74,14 +69,13 @@ (rx/of (dcm/create-draft params)))))))) (defn center-to-comment-thread - [{:keys [id position] :as thread}] + [{:keys [position] :as thread}] (us/assert ::dcm/comment-thread thread) (ptk/reify :center-to-comment-thread ptk/UpdateEvent (update [_ state] (update state :workspace-local - (fn [{:keys [vbox vport zoom] :as local}] - (prn "center-to-comment-thread" vbox) + (fn [{:keys [vbox zoom] :as local}] (let [pw (/ 50 zoom) ph (/ 200 zoom) nw (mth/round (- (/ (:width vbox) 2) pw)) @@ -93,11 +87,11 @@ (update local :vbox assoc :x nx :y ny))))))) (defn navigate - [{:keys [project-id file-id page-id] :as thread}] + [thread] (us/assert ::dcm/comment-thread thread) (ptk/reify ::navigate ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (let [pparams {:project-id (:project-id thread) :file-id (:file-id thread)} qparams {:page-id (:page-id thread)}] diff --git a/frontend/src/app/main/data/workspace/common.cljs b/frontend/src/app/main/data/workspace/common.cljs index 0e313686bc..75e9ce724e 100644 --- a/frontend/src/app/main/data/workspace/common.cljs +++ b/frontend/src/app/main/data/workspace/common.cljs @@ -40,15 +40,13 @@ [{:keys [file] :as bundle}] (ptk/reify ::setup-selection-index ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (let [msg {:cmd :initialize-indices :file-id (:id file) :data (:data file)}] (->> (uw/ask! msg) (rx/map (constantly ::index-initialized))))))) - - ;; --- Common Helpers & Events (defn get-frame-at-point @@ -59,7 +57,7 @@ (defn- extract-numeric-suffix [basename] - (if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] + (if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] [p1 (+ 1 (d/parse-integer p2))] [basename 1])) @@ -112,7 +110,7 @@ (def undo (ptk/reify ::undo ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [edition (get-in state [:workspace-local :edition]) drawing (get state :workspace-drawing)] ;; Editors handle their own undo's @@ -131,7 +129,7 @@ (def redo (ptk/reify ::redo ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [edition (get-in state [:workspace-local :edition]) drawing (get state :workspace-drawing)] (when-not (or (some? edition) (not-empty drawing)) @@ -180,10 +178,10 @@ (assoc-in state [:workspace-local :selected] ids)) ptk/WatchEvent - (watch [it state stream] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id)] - (rx/of (expand-all-parents ids objects)))))) + (watch [_ state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id)] + (rx/of (expand-all-parents ids objects)))))) (declare clear-edition-mode) @@ -202,12 +200,11 @@ state))) ptk/WatchEvent - (watch [it state stream] - (let [objects (wsh/lookup-page-objects state)] - (->> stream - (rx/filter interrupt?) - (rx/take 1) - (rx/map (constantly clear-edition-mode))))))) + (watch [_ _ stream] + (->> stream + (rx/filter interrupt?) + (rx/take 1) + (rx/map (constantly clear-edition-mode)))))) ;; If these event change modules review /src/app/main/data/workspace/path/undo.cljs (def clear-edition-mode @@ -282,7 +279,7 @@ (us/verify ::shape-attrs attrs) (ptk/reify ::add-shape ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -313,7 +310,7 @@ (defn move-shapes-into-frame [frame-id shapes] (ptk/reify ::move-shapes-into-frame ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) to-move-shapes (->> (cp/select-toplevel-shapes objects {:include-frames? false}) @@ -349,7 +346,7 @@ (us/assert ::set-of-uuid ids) (ptk/reify ::delete-shapes ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -504,7 +501,7 @@ [type frame-x frame-y data] (ptk/reify ::create-and-add-shape ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [{:keys [width height]} data [vbc-x vbc-y] (viewport-center state) @@ -524,7 +521,7 @@ [image {:keys [x y]}] (ptk/reify ::image-uploaded ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (let [{:keys [name width height id mtype]} image shape {:name name :width width diff --git a/frontend/src/app/main/data/workspace/drawing.cljs b/frontend/src/app/main/data/workspace/drawing.cljs index 503d6bee97..14ec47a2ac 100644 --- a/frontend/src/app/main/data/workspace/drawing.cljs +++ b/frontend/src/app/main/data/workspace/drawing.cljs @@ -7,17 +7,15 @@ (ns app.main.data.workspace.drawing "Drawing interactions." (:require - [beicon.core :as rx] - [potok.core :as ptk] - [app.common.spec :as us] [app.common.pages :as cp] [app.common.uuid :as uuid] [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.selection :as dws] - [app.main.data.workspace.path :as path] + [app.main.data.workspace.drawing.box :as box] [app.main.data.workspace.drawing.common :as common] [app.main.data.workspace.drawing.curve :as curve] - [app.main.data.workspace.drawing.box :as box])) + [app.main.data.workspace.path :as path] + [beicon.core :as rx] + [potok.core :as ptk])) (declare start-drawing) (declare handle-drawing) @@ -38,7 +36,7 @@ (update :workspace-layout disj :scale-text))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (let [stoper (rx/filter (ptk/type? ::clear-drawing) stream)] (rx/merge (when (= tool :path) @@ -88,16 +86,17 @@ (update-in state [:workspace-drawing :object] merge data))) ptk/WatchEvent - (watch [_ state stream] - (rx/of (case type - :path - (path/handle-new-shape) + (watch [_ _ _] + (rx/of + (case type + :path + (path/handle-new-shape) - :curve - (curve/handle-drawing-curve) + :curve + (curve/handle-drawing-curve) - ;; default - (box/handle-drawing-box)))))) + ;; default + (box/handle-drawing-box)))))) diff --git a/frontend/src/app/main/data/workspace/drawing/box.cljs b/frontend/src/app/main/data/workspace/drawing/box.cljs index d9e3879a7b..01632a744c 100644 --- a/frontend/src/app/main/data/workspace/drawing/box.cljs +++ b/frontend/src/app/main/data/workspace/drawing/box.cljs @@ -21,7 +21,7 @@ (defn truncate-zero [num default] (if (mth/almost-zero? num) default num)) -(defn resize-shape [{:keys [x y width height transform transform-inverse] :as shape} point lock?] +(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) @@ -53,9 +53,7 @@ (ptk/reify ::handle-drawing-box ptk/WatchEvent (watch [_ state stream] - (let [{:keys [flags]} (:workspace-local state) - - stoper? #(or (ms/mouse-up? %) (= % :interrupt)) + (let [stoper? #(or (ms/mouse-up? %) (= % :interrupt)) stoper (rx/filter stoper? stream) initial @ms/mouse-position diff --git a/frontend/src/app/main/data/workspace/drawing/common.cljs b/frontend/src/app/main/data/workspace/drawing/common.cljs index 92db099600..fb93c6f5cc 100644 --- a/frontend/src/app/main/data/workspace/drawing/common.cljs +++ b/frontend/src/app/main/data/workspace/drawing/common.cljs @@ -6,15 +6,13 @@ (ns app.main.data.workspace.drawing.common (:require - [beicon.core :as rx] - [potok.core :as ptk] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.selection :as dws] [app.main.data.workspace.undo :as dwu] - [app.main.streams :as ms] - [app.main.worker :as uw])) + [app.main.worker :as uw] + [beicon.core :as rx] + [potok.core :as ptk])) (def clear-drawing (ptk/reify ::clear-drawing @@ -25,7 +23,7 @@ (def handle-finish-drawing (ptk/reify ::handle-finish-drawing ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [shape (get-in state [:workspace-drawing :object])] (rx/concat (when (:initialized? shape) diff --git a/frontend/src/app/main/data/workspace/drawing/curve.cljs b/frontend/src/app/main/data/workspace/drawing/curve.cljs index 333404214f..463c1e9ead 100644 --- a/frontend/src/app/main/data/workspace/drawing/curve.cljs +++ b/frontend/src/app/main/data/workspace/drawing/curve.cljs @@ -6,7 +6,6 @@ (ns app.main.data.workspace.drawing.curve (:require - [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.geom.shapes.path :as gsp] [app.common.pages :as cp] @@ -19,7 +18,7 @@ (def simplify-tolerance 0.3) -(defn stoper-event? [{:keys [type shift] :as event}] +(defn stoper-event? [{:keys [type] :as event}] (ms/mouse-event? event) (= type :up)) (defn initialize-drawing [state] @@ -73,9 +72,8 @@ (defn handle-drawing-curve [] (ptk/reify ::handle-drawing-curve ptk/WatchEvent - (watch [_ state stream] - (let [{:keys [flags]} (:workspace-local state) - stoper (rx/filter stoper-event? stream) + (watch [_ _ stream] + (let [stoper (rx/filter stoper-event? stream) mouse (rx/sample 10 ms/mouse-position)] (rx/concat (rx/of initialize-drawing) diff --git a/frontend/src/app/main/data/workspace/grid.cljs b/frontend/src/app/main/data/workspace/grid.cljs index e405c23646..b04b99f6aa 100644 --- a/frontend/src/app/main/data/workspace/grid.cljs +++ b/frontend/src/app/main/data/workspace/grid.cljs @@ -6,11 +6,11 @@ (ns app.main.data.workspace.grid (:require - [beicon.core :as rx] - [potok.core :as ptk] [app.common.data :as d] [app.common.spec :as us] - [app.main.data.workspace.changes :as dch])) + [app.main.data.workspace.changes :as dch] + [beicon.core :as rx] + [potok.core :as ptk])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Grid @@ -40,7 +40,7 @@ (us/assert ::us/uuid frame-id) (ptk/reify ::add-frame-grid ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [page-id (:current-page-id state) data (get-in state [:workspace-data :pages-index page-id]) params (or (get-in data [:options :saved-grids :square]) @@ -56,21 +56,21 @@ [frame-id index] (ptk/reify ::set-frame-grid ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (rx/of (dch/update-shapes [frame-id] (fn [o] (update o :grids (fnil #(d/remove-at-index % index) [])))))))) (defn set-frame-grid [frame-id index data] (ptk/reify ::set-frame-grid ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (rx/of (dch/update-shapes [frame-id] #(assoc-in % [:grids index] data)))))) (defn set-default-grid [type params] (ptk/reify ::set-default-grid ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [pid (:current-page-id state) prev-value (get-in state [:workspace-data :pages-index pid :options :saved-grids type])] (rx/of (dch/commit-changes diff --git a/frontend/src/app/main/data/workspace/groups.cljs b/frontend/src/app/main/data/workspace/groups.cljs index 849fdde94a..fc88141b31 100644 --- a/frontend/src/app/main/data/workspace/groups.cljs +++ b/frontend/src/app/main/data/workspace/groups.cljs @@ -1,3 +1,9 @@ +;; 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.main.data.workspace.groups (:require [app.common.data :as d] @@ -5,7 +11,6 @@ [app.common.pages :as cp] [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.state-helpers :as wsh] [beicon.core :as rx] [potok.core :as ptk])) @@ -22,7 +27,6 @@ [shapes prefix keep-name] (let [selrect (gsh/selection-rect shapes) frame-id (-> shapes first :frame-id) - parent-id (-> shapes first :parent-id) group-name (if (and keep-name (= (count shapes) 1) (= (:type (first shapes)) :group)) @@ -166,7 +170,7 @@ (def group-selected (ptk/reify ::group-selected ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state) @@ -183,7 +187,7 @@ (def ungroup-selected (ptk/reify ::ungroup-selected ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state) @@ -200,7 +204,7 @@ (def mask-group (ptk/reify ::mask-group ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state) @@ -257,7 +261,7 @@ (def unmask-group (ptk/reify ::unmask-group ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state)] diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index f4f177274d..7996e492ad 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -46,7 +46,7 @@ (:component-id change) :objects (:id change)]) - :default nil)) + :else nil)) prefix (if (:component-id change) "[C] " "[P] ") @@ -90,7 +90,7 @@ (us/assert ::cp/color color) (ptk/reify ::add-color ptk/WatchEvent - (watch [it state s] + (watch [it _ _] (let [rchg {:type :add-color :color color} uchg {:type :del-color @@ -104,7 +104,7 @@ (us/assert ::cp/recent-color color) (ptk/reify ::add-recent-color ptk/WatchEvent - (watch [it state s] + (watch [it _ _] (let [rchg {:type :add-recent-color :color color}] (rx/of (dch/commit-changes {:redo-changes [rchg] @@ -123,7 +123,7 @@ (us/assert ::us/uuid file-id) (ptk/reify ::update-color ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [[path name] (cp/parse-path-name (:name color)) color (assoc color :path path :name name) prev (get-in state [:workspace-data :colors id]) @@ -141,7 +141,7 @@ (us/assert ::us/uuid id) (ptk/reify ::delete-color ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [prev (get-in state [:workspace-data :colors id]) rchg {:type :del-color :id id} @@ -156,7 +156,7 @@ (us/assert ::cp/media-object media) (ptk/reify ::add-media ptk/WatchEvent - (watch [it state stream] + (watch [it _ _] (let [obj (select-keys media [:id :name :width :height :mtype]) rchg {:type :add-media :object obj} @@ -172,7 +172,7 @@ (us/assert ::us/string new-name) (ptk/reify ::rename-media ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [object (get-in state [:workspace-data :media id]) [path name] (cp/parse-path-name new-name) @@ -195,7 +195,7 @@ (us/assert ::us/uuid id) (ptk/reify ::delete-media ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [prev (get-in state [:workspace-data :media id]) rchg {:type :del-media :id id} @@ -212,7 +212,7 @@ (us/assert ::cp/typography typography) (ptk/reify ::add-typography ptk/WatchEvent - (watch [it state s] + (watch [it _ _] (let [rchg {:type :add-typography :typography typography} uchg {:type :del-typography @@ -230,7 +230,7 @@ (us/assert ::us/uuid file-id) (ptk/reify ::update-typography ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [[path name] (cp/parse-path-name (:name typography)) typography (assoc typography :path path :name name) prev (get-in state [:workspace-data :typographies (:id typography)]) @@ -248,7 +248,7 @@ (us/assert ::us/uuid id) (ptk/reify ::delete-typography ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [prev (get-in state [:workspace-data :typographies id]) rchg {:type :del-typography :id id} @@ -262,7 +262,7 @@ "Add a new component to current file library, from the currently selected shapes." (ptk/reify ::add-component ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [file-id (:current-file-id state) page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -285,7 +285,7 @@ (us/assert ::us/string new-name) (ptk/reify ::rename-component ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [[path name] (cp/parse-path-name new-name) component (get-in state [:workspace-data :components id]) objects (get component :objects) @@ -315,7 +315,7 @@ [{:keys [id] :as params}] (ptk/reify ::duplicate-component ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [component (cp/get-component id (:current-file-id state) (dwlh/get-local-file state) @@ -324,7 +324,7 @@ unames (set (map :name all-components)) new-name (dwc/generate-unique-name unames (:name component)) - [new-shape new-shapes updated-shapes] + [new-shape new-shapes _updated-shapes] (dwlh/duplicate-component component) rchanges [{:type :add-component @@ -346,7 +346,7 @@ (us/assert ::us/uuid id) (ptk/reify ::delete-component ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [component (get-in state [:workspace-data :components id]) rchanges [{:type :del-component @@ -371,7 +371,7 @@ (us/assert ::us/point position) (ptk/reify ::instantiate-component ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [local-library (dwlh/get-local-file state) libraries (get state :workspace-libraries) component (cp/get-component component-id file-id local-library libraries) @@ -449,7 +449,7 @@ (us/assert ::us/uuid id) (ptk/reify ::detach-component ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) shapes (cp/get-object-with-children id objects) @@ -511,13 +511,13 @@ (us/assert ::us/uuid file-id) (ptk/reify ::nav-to-component-file ptk/WatchEvent - (watch [it state stream] - (let [file (get-in state [:workspace-libraries file-id]) + (watch [_ state _] + (let [file (get-in state [:workspace-libraries file-id]) pparams {:project-id (:project-id file) :file-id (:id file)} qparams {:page-id (first (get-in file [:data :pages])) :layout :assets}] - (st/emit! (rt/nav-new-window :workspace pparams qparams)))))) + (rx/of (rt/nav-new-window :workspace pparams qparams)))))) (defn ext-library-changed [file-id modified-at revn changes] @@ -540,7 +540,7 @@ (us/assert ::us/uuid id) (ptk/reify ::reset-component ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (log/info :msg "RESET-COMPONENT of shape" :id (str id)) (let [local-library (dwlh/get-local-file state) libraries (dwlh/get-libraries state) @@ -574,7 +574,7 @@ (us/assert ::us/uuid id) (ptk/reify ::update-component ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (log/info :msg "UPDATE-COMPONENT of shape" :id (str id)) (let [page-id (get state :current-page-id) local-library (dwlh/get-local-file state) @@ -642,7 +642,7 @@ state)) ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (log/info :msg "SYNC-FILE" :file (dwlh/pretty-file file-id state) :library (dwlh/pretty-file library-id state)) @@ -702,7 +702,7 @@ (us/assert ::us/uuid library-id) (ptk/reify ::sync-file-2nd-stage ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (log/info :msg "SYNC-FILE (2nd stage)" :file (dwlh/pretty-file file-id state) :library (dwlh/pretty-file library-id state)) @@ -727,7 +727,7 @@ (assoc-in state [:workspace-file :ignore-sync-until] (dt/now))) ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (rp/mutation :ignore-sync {:file-id (get-in state [:workspace-file :id]) :date (dt/now)})))) @@ -737,7 +737,7 @@ (us/assert ::us/uuid file-id) (ptk/reify ::notify-sync-file ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [libraries-need-sync (filter #(> (:modified-at %) (:synced-at %)) (vals (get state :workspace-libraries))) do-update #(do (apply st/emit! (map (fn [library] diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 738491e7d8..7a88d948b6 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -90,7 +90,7 @@ (assert (nil? (:shape-ref shape))) (let [;; Ensure that the component root is not an instance and ;; it's no longer tied to a frame. - update-new-shape (fn [new-shape original-shape] + update-new-shape (fn [new-shape _original-shape] (cond-> new-shape true (-> (assoc :frame-id nil) @@ -338,10 +338,10 @@ (defmulti generate-sync-shape "Generate changes to synchronize one shape with all assets of the given type that is using, in the given library." - (fn [type library-id state container shape] type)) + (fn [type _library-id _state _container _shape] type)) (defmethod generate-sync-shape :components - [_ library-id state container shape] + [_ _ state container shape] (generate-sync-shape-direct container (:id shape) (get-local-file state) @@ -666,7 +666,7 @@ [(d/concat rchanges child-rchanges) (d/concat uchanges child-uchanges)])) -(defn- generate-sync-shape-inverse +(defn generate-sync-shape-inverse "Generate changes to update the component a shape is linked to, from the values in the shape and all its children." [page-id shape-id local-library libraries] @@ -886,10 +886,10 @@ set-remote-synced? (assoc :remote-synced? true)))) - update-original-shape (fn [original-shape new-shape] + update-original-shape (fn [original-shape _new-shape] original-shape) - [new-shape new-shapes _] + [_ new-shapes _] (cp/clone-object component-shape (:id parent-shape) (get component :objects) @@ -939,7 +939,7 @@ (cp/get-parents (:id component-parent-shape) (:objects component)))) - update-new-shape (fn [new-shape original-shape] + update-new-shape (fn [new-shape _original-shape] (reposition-shape new-shape root-instance root-main)) @@ -950,7 +950,7 @@ :shape-ref (:id new-shape)) original-shape)) - [new-shape new-shapes updated-shapes] + [_new-shape new-shapes updated-shapes] (cp/clone-object shape (:id component-parent-shape) (get page :objects) @@ -1141,33 +1141,6 @@ :remote-synced? (:remote-synced? shape)}]})]] [rchanges uchanges])))) -(defn- set-touched-shapes-group - [shape container] - (if-not (:shape-ref shape) - empty-changes - (do - (log/info :msg (str "SET-TOUCHED-SHAPES-GROUP " - (if (cp/page? container) "[P] " "[C] ") - (:name shape))) - (let [rchanges [(make-change - container - {:type :mod-obj - :id (:id shape) - :operations - [{:type :set-touched - :touched (cp/set-touched-group - (:touched shape) - :shapes-group)}]})] - - uchanges [(make-change - container - {:type :mod-obj - :id (:id shape) - :operations - [{:type :set-touched - :touched (:touched shape)}]})]] - [rchanges uchanges])))) - (defn- update-attrs "The main function that implements the attribute sync algorithm. Copy attributes that have changed in the origin shape to the dest shape. diff --git a/frontend/src/app/main/data/workspace/notifications.cljs b/frontend/src/app/main/data/workspace/notifications.cljs index 49c7d13877..93dcf92291 100644 --- a/frontend/src/app/main/data/workspace/notifications.cljs +++ b/frontend/src/app/main/data/workspace/notifications.cljs @@ -16,11 +16,7 @@ [app.main.data.workspace.changes :as dch] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.persistence :as dwp] - [app.main.repo :as rp] - [app.main.store :as st] [app.main.streams :as ms] - [app.util.avatars :as avatars] - [app.util.i18n :as i18n :refer [tr]] [app.util.time :as dt] [app.util.websockets :as ws] [beicon.core :as rx] @@ -104,7 +100,7 @@ [file-id] (ptk/reify ::send-keepalive ptk/EffectEvent - (effect [_ state stream] + (effect [_ state _] (when-let [ws (get-in state [:ws file-id])] (ws/send! ws {:type :keepalive}))))) @@ -112,9 +108,8 @@ [file-id point] (ptk/reify ::handle-pointer-update ptk/EffectEvent - (effect [_ state stream] + (effect [_ state _] (let [ws (get-in state [:ws file-id]) - sid (:session-id state) pid (:current-page-id state) msg {:type :pointer-update :page-id pid @@ -128,7 +123,7 @@ [file-id] (ptk/reify ::finalize ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (when-let [ws (get-in state [:ws file-id])] (ws/-close ws)) (rx/of ::finalize)))) @@ -187,7 +182,7 @@ (update state :workspace-presence update-presence)))))) (defn handle-pointer-update - [{:keys [page-id profile-id session-id x y] :as msg}] + [{:keys [page-id session-id x y] :as msg}] (ptk/reify ::handle-pointer-update ptk/UpdateEvent (update [_ state] @@ -213,7 +208,7 @@ (us/assert ::file-change-event msg) (ptk/reify ::handle-file-change ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (let [changes-by-pages (group-by :page-id changes) process-page-changes (fn [[page-id changes]] @@ -239,7 +234,7 @@ (us/assert ::library-change-event msg) (ptk/reify ::handle-library-change ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (when (contains? (:workspace-libraries state) file-id) (rx/of (dwl/ext-library-changed file-id modified-at revn changes) (dwl/notify-sync-file file-id)))))) diff --git a/frontend/src/app/main/data/workspace/path/changes.cljs b/frontend/src/app/main/data/workspace/path/changes.cljs index cddf0a849f..c22fc25e0e 100644 --- a/frontend/src/app/main/data/workspace/path/changes.cljs +++ b/frontend/src/app/main/data/workspace/path/changes.cljs @@ -84,7 +84,7 @@ (assoc-in state (st/get-path state :content) content))) ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [objects (wsh/lookup-page-objects state) page-id (:current-page-id state) id (get-in state [:workspace-local :edition]) diff --git a/frontend/src/app/main/data/workspace/path/common.cljs b/frontend/src/app/main/data/workspace/path/common.cljs index d8e6e19cbf..f9313126db 100644 --- a/frontend/src/app/main/data/workspace/path/common.cljs +++ b/frontend/src/app/main/data/workspace/path/common.cljs @@ -16,7 +16,8 @@ [state] (dissoc state :last-point :prev-handler :drag-handler :preview)) -(defn finish-path [source] +(defn finish-path + [_source] (ptk/reify ::finish-path ptk/UpdateEvent (update [_ state] diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 75c2f2202c..287dd61f00 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -17,7 +17,6 @@ [app.main.data.workspace.path.spec :as spec] [app.main.data.workspace.path.state :as st] [app.main.data.workspace.path.streams :as streams] - [app.main.data.workspace.path.tools :as tools] [app.main.data.workspace.path.undo :as undo] [app.main.data.workspace.state-helpers :as wsh] [app.main.streams :as ms] @@ -60,9 +59,8 @@ state))))) (defn drag-handler - ([{:keys [x y alt? shift?] :as position}] + ([position] (drag-handler nil nil :c1 position)) - ([position index prefix {:keys [x y alt? shift?]}] (ptk/reify ::drag-handler ptk/UpdateEvent @@ -110,7 +108,7 @@ (update-in (st/get-path state) helpers/update-selrect)))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [id (st/get-path-id state) handler (get-in state [:workspace-local :edit-path id :prev-handler])] ;; Update the preview because can be outdated after the dragging @@ -124,9 +122,6 @@ ptk/WatchEvent (watch [_ state stream] (let [id (st/get-path-id state) - zoom (get-in state [:workspace-local :zoom]) - start-position @ms/mouse-position - stop-stream (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) @@ -166,9 +161,7 @@ (ptk/reify ::start-path-from-point ptk/WatchEvent (watch [_ state stream] - (let [start-point @ms/mouse-position - zoom (get-in state [:workspace-local :zoom]) - mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) + (let [mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) content (get-in state (st/get-path state :content)) points (upg/content->points content) @@ -195,7 +188,7 @@ (rx/merge-map #(rx/empty)))) (defn make-drag-stream - [stream snap-toggled zoom points down-event] + [stream snap-toggled _zoom points down-event] (let [mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) @@ -211,7 +204,7 @@ (rx/of (finish-drag))))))) (defn handle-drawing-path - [id] + [_id] (ptk/reify ::handle-drawing-path ptk/UpdateEvent (update [_ state] @@ -278,11 +271,11 @@ state))) ptk/WatchEvent - (watch [_ state stream] - (->> (rx/of (setup-frame-path) - dwdc/handle-finish-drawing - (dwc/start-edition-mode shape-id) - (change-edit-mode :draw)))))) + (watch [_ _ _] + (rx/of (setup-frame-path) + dwdc/handle-finish-drawing + (dwc/start-edition-mode shape-id) + (change-edit-mode :draw))))) (defn handle-new-shape "Creates a new path shape" @@ -333,7 +326,7 @@ (defn check-changed-content [] (ptk/reify ::check-changed-content ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [id (st/get-path-id state) content (get-in state (st/get-path state :content)) old-content (get-in state [:workspace-local :edit-path id :old-content]) @@ -354,7 +347,7 @@ id (assoc-in [:workspace-local :edit-path id :edit-mode] mode)))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [id (st/get-path-id state)] (cond (and id (= :move mode)) (rx/of (common/finish-path "change-edit-mode")) diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 4a93e57dd3..2005ed10cd 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -8,11 +8,9 @@ (:require [app.common.data :as d] [app.common.geom.point :as gpt] - [app.common.math :as mth] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.changes :as dch] + [app.main.data.workspace.common :as dwc] [app.main.data.workspace.path.changes :as changes] - [app.main.data.workspace.path.common :as common] [app.main.data.workspace.path.drawing :as drawing] [app.main.data.workspace.path.helpers :as helpers] [app.main.data.workspace.path.selection :as selection] @@ -47,7 +45,7 @@ (defn apply-content-modifiers [] (ptk/reify ::apply-content-modifiers ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [objects (wsh/lookup-page-objects state) id (st/get-path-id state) @@ -137,7 +135,7 @@ [position shift?] (ptk/reify ::start-move-path-point ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [id (get-in state [:workspace-local :edition]) selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) selected? (contains? selected-points position)] @@ -151,7 +149,7 @@ [start-position] (ptk/reify ::drag-selected-points ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [stopper (->> stream (rx/filter ms/mouse-up?)) id (get-in state [:workspace-local :edition]) snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled]) @@ -206,7 +204,7 @@ state))) ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [id (get-in state [:workspace-local :edition]) current-move (get-in state [:workspace-local :edit-path id :current-move])] (if (= same-event current-move) @@ -240,7 +238,7 @@ [index prefix] (ptk/reify ::start-move-handler ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [id (get-in state [:workspace-local :edition]) cx (d/prefix-keyword prefix :x) cy (d/prefix-keyword prefix :y) @@ -297,7 +295,7 @@ (assoc-in [:workspace-local :edit-path id :edit-mode] :draw)))) ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [mode (get-in state [:workspace-local :edit-path id :edit-mode])] (rx/concat (rx/of (undo/start-path-undo)) @@ -327,5 +325,5 @@ (update-in (st/get-path state :content) upt/split-segments #{from-p to-p} t)))) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (rx/of (changes/save-path-content {:preserve-move-to true}))))) diff --git a/frontend/src/app/main/data/workspace/path/helpers.cljs b/frontend/src/app/main/data/workspace/path/helpers.cljs index cbf9d383ca..deff63dce1 100644 --- a/frontend/src/app/main/data/workspace/path/helpers.cljs +++ b/frontend/src/app/main/data/workspace/path/helpers.cljs @@ -6,25 +6,23 @@ (ns app.main.data.workspace.path.helpers (:require - [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.main.data.workspace.path.common :as common] - [app.main.data.workspace.path.state :refer [get-path]] [app.main.streams :as ms] [app.util.path.commands :as upc] [app.util.path.subpaths :as ups] [potok.core :as ptk])) -(defn end-path-event? [{:keys [type shift] :as event}] +(defn end-path-event? [event] (or (= (ptk/type event) ::common/finish-path) (= (ptk/type event) :esc-pressed) (= :app.main.data.workspace.common/clear-edition-mode (ptk/type event)) (= :app.main.data.workspace/finalize-page (ptk/type event)) (= event :interrupt) ;; ESC - (and (ms/mouse-double-click? event)))) + (ms/mouse-double-click? event))) (defn content-center [content] @@ -35,7 +33,6 @@ (defn content->points+selrect "Given the content of a shape, calculate its points and selrect" [shape content] - (let [{:keys [flip-x flip-y]} shape transform (cond-> (:transform shape (gmt/matrix)) diff --git a/frontend/src/app/main/data/workspace/path/shortcuts.cljs b/frontend/src/app/main/data/workspace/path/shortcuts.cljs index b88ee5268a..55dbcd592d 100644 --- a/frontend/src/app/main/data/workspace/path/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/path/shortcuts.cljs @@ -22,7 +22,7 @@ (defn esc-pressed [] (ptk/reify :esc-pressed ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] ;; Not interrupt when we're editing a path (let [edition-id (or (get-in state [:workspace-drawing :object :id]) (get-in state [:workspace-local :edition])) diff --git a/frontend/src/app/main/data/workspace/path/spec.cljs b/frontend/src/app/main/data/workspace/path/spec.cljs index 96ad24fa0a..434a971170 100644 --- a/frontend/src/app/main/data/workspace/path/spec.cljs +++ b/frontend/src/app/main/data/workspace/path/spec.cljs @@ -40,8 +40,8 @@ (s/def ::content-entry (s/keys :req-un [::command] - :req-opt [::params - ::relative?])) + :opt-un [::params + ::relative?])) (s/def ::content (s/coll-of ::content-entry :kind vector?)) diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index f8059c483a..94c03259e4 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -6,17 +6,16 @@ (ns app.main.data.workspace.path.streams (:require - [app.main.data.workspace.path.helpers :as helpers] - [app.main.data.workspace.path.state :as state] [app.common.geom.point :as gpt] + [app.common.math :as mth] + [app.main.data.workspace.path.state :as state] + [app.main.snap :as snap] [app.main.store :as st] [app.main.streams :as ms] + [app.util.path.geom :as upg] [beicon.core :as rx] - [potok.core :as ptk] - [app.common.math :as mth] - [app.main.snap :as snap] [okulary.core :as l] - [app.util.path.geom :as upg])) + [potok.core :as ptk])) (defonce drag-threshold 5) @@ -50,7 +49,7 @@ (if (= value ::empty) not-drag-stream (rx/empty))))) - + (->> position-stream (rx/merge-map (fn [] to-stream))))))) @@ -107,7 +106,7 @@ (<= (- 180 rot-angle) 5))] (cond - snap-opposite-angle? + snap-opposite-angle? (let [rot-handler (gpt/rotate handler node (- 180 (* rot-sign rot-angle))) snap (gpt/to-vec handler rot-handler)] (merge position (gpt/add position snap))) @@ -122,7 +121,7 @@ (rx/map check-path-snap)))) (defn position-stream - [snap-toggled points] + [snap-toggled _points] (let [zoom (get-in @st/state [:workspace-local :zoom] 1) ;; ranges (snap/create-ranges points) d-pos (/ snap/snap-path-accuracy zoom) diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index 610897cd23..d13fbf7f70 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -6,11 +6,9 @@ (ns app.main.data.workspace.path.tools (:require - [app.common.geom.point :as gpt] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.path.changes :as changes] - [app.main.data.workspace.path.common :as common] [app.main.data.workspace.path.state :as st] [app.main.data.workspace.state-helpers :as wsh] [app.util.path.subpaths :as ups] @@ -25,7 +23,7 @@ ([points tool-fn] (ptk/reify ::process-path-tool ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [objects (wsh/lookup-page-objects state) id (st/get-path-id state) page-id (:current-page-id state) @@ -33,7 +31,7 @@ selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) points (or points selected-points)] - (when (and (not (empty? points)) (some? shape)) + (when (and (seq points) (some? shape)) (let [new-content (-> (tool-fn (:content shape) points) (ups/close-subpaths)) [rch uch] (changes/generate-path-changes objects page-id shape (:content shape) new-content)] diff --git a/frontend/src/app/main/data/workspace/path/undo.cljs b/frontend/src/app/main/data/workspace/path/undo.cljs index 74c8fcb348..8472d5c88a 100644 --- a/frontend/src/app/main/data/workspace/path/undo.cljs +++ b/frontend/src/app/main/data/workspace/path/undo.cljs @@ -9,8 +9,8 @@ [app.common.data :as d] [app.common.data.undo-stack :as u] [app.common.uuid :as uuid] - [app.main.data.workspace.path.state :as st] [app.main.data.workspace.path.changes :as changes] + [app.main.data.workspace.path.state :as st] [app.main.store :as store] [beicon.core :as rx] [okulary.core :as l] @@ -64,7 +64,7 @@ undo-stack))))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (rx/of (changes/save-path-content {:preserve-move-to true}))))) (defn redo-path [] @@ -82,7 +82,7 @@ undo-stack)))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] (rx/of (changes/save-path-content))))) (defn merge-head @@ -92,10 +92,9 @@ (ptk/reify ::add-undo-entry ptk/UpdateEvent (update [_ state] - (let [id (st/get-path-id state) - entry (make-entry state) + (let [id (st/get-path-id state) stack (get-in state [:workspace-local :edit-path id :undo-stack]) - head (u/peek stack) + head (u/peek stack) stack (-> stack (u/undo) (u/fixup head))] (-> state (d/assoc-in-when @@ -145,7 +144,7 @@ assoc :undo-lock lock :undo-stack (u/make-stack))))) - + ptk/WatchEvent (watch [_ state stream] (let [undo-lock (get-in state [:workspace-local :edit-path (st/get-path-id state) :undo-lock])] diff --git a/frontend/src/app/main/data/workspace/persistence.cljs b/frontend/src/app/main/data/workspace/persistence.cljs index 59af222a08..76609c41a9 100644 --- a/frontend/src/app/main/data/workspace/persistence.cljs +++ b/frontend/src/app/main/data/workspace/persistence.cljs @@ -8,8 +8,6 @@ (:require [app.common.data :as d] [app.common.exceptions :as ex] - [app.common.geom.point :as gpt] - [app.common.media :as cm] [app.common.pages :as cp] [app.common.spec :as us] [app.common.uuid :as uuid] @@ -26,11 +24,9 @@ [app.main.refs :as refs] [app.main.repo :as rp] [app.main.store :as st] - [app.util.avatars :as avatars] [app.util.http :as http] [app.util.i18n :as i18n :refer [tr]] [app.util.object :as obj] - [app.util.router :as rt] [app.util.time :as dt] [app.util.uri :as uu] [beicon.core :as rx] @@ -52,7 +48,7 @@ [file-id] (ptk/reify ::initialize-persistence ptk/EffectEvent - (effect [_ state stream] + (effect [_ _ stream] (let [stoper (rx/filter #(= ::finalize %) stream) forcer (rx/filter #(= ::force-persist %) stream) notifier (->> stream @@ -120,12 +116,11 @@ (ptk/reify ::persist-changes ptk/UpdateEvent (update [_ state] - (let [conj (fnil conj []) - into* (fnil into [])] + (let [into* (fnil into [])] (update-in state [:workspace-persistence :queue] into* changes))) ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [sid (:session-id state) file (get state :workspace-file) queue (get-in state [:workspace-persistence :queue] []) @@ -176,7 +171,7 @@ (us/verify ::us/uuid file-id) (ptk/reify ::persist-synchronous-changes ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [sid (:session-id state) file (get-in state [:workspace-libraries file-id]) @@ -259,7 +254,7 @@ [project-id file-id] (ptk/reify ::fetch-bundle ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (->> (rx/zip (rp/query :file {:id file-id}) (rp/query :team-users {:file-id file-id}) (rp/query :project {:id project-id}) @@ -285,7 +280,7 @@ (assoc-in state [:workspace-file :is-shared] is-shared)) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (let [params {:id id :is-shared is-shared}] (->> (rp/mutation :set-file-shared params) (rx/ignore)))))) @@ -300,7 +295,7 @@ (us/assert ::us/uuid team-id) (ptk/reify ::fetch-shared-files ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (->> (rp/query :team-shared-files {:team-id team-id}) (rx/map shared-files-fetched))))) @@ -320,7 +315,7 @@ [file-id library-id] (ptk/reify ::link-file-to-library ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (let [fetched #(assoc-in %2 [:workspace-libraries (:id %1)] %1) params {:file-id file-id :library-id library-id}] @@ -332,7 +327,7 @@ [file-id library-id] (ptk/reify ::unlink-file-from-library ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (let [unlinked #(d/dissoc-in % [:workspace-libraries library-id]) params {:file-id file-id :library-id library-id}] @@ -348,7 +343,7 @@ (->> (rx/of (-> (tubax/xml->clj text) (assoc :name name)))) - (catch :default err + (catch :default _err (rx/throw {:type :svg-parser})))) (defn fetch-svg [name uri] @@ -458,7 +453,7 @@ (s/def ::process-media-objects (s/and (s/keys :req-un [::file-id ::local?] - :opt-in [::name ::data ::uris ::mtype]) + :opt-un [::name ::data ::uris ::mtype]) (fn [props] (or (contains? props :blobs) (contains? props :uris))))) @@ -468,7 +463,7 @@ (us/assert ::process-media-objects params) (ptk/reify ::process-media-objects ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (rx/concat (rx/of (dm/show {:content (tr "media.loading") :type :info @@ -515,7 +510,7 @@ (us/assert ::clone-media-objects-params params) (ptk/reify ::clone-media-objects ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] (let [{:keys [on-success on-error] :or {on-success identity on-error identity}} (meta params) @@ -548,7 +543,7 @@ [ids] (ptk/reify ::remove-thumbnails ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ _] ;; Removes the thumbnail while it's regenerated (rx/of (dch/update-shapes ids diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index 52d36ee355..fe72eafe20 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -61,7 +61,7 @@ :height (mth/abs (- end-y start-y))}))] (ptk/reify ::handle-selection ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) stoper (->> stream (rx/filter stop?))] (rx/concat @@ -100,7 +100,7 @@ (conj selected id)))))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id)] (rx/of (dwc/expand-all-parents [id] objects))))))) @@ -136,7 +136,7 @@ (assoc-in state [:workspace-local :selected] ids)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [objects (wsh/lookup-page-objects state)] (rx/of (dwc/expand-all-parents ids objects)))))) @@ -144,7 +144,7 @@ [] (ptk/reify ::select-all ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) new-selected (let [selected-objs @@ -204,7 +204,7 @@ [preserve?] (ptk/reify ::select-shapes-by-current-selrect ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state) selected (wsh/lookup-selected state) @@ -224,25 +224,23 @@ (rx/map select-shapes)))))))) (defn select-inside-group - ([group-id position] (select-inside-group group-id position false)) - ([group-id position deep-children] - (ptk/reify ::select-inside-group - ptk/WatchEvent - (watch [_ state stream] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - group (get objects group-id) - children (map #(get objects %) (:shapes group)) + [group-id position] + (ptk/reify ::select-inside-group + ptk/WatchEvent + (watch [_ state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + group (get objects group-id) + children (map #(get objects %) (:shapes group)) - ;; We need to reverse the children because if two children - ;; overlap we want to select the one that's over (and it's - ;; in the later vector position - selected (->> children - reverse + ;; We need to reverse the children because if two children + ;; overlap we want to select the one that's over (and it's + ;; in the later vector position + selected (->> children + reverse (d/seek #(geom/has-point? % position)))] - (when selected - (rx/of (select-shape (:id selected))))))))) - + (when selected + (rx/of (select-shape (:id selected)))))))) ;; --- Duplicate Shapes (declare prepare-duplicate-change) @@ -321,7 +319,6 @@ name (dwc/generate-unique-name names (:name obj)) renamed-obj (assoc obj :id id :name name) moved-obj (geom/move renamed-obj delta) - frames (cp/select-frames objects) parent-id (or parent-id frame-id) children-changes @@ -378,7 +375,7 @@ (def duplicate-selected (ptk/reify ::duplicate-selected ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state) diff --git a/frontend/src/app/main/data/workspace/shortcuts.cljs b/frontend/src/app/main/data/workspace/shortcuts.cljs index 7d2fe9bf4f..9d8ce96180 100644 --- a/frontend/src/app/main/data/workspace/shortcuts.cljs +++ b/frontend/src/app/main/data/workspace/shortcuts.cljs @@ -10,14 +10,13 @@ [app.main.data.workspace :as dw] [app.main.data.workspace.colors :as mdc] [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.undo :as dwu] [app.main.data.workspace.drawing :as dwd] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.texts :as dwtxt] [app.main.data.workspace.transforms :as dwt] + [app.main.data.workspace.undo :as dwu] [app.main.store :as st] - [app.util.dom :as dom] - [potok.core :as ptk])) + [app.util.dom :as dom])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Shortcuts diff --git a/frontend/src/app/main/data/workspace/svg_upload.cljs b/frontend/src/app/main/data/workspace/svg_upload.cljs index f2828eb3fa..048e4bb965 100644 --- a/frontend/src/app/main/data/workspace/svg_upload.cljs +++ b/frontend/src/app/main/data/workspace/svg_upload.cljs @@ -9,7 +9,6 @@ [app.common.data :as d] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] - [app.common.geom.proportions :as gpr] [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.uuid :as uuid] @@ -18,14 +17,12 @@ [app.main.data.workspace.state-helpers :as wsh] [app.main.repo :as rp] [app.util.color :as uc] - [app.util.object :as obj] [app.util.path.parser :as upp] [app.util.svg :as usvg] [app.util.uri :as uu] [beicon.core :as rx] [cuerdas.core :as str] - [potok.core :as ptk] - [promesa.core :as p])) + [potok.core :as ptk])) (defonce default-rect {:x 0 :y 0 :width 1 :height 1 :rx 0 :ry 0}) (defonce default-circle {:r 0 :cx 0 :cy 0}) @@ -163,7 +160,7 @@ (gsh/setup-selrect)))) (defn create-path-shape [name frame-id svg-data {:keys [attrs] :as data}] - (when (and (contains? attrs :d) (not (empty? (:d attrs)) )) + (when (and (contains? attrs :d) (seq (:d attrs))) (let [svg-transform (usvg/parse-transform (:transform attrs)) path-content (upp/parse-path (:d attrs)) content (cond-> path-content @@ -387,7 +384,7 @@ [svg-data file-id position] (ptk/reify ::svg-uploaded ptk/WatchEvent - (watch [it state stream] + (watch [_ _ _] ;; Once the SVG is uploaded, we need to extract all the bitmap ;; images and upload them separatelly, then proceed to create ;; all shapes. @@ -414,7 +411,7 @@ [svg-data {:keys [x y] :as position}] (ptk/reify ::create-svg-shapes ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (try (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index f789a50ff7..d76abc46b1 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -16,17 +16,11 @@ [app.main.data.workspace.common :as dwc] [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] - [app.main.data.workspace.transforms :as dwt] [app.main.data.workspace.undo :as dwu] - [app.main.fonts :as fonts] - [app.util.object :as obj] + [app.util.router :as rt] [app.util.text-editor :as ted] [app.util.timers :as ts] - [app.util.router :as rt] [beicon.core :as rx] - [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [goog.object :as gobj] [potok.core :as ptk])) (defn update-editor @@ -42,7 +36,7 @@ [] (ptk/reify ::focus-editor ptk/EffectEvent - (effect [_ state stream] + (effect [_ state _] (when-let [editor (:workspace-editor state)] (ts/schedule #(.focus ^js editor)))))) @@ -59,7 +53,7 @@ [{:keys [id] :as shape}] (ptk/reify ::finalize-editor-state ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [content (-> (get-in state [:workspace-editor-state id]) (ted/get-editor-current-content))] @@ -88,7 +82,7 @@ decorator)))) ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] ;; We need to finalize editor on two main events: (1) when user ;; explicitly navigates to other section or page; (2) when user ;; leaves the editor. @@ -149,7 +143,7 @@ [{:keys [id attrs]}] (ptk/reify ::update-root-attrs ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [objects (wsh/lookup-page-objects state) shape (get objects id) @@ -168,7 +162,7 @@ (d/update-in-when state [:workspace-editor-state id] ted/update-editor-current-block-data attrs)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (when-not (some? (get-in state [:workspace-editor-state id])) (let [objects (wsh/lookup-page-objects state) shape (get objects id) @@ -195,7 +189,7 @@ (d/update-in-when state [:workspace-editor-state id] ted/update-editor-current-inline-styles attrs)) ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (when-not (some? (get-in state [:workspace-editor-state id])) (let [objects (wsh/lookup-page-objects state) shape (get objects id) @@ -232,7 +226,7 @@ (defn resize-text-batch [changes] (ptk/reify ::resize-text-batch ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (get-in state [:workspace-data :pages-index page-id :objects])] (if-not (every? #(contains? objects(first %)) changes) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 8f3955b104..6438317716 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -11,7 +11,6 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.math :as mth] [app.common.pages :as cp] [app.common.spec :as us] [app.main.data.workspace.changes :as dch] @@ -19,13 +18,10 @@ [app.main.data.workspace.selection :as dws] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.undo :as dwu] - [app.main.refs :as refs] [app.main.snap :as snap] - [app.main.store :as st] [app.main.streams :as ms] [app.util.path.shapes-to-path :as ups] [beicon.core :as rx] - [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk])) diff --git a/frontend/src/app/main/data/workspace/undo.cljs b/frontend/src/app/main/data/workspace/undo.cljs index a63225870b..85651e0d88 100644 --- a/frontend/src/app/main/data/workspace/undo.cljs +++ b/frontend/src/app/main/data/workspace/undo.cljs @@ -6,21 +6,10 @@ (ns app.main.data.workspace.undo (:require - [app.common.data :as d] - [app.common.geom.proportions :as gpr] - [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.common.pages.spec :as spec] [app.common.spec :as us] - [app.common.uuid :as uuid] - [app.main.worker :as uw] - [app.main.streams :as ms] - [app.util.logging :as log] - [app.util.timers :as ts] - [beicon.core :as rx] [cljs.spec.alpha :as s] - [clojure.set :as set] - [cuerdas.core :as str] [potok.core :as ptk])) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -42,7 +31,7 @@ (subvec undo (- cnt MAX-UNDO-SIZE)) undo))) -(defn- materialize-undo +(defn materialize-undo [changes index] (ptk/reify ::materialize-undo ptk/UpdateEvent @@ -51,15 +40,6 @@ (update :workspace-data cp/process-changes changes) (assoc-in [:workspace-undo :index] index))))) -(defn- reset-undo - [index] - (ptk/reify ::reset-undo - ptk/UpdateEvent - (update [_ state] - (-> state - (update :workspace-undo dissoc :undo-index) - (update-in [:workspace-undo :items] (fn [queue] (into [] (take (inc index) queue)))))))) - (defn- add-undo-entry [state entry] (if (and entry @@ -81,7 +61,7 @@ (update-in [:workspace-undo :transaction :undo-changes] #(into undo-changes %)) (update-in [:workspace-undo :transaction :redo-changes] #(into % redo-changes)))) -(defn- append-undo +(defn append-undo [entry] (us/assert ::undo-entry entry) (ptk/reify ::append-undo diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index cb5a327d54..f3ae1e8183 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -17,7 +17,6 @@ [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.embed :as embed] [app.main.ui.shapes.export :as use] - [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.group :as group] [app.main.ui.shapes.image :as image] diff --git a/frontend/src/app/main/fonts.clj b/frontend/src/app/main/fonts.clj index d36810d457..c947cdabb7 100644 --- a/frontend/src/app/main/fonts.clj +++ b/frontend/src/app/main/fonts.clj @@ -7,9 +7,9 @@ (ns app.main.fonts "A fonts loading macros." (:require - [cuerdas.core :as str] + [clojure.data.json :as json] [clojure.java.io :as io] - [clojure.data.json :as json])) + [cuerdas.core :as str])) (defn- parse-gfont-variant [variant] diff --git a/frontend/src/app/main/fonts.cljs b/frontend/src/app/main/fonts.cljs index fcdd27ce30..dbd50cde36 100644 --- a/frontend/src/app/main/fonts.cljs +++ b/frontend/src/app/main/fonts.cljs @@ -15,7 +15,6 @@ [app.util.http :as http] [app.util.logging :as log] [app.util.object :as obj] - [app.util.timers :as ts] [beicon.core :as rx] [clojure.set :as set] [cuerdas.core :as str] @@ -104,7 +103,7 @@ [url on-loaded] (let [node (create-link-element url) head (.-head ^js js/document)] - (gev/listenOnce node "load" (fn [event] + (gev/listenOnce node "load" (fn [_] (when (fn? on-loaded) (on-loaded)))) (dom/append-child! head node))) @@ -140,7 +139,7 @@ (str base ":" variants "&display=block"))) (defmethod load-font :google - [{:keys [id family variants ::on-loaded] :as font}] + [{:keys [id ::on-loaded] :as font}] (when (exists? js/window) (log/debug :action "load-font" :font-id id :backend "google") (let [url (generate-gfonts-url font)] @@ -183,11 +182,13 @@ (str/join "\n"))) (defmethod load-font :custom - [{:keys [id family variants ::on-loaded] :as font}] + [{:keys [id ::on-loaded] :as font}] (when (exists? js/window) (js/console.log "[debug:fonts]: loading custom font" id) (let [css (generate-custom-font-css font)] - (add-font-css! css)))) + (add-font-css! css) + (when (fn? on-loaded) + (on-loaded))))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; LOAD API diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index bb3f8a5cb0..22e9adebe2 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -10,11 +10,8 @@ (:require [app.common.data :as d] [app.common.pages :as cp] - [app.common.uuid :as uuid] - [app.main.constants :as c] [app.main.data.workspace.state-helpers :as wsh] [app.main.store :as st] - [beicon.core :as rx] [okulary.core :as l])) ;; ---- Global refs diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 5ba1ef0d3b..26e62bacc6 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -9,7 +9,6 @@ ["react-dom/server" :as rds] [app.config :as cfg] [app.main.exports :as exports] - [app.main.exports :as svg] [app.main.fonts :as fonts] [app.util.http :as http] [beicon.core :as rx] @@ -31,16 +30,13 @@ [])) (defn populate-images-cache - ([data] - (populate-images-cache data nil)) - - ([data {:keys [resolve-media?] :or {resolve-media? false}}] - (let [images (->> (:objects data) - (vals) - (mapcat get-image-data))] - (->> (rx/from images) - (rx/map #(cfg/resolve-file-media %)) - (rx/flat-map http/fetch-data-uri))))) + [data] + (let [images (->> (:objects data) + (vals) + (mapcat get-image-data))] + (->> (rx/from images) + (rx/map #(cfg/resolve-file-media %)) + (rx/flat-map http/fetch-data-uri)))) (defn populate-fonts-cache [data] (let [texts (->> (:objects data) diff --git a/frontend/src/app/main/repo.cljs b/frontend/src/app/main/repo.cljs index cd79ddef75..e9df95ee65 100644 --- a/frontend/src/app/main/repo.cljs +++ b/frontend/src/app/main/repo.cljs @@ -10,9 +10,7 @@ [app.common.uri :as u] [app.config :as cfg] [app.util.http :as http] - [app.util.time :as dt] - [beicon.core :as rx] - [cuerdas.core :as str])) + [beicon.core :as rx])) (defn handle-response [{:keys [status body] :as response}] @@ -86,7 +84,7 @@ ([id params] (mutation id params))) (defmethod mutation :login-with-oauth - [id {:keys [provider] :as params}] + [_ {:keys [provider] :as params}] (let [uri (u/join base-uri "api/auth/oauth/" (d/name provider)) params (dissoc params :provider)] (->> (http/send! {:method :post :uri uri :query params}) @@ -94,7 +92,7 @@ (rx/mapcat handle-response)))) (defmethod mutation :send-feedback - [id params] + [_ params] (->> (http/send! {:method :post :uri (u/join base-uri "api/feedback") :body (http/transit-data params)}) @@ -102,7 +100,7 @@ (rx/mapcat handle-response))) (defmethod query :export - [id params] + [_ params] (->> (http/send! {:method :post :uri (u/join base-uri "export") :body (http/transit-data params) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index b8e700b6bf..0479c81e8d 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -19,24 +19,24 @@ [beicon.core :as rx] [clojure.set :as set])) -(defonce ^:private snap-accuracy 5) -(defonce ^:private snap-path-accuracy 10) -(defonce ^:private snap-distance-accuracy 10) +(def ^:const snap-accuracy 5) +(def ^:const snap-path-accuracy 10) +(def ^:const snap-distance-accuracy 10) (defn- remove-from-snap-points [remove-id?] (fn [query-result] (->> query-result (map (fn [[value data]] [value (remove (comp remove-id? second) data)])) - (filter (fn [[_ data]] (not (empty? data))))))) + (filter (fn [[_ data]] (seq data)))))) (defn- flatten-to-points [query-result] - (mapcat (fn [[v data]] (map (fn [[point _]] point) data)) query-result)) + (mapcat (fn [[_ data]] (map (fn [[point _]] point) data)) query-result)) (defn- calculate-distance [query-result point coord] (->> query-result - (map (fn [[value data]] [(mth/abs (- value (coord point))) [(coord point) value]])))) + (map (fn [[value _]] [(mth/abs (- value (coord point))) [(coord point) value]])))) (defn- get-min-distance-snap [points coord] (fn [query-result] @@ -286,8 +286,9 @@ (fn [matches other] (let [matches (into {} matches) - other (into {} other) - keys (set/union (keys matches) (keys other))] + other (into {} other) + keys (set/union (set (keys matches)) + (set (keys other)))] (into {} (map (fn [key] [key @@ -308,7 +309,7 @@ min-match-coord (fn [matches] - (if (and (seq matches) (not (empty? matches))) + (if (seq matches) (->> matches (reduce get-min)) default))] diff --git a/frontend/src/app/main/store.cljs b/frontend/src/app/main/store.cljs index f4f15b9416..4fd743ecd3 100644 --- a/frontend/src/app/main/store.cljs +++ b/frontend/src/app/main/store.cljs @@ -75,12 +75,12 @@ (logjs "state" (get-in @state [:workspace-data :pages-index page-id :objects])))) (defn ^:export dump-object [name] - (let [page-id (get @state :current-page-id)] - (let [objects (get-in @state [:workspace-data :pages-index page-id :objects]) - target (or (d/seek (fn [[id shape]] (= name (:name shape))) objects) - (get objects (uuid name)))] - (->> target - (logjs "state"))))) + (let [page-id (get @state :current-page-id) + objects (get-in @state [:workspace-data :pages-index page-id :objects]) + target (or (d/seek (fn [[_ shape]] (= name (:name shape))) objects) + (get objects (uuid name)))] + (->> target + (logjs "state")))) (defn ^:export dump-tree ([] (dump-tree false false)) @@ -89,7 +89,7 @@ (let [page-id (get @state :current-page-id) objects (get-in @state [:workspace-data :pages-index page-id :objects]) components (get-in @state [:workspace-data :components]) - libraries (get-in @state [:workspace-libraries]) + libraries (get @state :workspace-libraries) root (d/seek #(nil? (:parent-id %)) (vals objects))] (letfn [(show-shape [shape-id level objects] diff --git a/frontend/src/app/main/streams.cljs b/frontend/src/app/main/streams.cljs index e71667fd6a..15a05e01ae 100644 --- a/frontend/src/app/main/streams.cljs +++ b/frontend/src/app/main/streams.cljs @@ -7,12 +7,10 @@ (ns app.main.streams "User interaction events and streams." (:require - [beicon.core :as rx] [app.main.store :as st] - [app.main.refs :as refs] - [app.common.geom.point :as gpt] [app.util.globals :as globals] - [app.util.keyboard :as kbd])) + [app.util.keyboard :as kbd] + [beicon.core :as rx])) ;; --- User Events diff --git a/frontend/src/app/main/worker.cljs b/frontend/src/app/main/worker.cljs index de564536dc..c9004a9f00 100644 --- a/frontend/src/app/main/worker.cljs +++ b/frontend/src/app/main/worker.cljs @@ -6,9 +6,7 @@ (ns app.main.worker (:require - [cljs.spec.alpha :as s] [app.config :as cfg] - [app.common.spec :as us] [app.util.worker :as uw])) (defn on-error From 30e77556db341a1201757ac1a467f5afe63a5f59 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 16:06:32 +0200 Subject: [PATCH 120/204] :sparkles: Fix linter issues on frontend (part 3). --- .clj-kondo/config.edn | 1 - frontend/src/app/main/ui/auth.cljs | 23 ++---- frontend/src/app/main/ui/auth/login.cljs | 5 +- frontend/src/app/main/ui/auth/recovery.cljs | 23 +++--- .../app/main/ui/auth/recovery_request.cljs | 6 +- frontend/src/app/main/ui/auth/register.cljs | 13 ++-- .../src/app/main/ui/auth/verify_token.cljs | 11 +-- frontend/src/app/main/ui/comments.cljs | 9 +-- .../app/main/ui/components/color_bullet.cljs | 6 +- .../app/main/ui/components/color_input.cljs | 9 +-- .../app/main/ui/components/context_menu.cljs | 9 ++- .../app/main/ui/components/copy_button.cljs | 13 ++-- .../src/app/main/ui/components/dropdown.cljs | 11 ++- .../main/ui/components/editable_label.cljs | 3 +- .../main/ui/components/editable_select.cljs | 10 +-- .../app/main/ui/components/file_uploader.cljs | 5 +- .../app/main/ui/components/fullscreen.cljs | 3 +- .../app/main/ui/components/numeric_input.cljs | 2 +- .../src/app/main/ui/components/select.cljs | 11 +-- .../app/main/ui/components/tab_container.cljs | 2 +- frontend/src/app/main/ui/confirm.cljs | 8 +-- frontend/src/app/main/ui/cursors.clj | 4 +- frontend/src/app/main/ui/cursors.cljs | 9 +-- frontend/src/app/main/ui/dashboard.cljs | 17 ++--- .../src/app/main/ui/dashboard/comments.cljs | 20 +----- frontend/src/app/main/ui/dashboard/files.cljs | 11 +-- frontend/src/app/main/ui/dashboard/fonts.cljs | 71 ++++++++----------- frontend/src/app/main/ui/dashboard/grid.cljs | 15 ++-- .../src/app/main/ui/dashboard/import.cljs | 1 - .../src/app/main/ui/dashboard/libraries.cljs | 5 +- .../app/main/ui/dashboard/project_menu.cljs | 4 +- .../src/app/main/ui/dashboard/projects.cljs | 6 +- .../src/app/main/ui/dashboard/search.cljs | 1 - .../src/app/main/ui/dashboard/sidebar.cljs | 32 +++------ frontend/src/app/main/ui/dashboard/team.cljs | 15 ++-- .../src/app/main/ui/dashboard/team_form.cljs | 10 +-- frontend/src/app/main/ui/handoff.cljs | 4 -- .../src/app/main/ui/handoff/attributes.cljs | 16 ++--- .../app/main/ui/handoff/attributes/blur.cljs | 9 ++- .../main/ui/handoff/attributes/common.cljs | 17 ++--- .../app/main/ui/handoff/attributes/fill.cljs | 20 +++--- .../app/main/ui/handoff/attributes/image.cljs | 20 +++--- 42 files changed, 183 insertions(+), 307 deletions(-) diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 50e8689fda..c1fd2acc7f 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -15,7 +15,6 @@ "app/util/perf.cljs" "app/util/import/.*" "app/worker/.*" - "app/main/ui.*" "app/libs/.*" "app/main/data/workspace/path/selection.cljs" "app/main/data/workspace/transforms.cljs" diff --git a/frontend/src/app/main/ui/auth.cljs b/frontend/src/app/main/ui/auth.cljs index 96996970e5..5cf1a715bd 100644 --- a/frontend/src/app/main/ui/auth.cljs +++ b/frontend/src/app/main/ui/auth.cljs @@ -6,38 +6,27 @@ (ns app.main.ui.auth (:require - [app.common.uuid :as uuid] - [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.auth.login :refer [login-page]] [app.main.ui.auth.recovery :refer [recovery-page]] [app.main.ui.auth.recovery-request :refer [recovery-request-page]] [app.main.ui.auth.register :refer [register-page register-success-page register-validate-page]] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.forms :as fm] - [app.util.i18n :as i18n :refer [t]] - [app.util.router :as rt] - [app.util.timers :as ts] - [beicon.core :as rx] - [cljs.spec.alpha :as s] + [app.util.i18n :as i18n :refer [tr]] [rumext.alpha :as mf])) (mf/defc auth [{:keys [route] :as props}] (let [section (get-in route [:data :name]) - locale (mf/deref i18n/locale) params (:query-params route)] (mf/use-effect - #(dom/set-html-title (t locale "title.default"))) + #(dom/set-html-title (tr "title.default"))) [:div.auth [:section.auth-sidebar - [:a.logo {:href "#/"} i/logo] - [:span.tagline (t locale "auth.sidebar-tagline")]] + [:a.logo {:href "https://penpot.app"} i/logo] + [:span.tagline (tr "auth.sidebar-tagline")]] [:section.auth-content (case section @@ -54,10 +43,10 @@ [:& login-page {:params params}] :auth-recovery-request - [:& recovery-request-page {:locale locale}] + [:& recovery-request-page] :auth-recovery - [:& recovery-page {:locale locale :params params}]) + [:& recovery-page {:params params}]) [:div.terms-login [:a {:href "https://penpot.app/terms.html" :target "_blank"} "Terms of service"] diff --git a/frontend/src/app/main/ui/auth/login.cljs b/frontend/src/app/main/ui/auth/login.cljs index c095f3a034..3f8e89e662 100644 --- a/frontend/src/app/main/ui/auth/login.cljs +++ b/frontend/src/app/main/ui/auth/login.cljs @@ -16,8 +16,7 @@ [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] [app.util.dom :as dom] - [app.util.i18n :refer [tr t]] - [app.util.object :as obj] + [app.util.i18n :refer [tr]] [app.util.router :as rt] [beicon.core :as rx] [cljs.spec.alpha :as s] @@ -74,7 +73,7 @@ on-submit (mf/use-callback (mf/deps form) - (fn [event] + (fn [_] (reset! error nil) (let [params (with-meta (:clean-data @form) {:on-error on-error})] diff --git a/frontend/src/app/main/ui/auth/recovery.cljs b/frontend/src/app/main/ui/auth/recovery.cljs index 7040059867..e18471d2b3 100644 --- a/frontend/src/app/main/ui/auth/recovery.cljs +++ b/frontend/src/app/main/ui/auth/recovery.cljs @@ -11,12 +11,9 @@ [app.main.data.users :as du] [app.main.store :as st] [app.main.ui.components.forms :as fm] - [app.main.ui.icons :as i] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t tr]] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [cljs.spec.alpha :as s] - [cuerdas.core :as str] [rumext.alpha :as mf])) (s/def ::password-1 ::us/not-empty-string) @@ -40,7 +37,7 @@ (assoc :password-1 {:message "errors.password-too-short"})))) (defn- on-error - [form error] + [_form _error] (st/emit! (dm/error (tr "auth.notifications.invalid-token-error")))) (defn- on-success @@ -49,7 +46,7 @@ (rt/nav :auth-login))) (defn- on-submit - [form event] + [form _event] (let [mdata {:on-error on-error :on-success on-success} params {:token (get-in @form [:clean-data :token]) @@ -57,7 +54,7 @@ (st/emit! (du/recover-profile (with-meta params mdata))))) (mf/defc recovery-form - [{:keys [locale params] :as props}] + [{:keys [params] :as props}] (let [form (fm/use-form :spec ::recovery-form :validators [password-equality] :initial params)] @@ -66,28 +63,28 @@ [:div.fields-row [:& fm/input {:type "password" :name :password-1 - :label (t locale "auth.new-password")}]] + :label (tr "auth.new-password")}]] [:div.fields-row [:& fm/input {:type "password" :name :password-2 - :label (t locale "auth.confirm-password")}]] + :label (tr "auth.confirm-password")}]] [:& fm/submit-button - {:label (t locale "auth.recovery-submit")}]])) + {:label (tr "auth.recovery-submit")}]])) ;; --- Recovery Request Page (mf/defc recovery-page - [{:keys [locale params] :as props}] + [{:keys [params] :as props}] [:section.generic-form [:div.form-container [:h1 "Forgot your password?"] [:div.subtitle "Please enter your new password"] - [:& recovery-form {:locale locale :params params}] + [:& recovery-form {:params params}] [:div.links [:div.link-entry [:a {:on-click #(st/emit! (rt/nav :auth-login))} - (t locale "profile.recovery.go-to-login")]]]]]) + (tr "profile.recovery.go-to-login")]]]]]) diff --git a/frontend/src/app/main/ui/auth/recovery_request.cljs b/frontend/src/app/main/ui/auth/recovery_request.cljs index 5c7cb5c2d1..e5a16089f2 100644 --- a/frontend/src/app/main/ui/auth/recovery_request.cljs +++ b/frontend/src/app/main/ui/auth/recovery_request.cljs @@ -12,12 +12,10 @@ [app.main.store :as st] [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [tr t]] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [beicon.core :as rx] [cljs.spec.alpha :as s] - [cuerdas.core :as str] [rumext.alpha :as mf])) (s/def ::email ::us/email) @@ -30,7 +28,7 @@ on-success (mf/use-callback - (fn [data] + (fn [_ _] (reset! submitted false) (st/emit! (dm/info (tr "auth.notifications.recovery-token-sent")) (rt/nav :auth-login)))) diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 4e968b4fe3..d80405a5eb 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -8,21 +8,18 @@ (:require [app.common.spec :as us] [app.config :as cf] - [app.main.data.users :as du] [app.main.data.messages :as dm] - [app.main.store :as st] + [app.main.data.users :as du] [app.main.repo :as rp] + [app.main.store :as st] + [app.main.ui.auth.login :as login] [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] - [app.main.ui.auth.login :as login] - [app.util.dom :as dom] - [app.util.i18n :refer [tr t]] + [app.util.i18n :refer [tr]] [app.util.router :as rt] - [app.util.timers :as tm] [beicon.core :as rx] [cljs.spec.alpha :as s] - [cuerdas.core :as str] [rumext.alpha :as mf])) (mf/defc demo-warning @@ -84,7 +81,7 @@ on-submit (mf/use-callback - (fn [form event] + (fn [form _event] (reset! submitted? true) (let [params (:clean-data @form)] (->> (rp/mutation :prepare-register-profile params) diff --git a/frontend/src/app/main/ui/auth/verify_token.cljs b/frontend/src/app/main/ui/auth/verify_token.cljs index 3e0bf5323d..119a4cd404 100644 --- a/frontend/src/app/main/ui/auth/verify_token.cljs +++ b/frontend/src/app/main/ui/auth/verify_token.cljs @@ -6,23 +6,16 @@ (ns app.main.ui.auth.verify-token (:require - [app.common.uuid :as uuid] [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.auth.login :refer [login-page]] - [app.main.ui.auth.recovery :refer [recovery-page]] - [app.main.ui.auth.recovery-request :refer [recovery-request-page]] - [app.main.ui.auth.register :refer [register-page]] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.forms :as fm] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.timers :as ts] [beicon.core :as rx] - [cljs.spec.alpha :as s] [rumext.alpha :as mf])) (defmulti handle-token (fn [token] (:iss token))) @@ -34,7 +27,7 @@ (st/emit! (du/login-from-token data)))) (defmethod handle-token :change-email - [data] + [_data] (let [msg (tr "dashboard.notifications.email-changed-successfully")] (ts/schedule 100 #(st/emit! (dm/success msg))) (st/emit! (rt/nav :settings-profile) @@ -57,7 +50,7 @@ (st/emit! (rt/nav :auth-register {} {:invitation-token token}))))) (defmethod handle-token :default - [tdata] + [_tdata] (st/emit! (rt/nav :auth-login) (dm/warn (tr "errors.unexpected-token")))) diff --git a/frontend/src/app/main/ui/comments.cljs b/frontend/src/app/main/ui/comments.cljs index a4a98f35e6..3ae6832382 100644 --- a/frontend/src/app/main/ui/comments.cljs +++ b/frontend/src/app/main/ui/comments.cljs @@ -11,16 +11,13 @@ [app.main.data.modal :as modal] [app.main.refs :as refs] [app.main.store :as st] - [app.main.streams :as ms] [app.main.ui.components.dropdown :refer [dropdown]] - [app.main.ui.context :as ctx] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t tr]] + [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] [app.util.object :as obj] [app.util.time :as dt] - [cuerdas.core :as str] [okulary.core :as l] [rumext.alpha :as mf])) @@ -105,7 +102,7 @@ :on-focus on-focus :on-change on-change}] (when (or @show-buttons? - (not (empty? @content))) + (seq @content)) [:div.buttons [:input.btn-primary {:type "button" :value "Post" :on-click on-submit}] [:input.btn-secondary {:type "button" :value "Cancel" :on-click on-cancel}]])])) @@ -323,7 +320,7 @@ (mf/defc thread-bubble {::mf/wrap [mf/memo]} - [{:keys [thread zoom open? on-click] :as params}] + [{:keys [thread zoom on-click] :as params}] (let [pos (:position thread) pos-x (* (:x pos) zoom) pos-y (* (:y pos) zoom) diff --git a/frontend/src/app/main/ui/components/color_bullet.cljs b/frontend/src/app/main/ui/components/color_bullet.cljs index 54872e605d..89f22128bb 100644 --- a/frontend/src/app/main/ui/components/color_bullet.cljs +++ b/frontend/src/app/main/ui/components/color_bullet.cljs @@ -6,9 +6,9 @@ (ns app.main.ui.components.color-bullet (:require - [rumext.alpha :as mf] + [app.util.color :as uc] [app.util.i18n :as i18n :refer [tr]] - [app.util.color :as uc])) + [rumext.alpha :as mf])) (mf/defc color-bullet [{:keys [color on-click]}] (if (uc/multiple? color) @@ -31,7 +31,7 @@ (mf/defc color-name [{:keys [color size on-click on-double-click]}] (let [color (if (string? color) {:color color :opacity 1} color) - {:keys [name color opacity gradient]} color + {:keys [name color gradient]} color color-str (or name color (gradient-type->string (:type gradient)))] (when (or (not size) (= size :big)) [:span.color-text {:on-click #(when on-click (on-click %)) diff --git a/frontend/src/app/main/ui/components/color_input.cljs b/frontend/src/app/main/ui/components/color_input.cljs index 8b851e5465..254a6c25f2 100644 --- a/frontend/src/app/main/ui/components/color_input.cljs +++ b/frontend/src/app/main/ui/components/color_input.cljs @@ -6,16 +6,11 @@ (ns app.main.ui.components.color-input (:require - [app.common.data :as d] - [app.common.math :as math] - [app.common.spec :as us] - [app.common.uuid :as uuid] [app.util.color :as uc] [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] [app.util.object :as obj] - [app.util.simple-math :as sm] - [app.util.i18n :as i18n :refer [tr]] [rumext.alpha :as mf])) (mf/defc color-input @@ -80,7 +75,7 @@ handle-blur (mf/use-callback (mf/deps parse-value apply-value update-input) - (fn [event] + (fn [_] (let [new-value (parse-value)] (if new-value (apply-value new-value) diff --git a/frontend/src/app/main/ui/components/context_menu.cljs b/frontend/src/app/main/ui/components/context_menu.cljs index 16b1ccf4c1..e8198377dc 100644 --- a/frontend/src/app/main/ui/components/context_menu.cljs +++ b/frontend/src/app/main/ui/components/context_menu.cljs @@ -6,13 +6,12 @@ (ns app.main.ui.components.context-menu (:require - [rumext.alpha :as mf] - [goog.object :as gobj] [app.main.ui.components.dropdown :refer [dropdown']] [app.main.ui.icons :as i] - [app.common.uuid :as uuid] [app.util.dom :as dom] - [app.util.object :as obj])) + [app.util.object :as obj] + [goog.object :as gobj] + [rumext.alpha :as mf])) (mf/defc context-menu {::mf/wrap-props false} @@ -52,7 +51,7 @@ (- node-height) 0)] - (if (not= target-offset (:offset @local)) + (when (not= target-offset (:offset @local)) (swap! local assoc :offset target-offset)))))) enter-submenu diff --git a/frontend/src/app/main/ui/components/copy_button.cljs b/frontend/src/app/main/ui/components/copy_button.cljs index 8436484410..6911ba5ae4 100644 --- a/frontend/src/app/main/ui/components/copy_button.cljs +++ b/frontend/src/app/main/ui/components/copy_button.cljs @@ -6,11 +6,11 @@ (ns app.main.ui.components.copy-button (:require - [beicon.core :as rx] - [rumext.alpha :as mf] - [app.util.webapi :as wapi] + [app.main.ui.icons :as i] [app.util.timers :as timers] - [app.main.ui.icons :as i])) + [app.util.webapi :as wapi] + [beicon.core :as rx] + [rumext.alpha :as mf])) (mf/defc copy-button [{:keys [data]}] (let [just-copied (mf/use-state false)] @@ -24,9 +24,8 @@ [:button.copy-button {:on-click #(when-not @just-copied - (do - (reset! just-copied true) - (wapi/write-to-clipboard data)))} + (reset! just-copied true) + (wapi/write-to-clipboard data))} (if @just-copied i/tick i/copy)])) diff --git a/frontend/src/app/main/ui/components/dropdown.cljs b/frontend/src/app/main/ui/components/dropdown.cljs index 54e33925fc..114617fa5b 100644 --- a/frontend/src/app/main/ui/components/dropdown.cljs +++ b/frontend/src/app/main/ui/components/dropdown.cljs @@ -1,11 +1,16 @@ +;; 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.main.ui.components.dropdown (:require - [rumext.alpha :as mf] - [app.common.uuid :as uuid] [app.util.dom :as dom] [app.util.keyboard :as kbd] [goog.events :as events] - [goog.object :as gobj]) + [goog.object :as gobj] + [rumext.alpha :as mf]) (:import goog.events.EventType)) (mf/defc dropdown' diff --git a/frontend/src/app/main/ui/components/editable_label.cljs b/frontend/src/app/main/ui/components/editable_label.cljs index 60e5feea4d..02907d5abd 100644 --- a/frontend/src/app/main/ui/components/editable_label.cljs +++ b/frontend/src/app/main/ui/components/editable_label.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.components.editable-label (:require [app.main.ui.icons :as i] - [app.util.data :refer [classnames]] [app.util.dom :as dom] [app.util.keyboard :as kbd] [app.util.timers :as timers] @@ -32,7 +31,7 @@ cancel-editing (fn [] (stop-editing) (when on-cancel (on-cancel))) - on-dbl-click (fn [e] (when (not disable-dbl-click?) (start-editing))) + on-dbl-click (fn [_] (when (not disable-dbl-click?) (start-editing))) on-key-up (fn [e] (cond (kbd/esc? e) diff --git a/frontend/src/app/main/ui/components/editable_select.cljs b/frontend/src/app/main/ui/components/editable_select.cljs index 27fe4d897e..40c13e7702 100644 --- a/frontend/src/app/main/ui/components/editable_select.cljs +++ b/frontend/src/app/main/ui/components/editable_select.cljs @@ -6,13 +6,13 @@ (ns app.main.ui.components.editable-select (:require - [rumext.alpha :as mf] - [app.common.uuid :as uuid] [app.common.data :as d] + [app.common.uuid :as uuid] + [app.main.ui.components.dropdown :refer [dropdown]] + [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.timers :as timers] - [app.main.ui.icons :as i] - [app.main.ui.components.dropdown :refer [dropdown]])) + [rumext.alpha :as mf])) (mf/defc editable-select [{:keys [value type options class on-change placeholder]}] (let [state (mf/use-state {:id (uuid/next) @@ -24,7 +24,7 @@ open-dropdown #(swap! state assoc :is-open? true) close-dropdown #(swap! state assoc :is-open? false) select-item (fn [value] - (fn [event] + (fn [_] (swap! state assoc :current-value value) (when on-change (on-change value)))) diff --git a/frontend/src/app/main/ui/components/file_uploader.cljs b/frontend/src/app/main/ui/components/file_uploader.cljs index 1151976143..8aa5c55b53 100644 --- a/frontend/src/app/main/ui/components/file_uploader.cljs +++ b/frontend/src/app/main/ui/components/file_uploader.cljs @@ -6,10 +6,9 @@ (ns app.main.ui.components.file-uploader (:require - [rumext.alpha :as mf] - [app.main.data.workspace :as dw] [app.main.store :as st] - [app.util.dom :as dom])) + [app.util.dom :as dom] + [rumext.alpha :as mf])) (mf/defc file-uploader {::mf/forward-ref true} diff --git a/frontend/src/app/main/ui/components/fullscreen.cljs b/frontend/src/app/main/ui/components/fullscreen.cljs index baf9ceca2f..21ee1f29b7 100644 --- a/frontend/src/app/main/ui/components/fullscreen.cljs +++ b/frontend/src/app/main/ui/components/fullscreen.cljs @@ -8,7 +8,6 @@ (:require [app.util.dom :as dom] [app.util.webapi :as wapi] - [beicon.core :as rx] [rumext.alpha :as mf])) (def fullscreen-context @@ -21,7 +20,7 @@ change (mf/use-callback - (fn [event] + (fn [_] (let [val (dom/fullscreen?)] (reset! state val)))) diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index b460d7b6c4..b1cf44c10e 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -132,7 +132,7 @@ handle-blur (mf/use-callback (mf/deps parse-value apply-value update-input) - (fn [event] + (fn [_] (let [new-value (parse-value)] (if new-value (apply-value new-value) diff --git a/frontend/src/app/main/ui/components/select.cljs b/frontend/src/app/main/ui/components/select.cljs index c88138c8f1..d651313177 100644 --- a/frontend/src/app/main/ui/components/select.cljs +++ b/frontend/src/app/main/ui/components/select.cljs @@ -6,10 +6,10 @@ (ns app.main.ui.components.select (:require - [rumext.alpha :as mf] [app.common.uuid :as uuid] + [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] - [app.main.ui.components.dropdown :refer [dropdown]])) + [rumext.alpha :as mf])) (mf/defc select [{:keys [default-value options class on-change]}] (let [state (mf/use-state {:id (uuid/next) @@ -17,9 +17,10 @@ :current-value default-value}) open-dropdown #(swap! state assoc :is-open? true) close-dropdown #(swap! state assoc :is-open? false) - select-item (fn [value] (fn [event] - (swap! state assoc :current-value value) - (when on-change (on-change value)))) + select-item (fn [value] + (fn [_] + (swap! state assoc :current-value value) + (when on-change (on-change value)))) as-key-value (fn [item] (if (map? item) [(:value item) (:label item)] [item item])) value->label (into {} (->> options (map as-key-value))) ] diff --git a/frontend/src/app/main/ui/components/tab_container.cljs b/frontend/src/app/main/ui/components/tab_container.cljs index 25a375fc43..126acff2dc 100644 --- a/frontend/src/app/main/ui/components/tab_container.cljs +++ b/frontend/src/app/main/ui/components/tab_container.cljs @@ -8,7 +8,7 @@ (:require [rumext.alpha :as mf])) (mf/defc tab-element - [{:keys [children id title]}] + [{:keys [children]}] [:div.tab-element [:div.tab-element-content children]]) diff --git a/frontend/src/app/main/ui/confirm.cljs b/frontend/src/app/main/ui/confirm.cljs index a622236018..030aafd203 100644 --- a/frontend/src/app/main/ui/confirm.cljs +++ b/frontend/src/app/main/ui/confirm.cljs @@ -57,10 +57,10 @@ (let [on-keydown (fn [event] (when (k/enter? event) - (do (dom/prevent-default event) - (dom/stop-propagation event) - (st/emit! (modal/hide)) - (on-accept props)))) + (dom/prevent-default event) + (dom/stop-propagation event) + (st/emit! (modal/hide)) + (on-accept props))) key (events/listen js/document EventType.KEYDOWN on-keydown)] #(events/unlistenByKey key)))) diff --git a/frontend/src/app/main/ui/cursors.clj b/frontend/src/app/main/ui/cursors.clj index 5d0bedbe1b..0b63e5f579 100644 --- a/frontend/src/app/main/ui/cursors.clj +++ b/frontend/src/app/main/ui/cursors.clj @@ -6,9 +6,9 @@ (ns app.main.ui.cursors (:require + [app.common.uri :as u] [clojure.java.io :as io] - [cuerdas.core :as str] - [app.common.uri :as u])) + [cuerdas.core :as str])) (def cursor-folder "images/cursors") diff --git a/frontend/src/app/main/ui/cursors.cljs b/frontend/src/app/main/ui/cursors.cljs index 5748bb54fe..c52be68919 100644 --- a/frontend/src/app/main/ui/cursors.cljs +++ b/frontend/src/app/main/ui/cursors.cljs @@ -6,9 +6,10 @@ (ns app.main.ui.cursors (:require-macros [app.main.ui.cursors :refer [cursor-ref cursor-fn]]) - (:require [rumext.alpha :as mf] - [cuerdas.core :as str] - [app.util.timers :as ts])) + (:require + [app.util.timers :as ts] + [cuerdas.core :as str] + [rumext.alpha :as mf])) ;; Static cursors (def comments (cursor-ref :comments 0 2 20)) @@ -40,7 +41,7 @@ (mf/defc debug-preview {::mf/wrap-props false} - [props] + [] (let [rotation (mf/use-state 0)] (mf/use-effect (fn [] (ts/interval 100 #(reset! rotation inc)))) diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index dc14df9cd6..ef87216974 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -6,9 +6,7 @@ (ns app.main.ui.dashboard (:require - [app.common.exceptions :as ex] [app.common.spec :as us] - [app.common.uuid :as uuid] [app.config :as cf] [app.main.data.dashboard :as dd] [app.main.data.modal :as modal] @@ -16,19 +14,13 @@ [app.main.store :as st] [app.main.ui.context :as ctx] [app.main.ui.dashboard.files :refer [files-section]] + [app.main.ui.dashboard.fonts :refer [fonts-page font-providers-page]] [app.main.ui.dashboard.libraries :refer [libraries-page]] [app.main.ui.dashboard.projects :refer [projects-section]] - [app.main.ui.dashboard.fonts :refer [fonts-page font-providers-page]] [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]] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]] - [app.util.router :as rt] [app.util.timers :as tm] - [beicon.core :as rx] - [cuerdas.core :as str] - [okulary.core :as l] [rumext.alpha :as mf])) (defn ^boolean uuid-str? @@ -37,9 +29,8 @@ (boolean (re-seq us/uuid-rx s)))) (defn- parse-params - [route profile] - (let [route-name (get-in route [:data :name]) - search-term (get-in route [:params :query :search-term]) + [route] + (let [search-term (get-in route [:params :query :search-term]) team-id (get-in route [:params :path :team-id]) project-id (get-in route [:params :path :project-id])] (cond-> @@ -87,7 +78,7 @@ [{:keys [route] :as props}] (let [profile (mf/deref refs/profile) section (get-in route [:data :name]) - params (parse-params route profile) + params (parse-params route) project-id (:project-id params) team-id (:team-id params) diff --git a/frontend/src/app/main/ui/dashboard/comments.cljs b/frontend/src/app/main/ui/dashboard/comments.cljs index 48703fdf25..ce2f8b4f53 100644 --- a/frontend/src/app/main/ui/dashboard/comments.cljs +++ b/frontend/src/app/main/ui/dashboard/comments.cljs @@ -6,29 +6,15 @@ (ns app.main.ui.dashboard.comments (:require - [okulary.core :as l] - [app.common.data :as d] - [app.common.spec :as us] - [app.config :as cfg] - [app.main.data.dashboard :as dd] - [app.main.data.workspace :as dw] - [app.main.data.workspace.comments :as dwcm] [app.main.data.comments :as dcm] + [app.main.data.workspace.comments :as dwcm] [app.main.refs :as refs] - [app.main.repo :as rp] [app.main.store :as st] - [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.comments :as cmt] + [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t tr]] - [app.util.object :as obj] - [app.util.router :as rt] - [app.util.time :as dt] - [app.util.timers :as tm] - [beicon.core :as rx] - [cljs.spec.alpha :as s] - [cuerdas.core :as str] + [app.util.i18n :as i18n :refer [tr]] [rumext.alpha :as mf])) (mf/defc comments-section diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs index dc7b44e8b4..2ec4874139 100644 --- a/frontend/src/app/main/ui/dashboard/files.cljs +++ b/frontend/src/app/main/ui/dashboard/files.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.dashboard.files (:require [app.main.data.dashboard :as dd] - [app.main.data.modal :as modal] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.dashboard.grid :refer [grid]] @@ -16,18 +15,12 @@ [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [app.util.keyboard :as kbd] - [app.util.router :as rt] - [okulary.core :as l] [rumext.alpha :as mf])) (mf/defc header - [{:keys [team project] :as props}] + [{:keys [project] :as props}] (let [local (mf/use-state {:menu-open false :edition false}) - project-id (:id project) - team-id (:id team) - on-menu-click (mf/use-callback (fn [event] @@ -83,7 +76,7 @@ (if (:is-pinned project) i/pin-fill i/pin)] - + [:div.icon.tooltip.tooltip-bottom {:on-click on-menu-click :alt (tr "dashboard.options")} i/actions]]])) diff --git a/frontend/src/app/main/ui/dashboard/fonts.cljs b/frontend/src/app/main/ui/dashboard/fonts.cljs index 9363698bf4..72d65eb27f 100644 --- a/frontend/src/app/main/ui/dashboard/fonts.cljs +++ b/frontend/src/app/main/ui/dashboard/fonts.cljs @@ -6,27 +6,22 @@ (ns app.main.ui.dashboard.fonts (:require - [app.common.data :as d] [app.common.media :as cm] - [app.common.uuid :as uuid] - [app.main.data.dashboard :as dd] [app.main.data.fonts :as df] [app.main.data.modal :as modal] - [app.main.ui.components.file-uploader :refer [file-uploader]] - [app.main.ui.components.context-menu :refer [context-menu]] - [app.main.store :as st] - [app.main.repo :as rp] [app.main.refs :as refs] + [app.main.repo :as rp] + [app.main.store :as st] + [app.main.ui.components.context-menu :refer [context-menu]] + [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [app.util.logging :as log] [app.util.keyboard :as kbd] - [app.util.router :as rt] - [app.util.webapi :as wa] - [cuerdas.core :as str] + [app.util.logging :as log] + ;; [app.util.router :as rt] [beicon.core :as rx] - [okulary.core :as l] + [cuerdas.core :as str] [rumext.alpha :as mf])) (log/set-level! :trace) @@ -47,29 +42,29 @@ (mf/defc header {::mf/wrap [mf/memo]} [{:keys [section team] :as props}] - (let [go-fonts - (mf/use-callback - (mf/deps team) - (st/emitf (rt/nav :dashboard-fonts {:team-id (:id team)}))) + ;; (let [go-fonts + ;; (mf/use-callback + ;; (mf/deps team) + ;; (st/emitf (rt/nav :dashboard-fonts {:team-id (:id team)}))) - go-providers - (mf/use-callback - (mf/deps team) - (st/emitf (rt/nav :dashboard-font-providers {:team-id (:id team)})))] + ;; go-providers + ;; (mf/use-callback + ;; (mf/deps team) + ;; (st/emitf (rt/nav :dashboard-font-providers {:team-id (:id team)})))] - (use-set-page-title team section) + (use-set-page-title team section) - [:header.dashboard-header - [:div.dashboard-title - [:h1 (tr "labels.fonts")]] - [:nav - #_[:ul - [:li {:class (when (= section :fonts) "active")} - [:a {:on-click go-fonts} (tr "labels.custom-fonts")]] - [:li {:class (when (= section :providers) "active")} - [:a {:on-click go-providers} (tr "labels.font-providers")]]]] + [:header.dashboard-header + [:div.dashboard-title + [:h1 (tr "labels.fonts")]] + [:nav + #_[:ul + [:li {:class (when (= section :fonts) "active")} + [:a {:on-click go-fonts} (tr "labels.custom-fonts")]] + [:li {:class (when (= section :providers) "active")} + [:a {:on-click go-providers} (tr "labels.font-providers")]]]] - [:div]])) + [:div]]) (mf/defc font-variant-display-name [{:keys [variant]}] @@ -88,9 +83,6 @@ on-click (mf/use-callback #(dom/click (mf/ref-val input-ref))) - font-key-fn - (mf/use-callback (juxt :font-family :font-weight :font-style)) - on-selected (mf/use-callback (mf/deps team installed-fonts) @@ -190,7 +182,7 @@ (reset! state (dom/get-target-val event))) on-save - (fn [event] + (fn [_] (let [font-family @state] (when-not (str/blank? font-family) (st/emit! (df/update-font @@ -204,7 +196,7 @@ (on-save event))) on-cancel - (fn [event] + (fn [_] (reset! edit? false) (reset! state (:font-family font))) @@ -221,8 +213,7 @@ :title (tr "modals.delete-font.title") :message (tr "modals.delete-font.message") :accept-label (tr "labels.delete") - :on-accept (fn [props] - (delete-font-fn))}))) + :on-accept (fn [_props] (delete-font-fn))}))) on-delete-variant (fn [id] @@ -231,7 +222,7 @@ :title (tr "modals.delete-font-variant.title") :message (tr "modals.delete-font-variant.message") :accept-label (tr "labels.delete") - :on-accept (fn [props] + :on-accept (fn [_props] (delete-variant-fn id))})))] [:div.font-item.table-row @@ -276,7 +267,7 @@ (mf/defc installed-fonts - [{:keys [team fonts] :as props}] + [{:keys [fonts] :as props}] (let [sterm (mf/use-state "") matches? diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index 3afa25470d..c15b2e6641 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -7,11 +7,8 @@ (ns app.main.ui.dashboard.grid (:require [app.common.math :as mth] - [app.common.uuid :as uuid] - [app.config :as cfg] [app.main.data.dashboard :as dd] [app.main.data.messages :as dm] - [app.main.data.modal :as modal] [app.main.fonts :as fonts] [app.main.refs :as refs] [app.main.store :as st] @@ -23,12 +20,10 @@ [app.util.dom.dnd :as dnd] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] - [app.util.router :as rt] [app.util.time :as dt] [app.util.timers :as ts] [app.util.webapi :as wapi] [beicon.core :as rx] - [cuerdas.core :as str] [rumext.alpha :as mf])) ;; --- Grid Item Thumbnail @@ -59,7 +54,7 @@ (str (tr "ds.updated-at" time)))) (defn create-counter-element - [element file-count] + [_element file-count] (let [counter-el (dom/create-element "div")] (dom/set-property! counter-el "class" "drag-counter") (dom/set-text! counter-el (str file-count)) @@ -215,7 +210,7 @@ [:div.text (tr "dashboard.loading-files")]]) (mf/defc grid - [{:keys [id opts files] :as props}] + [{:keys [files] :as props}] [:section.dashboard-grid (cond (nil? files) @@ -233,7 +228,7 @@ [:& empty-placeholder])]) (mf/defc line-grid-row - [{:keys [files team-id selected-files on-load-more dragging?] :as props}] + [{:keys [files selected-files on-load-more dragging?] :as props}] (let [rowref (mf/use-ref) width (mf/use-state nil) @@ -288,7 +283,7 @@ (tr "dashboard.show-all-files")]])])) (mf/defc line-grid - [{:keys [project-id team-id opts files on-load-more] :as props}] + [{:keys [project-id team-id files on-load-more] :as props}] (let [dragging? (mf/use-state false) selected-files (mf/deref refs/dashboard-selected-files) @@ -326,7 +321,7 @@ on-drop (mf/use-callback (mf/deps files selected-files) - (fn [e] + (fn [_] (reset! dragging? false) (when (not= selected-project project-id) (let [data {:ids (into #{} (keys selected-files)) diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index c540a744aa..e3f63b38d1 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.dashboard.import (:require [app.main.ui.components.file-uploader :refer [file-uploader]] - [app.main.ui.icons :as i] [app.main.worker :as uw] [app.util.dom :as dom] [app.util.logging :as log] diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs index e61d4bcd4d..7130c97216 100644 --- a/frontend/src/app/main/ui/dashboard/libraries.cljs +++ b/frontend/src/app/main/ui/dashboard/libraries.cljs @@ -7,14 +7,11 @@ (ns app.main.ui.dashboard.libraries (:require [app.main.data.dashboard :as dd] + [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.dashboard.grid :refer [grid]] - [app.main.ui.icons :as i] - [app.main.refs :as refs] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [app.util.router :as rt] - [okulary.core :as l] [rumext.alpha :as mf])) (mf/defc libraries-page diff --git a/frontend/src/app/main/ui/dashboard/project_menu.cljs b/frontend/src/app/main/ui/dashboard/project_menu.cljs index 68c166a326..48e0ab05e4 100644 --- a/frontend/src/app/main/ui/dashboard/project_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/project_menu.cljs @@ -10,7 +10,6 @@ [app.main.data.messages :as dm] [app.main.data.modal :as modal] [app.main.refs :as refs] - [app.main.repo :as rp] [app.main.store :as st] [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.context :as ctx] @@ -19,7 +18,6 @@ [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] - [beicon.core :as rx] [rumext.alpha :as mf])) (mf/defc project-menu @@ -62,7 +60,7 @@ (dd/move-project (with-meta data mdata))))) delete-fn - (fn [event] + (fn [_] (st/emit! (dm/success (tr "dashboard.success-delete-project")) (dd/delete-project project) (dd/go-to-projects (:team-id project)))) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 262fcaac62..66e12999a6 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -6,8 +6,6 @@ (ns app.main.ui.dashboard.projects (:require - [app.common.exceptions :as ex] - [app.main.constants :as c] [app.main.data.dashboard :as dd] [app.main.refs :as refs] [app.main.store :as st] @@ -16,8 +14,7 @@ [app.main.ui.dashboard.project-menu :refer [project-menu]] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t tr]] - [app.util.keyboard :as kbd] + [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] [app.util.time :as dt] [okulary.core :as l] @@ -38,7 +35,6 @@ [{:keys [project first? files] :as props}] (let [locale (mf/deref i18n/locale) - project-id (:id project) team-id (:team-id project) file-count (or (:count project) 0) diff --git a/frontend/src/app/main/ui/dashboard/search.cljs b/frontend/src/app/main/ui/dashboard/search.cljs index d3c6ed9ed6..56bc7439f0 100644 --- a/frontend/src/app/main/ui/dashboard/search.cljs +++ b/frontend/src/app/main/ui/dashboard/search.cljs @@ -13,7 +13,6 @@ [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [okulary.core :as l] [rumext.alpha :as mf])) (mf/defc search-page diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs index b35edc7e0d..07416cb045 100644 --- a/frontend/src/app/main/ui/dashboard/sidebar.cljs +++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs @@ -9,13 +9,11 @@ [app.common.data :as d] [app.common.spec :as us] [app.config :as cfg] - [app.main.data.comments :as dcm] [app.main.data.dashboard :as dd] [app.main.data.messages :as dm] [app.main.data.modal :as modal] [app.main.data.users :as du] [app.main.refs :as refs] - [app.main.repo :as rp] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.forms :as fm] @@ -24,23 +22,17 @@ [app.main.ui.dashboard.project-menu :refer [project-menu]] [app.main.ui.dashboard.team-form] [app.main.ui.icons :as i] - [app.util.avatars :as avatars] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] [app.util.i18n :as i18n :refer [tr]] - [app.util.keyboard :as kbd] [app.util.object :as obj] [app.util.router :as rt] - [app.util.time :as dt] - [beicon.core :as rx] [cljs.spec.alpha :as s] - [cuerdas.core :as str] [goog.functions :as f] - [okulary.core :as l] [rumext.alpha :as mf])) (mf/defc sidebar-project - [{:keys [item team-id selected?] :as props}] + [{:keys [item selected?] :as props}] (let [dstate (mf/deref refs/dashboard-local) selected-files (:selected-files dstate) selected-project (:selected-project dstate) @@ -111,7 +103,7 @@ on-drop (mf/use-callback (mf/deps item selected-files) - (fn [e] + (fn [_] (swap! local assoc :dragging? false) (when (not= selected-project (:id item)) (let [data {:ids selected-files @@ -157,7 +149,7 @@ on-search-blur (mf/use-callback - (fn [event] + (fn [_] (reset! focused? false))) on-search-change @@ -170,7 +162,7 @@ on-clear-click (mf/use-callback (mf/deps team-id) - (fn [event] + (fn [_] (let [search-input (dom/get-element "search-input")] (dom/clean-value! search-input) (dom/focus! search-input) @@ -189,7 +181,7 @@ :on-change on-search-change :ref #(when % (set! (.-value %) search-term))}] - (if (or @focused? (not (empty? search-term))) + (if (or @focused? (seq search-term)) [:div.clear-search {:on-click on-clear-click} i/close] @@ -199,9 +191,8 @@ i/search])])) (mf/defc teams-selector-dropdown - [{:keys [team profile] :as props}] - (let [show-dropdown? (mf/use-state false) - teams (mf/deref refs/teams) + [{:keys [profile] :as props}] + (let [teams (mf/deref refs/teams) on-create-clicked (mf/use-callback @@ -246,7 +237,7 @@ on-cancel (st/emitf (modal/hide)) on-accept - (fn [event] + (fn [_] (let [member-id (get-in @form [:clean-data :member-id])] (accept member-id)))] @@ -291,9 +282,6 @@ members-map (mf/deref refs/dashboard-team-members) members (vals members-map) - on-create-clicked - (st/emitf (modal/show :team-form {})) - on-rename-clicked (st/emitf (modal/show :team-form {:team team})) @@ -358,9 +346,7 @@ (mf/defc sidebar-team-switch [{:keys [team profile] :as props}] - (let [show-dropdown? (mf/use-state false) - - show-team-opts-ddwn? (mf/use-state false) + (let [show-team-opts-ddwn? (mf/use-state false) show-teams-ddwn? (mf/use-state false)] [:div.sidebar-team-switch diff --git a/frontend/src/app/main/ui/dashboard/team.cljs b/frontend/src/app/main/ui/dashboard/team.cljs index 4eb028314c..4c87aa4d12 100644 --- a/frontend/src/app/main/ui/dashboard/team.cljs +++ b/frontend/src/app/main/ui/dashboard/team.cljs @@ -7,10 +7,8 @@ (ns app.main.ui.dashboard.team (:require [app.common.data :as d] - [app.common.exceptions :as ex] [app.common.spec :as us] [app.config :as cfg] - [app.main.constants :as c] [app.main.data.dashboard :as dd] [app.main.data.messages :as dm] [app.main.data.modal :as modal] @@ -23,15 +21,12 @@ [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [app.util.router :as rt] - [app.util.time :as dt] [cljs.spec.alpha :as s] - [okulary.core :as l] [rumext.alpha :as mf])) (mf/defc header {::mf/wrap [mf/memo]} - [{:keys [section team] :as props}] + [{:keys [section] :as props}] (let [go-members (st/emitf (dd/go-to-team-members)) go-settings (st/emitf (dd/go-to-team-settings)) invite-member (st/emitf (modal/show {:type ::invite-member})) @@ -43,7 +38,7 @@ [:h1 (cond members-section? (tr "labels.members") settings-section? (tr "labels.settings") - nil)]] + :else nil)]] [:nav [:ul [:li {:class (when members-section? "active")} @@ -96,7 +91,7 @@ (dm/error (tr "errors.member-is-muted")) (and (= :validation type) - (= :email-has-permanent-bounces)) + (= :email-has-permanent-bounces code)) (dm/error (tr "errors.email-has-permanent-bounces" email)) :else @@ -136,7 +131,7 @@ set-owner-fn (partial set-role :owner) set-admin (partial set-role :admin) set-editor (partial set-role :editor) - set-viewer (partial set-role :viewer) + ;; set-viewer (partial set-role :viewer) set-owner (st/emitf (modal/show @@ -242,7 +237,7 @@ :members-map members-map}]]])) (mf/defc team-settings-page - [{:keys [team profile] :as props}] + [{:keys [team] :as props}] (let [finput (mf/use-ref) members-map (mf/deref refs/dashboard-team-members) diff --git a/frontend/src/app/main/ui/dashboard/team_form.cljs b/frontend/src/app/main/ui/dashboard/team_form.cljs index 758477b702..1a936b26fb 100644 --- a/frontend/src/app/main/ui/dashboard/team_form.cljs +++ b/frontend/src/app/main/ui/dashboard/team_form.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.dashboard.team-form (:require - [app.common.data :as d] [app.common.spec :as us] [app.main.data.dashboard :as dd] [app.main.data.messages :as dm] @@ -14,13 +13,10 @@ [app.main.store :as st] [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] - [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [app.util.object :as obj] [app.util.router :as rt] [beicon.core :as rx] [cljs.spec.alpha :as s] - [cuerdas.core :as str] [rumext.alpha :as mf])) (s/def ::name ::us/not-empty-string) @@ -28,20 +24,20 @@ (s/keys :req-un [::name])) (defn- on-create-success - [form response] + [_form response] (let [msg "Team created successfuly"] (st/emit! (dm/success msg) (modal/hide) (rt/nav :dashboard-projects {:team-id (:id response)})))) (defn- on-update-success - [form response] + [_form _response] (let [msg "Team created successfuly"] (st/emit! (dm/success msg) (modal/hide)))) (defn- on-error - [form response] + [form _response] (let [id (get-in @form [:clean-data :id])] (if id (rx/of (dm/error "Error on updating team.")) diff --git a/frontend/src/app/main/ui/handoff.cljs b/frontend/src/app/main/ui/handoff.cljs index ee82788f08..bee408f97b 100644 --- a/frontend/src/app/main/ui/handoff.cljs +++ b/frontend/src/app/main/ui/handoff.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.handoff (:require - [app.common.exceptions :as ex] [app.main.data.viewer :as dv] [app.main.data.viewer.shortcuts :as sc] [app.main.refs :as refs] @@ -15,15 +14,12 @@ [app.main.ui.handoff.render :refer [render-frame-svg]] [app.main.ui.handoff.right-sidebar :refer [right-sidebar]] [app.main.ui.hooks :as hooks] - [app.main.ui.icons :as i] [app.main.ui.viewer.header :refer [header]] [app.main.ui.viewer.thumbnails :refer [thumbnails-panel]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t tr]] [app.util.keyboard :as kbd] - [beicon.core :as rx] [goog.events :as events] - [okulary.core :as l] [rumext.alpha :as mf]) (:import goog.events.EventType)) diff --git a/frontend/src/app/main/ui/handoff/attributes.cljs b/frontend/src/app/main/ui/handoff/attributes.cljs index 51a59746fa..cdce9e76d1 100644 --- a/frontend/src/app/main/ui/handoff/attributes.cljs +++ b/frontend/src/app/main/ui/handoff/attributes.cljs @@ -6,18 +6,18 @@ (ns app.main.ui.handoff.attributes (:require - [rumext.alpha :as mf] - [app.util.i18n :as i18n] [app.common.geom.shapes :as gsh] - [app.main.ui.handoff.exports :refer [exports]] - [app.main.ui.handoff.attributes.layout :refer [layout-panel]] - [app.main.ui.handoff.attributes.fill :refer [fill-panel]] - [app.main.ui.handoff.attributes.stroke :refer [stroke-panel]] - [app.main.ui.handoff.attributes.shadow :refer [shadow-panel]] [app.main.ui.handoff.attributes.blur :refer [blur-panel]] + [app.main.ui.handoff.attributes.fill :refer [fill-panel]] [app.main.ui.handoff.attributes.image :refer [image-panel]] + [app.main.ui.handoff.attributes.layout :refer [layout-panel]] + [app.main.ui.handoff.attributes.shadow :refer [shadow-panel]] + [app.main.ui.handoff.attributes.stroke :refer [stroke-panel]] + [app.main.ui.handoff.attributes.svg :refer [svg-panel]] [app.main.ui.handoff.attributes.text :refer [text-panel]] - [app.main.ui.handoff.attributes.svg :refer [svg-panel]])) + [app.main.ui.handoff.exports :refer [exports]] + [app.util.i18n :as i18n] + [rumext.alpha :as mf])) (def type->options {:multiple [:fill :stroke :image :text :shadow :blur] diff --git a/frontend/src/app/main/ui/handoff/attributes/blur.cljs b/frontend/src/app/main/ui/handoff/attributes/blur.cljs index 9d3ca9d969..67f3b9194b 100644 --- a/frontend/src/app/main/ui/handoff/attributes/blur.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/blur.cljs @@ -6,12 +6,11 @@ (ns app.main.ui.handoff.attributes.blur (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] - [app.util.i18n :refer [t]] - [app.main.ui.icons :as i] + [app.main.ui.components.copy-button :refer [copy-button]] [app.util.code-gen :as cg] - [app.main.ui.components.copy-button :refer [copy-button]])) + [app.util.i18n :refer [t]] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (defn has-blur? [shape] (:blur shape)) diff --git a/frontend/src/app/main/ui/handoff/attributes/common.cljs b/frontend/src/app/main/ui/handoff/attributes/common.cljs index 03c3410740..2171979a50 100644 --- a/frontend/src/app/main/ui/handoff/attributes/common.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/common.cljs @@ -6,19 +6,16 @@ (ns app.main.ui.handoff.attributes.common (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] - [okulary.core :as l] [app.common.math :as mth] + [app.main.store :as st] + [app.main.ui.components.color-bullet :refer [color-bullet color-name]] + [app.main.ui.components.copy-button :refer [copy-button]] + [app.util.color :as uc] [app.util.dom :as dom] [app.util.i18n :refer [t] :as i18n] - [app.util.color :as uc] - [app.util.code-gen :as cg] - [app.util.webapi :as wapi] - [app.main.ui.icons :as i] - [app.main.store :as st] - [app.main.ui.components.copy-button :refer [copy-button]] - [app.main.ui.components.color-bullet :refer [color-bullet color-name]])) + [cuerdas.core :as str] + [okulary.core :as l] + [rumext.alpha :as mf])) (def file-colors-ref diff --git a/frontend/src/app/main/ui/handoff/attributes/fill.cljs b/frontend/src/app/main/ui/handoff/attributes/fill.cljs index fdb15cf5c9..a2b6301dc0 100644 --- a/frontend/src/app/main/ui/handoff/attributes/fill.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/fill.cljs @@ -6,13 +6,12 @@ (ns app.main.ui.handoff.attributes.fill (:require - [rumext.alpha :as mf] - [app.util.i18n :refer [t]] - [app.util.color :as uc] - [app.main.ui.icons :as i] - [app.util.code-gen :as cg] [app.main.ui.components.copy-button :refer [copy-button]] - [app.main.ui.handoff.attributes.common :refer [color-row]])) + [app.main.ui.handoff.attributes.common :refer [color-row]] + [app.util.code-gen :as cg] + [app.util.color :as uc] + [app.util.i18n :refer [tr]] + [rumext.alpha :as mf])) (def fill-attributes [:fill-color :fill-color-gradient]) @@ -36,7 +35,7 @@ {:to-prop "background" :format #(uc/color->background (shape->color shape))})) -(mf/defc fill-block [{:keys [shape locale]}] +(mf/defc fill-block [{:keys [shape]}] (let [color-format (mf/use-state :hex) color (shape->color shape)] @@ -46,16 +45,15 @@ :copy-data (copy-data shape)}])) (mf/defc fill-panel - [{:keys [shapes locale]}] + [{:keys [shapes]}] (let [shapes (->> shapes (filter has-color?))] (when (seq shapes) [:div.attributes-block [:div.attributes-block-title - [:div.attributes-block-title-text (t locale "handoff.attributes.fill")] + [:div.attributes-block-title-text (tr "handoff.attributes.fill")] (when (= (count shapes) 1) [:& copy-button {:data (copy-data (first shapes))}])] (for [shape shapes] [:& fill-block {:key (str "fill-block-" (:id shape)) - :shape shape - :locale locale}])]))) + :shape shape}])]))) diff --git a/frontend/src/app/main/ui/handoff/attributes/image.cljs b/frontend/src/app/main/ui/handoff/attributes/image.cljs index 238b7dea89..e798188682 100644 --- a/frontend/src/app/main/ui/handoff/attributes/image.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/image.cljs @@ -6,19 +6,17 @@ (ns app.main.ui.handoff.attributes.image (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] [app.config :as cfg] - [app.util.i18n :refer [t]] - [app.util.dom :as dom] - [app.main.ui.icons :as i] + [app.main.ui.components.copy-button :refer [copy-button]] [app.util.code-gen :as cg] - [app.main.ui.components.copy-button :refer [copy-button]])) + [app.util.dom :as dom] + [app.util.i18n :refer [tr]] + [rumext.alpha :as mf])) (defn has-image? [shape] - (and (= (:type shape) :image))) + (= (:type shape) :image)) -(mf/defc image-panel [{:keys [shapes locale]}] +(mf/defc image-panel [{:keys [shapes]}] (let [shapes (->> shapes (filter has-image?))] (for [shape shapes] [:div.attributes-block {:key (str "image-" (:id shape))} @@ -27,12 +25,12 @@ [:img {:src (cfg/resolve-file-media (-> shape :metadata))}]]] [:div.attributes-unit-row - [:div.attributes-label (t locale "handoff.attributes.image.width")] + [:div.attributes-label (tr "handoff.attributes.image.width")] [:div.attributes-value (-> shape :metadata :width) "px"] [:& copy-button {:data (cg/generate-css-props shape :width)}]] [:div.attributes-unit-row - [:div.attributes-label (t locale "handoff.attributes.image.height")] + [:div.attributes-label (tr "handoff.attributes.image.height")] [:div.attributes-value (-> shape :metadata :height) "px"] [:& copy-button {:data (cg/generate-css-props shape :height)}]] @@ -44,4 +42,4 @@ (str name "." extension) name) :href (cfg/resolve-file-media (-> shape :metadata))} - (t locale "handoff.attributes.image.download")])]))) + (tr "handoff.attributes.image.download")])]))) From e0846ce00e31f7b0b94da42091d790fdc6a55ae8 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 16:29:52 +0200 Subject: [PATCH 121/204] :sparkles: Fix linter issues on frontend (part 4). --- .../main/ui/handoff/attributes/layout.cljs | 9 ++-- .../main/ui/handoff/attributes/shadow.cljs | 32 ++++++------- .../main/ui/handoff/attributes/stroke.cljs | 13 +++--- .../app/main/ui/handoff/attributes/svg.cljs | 12 ++--- .../app/main/ui/handoff/attributes/text.cljs | 16 ++----- frontend/src/app/main/ui/handoff/code.cljs | 17 +++---- frontend/src/app/main/ui/handoff/exports.cljs | 26 +++++------ .../src/app/main/ui/handoff/left_sidebar.cljs | 5 +- frontend/src/app/main/ui/handoff/render.cljs | 46 +++++++++---------- .../app/main/ui/handoff/right_sidebar.cljs | 12 ++--- .../main/ui/handoff/selection_feedback.cljs | 12 ++--- frontend/src/app/main/ui/hooks.cljs | 18 ++------ frontend/src/app/main/ui/icons.cljs | 4 +- frontend/src/app/main/ui/loader.cljs | 4 +- frontend/src/app/main/ui/measurements.cljs | 13 ++---- frontend/src/app/main/ui/messages.cljs | 1 - frontend/src/app/main/ui/modal.cljs | 15 +----- frontend/src/app/main/ui/onboarding.cljs | 14 +++--- frontend/src/app/main/ui/render.cljs | 6 +-- frontend/src/app/main/ui/settings.cljs | 7 ++- .../app/main/ui/settings/change_email.cljs | 18 ++++---- .../app/main/ui/settings/delete_account.cljs | 4 +- .../src/app/main/ui/settings/options.cljs | 5 +- .../src/app/main/ui/settings/password.cljs | 3 +- .../src/app/main/ui/settings/profile.cljs | 11 ++--- .../sidebar/options/menus/exports.cljs | 2 +- 26 files changed, 130 insertions(+), 195 deletions(-) diff --git a/frontend/src/app/main/ui/handoff/attributes/layout.cljs b/frontend/src/app/main/ui/handoff/attributes/layout.cljs index 2a55eff534..4704bbb0a9 100644 --- a/frontend/src/app/main/ui/handoff/attributes/layout.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/layout.cljs @@ -6,13 +6,12 @@ (ns app.main.ui.handoff.attributes.layout (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] - [app.util.i18n :refer [t]] [app.common.math :as mth] - [app.main.ui.icons :as i] + [app.main.ui.components.copy-button :refer [copy-button]] [app.util.code-gen :as cg] - [app.main.ui.components.copy-button :refer [copy-button]])) + [app.util.i18n :refer [t]] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (def properties [:width :height :x :y :radius :rx :r1]) diff --git a/frontend/src/app/main/ui/handoff/attributes/shadow.cljs b/frontend/src/app/main/ui/handoff/attributes/shadow.cljs index 9d757d7880..892f466794 100644 --- a/frontend/src/app/main/ui/handoff/attributes/shadow.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/shadow.cljs @@ -6,15 +6,13 @@ (ns app.main.ui.handoff.attributes.shadow (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] [app.common.data :as d] - [app.util.i18n :refer [t]] - [app.util.code-gen :as cg] - [app.main.ui.icons :as i] - [app.util.code-gen :as cg] [app.main.ui.components.copy-button :refer [copy-button]] - [app.main.ui.handoff.attributes.common :refer [color-row]])) + [app.main.ui.handoff.attributes.common :refer [color-row]] + [app.util.code-gen :as cg] + [app.util.i18n :refer [tr]] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (defn has-shadow? [shape] (:shadow shape)) @@ -33,26 +31,25 @@ {:to-prop "box-shadow" :format #(cg/shadow->css shadow)})) -(mf/defc shadow-block [{:keys [shape locale shadow]}] - (let [color-format (mf/use-state :hex) - copy-data (shadow-copy-data shadow)] +(mf/defc shadow-block [{:keys [shadow]}] + (let [color-format (mf/use-state :hex)] [:div.attributes-shadow-block [:div.attributes-shadow-row - [:div.attributes-label (->> shadow :style d/name (str "handoff.attributes.shadow.style.") (t locale))] + [:div.attributes-label (->> shadow :style d/name (str "handoff.attributes.shadow.style.") (tr))] [:div.attributes-shadow - [:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-x")] + [:div.attributes-label (tr "handoff.attributes.shadow.shorthand.offset-x")] [:div.attributes-value (str (:offset-x shadow))]] [:div.attributes-shadow - [:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.offset-y")] + [:div.attributes-label (tr "handoff.attributes.shadow.shorthand.offset-y")] [:div.attributes-value (str (:offset-y shadow))]] [:div.attributes-shadow - [:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.blur")] + [:div.attributes-label (tr "handoff.attributes.shadow.shorthand.blur")] [:div.attributes-value (str (:blur shadow))]] [:div.attributes-shadow - [:div.attributes-label (t locale "handoff.attributes.shadow.shorthand.spread")] + [:div.attributes-label (tr "handoff.attributes.shadow.shorthand.spread")] [:div.attributes-value (str (:spread shadow))]] [:& copy-button {:data (shadow-copy-data shadow)}]] @@ -61,12 +58,12 @@ :format @color-format :on-change-format #(reset! color-format %)}]])) -(mf/defc shadow-panel [{:keys [shapes locale]}] +(mf/defc shadow-panel [{:keys [shapes]}] (let [shapes (->> shapes (filter has-shadow?))] (when (seq shapes) [:div.attributes-block [:div.attributes-block-title - [:div.attributes-block-title-text (t locale "handoff.attributes.shadow")] + [:div.attributes-block-title-text (tr "handoff.attributes.shadow")] (when (= (count shapes) 1) [:& copy-button {:data (shape-copy-data (first shapes))}])] @@ -74,5 +71,4 @@ (for [shape shapes] (for [shadow (:shadow shape)] [:& shadow-block {:shape shape - :locale locale :shadow shadow}]))]]))) diff --git a/frontend/src/app/main/ui/handoff/attributes/stroke.cljs b/frontend/src/app/main/ui/handoff/attributes/stroke.cljs index 075daae30d..ddd5a3263c 100644 --- a/frontend/src/app/main/ui/handoff/attributes/stroke.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/stroke.cljs @@ -6,16 +6,15 @@ (ns app.main.ui.handoff.attributes.stroke (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] [app.common.data :as d] [app.common.math :as mth] - [app.util.i18n :refer [t]] - [app.util.color :as uc] - [app.main.ui.icons :as i] - [app.util.code-gen :as cg] [app.main.ui.components.copy-button :refer [copy-button]] - [app.main.ui.handoff.attributes.common :refer [color-row]])) + [app.main.ui.handoff.attributes.common :refer [color-row]] + [app.util.code-gen :as cg] + [app.util.color :as uc] + [app.util.i18n :refer [t]] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (defn shape->color [shape] {:color (:stroke-color shape) diff --git a/frontend/src/app/main/ui/handoff/attributes/svg.cljs b/frontend/src/app/main/ui/handoff/attributes/svg.cljs index 033ad073fe..a2308c5986 100644 --- a/frontend/src/app/main/ui/handoff/attributes/svg.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/svg.cljs @@ -6,14 +6,14 @@ (ns app.main.ui.handoff.attributes.svg (:require - [rumext.alpha :as mf] - [app.common.data :as d] - [cuerdas.core :as str] - [app.util.i18n :refer [tr]] #_[app.common.math :as mth] #_[app.main.ui.icons :as i] #_[app.util.code-gen :as cg] - [app.main.ui.components.copy-button :refer [copy-button]])) + [app.common.data :as d] + [app.main.ui.components.copy-button :refer [copy-button]] + [app.util.i18n :refer [tr]] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (defn map->css [attr] @@ -47,7 +47,7 @@ [{:keys [shapes]}] (let [shape (first shapes)] - (when (and (:svg-attrs shape) (not (empty? (:svg-attrs shape)))) + (when (seq (:svg-attrs shape)) [:div.attributes-block [:div.attributes-block-title [:div.attributes-block-title-text (tr "workspace.sidebar.options.svg-attrs.title")]] diff --git a/frontend/src/app/main/ui/handoff/attributes/text.cljs b/frontend/src/app/main/ui/handoff/attributes/text.cljs index f49276d6f4..694c3525b8 100644 --- a/frontend/src/app/main/ui/handoff/attributes/text.cljs +++ b/frontend/src/app/main/ui/handoff/attributes/text.cljs @@ -11,11 +11,9 @@ [app.main.store :as st] [app.main.ui.components.copy-button :refer [copy-button]] [app.main.ui.handoff.attributes.common :refer [color-row]] - [app.main.ui.icons :as i] - [app.util.i18n :refer [tr]] [app.util.code-gen :as cg] [app.util.color :as uc] - [app.util.webapi :as wapi] + [app.util.i18n :refer [tr]] [cuerdas.core :as str] [okulary.core :as l] [rumext.alpha :as mf])) @@ -68,7 +66,7 @@ ([style & properties] (cg/generate-css-props style properties params))) -(mf/defc typography-block [{:keys [shape text style full-style]}] +(mf/defc typography-block [{:keys [text style full-style]}] (let [typography-library-ref (mf/use-memo (mf/deps (:typography-ref-file style)) (make-typographies-library-ref (:typography-ref-file style))) @@ -77,7 +75,6 @@ file-typographies (mf/deref file-typographies-ref) color-format (mf/use-state :hex) - color (shape->color style) typography (get (or typography-library file-typographies) (:typography-ref-id style))] @@ -163,14 +160,11 @@ m1)) (mf/defc text-block [{:keys [shape]}] - (let [font (cg/search-text-attrs (:content shape) - (keys txt/default-text-attrs)) - style-text-blocks (->> (keys txt/default-text-attrs) + (let [style-text-blocks (->> (keys txt/default-text-attrs) (cg/parse-style-text-blocks (:content shape)) - (remove (fn [[style text]] (str/empty? (str/trim text)))) - (mapv (fn [[style text]] (vector (merge txt/default-text-attrs style) text)))) + (remove (fn [[_ text]] (str/empty? (str/trim text)))) + (mapv (fn [[style text]] (vector (merge txt/default-text-attrs style) text))))] - font (merge txt/default-text-attrs font)] (for [[idx [full-style text]] (map-indexed vector style-text-blocks)] (let [previus-style (first (nth style-text-blocks (dec idx) nil)) style (remove-equal-values full-style previus-style) diff --git a/frontend/src/app/main/ui/handoff/code.cljs b/frontend/src/app/main/ui/handoff/code.cljs index 2bf92cc3d4..b6eab07445 100644 --- a/frontend/src/app/main/ui/handoff/code.cljs +++ b/frontend/src/app/main/ui/handoff/code.cljs @@ -7,17 +7,16 @@ (ns app.main.ui.handoff.code (:require ["js-beautify" :as beautify] - [cuerdas.core :as str] - [rumext.alpha :as mf] - [app.util.i18n :as i18n] - [app.util.dom :as dom] - [app.util.code-gen :as cg] - [app.main.ui.icons :as i] [app.common.geom.shapes :as gsh] + [app.main.ui.components.code-block :refer [code-block]] [app.main.ui.components.copy-button :refer [copy-button]] - [app.main.ui.components.code-block :refer [code-block]])) + [app.main.ui.icons :as i] + [app.util.code-gen :as cg] + [app.util.dom :as dom] + [cuerdas.core :as str] + [rumext.alpha :as mf])) -(defn generate-markup-code [type shapes] +(defn generate-markup-code [_type shapes] (let [frame (dom/query js/document "#svg-frame") markup-shape (fn [shape] @@ -42,8 +41,6 @@ [{:keys [shapes frame on-expand]}] (let [style-type (mf/use-state "css") markup-type (mf/use-state "svg") - - locale (mf/deref i18n/locale) shapes (->> shapes (map #(gsh/translate-to-frame % frame))) diff --git a/frontend/src/app/main/ui/handoff/exports.cljs b/frontend/src/app/main/ui/handoff/exports.cljs index a8a36b2bf2..670a8f312f 100644 --- a/frontend/src/app/main/ui/handoff/exports.cljs +++ b/frontend/src/app/main/ui/handoff/exports.cljs @@ -6,21 +6,19 @@ (ns app.main.ui.handoff.exports (:require - [rumext.alpha :as mf] - [beicon.core :as rx] - [app.util.i18n :refer [t] :as i18n] - [app.common.geom.shapes :as gsh] - [app.main.ui.icons :as i] [app.common.data :as d] - [app.util.dom :as dom] - [app.main.store :as st] [app.main.data.messages :as dm] - [app.main.ui.workspace.sidebar.options.menus.exports :as we])) + [app.main.store :as st] + [app.main.ui.icons :as i] + [app.main.ui.workspace.sidebar.options.menus.exports :as we] + [app.util.dom :as dom] + [app.util.i18n :refer [tr]] + [beicon.core :as rx] + [rumext.alpha :as mf])) (mf/defc exports [{:keys [shape page-id file-id] :as props}] - (let [locale (mf/deref i18n/locale) - exports (mf/use-state (:exports shape [])) + (let [exports (mf/use-state (:exports shape [])) loading? (mf/use-state false) on-download @@ -35,7 +33,7 @@ (js/console.log status body) (if (= status 200) (dom/trigger-download (:name shape) body) - (st/emit! (dm/error (t locale "errors.unexpected-error"))))) + (st/emit! (dm/error (tr "errors.unexpected-error"))))) (constantly nil) (fn [] (swap! loading? not)))))) @@ -90,7 +88,7 @@ [:div.element-set.exports-options [:div.element-set-title - [:span (t locale "workspace.options.export")] + [:span (tr "workspace.options.export")] [:div.add-page {:on-click add-export} i/close]] (when (seq @exports) @@ -124,6 +122,6 @@ :class (dom/classnames :btn-disabled @loading?) :disabled @loading?} (if @loading? - (t locale "workspace.options.exporting-object") - (t locale "workspace.options.export-object"))]])])) + (tr "workspace.options.exporting-object") + (tr "workspace.options.export-object"))]])])) diff --git a/frontend/src/app/main/ui/handoff/left_sidebar.cljs b/frontend/src/app/main/ui/handoff/left_sidebar.cljs index e378a4d5b1..12d4ea0318 100644 --- a/frontend/src/app/main/ui/handoff/left_sidebar.cljs +++ b/frontend/src/app/main/ui/handoff/left_sidebar.cljs @@ -7,11 +7,10 @@ (ns app.main.ui.handoff.left-sidebar (:require [app.common.data :as d] - [app.common.uuid :as uuid] [app.main.data.viewer :as dv] [app.main.store :as st] [app.main.ui.icons :as i] - [app.main.ui.workspace.sidebar.layers :refer [element-icon layer-name frame-wrapper]] + [app.main.ui.workspace.sidebar.layers :refer [element-icon layer-name]] [app.util.dom :as dom] [app.util.keyboard :as kbd] [okulary.core :as l] @@ -29,7 +28,7 @@ (l/derived st/state))) (mf/defc layer-item - [{:keys [index item selected objects disable-collapse?] :as props}] + [{:keys [item selected objects disable-collapse?] :as props}] (let [id (:id item) selected? (contains? selected id) item-ref (mf/use-ref nil) diff --git a/frontend/src/app/main/ui/handoff/render.cljs b/frontend/src/app/main/ui/handoff/render.cljs index 6d28af9066..d4d8a89e4e 100644 --- a/frontend/src/app/main/ui/handoff/render.cljs +++ b/frontend/src/app/main/ui/handoff/render.cljs @@ -7,50 +7,46 @@ (ns app.main.ui.handoff.render "The main container for a frame in handoff mode" (:require - [rumext.alpha :as mf] - [app.util.object :as obj] - [app.util.dom :as dom] [app.common.data :as d] - [app.common.pages :as cp] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as geom] - [app.main.refs :as refs] - [app.main.store :as st] + [app.common.pages :as cp] [app.main.data.viewer :as dv] - [app.main.ui.shapes.filters :as filters] + [app.main.store :as st] + [app.main.ui.handoff.selection-feedback :refer [selection-feedback]] [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.group :as group] - [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.image :as image] [app.main.ui.shapes.path :as path] [app.main.ui.shapes.rect :as rect] + [app.main.ui.shapes.shape :refer [shape-container]] + [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.text :as text] - [app.main.ui.handoff.selection-feedback :refer [selection-feedback]] - [app.main.ui.shapes.shape :refer [shape-container]])) + [app.util.dom :as dom] + [app.util.object :as obj] + [rumext.alpha :as mf])) (declare shape-container-factory) (defn handle-hover-shape [{:keys [type id]} hover?] #(when-not (#{:group :frame} type) - (do - (dom/prevent-default %) - (dom/stop-propagation %) - (st/emit! (dv/hover-shape id hover?))))) + (dom/prevent-default %) + (dom/stop-propagation %) + (st/emit! (dv/hover-shape id hover?)))) (defn select-shape [{:keys [type id]}] (fn [event] (when-not (#{:group :frame} type) - (do - (dom/stop-propagation event) - (dom/prevent-default event) - (cond - (.-shiftKey event) - (st/emit! (dv/toggle-selection id)) + (dom/stop-propagation event) + (dom/prevent-default event) + (cond + (.-shiftKey event) + (st/emit! (dv/toggle-selection id)) - :else - (st/emit! (dv/select-shape id))))))) + :else + (st/emit! (dv/select-shape id)))))) (defn shape-wrapper-factory [component] @@ -114,8 +110,8 @@ (defn svg-raw-container-factory [objects] (let [shape-container (shape-container-factory objects) - svg-raw-shape (svg-raw/svg-raw-shape shape-container) - svg-raw-wrapper (shape-wrapper-factory svg-raw-shape)] + svg-raw-shape (svg-raw/svg-raw-shape shape-container) + svg-raw-wrapper (shape-wrapper-factory svg-raw-shape)] (mf/fnc group-container {::mf/wrap-props false} [props] @@ -127,7 +123,7 @@ [:> svg-raw-wrapper props])))) (defn shape-container-factory - [objects show-interactions?] + [objects] (let [path-wrapper (shape-wrapper-factory path/path-shape) text-wrapper (shape-wrapper-factory text/text-shape) rect-wrapper (shape-wrapper-factory rect/rect-shape) diff --git a/frontend/src/app/main/ui/handoff/right_sidebar.cljs b/frontend/src/app/main/ui/handoff/right_sidebar.cljs index 5773417523..390aeaf421 100644 --- a/frontend/src/app/main/ui/handoff/right_sidebar.cljs +++ b/frontend/src/app/main/ui/handoff/right_sidebar.cljs @@ -6,16 +6,16 @@ (ns app.main.ui.handoff.right-sidebar (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [app.util.i18n :refer [t] :as i18n] [app.common.data :as d] [app.main.store :as st] - [app.main.ui.icons :as i] [app.main.ui.components.tab-container :refer [tab-container tab-element]] - [app.main.ui.workspace.sidebar.layers :refer [element-icon]] [app.main.ui.handoff.attributes :refer [attributes]] - [app.main.ui.handoff.code :refer [code]])) + [app.main.ui.handoff.code :refer [code]] + [app.main.ui.icons :as i] + [app.main.ui.workspace.sidebar.layers :refer [element-icon]] + [app.util.i18n :refer [t] :as i18n] + [okulary.core :as l] + [rumext.alpha :as mf])) (defn make-selected-shapes-iref [] diff --git a/frontend/src/app/main/ui/handoff/selection_feedback.cljs b/frontend/src/app/main/ui/handoff/selection_feedback.cljs index ccfd218243..70df269238 100644 --- a/frontend/src/app/main/ui/handoff/selection_feedback.cljs +++ b/frontend/src/app/main/ui/handoff/selection_feedback.cljs @@ -6,15 +6,11 @@ (ns app.main.ui.handoff.selection-feedback (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] - [okulary.core :as l] - [app.common.data :as d] - [app.common.math :as mth] [app.common.geom.shapes :as gsh] - [app.common.geom.point :as gpt] [app.main.store :as st] - [app.main.ui.measurements :refer [selection-guides size-display measurement]])) + [app.main.ui.measurements :refer [selection-guides size-display measurement]] + [okulary.core :as l] + [rumext.alpha :as mf])) ;; ------------------------------------------------ ;; CONSTANTS @@ -67,7 +63,7 @@ ;; COMPONENTS ;; ------------------------------------------------ -(mf/defc selection-rect [{:keys [frame selrect zoom]}] +(mf/defc selection-rect [{:keys [selrect zoom]}] (let [{:keys [x y width height]} selrect selection-rect-width (/ selection-rect-width zoom)] [:g.selection-rect diff --git a/frontend/src/app/main/ui/hooks.cljs b/frontend/src/app/main/ui/hooks.cljs index 830906bc17..257b3b2393 100644 --- a/frontend/src/app/main/ui/hooks.cljs +++ b/frontend/src/app/main/ui/hooks.cljs @@ -7,20 +7,14 @@ (ns app.main.ui.hooks "A collection of general purpose react hooks." (:require - [app.common.spec :as us] [app.main.data.shortcuts :as dsc] [app.main.store :as st] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] [app.util.logging :as log] - [app.util.object :as obj] [app.util.timers :as ts] - [app.util.webapi :as wapi] [beicon.core :as rx] - [cljs.spec.alpha :as s] - [goog.events :as events] - [rumext.alpha :as mf]) - (:import goog.events.EventType)) + [rumext.alpha :as mf])) (log/set-level! :warn) @@ -98,10 +92,7 @@ cleanup (fn [] - ;; (js/console.log "cleanup" (:name data)) - (when-let [subscr (:subscr @state)] - ;; (js/console.log "unsubscribing" (:name data)) - (rx/unsub! (:subscr @state))) + (some-> (:subscr @state) rx/unsub!) (swap! state (fn [state] (-> state (cancel-timer) @@ -118,9 +109,8 @@ (fn [event] (if disabled (dom/prevent-default event) - (let [target (dom/get-target event)] + (do (dom/stop-propagation event) - ;; (dnd/trace event data "drag-start") (dnd/set-data! event data-type data) (dnd/set-drag-image! event (invisible-image)) (dnd/set-allowed-effect! event "move") @@ -237,7 +227,7 @@ [] (try (not js/window) - (catch :default e + (catch :default _e ;; When exception accessing window we're in ssr true))) diff --git a/frontend/src/app/main/ui/icons.cljs b/frontend/src/app/main/ui/icons.cljs index e577e082be..b4b4cd7126 100644 --- a/frontend/src/app/main/ui/icons.cljs +++ b/frontend/src/app/main/ui/icons.cljs @@ -5,7 +5,7 @@ ;; Copyright (c) UXBOX Labs SL (ns app.main.ui.icons - (:refer-clojure :exclude [import]) + (:refer-clojure :exclude [import mask]) (:require-macros [app.main.ui.icons :refer [icon-xref]]) (:require [rumext.alpha :as mf])) @@ -163,7 +163,7 @@ (mf/defc debug-icons-preview {::mf/wrap-props false} - [props] + [] [:section.debug-icons-preview (for [[key val] (sort-by first (ns-publics 'app.main.ui.icons))] (when (not= key 'debug-icons-preview) diff --git a/frontend/src/app/main/ui/loader.cljs b/frontend/src/app/main/ui/loader.cljs index 503d59be1e..bd5500b18c 100644 --- a/frontend/src/app/main/ui/loader.cljs +++ b/frontend/src/app/main/ui/loader.cljs @@ -6,9 +6,9 @@ (ns app.main.ui.loader (:require - [rumext.alpha :as mf] + [app.main.store :as st] [app.main.ui.icons :as i] - [app.main.store :as st])) + [rumext.alpha :as mf])) ;; --- Component diff --git a/frontend/src/app/main/ui/measurements.cljs b/frontend/src/app/main/ui/measurements.cljs index 3cdb0faff7..4fc319c1f1 100644 --- a/frontend/src/app/main/ui/measurements.cljs +++ b/frontend/src/app/main/ui/measurements.cljs @@ -11,9 +11,7 @@ [app.common.geom.shapes :as gsh] [app.common.math :as mth] [app.common.uuid :as uuid] - [app.main.store :as st] [cuerdas.core :as str] - [okulary.core :as l] [rumext.alpha :as mf])) ;; ------------------------------------------------ @@ -82,10 +80,10 @@ (and (neg? ss) (> ss se))) (conj [ from-s (+ from-s ss) ]) - (or (and (neg? se) (<= ss se))) + (and (neg? se) (<= ss se)) (conj [ from-s (+ from-s se) ]) - (or (and (pos? es) (<= es ee))) + (and (pos? es) (<= es ee)) (conj [ from-e (+ from-e es) ]) (or (and (pos? ee) (neg? es)) @@ -97,7 +95,7 @@ ;; COMPONENTS ;; ------------------------------------------------ -(mf/defc size-display [{:keys [type selrect zoom]}] +(mf/defc size-display [{:keys [selrect zoom]}] (let [{:keys [x y width height]} selrect size-label (str/fmt "%s x %s" (mth/round width) (mth/round height)) @@ -127,7 +125,6 @@ (mf/defc distance-display-pill [{:keys [x y zoom distance bounds]}] (let [distance-pill-width (/ distance-pill-width zoom) distance-pill-height (/ distance-pill-height zoom) - distance-line-stroke (/ distance-line-stroke zoom) font-size (/ font-size zoom) text-padding (/ 3 zoom) distance-border-radius (/ distance-border-radius zoom) @@ -169,7 +166,7 @@ :font-size font-size}} distance]])) -(mf/defc selection-rect [{:keys [frame selrect zoom]}] +(mf/defc selection-rect [{:keys [selrect zoom]}] (let [{:keys [x y width height]} selrect selection-rect-width (/ selection-rect-width zoom)] [:g.selection-rect @@ -181,7 +178,7 @@ :stroke hover-color :stroke-width selection-rect-width}}]])) -(mf/defc distance-display [{:keys [type from to zoom frame bounds]}] +(mf/defc distance-display [{:keys [from to zoom bounds]}] (let [fixed-x (if (gsh/fully-contained? from to) (+ (:x to) (/ (:width to) 2)) (+ (:x from) (/ (:width from) 2))) diff --git a/frontend/src/app/main/ui/messages.cljs b/frontend/src/app/main/ui/messages.cljs index 70aecb9b2b..aff6c6f81e 100644 --- a/frontend/src/app/main/ui/messages.cljs +++ b/frontend/src/app/main/ui/messages.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.messages (:require - [app.common.spec :as us] [app.common.uuid :as uuid] [app.main.data.messages :as dm] [app.main.refs :as refs] diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs index 0e462130e1..b07e3dd57f 100644 --- a/frontend/src/app/main/ui/modal.cljs +++ b/frontend/src/app/main/ui/modal.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.modal (:require [app.main.data.modal :as dm] - [app.main.refs :as refs] [app.main.store :as st] [app.util.dom :as dom] [app.util.keyboard :as k] @@ -19,8 +18,8 @@ (defn- on-esc-clicked [event allow-click-outside] (when (and (k/esc? event) (not allow-click-outside)) - (do (dom/stop-propagation event) - (st/emit! (dm/hide))))) + (dom/stop-propagation event) + (st/emit! (dm/hide)))) (defn- on-pop-state [event] @@ -29,16 +28,6 @@ (st/emit! (dm/hide)) (.forward js/history)) -(defn- on-parent-clicked - [event parent-ref] - (let [parent (mf/ref-val parent-ref) - current (dom/get-target event)] - (when (and (dom/equals? (.-firstElementChild ^js parent) current) - (= (.-className ^js current) "modal-overlay")) - (dom/stop-propagation event) - (dom/prevent-default event) - (st/emit! (dm/hide))))) - (defn- on-click-outside [event wrapper-ref type allow-click-outside] (let [wrapper (mf/ref-val wrapper-ref) diff --git a/frontend/src/app/main/ui/onboarding.cljs b/frontend/src/app/main/ui/onboarding.cljs index 3079151450..9d520a7f15 100644 --- a/frontend/src/app/main/ui/onboarding.cljs +++ b/frontend/src/app/main/ui/onboarding.cljs @@ -6,18 +6,18 @@ (ns app.main.ui.onboarding (:require - [app.config :as cf] [app.common.spec :as us] + [app.config :as cf] [app.main.data.dashboard :as dd] [app.main.data.messages :as dm] [app.main.data.modal :as modal] [app.main.data.users :as du] [app.main.store :as st] - [app.main.ui.components.forms :as fm :refer [input submit-button form]] + [app.main.ui.components.forms :as fm] [app.util.dom :as dom] + [app.util.object :as obj] [app.util.router :as rt] [app.util.timers :as tm] - [app.util.object :as obj] [cljs.spec.alpha :as s] [rumext.alpha :as mf])) @@ -186,24 +186,24 @@ (mf/defc onboarding-team-modal {::mf/register modal/components ::mf/register-as :onboarding-team} - [props] + [] (let [close (mf/use-fn (st/emitf (modal/hide))) form (fm/use-form :spec ::team-form :initial {}) on-success (mf/use-callback - (fn [form response] + (fn [_form response] (st/emit! (modal/hide) (rt/nav :dashboard-projects {:team-id (:id response)})))) on-error (mf/use-callback - (fn [form response] + (fn [_form _response] (st/emit! (dm/error "Error on creating team.")))) on-submit (mf/use-callback - (fn [form event] + (fn [form _event] (let [mdata {:on-success (partial on-success form) :on-error (partial on-error form)} params {:name (get-in @form [:clean-data :name])}] diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index 628a8ddb1b..cb09796fc2 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -9,7 +9,6 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.common.math :as mth] [app.common.pages :as cp] [app.common.uuid :as uuid] [app.main.data.fonts :as df] @@ -20,7 +19,6 @@ [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.shape :refer [shape-container]] [beicon.core :as rx] - [cljs.spec.alpha :as s] [cuerdas.core :as str] [rumext.alpha :as mf])) @@ -44,8 +42,6 @@ objects (reduce updt-fn objects mod-ids) object (get objects object-id) - {:keys [width height]} (gsh/points->selrect (:points object)) - ;; We need to get the shadows/blurs paddings to create the viewbox properly {:keys [x y width height]} (filters/get-filters-bounds object) @@ -115,7 +111,7 @@ (repo/query! :font-variants {:file-id file-id}) (repo/query! :file {:id file-id})) (rx/subs - (fn [[fonts {:keys [data]} :as kaka]] + (fn [[fonts {:keys [data]}]] (when (seq fonts) (st/emit! (df/fonts-fetched fonts))) (let [objs (get-in data [:pages-index page-id :objects]) diff --git a/frontend/src/app/main/ui/settings.cljs b/frontend/src/app/main/ui/settings.cljs index 7ca893dfef..7c9cd788ff 100644 --- a/frontend/src/app/main/ui/settings.cljs +++ b/frontend/src/app/main/ui/settings.cljs @@ -7,14 +7,13 @@ (ns app.main.ui.settings (:require [app.main.refs :as refs] - [app.main.store :as st] - [app.main.ui.settings.options :refer [options-page]] + [app.main.ui.settings.change-email] + [app.main.ui.settings.delete-account] [app.main.ui.settings.feedback :refer [feedback-page]] + [app.main.ui.settings.options :refer [options-page]] [app.main.ui.settings.password :refer [password-page]] [app.main.ui.settings.profile :refer [profile-page]] [app.main.ui.settings.sidebar :refer [sidebar]] - [app.main.ui.settings.change-email] - [app.main.ui.settings.delete-account] [app.util.i18n :as i18n :refer [tr]] [rumext.alpha :as mf])) diff --git a/frontend/src/app/main/ui/settings/change_email.cljs b/frontend/src/app/main/ui/settings/change_email.cljs index 46726c87fb..5ea32034f3 100644 --- a/frontend/src/app/main/ui/settings/change_email.cljs +++ b/frontend/src/app/main/ui/settings/change_email.cljs @@ -15,10 +15,9 @@ [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] - [app.util.i18n :as i18n :refer [tr t]] + [app.util.i18n :as i18n :refer [tr]] [beicon.core :as rx] [cljs.spec.alpha :as s] - [cuerdas.core :as str] [rumext.alpha :as mf])) (s/def ::email-1 ::us/email) @@ -63,7 +62,7 @@ (modal/hide))))) (defn- on-submit - [form event] + [form _event] (let [params {:email (get-in @form [:clean-data :email-1])} mdata {:on-error (partial on-error form) :on-success (partial on-success form)}] @@ -73,8 +72,7 @@ {::mf/register modal/components ::mf/register-as :change-email} [] - (let [locale (mf/deref i18n/locale) - profile (mf/deref refs/profile) + (let [profile (mf/deref refs/profile) form (fm/use-form :spec ::email-change-form :validators [email-equality] :initial profile) @@ -88,30 +86,30 @@ [:div.modal-header [:div.modal-header-title - [:h2 (t locale "modals.change-email.title")]] + [:h2 (tr "modals.change-email.title")]] [:div.modal-close-button {:on-click on-close} i/close]] [:div.modal-content [:& msgs/inline-banner {:type :info - :content (t locale "modals.change-email.info" (:email profile))}] + :content (tr "modals.change-email.info" (:email profile))}] [:div.fields-row [:& fm/input {:type "text" :name :email-1 - :label (t locale "modals.change-email.new-email") + :label (tr "modals.change-email.new-email") :trim true}]] [:div.fields-row [:& fm/input {:type "text" :name :email-2 - :label (t locale "modals.change-email.confirm-email") + :label (tr "modals.change-email.confirm-email") :trim true}]]] [:div.modal-footer [:div.action-buttons [:& fm/submit-button - {:label (t locale "modals.change-email.submit")}]]]]]])) + {:label (tr "modals.change-email.submit")}]]]]]])) diff --git a/frontend/src/app/main/ui/settings/delete_account.cljs b/frontend/src/app/main/ui/settings/delete_account.cljs index f26881ddb5..f4e7262192 100644 --- a/frontend/src/app/main/ui/settings/delete_account.cljs +++ b/frontend/src/app/main/ui/settings/delete_account.cljs @@ -13,9 +13,7 @@ [app.main.ui.icons :as i] [app.main.ui.messages :as msgs] [app.util.i18n :as i18n :refer [tr]] - [app.util.router :as rt] [beicon.core :as rx] - [cljs.spec.alpha :as s] [rumext.alpha :as mf])) (defn on-error @@ -28,7 +26,7 @@ (mf/defc delete-account-modal {::mf/register modal/components ::mf/register-as :delete-account} - [props] + [] (let [on-close (mf/use-callback (st/emitf (modal/hide))) diff --git a/frontend/src/app/main/ui/settings/options.cljs b/frontend/src/app/main/ui/settings/options.cljs index 0b7bfb597f..e3b3aed6b4 100644 --- a/frontend/src/app/main/ui/settings/options.cljs +++ b/frontend/src/app/main/ui/settings/options.cljs @@ -13,7 +13,6 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.forms :as fm] - [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t tr]] [cljs.spec.alpha :as s] @@ -26,11 +25,11 @@ (s/keys :opt-un [::lang ::theme])) (defn- on-success - [form] + [_] (st/emit! (dm/success (tr "notifications.profile-saved")))) (defn- on-submit - [form event] + [form _event] (let [data (:clean-data @form) data (cond-> data (empty? (:lang data)) diff --git a/frontend/src/app/main/ui/settings/password.cljs b/frontend/src/app/main/ui/settings/password.cljs index 6b63fc4d83..f0b6a32230 100644 --- a/frontend/src/app/main/ui/settings/password.cljs +++ b/frontend/src/app/main/ui/settings/password.cljs @@ -11,7 +11,6 @@ [app.main.data.users :as udu] [app.main.store :as st] [app.main.ui.components.forms :as fm] - [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t tr]] [cljs.spec.alpha :as s] @@ -29,7 +28,7 @@ (st/emit! (dm/error msg))))) (defn- on-success - [form] + [_] (let [msg (tr "dashboard.notifications.password-saved")] (st/emit! (dm/success msg)))) diff --git a/frontend/src/app/main/ui/settings/profile.cljs b/frontend/src/app/main/ui/settings/profile.cljs index 03a6ddda28..eded1e265e 100644 --- a/frontend/src/app/main/ui/settings/profile.cljs +++ b/frontend/src/app/main/ui/settings/profile.cljs @@ -6,8 +6,8 @@ (ns app.main.ui.settings.profile (:require - [app.config :as cfg] [app.common.spec :as us] + [app.config :as cfg] [app.main.data.messages :as dm] [app.main.data.modal :as modal] [app.main.data.users :as du] @@ -16,13 +16,10 @@ [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.forms :as fm] [app.main.ui.icons :as i] - [app.main.ui.messages :as msgs] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr t]] [cljs.spec.alpha :as s] - [cuerdas.core :as str] - [rumext.alpha :as mf] - [app.config :as cfg])) + [rumext.alpha :as mf])) (s/def ::fullname ::us/not-empty-string) (s/def ::email ::us/email) @@ -31,11 +28,11 @@ (s/keys :req-un [::fullname ::email])) (defn- on-success - [form] + [_] (st/emit! (dm/success (tr "notifications.profile-saved")))) (defn- on-submit - [form event] + [form _event] (let [data (:clean-data @form) mdata {:on-success (partial on-success form)}] (st/emit! (du/update-profile (with-meta data mdata))))) 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 dd62aef455..09b5a6d8f1 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 @@ -20,7 +20,7 @@ [app.util.http :as http] [app.util.i18n :as i18n :refer [tr t]])) -(defn- request-export +(defn request-export [shape exports] (rp/query! :export {:page-id (:page-id shape) From 0f3e4c289c418c71d0929dff511be69fd8f9e5c1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 17 Jun 2021 17:30:29 +0200 Subject: [PATCH 122/204] :sparkles: Fix linter issues on frontend (part 5). --- frontend/src/app/main/ui/shapes/attrs.cljs | 9 +- frontend/src/app/main/ui/shapes/circle.cljs | 8 +- .../src/app/main/ui/shapes/custom_stroke.cljs | 10 +- frontend/src/app/main/ui/shapes/export.cljs | 6 +- frontend/src/app/main/ui/shapes/filters.cljs | 2 +- frontend/src/app/main/ui/shapes/frame.cljs | 5 +- .../src/app/main/ui/shapes/gradients.cljs | 80 ++++---- frontend/src/app/main/ui/shapes/group.cljs | 8 +- frontend/src/app/main/ui/shapes/image.cljs | 47 +++-- frontend/src/app/main/ui/shapes/mask.cljs | 4 +- frontend/src/app/main/ui/shapes/path.cljs | 17 +- frontend/src/app/main/ui/shapes/rect.cljs | 3 +- frontend/src/app/main/ui/shapes/svg_defs.cljs | 3 +- frontend/src/app/main/ui/shapes/svg_raw.cljs | 11 +- frontend/src/app/main/ui/shapes/text.cljs | 6 +- .../app/main/ui/shapes/text/fontfaces.cljs | 6 +- .../src/app/main/ui/shapes/text/styles.cljs | 28 +-- frontend/src/app/main/ui/static.cljs | 11 +- frontend/src/app/main/ui/viewer.cljs | 10 +- frontend/src/app/main/ui/viewer/shapes.cljs | 11 +- .../src/app/main/ui/viewer/thumbnails.cljs | 34 ++-- frontend/src/app/main/ui/workspace.cljs | 2 +- .../app/main/ui/workspace/colorpalette.cljs | 21 +-- .../app/main/ui/workspace/colorpicker.cljs | 52 +++--- .../workspace/colorpicker/color_inputs.cljs | 16 +- .../ui/workspace/colorpicker/gradients.cljs | 16 +- .../ui/workspace/colorpicker/harmony.cljs | 20 +- .../main/ui/workspace/colorpicker/hsva.cljs | 18 +- .../ui/workspace/colorpicker/libraries.cljs | 35 ++-- .../main/ui/workspace/colorpicker/ramp.cljs | 22 +-- .../colorpicker/slider_selector.cljs | 14 +- .../src/app/main/ui/workspace/comments.cljs | 14 +- .../app/main/ui/workspace/context_menu.cljs | 97 +++++----- .../app/main/ui/workspace/coordinates.cljs | 2 +- .../src/app/main/ui/workspace/effects.cljs | 2 +- .../src/app/main/ui/workspace/header.cljs | 7 +- .../src/app/main/ui/workspace/libraries.cljs | 23 ++- .../src/app/main/ui/workspace/presence.cljs | 8 +- frontend/src/app/main/ui/workspace/rules.cljs | 5 +- .../ui/workspace/shapes/bounding_box.cljs | 22 +-- .../app/main/ui/workspace/shapes/frame.cljs | 7 +- .../app/main/ui/workspace/shapes/group.cljs | 9 +- .../app/main/ui/workspace/shapes/path.cljs | 3 - .../main/ui/workspace/shapes/path/editor.cljs | 21 +-- .../app/main/ui/workspace/shapes/svg_raw.cljs | 20 +- .../app/main/ui/workspace/shapes/text.cljs | 19 +- .../main/ui/workspace/shapes/text/editor.cljs | 30 ++- .../app/main/ui/workspace/sidebar/assets.cljs | 173 ++++++++---------- .../main/ui/workspace/sidebar/history.cljs | 17 +- .../app/main/ui/workspace/sidebar/layers.cljs | 5 +- .../main/ui/workspace/sidebar/options.cljs | 47 +++-- 51 files changed, 406 insertions(+), 660 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/attrs.cljs b/frontend/src/app/main/ui/shapes/attrs.cljs index a70dd66b94..dcb26a3695 100644 --- a/frontend/src/app/main/ui/shapes/attrs.cljs +++ b/frontend/src/app/main/ui/shapes/attrs.cljs @@ -6,12 +6,11 @@ (ns app.main.ui.shapes.attrs (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] - [app.common.data :as d] - [app.util.object :as obj] [app.main.ui.context :as muc] - [app.util.svg :as usvg])) + [app.util.object :as obj] + [app.util.svg :as usvg] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (defn- stroke-type->dasharray [style] diff --git a/frontend/src/app/main/ui/shapes/circle.cljs b/frontend/src/app/main/ui/shapes/circle.cljs index da19c2003e..74a4084fbd 100644 --- a/frontend/src/app/main/ui/shapes/circle.cljs +++ b/frontend/src/app/main/ui/shapes/circle.cljs @@ -6,17 +6,17 @@ (ns app.main.ui.shapes.circle (:require - [rumext.alpha :as mf] + [app.common.geom.shapes :as geom] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]] - [app.common.geom.shapes :as geom] - [app.util.object :as obj])) + [app.util.object :as obj] + [rumext.alpha :as mf])) (mf/defc circle-shape {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") - {:keys [id x y width height]} shape + {:keys [x y width height]} shape transform (geom/transform-matrix shape) cx (+ x (/ width 2)) diff --git a/frontend/src/app/main/ui/shapes/custom_stroke.cljs b/frontend/src/app/main/ui/shapes/custom_stroke.cljs index 0fa42483c7..741494946c 100644 --- a/frontend/src/app/main/ui/shapes/custom_stroke.cljs +++ b/frontend/src/app/main/ui/shapes/custom_stroke.cljs @@ -7,8 +7,6 @@ (ns app.main.ui.shapes.custom-stroke (:require [app.common.data :as d] - [app.common.geom.shapes :as geom] - [app.common.uuid :as uuid] [app.main.ui.context :as muc] [app.util.object :as obj] [cuerdas.core :as str] @@ -26,7 +24,7 @@ (-> props (obj/merge #js {:style style})))) (mf/defc inner-stroke-clip-path - [{:keys [shape render-id]}] + [{:keys [render-id]}] (let [clip-id (str "inner-stroke-" render-id) shape-id (str "stroke-shape-" render-id)] [:> "clipPath" #js {:id clip-id} @@ -70,8 +68,6 @@ child (obj/get props "children") base-props (obj/get child "props") elem-name (obj/get child "type") - shape (obj/get props "shape") - stroke-width (:stroke-width shape 0) stroke-mask-id (str "outer-stroke-" render-id) shape-id (str "stroke-shape-" render-id) @@ -148,8 +144,8 @@ stroke-width (:stroke-width shape 0) stroke-style (:stroke-style shape :none) stroke-position (:stroke-alignment shape :center) - has-stroke? (and (and (> stroke-width 0) - (not= stroke-style :none))) + has-stroke? (and (> stroke-width 0) + (not= stroke-style :none)) inner? (= :inner stroke-position) outer? (= :outer stroke-position)] diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index a74740040c..c7d60b3c2d 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.shapes.export (:require [app.common.data :as d] - [app.common.geom.matrix :as gmt] [app.common.geom.shapes :as gsh] [app.util.json :as json] [app.util.object :as obj] @@ -47,8 +46,7 @@ (cond-> props (some? val) (obj/set! ns-attr (trfn val))))))] - (let [frame? (= :frame (:type shape)) - group? (= :group (:type shape)) + (let [group? (= :group (:type shape)) rect? (= :rect (:type shape)) text? (= :text (:type shape)) mask? (and group? (:masked-group? shape)) @@ -175,6 +173,6 @@ (when (and (= (:type shape) :frame) - (not (empty? (:grids shape)))) + (seq (:grids shape))) [:& export-grid-data {:grids (:grids shape)}])])) diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs index 838d7e421b..c2664d695c 100644 --- a/frontend/src/app/main/ui/shapes/filters.cljs +++ b/frontend/src/app/main/ui/shapes/filters.cljs @@ -84,7 +84,7 @@ :result filter-id}]])) (mf/defc background-blur-filter - [{:keys [filter-id filter-in params]}] + [{:keys [filter-id params]}] [:* [:feGaussianBlur {:in "BackgroundImage" :stdDeviation (/ (:value params) 2)}] diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index bf48f0f378..aa1930e779 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -6,7 +6,6 @@ (ns app.main.ui.shapes.frame (:require - [app.common.data :as d] [app.main.ui.shapes.attrs :as attrs] [app.util.object :as obj] [rumext.alpha :as mf])) @@ -20,7 +19,7 @@ [props] (let [childs (unchecked-get props "childs") shape (unchecked-get props "shape") - {:keys [id width height]} shape + {:keys [width height]} shape props (-> (merge frame-default-props shape) (attrs/extract-style-attrs) @@ -32,7 +31,7 @@ :className "frame-background"}))] [:* [:> :rect props] - (for [[i item] (d/enumerate childs)] + (for [item childs] [:& shape-wrapper {:frame shape :shape item :key (:id item)}])]))) diff --git a/frontend/src/app/main/ui/shapes/gradients.cljs b/frontend/src/app/main/ui/shapes/gradients.cljs index 4cee1d330d..51a34a84fe 100644 --- a/frontend/src/app/main/ui/shapes/gradients.cljs +++ b/frontend/src/app/main/ui/shapes/gradients.cljs @@ -6,18 +6,15 @@ (ns app.main.ui.shapes.gradients (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] - [app.util.object :as obj] - [app.common.uuid :as uuid] - [app.main.ui.context :as muc] - [app.common.geom.point :as gpt] [app.common.geom.matrix :as gmt] - [app.common.geom.shapes :as gsh])) + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.main.ui.context :as muc] + [app.util.object :as obj] + [rumext.alpha :as mf])) (mf/defc linear-gradient [{:keys [id gradient shape]}] - (let [{:keys [x y width height]} (:selrect shape) - transform (when (= :path (:type shape)) (gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))] + (let [transform (when (= :path (:type shape)) (gsh/transform-matrix shape nil (gpt/point 0.5 0.5)))] [:> :linearGradient #js {:id id :x1 (:start-x gradient) :y1 (:start-y gradient) @@ -43,49 +40,46 @@ (mf/defc radial-gradient [{:keys [id gradient shape]}] (let [{:keys [x y width height]} (:selrect shape) - center (gsh/center-shape shape) transform (if (= :path (:type shape)) (gsh/transform-matrix shape) - (gmt/matrix))] - (let [[x y] (if (= (:type shape) :frame) [0 0] [x y]) - translate-vec (gpt/point (+ x (* width (:start-x gradient))) - (+ y (* height (:start-y gradient)))) + (gmt/matrix)) + [x y] (if (= (:type shape) :frame) [0 0] [x y]) + translate-vec (gpt/point (+ x (* width (:start-x gradient))) + (+ y (* height (:start-y gradient)))) - gradient-vec (gpt/to-vec (gpt/point (* width (:start-x gradient)) - (* height (:start-y gradient))) - (gpt/point (* width (:end-x gradient)) - (* height (:end-y gradient)))) + gradient-vec (gpt/to-vec (gpt/point (* width (:start-x gradient)) + (* height (:start-y gradient))) + (gpt/point (* width (:end-x gradient)) + (* height (:end-y gradient)))) - angle (gpt/angle gradient-vec - (gpt/point 1 0)) + angle (gpt/angle gradient-vec + (gpt/point 1 0)) - shape-height-vec (gpt/point 0 (/ height 2)) + scale-factor-y (/ (gpt/length gradient-vec) (/ height 2)) + scale-factor-x (* scale-factor-y (:width gradient)) - scale-factor-y (/ (gpt/length gradient-vec) (/ height 2)) - scale-factor-x (* scale-factor-y (:width gradient)) + scale-vec (gpt/point (* scale-factor-y (/ height 2)) + (* scale-factor-x (/ width 2))) - scale-vec (gpt/point (* scale-factor-y (/ height 2)) - (* scale-factor-x (/ width 2))) + transform (gmt/multiply transform + (gmt/translate-matrix translate-vec) + (gmt/rotate-matrix angle) + (gmt/scale-matrix scale-vec)) - transform (gmt/multiply transform - (gmt/translate-matrix translate-vec) - (gmt/rotate-matrix angle) - (gmt/scale-matrix scale-vec)) + base-props #js {:id id + :cx 0 + :cy 0 + :r 1 + :gradientUnits "userSpaceOnUse" + :gradientTransform transform} - base-props #js {:id id - :cx 0 - :cy 0 - :r 1 - :gradientUnits "userSpaceOnUse" - :gradientTransform transform} - - props (-> base-props (add-metadata gradient))] - [:> :radialGradient props - (for [{:keys [offset color opacity]} (:stops gradient)] - [:stop {:key (str id "-stop-" offset) - :offset (or offset 0) - :stop-color color - :stop-opacity opacity}])]))) + props (-> base-props (add-metadata gradient))] + [:> :radialGradient props + (for [{:keys [offset color opacity]} (:stops gradient)] + [:stop {:key (str id "-stop-" offset) + :offset (or offset 0) + :stop-color color + :stop-opacity opacity}])])) (mf/defc gradient {::mf/wrap-props false} diff --git a/frontend/src/app/main/ui/shapes/group.cljs b/frontend/src/app/main/ui/shapes/group.cljs index 9dc4ea0e9f..84d8d54a64 100644 --- a/frontend/src/app/main/ui/shapes/group.cljs +++ b/frontend/src/app/main/ui/shapes/group.cljs @@ -6,10 +6,9 @@ (ns app.main.ui.shapes.group (:require + [app.main.ui.shapes.mask :refer [mask-str clip-str mask-factory]] [app.util.object :as obj] - [rumext.alpha :as mf] - [app.main.ui.shapes.attrs :as attrs] - [app.main.ui.shapes.mask :refer [mask-str clip-str mask-factory]])) + [rumext.alpha :as mf])) (defn group-shape [shape-wrapper] @@ -20,9 +19,8 @@ (let [frame (unchecked-get props "frame") shape (unchecked-get props "shape") childs (unchecked-get props "childs") - pointer-events (unchecked-get props "pointer-events") - {:keys [id x y width height masked-group?]} shape + masked-group? (:masked-group? shape) [mask childs] (if masked-group? [(first childs) (rest childs)] diff --git a/frontend/src/app/main/ui/shapes/image.cljs b/frontend/src/app/main/ui/shapes/image.cljs index 16f9a84d78..5fa4eec47a 100644 --- a/frontend/src/app/main/ui/shapes/image.cljs +++ b/frontend/src/app/main/ui/shapes/image.cljs @@ -7,39 +7,38 @@ (ns app.main.ui.shapes.image (:require [app.common.geom.shapes :as geom] + [app.config :as cfg] [app.main.ui.shapes.attrs :as attrs] - [app.main.ui.shapes.embed :as se] + [app.main.ui.shapes.embed :as embed] [app.util.dom :as dom] [app.util.object :as obj] - [rumext.alpha :as mf] - [app.config :as cfg] - [app.main.ui.shapes.embed :as embed])) + [rumext.alpha :as mf])) (mf/defc image-shape {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") - {:keys [id x y width height rotation metadata]} shape - uri (cfg/resolve-file-media metadata) - embed (embed/use-data-uris [uri])] + {:keys [x y width height metadata]} shape + uri (cfg/resolve-file-media metadata) + embed (embed/use-data-uris [uri]) - (let [transform (geom/transform-matrix shape) - props (-> (attrs/extract-style-attrs shape) - (obj/merge! - #js {:x x - :y y - :transform transform - :width width - :height height - :preserveAspectRatio "none" - :data-loading (str (not (contains? embed uri)))})) + transform (geom/transform-matrix shape) + props (-> (attrs/extract-style-attrs shape) + (obj/merge! + #js {:x x + :y y + :transform transform + :width width + :height height + :preserveAspectRatio "none" + :data-loading (str (not (contains? embed uri)))})) - on-drag-start (fn [event] - ;; Prevent browser dragging of the image - (dom/prevent-default event))] + on-drag-start (fn [event] + ;; Prevent browser dragging of the image + (dom/prevent-default event))] - [:> "image" (obj/merge! - props - #js {:xlinkHref (get embed uri uri) - :onDragStart on-drag-start})]))) + [:> "image" (obj/merge! + props + #js {:xlinkHref (get embed uri uri) + :onDragStart on-drag-start})])) diff --git a/frontend/src/app/main/ui/shapes/mask.cljs b/frontend/src/app/main/ui/shapes/mask.cljs index c48f9c9c43..093ec72016 100644 --- a/frontend/src/app/main/ui/shapes/mask.cljs +++ b/frontend/src/app/main/ui/shapes/mask.cljs @@ -6,9 +6,9 @@ (ns app.main.ui.shapes.mask (:require - [rumext.alpha :as mf] + [app.common.geom.shapes :as gsh] [cuerdas.core :as str] - [app.common.geom.shapes :as gsh])) + [rumext.alpha :as mf])) (defn mask-str [mask] (str/fmt "url(#%s)" (str (:id mask) "-mask"))) diff --git a/frontend/src/app/main/ui/shapes/path.cljs b/frontend/src/app/main/ui/shapes/path.cljs index 9726803051..8c2decdc6f 100644 --- a/frontend/src/app/main/ui/shapes/path.cljs +++ b/frontend/src/app/main/ui/shapes/path.cljs @@ -6,26 +6,23 @@ (ns app.main.ui.shapes.path (:require - [cuerdas.core :as str] - [rumext.alpha :as mf] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]] [app.util.object :as obj] - [app.util.path.format :as upf])) + [app.util.path.format :as upf] + [rumext.alpha :as mf])) ;; --- Path Shape (mf/defc path-shape {::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - background? (unchecked-get props "background?") - {:keys [id x y width height]} (:selrect shape) + (let [shape (unchecked-get props "shape") content (:content shape) - pdata (mf/use-memo (mf/deps content) #(upf/format-path content)) - props (-> (attrs/extract-style-attrs shape) - (obj/merge! - #js {:d pdata}))] + pdata (mf/use-memo (mf/deps content) #(upf/format-path content)) + props (-> (attrs/extract-style-attrs shape) + (obj/merge! + #js {:d pdata}))] [:& shape-custom-stroke {:shape shape} [:> :path props]])) diff --git a/frontend/src/app/main/ui/shapes/rect.cljs b/frontend/src/app/main/ui/shapes/rect.cljs index bb0ccd60a8..a995f51ccb 100644 --- a/frontend/src/app/main/ui/shapes/rect.cljs +++ b/frontend/src/app/main/ui/shapes/rect.cljs @@ -9,7 +9,6 @@ [app.common.geom.shapes :as gsh] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.custom-stroke :refer [shape-custom-stroke]] - [app.main.ui.shapes.gradients :refer [gradient]] [app.util.object :as obj] [rumext.alpha :as mf])) @@ -17,7 +16,7 @@ {::mf/wrap-props false} [props] (let [shape (unchecked-get props "shape") - {:keys [id x y width height]} shape + {:keys [x y width height]} shape transform (gsh/transform-matrix shape) props (-> (attrs/extract-style-attrs shape) diff --git a/frontend/src/app/main/ui/shapes/svg_defs.cljs b/frontend/src/app/main/ui/shapes/svg_defs.cljs index 4002be4568..ac04f57f70 100644 --- a/frontend/src/app/main/ui/shapes/svg_defs.cljs +++ b/frontend/src/app/main/ui/shapes/svg_defs.cljs @@ -100,7 +100,8 @@ (cond->> id (contains? svg-defs id) (str render-id "-")))] - (when (and svg-defs (not (empty? svg-defs))) + ;; TODO: no key? + (when (seq svg-defs) (for [svg-def (vals svg-defs)] [:& svg-node {:node svg-def :prefix-id prefix-id diff --git a/frontend/src/app/main/ui/shapes/svg_raw.cljs b/frontend/src/app/main/ui/shapes/svg_raw.cljs index 01c9841041..407f5096d0 100644 --- a/frontend/src/app/main/ui/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/shapes/svg_raw.cljs @@ -6,15 +6,10 @@ (ns app.main.ui.shapes.svg-raw (:require - [app.common.data :as cd] - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] [app.main.ui.shapes.attrs :as usa] - [app.util.data :as ud] [app.util.object :as obj] [app.util.svg :as usvg] - [cuerdas.core :as str] [rumext.alpha :as mf])) ;; Graphic tags @@ -53,7 +48,7 @@ children (unchecked-get props "children") {:keys [x y width height]} shape - {:keys [tag attrs] :as content} (:content shape) + {:keys [attrs] :as content} (:content shape) ids-mapping (mf/use-memo #(usvg/generate-id-mapping content)) @@ -85,9 +80,7 @@ element-id (get-in content [:attrs :id]) attrs (cond-> (set-styles attrs shape) (and element-id (contains? ids-mapping element-id)) - (obj/set! "id" (get ids-mapping element-id))) - - {:keys [x y width height]} (:selrect shape)] + (obj/set! "id" (get ids-mapping element-id)))] [:> (name tag) attrs children])) (defn svg-raw-shape [shape-wrapper] diff --git a/frontend/src/app/main/ui/shapes/text.cljs b/frontend/src/app/main/ui/shapes/text.cljs index 48a5162400..b6e037dd01 100644 --- a/frontend/src/app/main/ui/shapes/text.cljs +++ b/frontend/src/app/main/ui/shapes/text.cljs @@ -8,12 +8,9 @@ (:require [app.common.data :as d] [app.common.geom.shapes :as geom] - [app.main.ui.context :as muc] [app.main.ui.shapes.attrs :as attrs] [app.main.ui.shapes.text.styles :as sts] - [app.util.color :as uc] [app.util.object :as obj] - [cuerdas.core :as str] [rumext.alpha :as mf])) (mf/defc render-text @@ -40,8 +37,7 @@ (mf/defc render-paragraph-set {::mf/wrap-props false} [props] - (let [node (obj/get props "node") - children (obj/get props "children") + (let [children (obj/get props "children") shape (obj/get props "shape") style (sts/generate-paragraph-set-styles shape)] [:div.paragraph-set {:style style} children])) diff --git a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs index e00a6919c6..e75f0407b2 100644 --- a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs +++ b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs @@ -46,7 +46,7 @@ #(rx/dispose! sub)))) (mf/ref-val fonts-css-ref))) - + (mf/defc fontfaces-style {::mf/wrap-props false ::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]} @@ -74,6 +74,6 @@ ;; Creates a style tag by replacing the urls with the data uri style (replace-embeds fonts-css fonts-urls fonts-embed)] - - (when (and (some? style) (not (empty? style))) + + (when (seq style) [:style style]))) diff --git a/frontend/src/app/main/ui/shapes/text/styles.cljs b/frontend/src/app/main/ui/shapes/text/styles.cljs index 8c15ccb43c..9d247e13fd 100644 --- a/frontend/src/app/main/ui/shapes/text/styles.cljs +++ b/frontend/src/app/main/ui/shapes/text/styles.cljs @@ -15,7 +15,7 @@ (defn generate-root-styles [shape node] - (let [valign (or (:vertical-align node "top")) + (let [valign (:vertical-align node "top") base #js {:height (or (:height shape) "100%") :width (or (:width shape) "100%")}] (cond-> base @@ -104,18 +104,18 @@ (when (and (string? font-id) (pos? (alength font-id))) (fonts/ensure-loaded! font-id) - (let [font (get fontsdb font-id)] - (let [font-family (str/quote - (or (:family font) - (:font-family data))) - font-variant (d/seek #(= font-variant-id (:id %)) - (:variants font)) - font-style (or (:style font-variant) - (:font-style data)) - font-weight (or (:weight font-variant) - (:font-weight data))] - (obj/set! base "fontFamily" font-family) - (obj/set! base "fontStyle" font-style) - (obj/set! base "fontWeight" font-weight)))) + (let [font (get fontsdb font-id) + font-family (str/quote + (or (:family font) + (:font-family data))) + font-variant (d/seek #(= font-variant-id (:id %)) + (:variants font)) + font-style (or (:style font-variant) + (:font-style data)) + font-weight (or (:weight font-variant) + (:font-weight data))] + (obj/set! base "fontFamily" font-family) + (obj/set! base "fontStyle" font-style) + (obj/set! base "fontWeight" font-weight))) base)) diff --git a/frontend/src/app/main/ui/static.cljs b/frontend/src/app/main/ui/static.cljs index 0cd9c0b5a8..aec0d11bc1 100644 --- a/frontend/src/app/main/ui/static.cljs +++ b/frontend/src/app/main/ui/static.cljs @@ -10,12 +10,9 @@ [app.main.data.users :as du] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.context :as ctx] [app.main.ui.icons :as i] [app.util.i18n :refer [tr]] [app.util.router :as rt] - [cljs.spec.alpha :as s] - [cuerdas.core :as str] [rumext.alpha :as mf])) (defn- go-to-dashboard @@ -24,7 +21,7 @@ (st/emit! (rt/nav :dashboard-projects {:team-id team-id})))) (mf/defc not-found - [{:keys [error] :as props}] + [] (let [profile (mf/deref refs/profile)] [:section.exception-layout [:div.exception-header @@ -42,7 +39,7 @@ (tr "labels.sign-out")]]]]])) (mf/defc bad-gateway - [{:keys [error] :as props}] + [] (let [profile (mf/deref refs/profile)] [:section.exception-layout [:div.exception-header @@ -59,7 +56,7 @@ (tr "labels.retry")]]]]])) (mf/defc service-unavailable - [{:keys [error] :as props}] + [] (let [profile (mf/deref refs/profile)] [:section.exception-layout [:div.exception-header @@ -76,7 +73,7 @@ (tr "labels.retry")]]]]])) (mf/defc internal-error - [props] + [] (let [profile (mf/deref refs/profile)] [:section.exception-layout [:div.exception-header diff --git a/frontend/src/app/main/ui/viewer.cljs b/frontend/src/app/main/ui/viewer.cljs index a5f330b7d8..7f6e493a10 100644 --- a/frontend/src/app/main/ui/viewer.cljs +++ b/frontend/src/app/main/ui/viewer.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.viewer (:require [app.common.data :as d] - [app.common.exceptions :as ex] [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as geom] @@ -19,9 +18,8 @@ [app.main.store :as st] [app.main.ui.comments :as cmt] [app.main.ui.hooks :as hooks] - [app.main.ui.icons :as i] [app.main.ui.viewer.header :refer [header]] - [app.main.ui.viewer.shapes :as shapes :refer [frame-svg]] + [app.main.ui.viewer.shapes :as shapes] [app.main.ui.viewer.thumbnails :refer [thumbnails-panel]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t tr]] @@ -44,7 +42,7 @@ (l/derived :comments-local st/state)) (mf/defc comments-layer - [{:keys [width height zoom frame data] :as props}] + [{:keys [zoom frame data] :as props}] (let [profile (mf/deref refs/profile) modifier1 (-> (gpt/point (:x frame) (:y frame)) @@ -62,7 +60,7 @@ mframe (geom/transform-shape frame) threads (->> (vals threads-map) (dcm/apply-filters cstate profile) - (filter (fn [{:keys [seqn position]}] + (filter (fn [{:keys [position]}] (frame-contains? mframe position)))) on-bubble-click @@ -127,7 +125,7 @@ (mf/defc viewport {::mf/wrap [mf/memo]} - [{:keys [state data index section] :or {zoom 1} :as props}] + [{:keys [state data index section] :as props}] (let [zoom (:zoom state) objects (:objects data) diff --git a/frontend/src/app/main/ui/viewer/shapes.cljs b/frontend/src/app/main/ui/viewer/shapes.cljs index 54dde52d74..39d90d175c 100644 --- a/frontend/src/app/main/ui/viewer/shapes.cljs +++ b/frontend/src/app/main/ui/viewer/shapes.cljs @@ -13,10 +13,8 @@ [app.common.geom.shapes :as geom] [app.common.pages :as cp] [app.main.data.viewer :as dv] - [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.shapes.circle :as circle] - [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.group :as group] [app.main.ui.shapes.image :as image] @@ -62,13 +60,10 @@ (mf/fnc generic-wrapper {::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") + (let [shape (unchecked-get props "shape") objects (unchecked-get props "objects") - {:keys [x y width height]} (:selrect shape) - frame? (= :frame (:type shape)) - - childs (unchecked-get props "childs") - frame (unchecked-get props "frame") + childs (unchecked-get props "childs") + frame (unchecked-get props "frame") interactions (->> (:interactions shape) (filter #(contains? objects (:destination %)))) diff --git a/frontend/src/app/main/ui/viewer/thumbnails.cljs b/frontend/src/app/main/ui/viewer/thumbnails.cljs index 66bf403b0b..9c1d09e7ca 100644 --- a/frontend/src/app/main/ui/viewer/thumbnails.cljs +++ b/frontend/src/app/main/ui/viewer/thumbnails.cljs @@ -6,25 +6,16 @@ (ns app.main.ui.viewer.thumbnails (:require - [goog.events :as events] - [goog.object :as gobj] - [rumext.alpha :as mf] - [app.main.ui.icons :as i] [app.common.data :as d] - [app.main.store :as st] [app.main.data.viewer :as dv] - [app.main.ui.components.dropdown :refer [dropdown']] - [app.main.ui.shapes.frame :as frame] [app.main.exports :as exports] + [app.main.store :as st] + [app.main.ui.components.dropdown :refer [dropdown']] + [app.main.ui.icons :as i] [app.util.data :refer [classnames]] - [app.util.dom :as dom] - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] - [app.util.i18n :as i18n :refer [t tr]] - [app.common.math :as mth] - [app.util.router :as rt] - [app.main.data.viewer :as vd]) - (:import goog.events.EventType)) + [app.util.i18n :as i18n :refer [tr]] + [goog.object :as gobj] + [rumext.alpha :as mf])) (mf/defc thumbnails-content [{:keys [children expanded? total] :as props}] @@ -35,14 +26,14 @@ offset (mf/use-state 0) on-left-arrow-click - (fn [event] + (fn [_] (swap! offset (fn [v] (if (pos? v) (dec v) v)))) on-right-arrow-click - (fn [event] + (fn [_] (let [visible (/ @width @element-width) max-val (- total visible)] (swap! offset (fn [v] @@ -90,26 +81,25 @@ [:span.name {:title (:name frame)} (:name frame)]]]) (mf/defc thumbnails-panel - [{:keys [data index screen] :as props}] + [{:keys [data index] :as props}] (let [expanded? (mf/use-state false) container (mf/use-ref) - page-id (get-in data [:page :id]) - file-id (get-in data [:file :id]) on-close #(st/emit! dv/toggle-thumbnails-panel) selected (mf/use-var false) on-mouse-leave - (fn [event] + (fn [_] (when @selected (on-close))) on-item-click - (fn [event index] + (fn [_ index] (compare-and-set! selected false true) (st/emit! (dv/go-to-frame-by-index index)) (when @expanded? (on-close)))] + [:& dropdown' {:on-close on-close :container container :show true} diff --git a/frontend/src/app/main/ui/workspace.cljs b/frontend/src/app/main/ui/workspace.cljs index 34877fdaa2..2086412dfb 100644 --- a/frontend/src/app/main/ui/workspace.cljs +++ b/frontend/src/app/main/ui/workspace.cljs @@ -97,7 +97,7 @@ (when page-id (st/emit! (dw/finalize-page page-id)))))) - (when-let [page (mf/deref trimmed-page-ref)] + (when (mf/deref trimmed-page-ref) [:& workspace-content {:key page-id :file file :layout layout}])) diff --git a/frontend/src/app/main/ui/workspace/colorpalette.cljs b/frontend/src/app/main/ui/workspace/colorpalette.cljs index f4495ccb3c..e4bbf6d37a 100644 --- a/frontend/src/app/main/ui/workspace/colorpalette.cljs +++ b/frontend/src/app/main/ui/workspace/colorpalette.cljs @@ -7,14 +7,12 @@ (ns app.main.ui.workspace.colorpalette (:require [app.common.math :as mth] - [app.main.data.workspace :as udw] [app.main.data.workspace.colors :as mdc] [app.main.data.workspace.state-helpers :as wsh] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.color-bullet :as cb] [app.main.ui.components.dropdown :refer [dropdown]] - [app.main.ui.context :as ctx] [app.main.ui.icons :as i] [app.util.color :as uc] [app.util.i18n :refer [tr]] @@ -39,14 +37,9 @@ (-> (l/in [:workspace-local :selected-palette-size]) (l/derived st/state))) -(defn- make-selected-palette-item-ref - [lib-id] - (-> (l/in [:library-items :palettes lib-id]) - (l/derived st/state))) - ;; --- Components (mf/defc palette-item - [{:keys [color size local?]}] + [{:keys [color size]}] (let [select-color (fn [event] (let [ids (wsh/lookup-selected @st/state)] @@ -70,13 +63,12 @@ max-offset (- (count current-colors) visible) - close-fn #(st/emit! (udw/toggle-layout-flags :colorpalette)) container (mf/use-ref nil) on-left-arrow-click (mf/use-callback (mf/deps max-offset visible) - (fn [event] + (fn [_] (swap! state update :offset (fn [offset] (if (pos? offset) @@ -86,7 +78,7 @@ on-right-arrow-click (mf/use-callback (mf/deps max-offset visible) - (fn [event] + (fn [_] (swap! state update :offset (fn [offset] (if (< offset max-offset) @@ -104,7 +96,7 @@ on-resize (mf/use-callback - (fn [event] + (fn [_] (let [dom (mf/ref-val container) width (obj/get dom "clientWidth")] (swap! state assoc :width width))))] @@ -132,7 +124,7 @@ (when (= selected (:id cur-library)) i/tick) [:div.library-name (str (:name cur-library) " " (str/format "(%s)" (count colors)))] [:div.color-sample - (for [[idx {:keys [id color]}] (map-indexed vector (take 7 colors))] + (for [[idx {:keys [color]}] (map-indexed vector (take 7 colors))] [:& cb/color-bullet {:key (str "color-" idx) :color color}])]])) @@ -193,8 +185,7 @@ (mf/defc colorpalette [] - (let [team-id (mf/use-ctx ctx/current-team-id) - recent-colors (mf/deref refs/workspace-recent-colors) + (let [recent-colors (mf/deref refs/workspace-recent-colors) file-colors (mf/deref refs/workspace-file-colors) shared-libs (mf/deref refs/workspace-libraries) selected (or (mf/deref selected-palette-ref) :recent) diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs index 993c1cdc83..96f338b59d 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs @@ -6,28 +6,24 @@ (ns app.main.ui.workspace.colorpicker (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [cuerdas.core :as str] - [app.common.geom.point :as gpt] - [app.common.math :as math] - [app.common.uuid :refer [uuid]] - [app.util.dom :as dom] - [app.util.color :as uc] - [app.util.object :as obj] - [app.main.store :as st] - [app.main.refs :as refs] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] + [app.main.data.workspace.colors :as dc] + [app.main.data.workspace.libraries :as dwl] + [app.main.refs :as refs] + [app.main.store :as st] [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]] + [app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]] [app.main.ui.workspace.colorpicker.gradients :refer [gradients]] [app.main.ui.workspace.colorpicker.harmony :refer [harmony-selector]] [app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]] + [app.main.ui.workspace.colorpicker.libraries :refer [libraries]] [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]] - [app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]] - [app.main.ui.workspace.colorpicker.libraries :refer [libraries]])) + [app.util.color :as uc] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [t]] + [cuerdas.core :as str] + [okulary.core :as l] + [rumext.alpha :as mf])) ;; --- Refs @@ -127,7 +123,6 @@ picking-color? (mf/deref picking-color?) picked-color (mf/deref picked-color) picked-color-select (mf/deref picked-color-select) - picked-shift? (mf/deref picked-shift?) editing-spot-state (mf/deref editing-spot-state-ref) current-gradient (mf/deref current-gradient-ref) @@ -156,11 +151,11 @@ handle-change-stop (fn [offset] (when-let [offset-color (get-in @state [:stops offset])] - (do (swap! state assoc - :current-color offset-color - :editing-stop offset) + (swap! state assoc + :current-color offset-color + :editing-stop offset) - (st/emit! (dc/select-gradient-stop offset))))) + (st/emit! (dc/select-gradient-stop offset)))) on-select-library-color (fn [color] @@ -172,7 +167,7 @@ (on-change color))))) on-add-library-color - (fn [color] + (fn [_] (st/emit! (dwl/add-color (state->data @state)))) on-activate-gradient @@ -197,7 +192,7 @@ (mf/use-effect (mf/deps current-color) (fn [] (let [node (mf/ref-val ref-picker) - {:keys [r g b h s v]} current-color + {:keys [r g b h v]} current-color rgb [r g b] hue-rgb (uc/hsv->rgb [h 1.0 255]) hsl-from (uc/hsv->hsl [h 0.0 v]) @@ -248,9 +243,8 @@ :end-x :end-y :width])] (when (not= (:gradient-data @state) gradient-data) - (do - (reset! dirty? true) - (swap! state assoc :gradient-data gradient-data))))))) + (reset! dirty? true) + (swap! state assoc :gradient-data gradient-data)))))) ;; Check if we've opened a color with gradient (mf/use-effect @@ -265,7 +259,7 @@ (mf/use-effect (mf/deps @state) (fn [] - (if @dirty? + (when @dirty? (let [color (state->data @state)] (reset! dirty? false) (reset! last-color color) @@ -357,7 +351,7 @@ (mf/defc colorpicker-modal {::mf/register modal/components ::mf/register-as :colorpicker} - [{:keys [x y default data page position + [{:keys [x y data position disable-gradient disable-opacity on-change on-close on-accept] :as props}] @@ -367,7 +361,7 @@ position (or position :left) style (calculate-position vport position x y) - handle-change (fn [new-data shift-clicked?] + handle-change (fn [new-data _shift-clicked?] (reset! dirty? (not= data new-data)) (reset! last-change new-data) (when on-change diff --git a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs index 08a0ddd2db..ed86777bdf 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/color_inputs.cljs @@ -6,22 +6,10 @@ (ns app.main.ui.workspace.colorpicker.color-inputs (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [cuerdas.core :as str] - [app.common.geom.point :as gpt] [app.common.math :as math] - [app.common.uuid :refer [uuid]] - [app.util.dom :as dom] [app.util.color :as uc] - [app.util.object :as obj] - [app.main.store :as st] - [app.main.refs :as refs] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.colors :as dc] - [app.main.data.modal :as modal] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]])) + [app.util.dom :as dom] + [rumext.alpha :as mf])) (mf/defc color-inputs [{:keys [type color disable-opacity on-change]}] (let [{red :r green :g blue :b diff --git a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs index 447bfbbe79..56c3ef0509 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/gradients.cljs @@ -6,22 +6,8 @@ (ns app.main.ui.workspace.colorpicker.gradients (:require - [rumext.alpha :as mf] - [okulary.core :as l] [cuerdas.core :as str] - [app.common.geom.point :as gpt] - [app.common.math :as math] - [app.common.uuid :refer [uuid]] - [app.util.dom :as dom] - [app.util.color :as uc] - [app.util.object :as obj] - [app.main.store :as st] - [app.main.refs :as refs] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.colors :as dc] - [app.main.data.modal :as modal] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]])) + [rumext.alpha :as mf])) (defn gradient->string [stops] (let [format-stop diff --git a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs index d9b9f57a23..d4a2e17726 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/harmony.cljs @@ -6,24 +6,14 @@ (ns app.main.ui.workspace.colorpicker.harmony (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [cuerdas.core :as str] [app.common.geom.point :as gpt] [app.common.math :as math] - [app.common.uuid :refer [uuid]] - [app.util.dom :as dom] + [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] [app.util.color :as uc] + [app.util.dom :as dom] [app.util.object :as obj] - [app.main.store :as st] - [app.main.refs :as refs] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.colors :as dc] - [app.main.data.modal :as modal] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]] - [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]])) - + [cuerdas.core :as str] + [rumext.alpha :as mf])) (defn create-color-wheel [canvas-node] @@ -100,7 +90,7 @@ (on-change {:hex hex :r r :g g :b b :v new-value}))) - on-complement-click (fn [ev] + on-complement-click (fn [_] (let [new-hue (mod (+ hue 180) 360) hex (uc/hsv->hex [new-hue saturation value]) [r g b] (uc/hex->rgb hex)] diff --git a/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs index 0e5e9b8bb6..e9523cf09c 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/hsva.cljs @@ -6,23 +6,9 @@ (ns app.main.ui.workspace.colorpicker.hsva (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [cuerdas.core :as str] - [app.common.geom.point :as gpt] - [app.common.math :as math] - [app.common.uuid :refer [uuid]] - [app.util.dom :as dom] + [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] [app.util.color :as uc] - [app.util.object :as obj] - [app.main.store :as st] - [app.main.refs :as refs] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.colors :as dc] - [app.main.data.modal :as modal] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]] - [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]])) + [rumext.alpha :as mf])) (mf/defc hsva-selector [{:keys [color disable-opacity on-change]}] (let [{hue :h saturation :s value :v alpha :alpha} color diff --git a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs index c866a08e1c..a305e8fed4 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/libraries.cljs @@ -6,42 +6,29 @@ (ns app.main.ui.workspace.colorpicker.libraries (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [cuerdas.core :as str] - [app.common.geom.point :as gpt] - [app.common.math :as math] [app.common.uuid :refer [uuid]] - [app.util.dom :as dom] - [app.util.color :as uc] - [app.util.object :as obj] - [app.main.store :as st] - [app.main.refs :as refs] - [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.colors :as dc] - [app.main.data.modal :as modal] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]] + [app.main.refs :as refs] + [app.main.store :as st] [app.main.ui.components.color-bullet :refer [color-bullet]] - [app.main.ui.workspace.colorpicker.gradients :refer [gradients]] - [app.main.ui.workspace.colorpicker.harmony :refer [harmony-selector]] - [app.main.ui.workspace.colorpicker.hsva :refer [hsva-selector]] - [app.main.ui.workspace.colorpicker.ramp :refer [ramp-selector]] - [app.main.ui.workspace.colorpicker.color-inputs :refer [color-inputs]])) + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [okulary.core :as l] + [rumext.alpha :as mf])) (def selected-palette-ref (-> (l/in [:workspace-local :selected-palette-colorpicker]) (l/derived st/state))) -(mf/defc libraries [{:keys [current-color on-select-color on-add-library-color - disable-gradient disable-opacity]}] +(mf/defc libraries + [{:keys [on-select-color on-add-library-color disable-gradient disable-opacity]}] (let [selected-library (or (mf/deref selected-palette-ref) :recent) current-library-colors (mf/use-state []) shared-libs (mf/deref refs/workspace-libraries) file-colors (mf/deref refs/workspace-file-colors) recent-colors (mf/deref refs/workspace-recent-colors) - locale (mf/deref i18n/locale) parse-selected (fn [selected-str] @@ -85,8 +72,8 @@ (when-let [val (parse-selected (dom/get-target-val e))] (st/emit! (dc/change-palette-selected-colorpicker val)))) :value (name selected-library)} - [:option {:value "recent"} (t locale "workspace.libraries.colors.recent-colors")] - [:option {:value "file"} (t locale "workspace.libraries.colors.file-library")] + [:option {:value "recent"} (tr "workspace.libraries.colors.recent-colors")] + [:option {:value "file"} (tr "workspace.libraries.colors.file-library")] (for [[_ {:keys [name id]}] shared-libs] [:option {:key id diff --git a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs index 23bb4f4133..ea79bbd3cf 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/ramp.cljs @@ -6,26 +6,14 @@ (ns app.main.ui.workspace.colorpicker.ramp (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [cuerdas.core :as str] - [app.common.geom.point :as gpt] [app.common.math :as math] - [app.common.uuid :refer [uuid]] - [app.util.dom :as dom] - [app.util.color :as uc] - [app.util.object :as obj] - [app.main.store :as st] - [app.main.refs :as refs] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.colors :as dc] - [app.main.data.modal :as modal] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]] [app.main.ui.components.color-bullet :refer [color-bullet]] - [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]])) + [app.main.ui.workspace.colorpicker.slider-selector :refer [slider-selector]] + [app.util.color :as uc] + [app.util.dom :as dom] + [rumext.alpha :as mf])) -(mf/defc value-saturation-selector [{:keys [hue saturation value on-change]}] +(mf/defc value-saturation-selector [{:keys [saturation value on-change]}] (let [dragging? (mf/use-state false) calculate-pos (fn [ev] diff --git a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs index 0c3a8e1da1..e0e630e437 100644 --- a/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs +++ b/frontend/src/app/main/ui/workspace/colorpicker/slider_selector.cljs @@ -6,22 +6,10 @@ (ns app.main.ui.workspace.colorpicker.slider-selector (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [cuerdas.core :as str] - [app.common.geom.point :as gpt] [app.common.math :as math] - [app.common.uuid :refer [uuid]] [app.util.dom :as dom] - [app.util.color :as uc] [app.util.object :as obj] - [app.main.store :as st] - [app.main.refs :as refs] - [app.main.data.workspace.libraries :as dwl] - [app.main.data.workspace.colors :as dc] - [app.main.data.modal :as modal] - [app.main.ui.icons :as i] - [app.util.i18n :as i18n :refer [t]])) + [rumext.alpha :as mf])) (mf/defc slider-selector [{:keys [value class min-value max-value vertical? reverse? on-change]}] diff --git a/frontend/src/app/main/ui/workspace/comments.cljs b/frontend/src/app/main/ui/workspace/comments.cljs index da126962e0..7084739318 100644 --- a/frontend/src/app/main/ui/workspace/comments.cljs +++ b/frontend/src/app/main/ui/workspace/comments.cljs @@ -16,7 +16,7 @@ [app.main.ui.context :as ctx] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t tr]] + [app.util.i18n :as i18n :refer [tr]] [app.util.timers :as tm] [rumext.alpha :as mf])) @@ -25,10 +25,8 @@ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (mf/defc sidebar-options - [{:keys [local] :as props}] + [] (let [{cmode :mode cshow :show} (mf/deref refs/comments-local) - locale (mf/deref i18n/locale) - update-mode (mf/use-callback (fn [mode] @@ -43,19 +41,19 @@ [:li {:class (dom/classnames :selected (or (= :all cmode) (nil? cmode))) :on-click #(update-mode :all)} [:span.icon i/tick] - [:span.label (t locale "labels.show-all-comments")]] + [:span.label (tr "labels.show-all-comments")]] [:li {:class (dom/classnames :selected (= :yours cmode)) :on-click #(update-mode :yours)} [:span.icon i/tick] - [:span.label (t locale "labels.show-your-comments")]] + [:span.label (tr "labels.show-your-comments")]] [:hr] [:li {:class (dom/classnames :selected (= :pending cshow)) :on-click #(update-show (if (= :pending cshow) :all :pending))} [:span.icon i/tick] - [:span.label (t locale "labels.hide-resolved-comments")]]])) + [:span.label (tr "labels.hide-resolved-comments")]]])) (mf/defc comments-sidebar [] @@ -109,7 +107,7 @@ :on-thread-click on-thread-click :users users :key (:page-id tgroup)}]])] - + [:div.thread-groups-placeholder i/chat (tr "labels.no-comments-available")])])) diff --git a/frontend/src/app/main/ui/workspace/context_menu.cljs b/frontend/src/app/main/ui/workspace/context_menu.cljs index 5720cd50b9..e493bb5fc5 100644 --- a/frontend/src/app/main/ui/workspace/context_menu.cljs +++ b/frontend/src/app/main/ui/workspace/context_menu.cljs @@ -9,23 +9,17 @@ (:require [app.main.data.modal :as modal] [app.main.data.workspace :as dw] - [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.undo :as dwu] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.shortcuts :as sc] + [app.main.data.workspace.undo :as dwu] [app.main.refs :as refs] [app.main.store :as st] - [app.main.streams :as ms] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.context :as ctx] - [app.main.ui.hooks :refer [use-rxsub]] - [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :refer [t] :as i18n] + [app.util.i18n :refer [tr] :as i18n] [app.util.timers :as timers] - [beicon.core :as rx] [okulary.core :as l] - [potok.core :as ptk] [rumext.alpha :as mf])) (def menu-ref @@ -43,13 +37,12 @@ [:span.shortcut (or shortcut "")]]) (mf/defc menu-separator - [props] + [] [:li.separator]) (mf/defc shape-context-menu [{:keys [mdata] :as props}] - (let [locale (mf/deref i18n/locale) - {:keys [id] :as shape} (:shape mdata) + (let [{:keys [id] :as shape} (:shape mdata) selected (:selected mdata) single? (= (count selected) 1) @@ -97,91 +90,91 @@ do-update-remote-component (st/emitf (modal/show {:type :confirm :message "" - :title (t locale "modals.update-remote-component.message") - :hint (t locale "modals.update-remote-component.hint") - :cancel-label (t locale "modals.update-remote-component.cancel") - :accept-label (t locale "modals.update-remote-component.accept") + :title (tr "modals.update-remote-component.message") + :hint (tr "modals.update-remote-component.hint") + :cancel-label (tr "modals.update-remote-component.cancel") + :accept-label (tr "modals.update-remote-component.accept") :accept-style :primary :on-accept confirm-update-remote-component})) do-show-component (st/emitf (dw/go-to-layout :assets)) do-navigate-component-file (st/emitf (dwl/nav-to-component-file (:component-file shape)))] [:* - [:& menu-entry {:title (t locale "workspace.shape.menu.copy") + [:& menu-entry {:title (tr "workspace.shape.menu.copy") :shortcut (sc/get-tooltip :copy) :on-click do-copy}] - [:& menu-entry {:title (t locale "workspace.shape.menu.cut") + [:& menu-entry {:title (tr "workspace.shape.menu.cut") :shortcut (sc/get-tooltip :cut) :on-click do-cut}] - [:& menu-entry {:title (t locale "workspace.shape.menu.paste") + [:& menu-entry {:title (tr "workspace.shape.menu.paste") :shortcut (sc/get-tooltip :paste) :on-click do-paste}] - [:& menu-entry {:title (t locale "workspace.shape.menu.duplicate") + [:& menu-entry {:title (tr "workspace.shape.menu.duplicate") :shortcut (sc/get-tooltip :duplicate) :on-click do-duplicate}] [:& menu-separator] - [:& menu-entry {:title (t locale "workspace.shape.menu.forward") + [:& menu-entry {:title (tr "workspace.shape.menu.forward") :shortcut (sc/get-tooltip :bring-forward) :on-click do-bring-forward}] - [:& menu-entry {:title (t locale "workspace.shape.menu.front") + [:& menu-entry {:title (tr "workspace.shape.menu.front") :shortcut (sc/get-tooltip :bring-front) :on-click do-bring-to-front}] - [:& menu-entry {:title (t locale "workspace.shape.menu.backward") + [:& menu-entry {:title (tr "workspace.shape.menu.backward") :shortcut (sc/get-tooltip :bring-backward) :on-click do-send-backward}] - [:& menu-entry {:title (t locale "workspace.shape.menu.back") + [:& menu-entry {:title (tr "workspace.shape.menu.back") :shortcut (sc/get-tooltip :bring-back) :on-click do-send-to-back}] [:& menu-separator] (when multiple? [:* - [:& menu-entry {:title (t locale "workspace.shape.menu.group") + [:& menu-entry {:title (tr "workspace.shape.menu.group") :shortcut (sc/get-tooltip :group) :on-click do-create-group}] - [:& menu-entry {:title (t locale "workspace.shape.menu.mask") + [:& menu-entry {:title (tr "workspace.shape.menu.mask") :shortcut (sc/get-tooltip :mask) :on-click do-mask-group}] [:& menu-separator]]) (when (or single? multiple?) [:* - [:& menu-entry {:title (t locale "workspace.shape.menu.flip-vertical") + [:& menu-entry {:title (tr "workspace.shape.menu.flip-vertical") :shortcut (sc/get-tooltip :flip-vertical) :on-click do-flip-vertical}] - [:& menu-entry {:title (t locale "workspace.shape.menu.flip-horizontal") + [:& menu-entry {:title (tr "workspace.shape.menu.flip-horizontal") :shortcut (sc/get-tooltip :flip-horizontal) :on-click do-flip-horizontal}] [:& menu-separator]]) (when (and single? (= (:type shape) :group)) [:* - [:& menu-entry {:title (t locale "workspace.shape.menu.ungroup") + [:& menu-entry {:title (tr "workspace.shape.menu.ungroup") :shortcut (sc/get-tooltip :ungroup) :on-click do-remove-group}] (if (:masked-group? shape) - [:& menu-entry {:title (t locale "workspace.shape.menu.unmask") + [:& menu-entry {:title (tr "workspace.shape.menu.unmask") :shortcut (sc/get-tooltip :unmask) :on-click do-unmask-group}] - [:& menu-entry {:title (t locale "workspace.shape.menu.mask") + [:& menu-entry {:title (tr "workspace.shape.menu.mask") :shortcut (sc/get-tooltip :group) :on-click do-mask-group}])]) (when (and single? editable-shape?) - [:& menu-entry {:title (t locale "workspace.shape.menu.edit") + [:& menu-entry {:title (tr "workspace.shape.menu.edit") :shortcut (sc/get-tooltip :start-editing) :on-click do-start-editing}]) (if (:hidden shape) - [:& menu-entry {:title (t locale "workspace.shape.menu.show") + [:& menu-entry {:title (tr "workspace.shape.menu.show") :on-click do-show-shape}] - [:& menu-entry {:title (t locale "workspace.shape.menu.hide") + [:& menu-entry {:title (tr "workspace.shape.menu.hide") :on-click do-hide-shape}]) (if (:blocked shape) - [:& menu-entry {:title (t locale "workspace.shape.menu.unlock") + [:& menu-entry {:title (tr "workspace.shape.menu.unlock") :on-click do-unlock-shape}] - [:& menu-entry {:title (t locale "workspace.shape.menu.lock") + [:& menu-entry {:title (tr "workspace.shape.menu.lock") :on-click do-lock-shape}]) (when (and (or (nil? (:shape-ref shape)) @@ -189,7 +182,7 @@ (not= (:type shape) :frame)) [:* [:& menu-separator] - [:& menu-entry {:title (t locale "workspace.shape.menu.create-component") + [:& menu-entry {:title (tr "workspace.shape.menu.create-component") :shortcut (sc/get-tooltip :create-component) :on-click do-add-component}]]) @@ -201,41 +194,39 @@ (if (= (:component-file shape) current-file-id) [:* [:& menu-separator] - [:& menu-entry {:title (t locale "workspace.shape.menu.detach-instance") + [:& menu-entry {:title (tr "workspace.shape.menu.detach-instance") :on-click do-detach-component}] - [:& menu-entry {:title (t locale "workspace.shape.menu.reset-overrides") + [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides") :on-click do-reset-component}] - [:& menu-entry {:title (t locale "workspace.shape.menu.update-main") + [:& menu-entry {:title (tr "workspace.shape.menu.update-main") :on-click do-update-component}] - [:& menu-entry {:title (t locale "workspace.shape.menu.show-main") + [:& menu-entry {:title (tr "workspace.shape.menu.show-main") :on-click do-show-component}]] [:* [:& menu-separator] - [:& menu-entry {:title (t locale "workspace.shape.menu.detach-instance") + [:& menu-entry {:title (tr "workspace.shape.menu.detach-instance") :on-click do-detach-component}] - [:& menu-entry {:title (t locale "workspace.shape.menu.reset-overrides") + [:& menu-entry {:title (tr "workspace.shape.menu.reset-overrides") :on-click do-reset-component}] - [:& menu-entry {:title (t locale "workspace.shape.menu.go-main") + [:& menu-entry {:title (tr "workspace.shape.menu.go-main") :on-click do-navigate-component-file}] - [:& menu-entry {:title (t locale "workspace.shape.menu.update-main") + [:& menu-entry {:title (tr "workspace.shape.menu.update-main") :on-click do-update-remote-component}]])) [:& menu-separator] - [:& menu-entry {:title (t locale "workspace.shape.menu.delete") + [:& menu-entry {:title (tr "workspace.shape.menu.delete") :shortcut (sc/get-tooltip :delete) :on-click do-delete}]])) (mf/defc viewport-context-menu - [{:keys [mdata] :as props}] - (let [locale (mf/deref i18n/locale) - do-paste (st/emitf dw/paste)] - [:* - [:& menu-entry {:title (t locale "workspace.shape.menu.paste") - :shortcut (sc/get-tooltip :paste) - :on-click do-paste}]])) + [] + (let [do-paste (st/emitf dw/paste)] + [:& menu-entry {:title (tr "workspace.shape.menu.paste") + :shortcut (sc/get-tooltip :paste) + :on-click do-paste}])) (mf/defc context-menu - [props] + [] (let [mdata (mf/deref menu-ref) top (- (get-in mdata [:position :y]) 20) left (get-in mdata [:position :x]) diff --git a/frontend/src/app/main/ui/workspace/coordinates.cljs b/frontend/src/app/main/ui/workspace/coordinates.cljs index bb8ad3e7f1..a261e9ae92 100644 --- a/frontend/src/app/main/ui/workspace/coordinates.cljs +++ b/frontend/src/app/main/ui/workspace/coordinates.cljs @@ -6,8 +6,8 @@ (ns app.main.ui.workspace.coordinates (:require - [app.main.ui.hooks :as hooks] [app.main.streams :as ms] + [app.main.ui.hooks :as hooks] [rumext.alpha :as mf])) (mf/defc coordinates diff --git a/frontend/src/app/main/ui/workspace/effects.cljs b/frontend/src/app/main/ui/workspace/effects.cljs index b42e1f92ba..12515dc0da 100644 --- a/frontend/src/app/main/ui/workspace/effects.cljs +++ b/frontend/src/app/main/ui/workspace/effects.cljs @@ -68,7 +68,7 @@ (dom/stop-propagation event) (let [toggle-selected? (and selected? shift?) - deselect? (and (not selected?) (not (empty? selected)) (not shift?))] + deselect? (and (not selected?) (seq selected) (not shift?))] (apply st/emit! (cond-> [] diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 9b5d230d82..6eccf64825 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -11,7 +11,6 @@ [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.shortcuts :as sc] - [app.main.data.workspace.shortcuts :as sc] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] @@ -89,8 +88,6 @@ (let [show-menu? (mf/use-state false) editing? (mf/use-state false) - locale (mf/deref i18n/locale) - edit-input-ref (mf/use-ref nil) add-shared-fn @@ -125,7 +122,7 @@ :on-accept del-shared-fn}))) - handle-blur (fn [event] + handle-blur (fn [_] (let [value (-> edit-input-ref mf/ref-val dom/get-value)] (st/emit! (dw/rename-file (:id file) value))) (reset! editing? false)) @@ -243,9 +240,7 @@ [{:keys [file layout project page-id] :as props}] (let [team-id (:team-id project) zoom (mf/deref refs/selected-zoom) - router (mf/deref refs/router) params {:page-id page-id :file-id (:id file)} - view-url (rt/resolve router :viewer params {:index 0}) go-back (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index f78a3aef11..c0766515d6 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -7,18 +7,18 @@ (ns app.main.ui.workspace.libraries (:require [app.common.data :as d] - [rumext.alpha :as mf] - [cuerdas.core :as str] - [okulary.core :as l] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t tr]] - [app.util.data :refer [classnames matches-search]] - [app.main.store :as st] - [app.main.refs :as refs] + [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.libraries :as dwl] + [app.main.refs :as refs] + [app.main.store :as st] [app.main.ui.icons :as i] - [app.main.data.modal :as modal])) + [app.util.data :refer [classnames matches-search]] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [t tr]] + [cuerdas.core :as str] + [okulary.core :as l] + [rumext.alpha :as mf])) (def workspace-file (l/derived :workspace-file st/state)) @@ -68,7 +68,7 @@ on-search-clear (mf/use-callback - (fn [event] + (fn [_] (reset! search-term ""))) link-library @@ -152,8 +152,7 @@ project (mf/deref refs/workspace-project) file (mf/deref workspace-file) libraries (->> (mf/deref refs/workspace-libraries) - (d/removem (fn [[key val]] - (:is-indirect val)))) + (d/removem (fn [[_ val]] (:is-indirect val)))) shared-files (mf/deref refs/workspace-shared-files) change-tab #(reset! selected-tab %) diff --git a/frontend/src/app/main/ui/workspace/presence.cljs b/frontend/src/app/main/ui/workspace/presence.cljs index 8947bfb009..22138de9d6 100644 --- a/frontend/src/app/main/ui/workspace/presence.cljs +++ b/frontend/src/app/main/ui/workspace/presence.cljs @@ -8,14 +8,12 @@ (:require [app.config :as cfg] [app.main.refs :as refs] - [app.main.store :as st] - [app.util.router :as rt] [rumext.alpha :as mf])) ;; --- SESSION WIDGET (mf/defc session-widget - [{:keys [session self? profile] :as props}] + [{:keys [session profile] :as props}] [:li.tooltip.tooltip-bottom {:alt (:fullname profile)} [:img {:style {:border-color (:color session)} @@ -24,15 +22,13 @@ (mf/defc active-sessions {::mf/wrap [mf/memo]} [] - (let [profile (mf/deref refs/profile) - users (mf/deref refs/users) + (let [users (mf/deref refs/users) presence (mf/deref refs/workspace-presence)] [:ul.active-users (for [session (vals presence)] [:& session-widget {:session session :profile (get users (:profile-id session)) - :self? (= (:profile-id session) (:id profile)) :key (:id session)}])])) diff --git a/frontend/src/app/main/ui/workspace/rules.cljs b/frontend/src/app/main/ui/workspace/rules.cljs index d599a6bacf..d455cf9705 100644 --- a/frontend/src/app/main/ui/workspace/rules.cljs +++ b/frontend/src/app/main/ui/workspace/rules.cljs @@ -6,10 +6,9 @@ (ns app.main.ui.workspace.rules (:require - [rumext.alpha :as mf] [app.common.math :as mth] [app.util.object :as obj] - [app.util.timers :as timers])) + [rumext.alpha :as mf])) (defn- calculate-step-size [zoom] @@ -30,7 +29,7 @@ :else 1)) (defn draw-rule! - [dctx {:keys [zoom size start count type] :or {count 200}}] + [dctx {:keys [zoom size start type]}] (when start (let [txfm (- (* (- 0 start) zoom) 20) step (calculate-step-size zoom) diff --git a/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs b/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs index 83e58e5c70..c7d599a2bb 100644 --- a/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/bounding_box.cljs @@ -6,15 +6,11 @@ (ns app.main.ui.workspace.shapes.bounding-box (:require - [cuerdas.core :as str] - [rumext.alpha :as mf] - [app.util.debug :as debug] + ["randomcolor" :as rdcolor] [app.common.geom.shapes :as gsh] - [app.common.geom.matrix :as gmt] - [app.common.geom.point :as gpt] - [app.util.debug :refer [debug?]] [app.main.refs :as refs] - ["randomcolor" :as rdcolor])) + [cuerdas.core :as str] + [rumext.alpha :as mf])) (defn fixed [num] @@ -58,15 +54,11 @@ (mf/defc bounding-box {::mf/wrap-props false} [props] - (let [shape (-> (unchecked-get props "shape")) - frame (unchecked-get props "frame") + (let [shape (unchecked-get props "shape") bounding-box (gsh/points->selrect (-> shape :points)) shape-center (gsh/center-shape shape) - line-color (rdcolor #js {:seed (str (:id shape))}) - zoom (mf/deref refs/selected-zoom) - childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape))) - childs (->> (mf/deref childs-ref) - (map gsh/transform-shape))] + line-color (rdcolor #js {:seed (str (:id shape))}) + zoom (mf/deref refs/selected-zoom)] [:g.bounding-box [:text {:x (:x bounding-box) @@ -81,7 +73,7 @@ [:& cross-point {:point shape-center :zoom zoom :color line-color}]] - + [:g.points (for [point (:points shape)] [:& cross-point {:point point diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 9387ad1ca8..12cbfca7cf 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -7,7 +7,6 @@ (ns app.main.ui.workspace.shapes.frame (:require [app.common.geom.shapes :as gsh] - [app.main.refs :as refs] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.text.fontfaces :as ff] @@ -77,14 +76,10 @@ objects (unchecked-get props "objects") thumbnail? (unchecked-get props "thumbnail?") - edition (mf/deref refs/selected-edition) - shape (gsh/transform-shape shape) children (mapv #(get objects %) (:shapes shape)) - ds-modifier (get-in shape [:modifiers :displacement]) - - rendered? (mf/use-state false) + rendered? (mf/use-state false) show-thumbnail? (and thumbnail? (some? (:thumbnail shape))) diff --git a/frontend/src/app/main/ui/workspace/shapes/group.cljs b/frontend/src/app/main/ui/workspace/shapes/group.cljs index 8e7f22fb48..745523a1bf 100644 --- a/frontend/src/app/main/ui/workspace/shapes/group.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/group.cljs @@ -6,15 +6,12 @@ (ns app.main.ui.workspace.shapes.group (:require - [app.common.geom.shapes :as gsh] [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] [app.main.streams :as ms] - [app.main.ui.hooks :as hooks] [app.main.ui.shapes.group :as group] [app.main.ui.shapes.shape :refer [shape-container]] - [app.util.debug :refer [debug?]] [app.util.dom :as dom] [rumext.alpha :as mf])) @@ -33,10 +30,8 @@ {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))] ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - frame (unchecked-get props "frame") - - {:keys [id x y width height]} shape + (let [shape (unchecked-get props "shape") + frame (unchecked-get props "frame") childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape) {:with-modifiers? true})) childs (mf/deref childs-ref)] diff --git a/frontend/src/app/main/ui/workspace/shapes/path.cljs b/frontend/src/app/main/ui/workspace/shapes/path.cljs index c9183c3fe9..bae6a5d990 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path.cljs @@ -6,13 +6,10 @@ (ns app.main.ui.workspace.shapes.path (:require - [app.main.data.workspace :as dw] [app.main.refs :as refs] - [app.main.store :as st] [app.main.ui.shapes.path :as path] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.workspace.shapes.path.common :as pc] - [app.util.dom :as dom] [app.util.path.commands :as upc] [rumext.alpha :as mf])) 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 5d050a58cd..e266380261 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -17,10 +17,10 @@ [app.main.ui.hooks :as hooks] [app.main.ui.workspace.shapes.path.common :as pc] [app.util.dom :as dom] - [app.util.path.geom :as upg] + [app.util.keyboard :as kbd] [app.util.path.commands :as upc] [app.util.path.format :as upf] - [app.util.keyboard :as kbd] + [app.util.path.geom :as upg] [clojure.set :refer [map-invert]] [goog.events :as events] [rumext.alpha :as mf]) @@ -31,12 +31,12 @@ on-enter (mf/use-callback - (fn [event] + (fn [_] (st/emit! (drp/path-pointer-enter position)))) - + on-leave (mf/use-callback - (fn [event] + (fn [_] (st/emit! (drp/path-pointer-leave position)))) on-mouse-down @@ -96,11 +96,11 @@ (when (and point handler) (let [{:keys [x y]} handler on-enter - (fn [event] + (fn [_] (st/emit! (drp/path-handler-enter index prefix))) on-leave - (fn [event] + (fn [_] (st/emit! (drp/path-handler-leave index prefix))) on-mouse-down @@ -135,7 +135,7 @@ :y (- y (/ 3 zoom)) :width (/ 6 zoom) :height (/ 6 zoom) - + :style {:stroke-width (/ 1 zoom) :stroke (cond (or selected? hover?) pc/black-color :else pc/primary-color) @@ -225,7 +225,6 @@ points (into #{} content-points) - last-command (last content) last-p (->> content last upc/command->point) handlers (upc/content->handlers content) @@ -247,7 +246,7 @@ moving-nodes)) handle-double-click-outside - (fn [event] + (fn [_] (when (= edit-mode :move) (st/emit! :interrupt)))] @@ -298,7 +297,7 @@ last-p? (= last-point (get point->base position)) pos-handlers (->> pos-handlers (filter show-handler?)) - curve? (not (empty? pos-handlers))] + curve? (boolean (seq pos-handlers))] [:g.path-node [:g.point-handlers {:pointer-events (when (= edit-mode :draw) "none")} diff --git a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs index d2a34d3162..dda2e33b3f 100644 --- a/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/svg_raw.cljs @@ -7,11 +7,9 @@ (ns app.main.ui.workspace.shapes.svg-raw (:require [app.main.refs :as refs] - [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.shape :refer [shape-container]] - [rumext.alpha :as mf] - [app.common.geom.shapes :as gsh] - [app.main.ui.context :as muc])) + [app.main.ui.shapes.svg-raw :as svg-raw] + [rumext.alpha :as mf])) (defn svg-raw-wrapper-factory [shape-wrapper] @@ -20,20 +18,12 @@ {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))] ::mf/wrap-props false} [props] - (let [shape (unchecked-get props "shape") - frame (unchecked-get props "frame") - - {:keys [id x y width height]} shape + (let [shape (unchecked-get props "shape") + frame (unchecked-get props "frame") childs-ref (mf/use-memo (mf/deps shape) #(refs/objects-by-id (:shapes shape))) - childs (mf/deref childs-ref) + childs (mf/deref childs-ref)] - {:keys [id x y width height]} shape - transform (gsh/transform-matrix shape) - - tag (get-in shape [:content :tag]) - - def-ctx? (mf/use-ctx muc/def-ctx)] (if (or (= (get-in shape [:content :tag]) :svg) (and (contains? shape :svg-attrs) (map? (:content shape)))) diff --git a/frontend/src/app/main/ui/workspace/shapes/text.cljs b/frontend/src/app/main/ui/workspace/shapes/text.cljs index 9e4a3efbec..6f3b652ce7 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text.cljs @@ -6,25 +6,20 @@ (ns app.main.ui.workspace.shapes.text (:require - [app.common.geom.shapes :as gsh] [app.common.math :as mth] - [app.main.data.workspace :as dw] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.texts :as dwt] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.context :as muc] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.text :as text] - [app.main.ui.workspace.shapes.common :as common] [app.util.dom :as dom] [app.util.logging :as log] [app.util.object :as obj] + [app.util.text-editor :as ted] [app.util.timers :as timers] [app.util.webapi :as wapi] - [app.util.text-editor :as ted] - [okulary.core :as l] [beicon.core :as rx] + [okulary.core :as l] [rumext.alpha :as mf])) ;; Change this to :info :debug or :trace to debug this module @@ -50,7 +45,7 @@ (mf/defc text-resize-content {::mf/wrap-props false} [props] - (let [{:keys [id name x y grow-type] :as shape} (obj/get props "shape") + (let [{:keys [id name grow-type] :as shape} (obj/get props "shape") ;; NOTE: this breaks the hooks rule of "no hooks inside ;; conditional code"; but we ensure that this component will @@ -77,8 +72,8 @@ #(let [width (obj/get-in entries [0 "contentRect" "width"]) height (obj/get-in entries [0 "contentRect" "height"])] (when (and (not (mth/almost-zero? width)) (not (mth/almost-zero? height))) - (do (log/debug :msg "Resize detected" :shape-id id :width width :height height) - (st/emit! (dwt/resize-text id (mth/ceil width) (mth/ceil height)))))))))) + (log/debug :msg "Resize detected" :shape-id id :width width :height height) + (st/emit! (dwt/resize-text id (mth/ceil width) (mth/ceil height))))))))) text-ref-cb (mf/use-callback @@ -109,7 +104,7 @@ (mf/defc text-wrapper {::mf/wrap-props false} [props] - (let [{:keys [id x y width height] :as shape} (unchecked-get props "shape") + (let [{:keys [id] :as shape} (unchecked-get props "shape") edition-ref (mf/use-memo (mf/deps id) #(l/derived (fn [o] (= id (:edition o))) refs/workspace-local)) edition? (mf/deref edition-ref)] @@ -124,4 +119,4 @@ ;; the component if the edition flag changes. [:& text-resize-content {:shape shape :edition? edition? - :key (str (:id shape) edition?)}]]])) + :key (str id edition?)}]]])) 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 8486228935..2859344226 100644 --- a/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/text/editor.cljs @@ -7,12 +7,9 @@ (ns app.main.ui.workspace.shapes.text.editor (:require ["draft-js" :as draft] - [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.text :as txt] [app.main.data.workspace :as dw] - [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.selection :as dws] [app.main.data.workspace.texts :as dwt] [app.main.refs :as refs] [app.main.store :as st] @@ -22,9 +19,7 @@ [app.util.keyboard :as kbd] [app.util.object :as obj] [app.util.text-editor :as ted] - [cuerdas.core :as str] [goog.events :as events] - [okulary.core :as l] [rumext.alpha :as mf]) (:import goog.events.EventType)) @@ -34,12 +29,11 @@ (mf/defc block-component {::mf/wrap-props false} [props] - (let [children (obj/get props "children") - bprops (obj/get props "blockProps") - data (obj/get bprops "data") - style (sts/generate-paragraph-styles (obj/get bprops "shape") + (let [bprops (obj/get props "blockProps") + data (obj/get bprops "data") + style (sts/generate-paragraph-styles (obj/get bprops "shape") (obj/get bprops "data")) - dir (:text-direction data "auto")] + dir (:text-direction data "auto")] [:div {:style style :dir dir} @@ -72,10 +66,9 @@ {::mf/wrap [mf/memo] ::mf/wrap-props false ::mf/forward-ref true} - [props ref] - (let [{:keys [id x y width height grow-type content] :as shape} (obj/get props "shape") + [props _] + (let [{:keys [id content] :as shape} (obj/get props "shape") - zoom (mf/deref refs/selected-zoom) state-map (mf/deref refs/workspace-editor-state) state (get state-map id empty-editor-state) self-ref (mf/use-ref) @@ -86,9 +79,8 @@ (fn [event] (dom/stop-propagation event) (when (kbd/esc? event) - (do - (st/emit! :interrupt) - (st/emit! dw/clear-edition-mode)))) + (st/emit! :interrupt) + (st/emit! dw/clear-edition-mode))) on-mount (fn [] @@ -111,7 +103,7 @@ on-focus (mf/use-callback (mf/deps shape state) - (fn [event] + (fn [_] (reset! blured false))) on-change @@ -131,7 +123,7 @@ handle-return (mf/use-callback - (fn [event state] + (fn [_ state] (st/emit! (dwt/update-editor-state shape (ted/editor-split-block state))) "handled")) ] @@ -165,7 +157,7 @@ {::mf/wrap [mf/memo] ::mf/wrap-props false ::mf/forward-ref true} - [props ref] + [props _] (let [{:keys [id x y width height grow-type] :as shape} (obj/get props "shape") clip-id (str "clip-" id)] [:g.text-editor {:clip-path (str "url(#" clip-id ")")} diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 2935531008..96e035cd4c 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -7,22 +7,18 @@ (ns app.main.ui.workspace.sidebar.assets (:require [app.common.data :as d] - [app.common.spec :as us] - [app.common.geom.point :as gpt] - [app.common.geom.shapes :as geom] [app.common.media :as cm] [app.common.pages :as cp] + [app.common.spec :as us] [app.common.text :as txt] - [app.common.uuid :as uuid] [app.config :as cfg] - [app.main.data.workspace.colors :as dc] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] - [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.undo :as dwu] + [app.main.data.workspace.colors :as dc] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.state-helpers :as wsh] [app.main.data.workspace.texts :as dwt] + [app.main.data.workspace.undo :as dwu] [app.main.exports :as exports] [app.main.refs :as refs] [app.main.store :as st] @@ -31,17 +27,15 @@ [app.main.ui.components.editable-label :refer [editable-label]] [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.components.forms :as fm] - [app.main.ui.components.tab-container :refer [tab-container tab-element]] [app.main.ui.context :as ctx] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.menus.typography :refer [typography-entry]] [app.util.data :refer [matches-search]] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] - [app.util.i18n :as i18n :refer [tr t]] + [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] [app.util.router :as rt] - [app.util.timers :as timers] [cljs.spec.alpha :as s] [cuerdas.core :as str] [okulary.core :as l] @@ -133,7 +127,7 @@ on-accept (mf/use-callback (mf/deps form) - (fn [event] + (fn [_] (let [asset-name (get-in @form [:clean-data :asset-name])] (if create? (accept asset-name) @@ -219,7 +213,7 @@ content)])) (mf/defc asset-section-block - [{:keys [children role]}] + [{:keys [children]}] [:* children]) (mf/defc asset-group-title @@ -352,11 +346,11 @@ selected-components (:components selected-assets) multi-components? (> (count selected-components) 1) - multi-assets? (or (not (empty? (:graphics selected-assets))) - (not (empty? (:colors selected-assets))) - (not (empty? (:typographies selected-assets)))) + multi-assets? (or (seq (:graphics selected-assets)) + (seq (:colors selected-assets)) + (seq (:typographies selected-assets))) - groups (group-assets components) + groups (group-assets components) on-duplicate (mf/use-callback @@ -603,9 +597,9 @@ selected-objects (:graphics selected-assets) multi-objects? (> (count selected-objects) 1) - multi-assets? (or (not (empty? (:components selected-assets))) - (not (empty? (:colors selected-assets))) - (not (empty? (:typographies selected-assets)))) + multi-assets? (or (seq (:components selected-assets)) + (seq (:colors selected-assets)) + (seq (:typographies selected-assets))) groups (group-assets objects) @@ -776,10 +770,8 @@ (mf/defc color-item [{:keys [color local? file-id selected-colors multi-colors? multi-assets? - on-asset-click on-assets-delete on-clear-selection on-group - colors locale] :as props}] + on-asset-click on-assets-delete on-clear-selection on-group] :as props}] (let [rename? (= (:color-for-rename @refs/workspace-local) (:id color)) - id (:id color) input-ref (mf/use-ref) state (mf/use-state {:editing rename?}) @@ -790,8 +782,9 @@ (:color color) (:color color) :else (:value color)) + ;; TODO: looks like the first argument is not necessary apply-color - (fn [color-id event] + (fn [_ event] (let [ids (wsh/lookup-selected @st/state)] (if (kbd/shift? event) (st/emit! (dc/change-stroke ids color)) @@ -892,17 +885,17 @@ {:on-close on-close-menu :state @menu-state :options [(when-not (or multi-colors? multi-assets?) - [(t locale "workspace.assets.rename") rename-color-clicked]) + [(tr "workspace.assets.rename") rename-color-clicked]) (when-not (or multi-colors? multi-assets?) - [(t locale "workspace.assets.edit") edit-color-clicked]) - [(t locale "workspace.assets.delete") delete-color] + [(tr "workspace.assets.edit") edit-color-clicked]) + [(tr "workspace.assets.delete") delete-color] (when-not multi-assets? [(tr "workspace.assets.group") (on-group (:id color))])]}])])) (mf/defc colors-group [{:keys [file-id prefix groups open-groups local? selected-colors multi-colors? multi-assets? on-asset-click on-assets-delete - on-clear-selection on-group on-rename-group on-ungroup colors locale]}] + on-clear-selection on-group on-rename-group on-ungroup colors]}] (let [group-open? (get open-groups prefix true)] [:* @@ -932,8 +925,7 @@ :on-assets-delete on-assets-delete :on-clear-selection on-clear-selection :on-group on-group - :colors colors - :locale locale}]))]) + :colors colors}]))]) (for [[path-item content] groups] (when-not (empty? path-item) [:& colors-group {:file-id file-id @@ -950,24 +942,23 @@ :on-group on-group :on-rename-group on-rename-group :on-ungroup on-ungroup - :colors colors - :locale locale}]))])])) + :colors colors}]))])])) (mf/defc colors-box - [{:keys [file-id local? colors locale open? open-groups selected-assets + [{:keys [file-id local? colors open? open-groups selected-assets on-asset-click on-assets-delete on-clear-selection] :as props}] (let [selected-colors (:colors selected-assets) multi-colors? (> (count selected-colors) 1) - multi-assets? (or (not (empty? (:components selected-assets))) - (not (empty? (:graphics selected-assets))) - (not (empty? (:typographies selected-assets)))) + multi-assets? (or (seq (:components selected-assets)) + (seq (:graphics selected-assets)) + (seq (:typographies selected-assets))) - groups (group-assets colors) + groups (group-assets colors) add-color (mf/use-callback (mf/deps file-id) - (fn [value opacity] + (fn [value _opacity] (st/emit! (dwl/add-color value)))) add-color-clicked @@ -1072,8 +1063,7 @@ :on-group on-group :on-rename-group on-rename-group :on-ungroup on-ungroup - :colors colors - :locale locale}]]])) + :colors colors}]]])) ;; ---- Typography box ---- @@ -1127,7 +1117,7 @@ :on-context-menu on-context-menu}]))])])) (mf/defc typographies-box - [{:keys [file file-id local? typographies locale open? open-groups selected-assets + [{:keys [file file-id local? typographies open? open-groups selected-assets on-asset-click on-assets-delete on-clear-selection] :as props}] (let [state (mf/use-state {:detail-open? false :id nil}) @@ -1140,14 +1130,14 @@ selected-typographies (:typographies selected-assets) multi-typographies? (> (count selected-typographies) 1) - multi-assets? (or (not (empty? (:components selected-assets))) - (not (empty? (:graphics selected-assets))) - (not (empty? (:colors selected-assets)))) + multi-assets? (or (seq (:components selected-assets)) + (seq (:graphics selected-assets)) + (seq (:colors selected-assets))) add-typography (mf/use-callback (mf/deps file-id) - (fn [value opacity] + (fn [_] (st/emit! (dwl/add-typography txt/default-typography)))) handle-change @@ -1157,7 +1147,7 @@ (st/emit! (dwl/update-typography (merge typography changes) file-id)))) apply-typography - (fn [typography event] + (fn [typography _event] (let [ids (wsh/lookup-selected @st/state) attrs (merge {:typography-ref-file file-id @@ -1243,11 +1233,6 @@ (fn [] (swap! menu-state close-auto-pos-menu))) - closed-typography-edit - (mf/use-callback - (mf/deps file-id) - (fn [event] )) - handle-rename-typography-clicked (fn [] (st/emit! #(assoc-in % [:workspace-local :rename-typography] (:id @state)))) @@ -1307,10 +1292,10 @@ {:on-close on-close-menu :state @menu-state :options [(when-not (or multi-typographies? multi-assets?) - [(t locale "workspace.assets.rename") handle-rename-typography-clicked]) + [(tr "workspace.assets.rename") handle-rename-typography-clicked]) (when-not (or multi-typographies? multi-assets?) - [(t locale "workspace.assets.edit") handle-edit-typography-clicked]) - [(t locale "workspace.assets.delete") handle-delete-typography] + [(tr "workspace.assets.edit") handle-edit-typography-clicked]) + [(tr "workspace.assets.delete") handle-delete-typography] (when-not multi-assets? [(tr "workspace.assets.group") on-group])]}])]])) @@ -1322,7 +1307,7 @@ (l/derived (fn [state] (let [wfile (:workspace-data state)] (if (= (:id wfile) id) - (vals (get-in wfile [:colors])) + (vals (get wfile :colors)) (vals (get-in state [:workspace-libraries id :data :colors]))))) st/state =)) @@ -1331,7 +1316,7 @@ (l/derived (fn [state] (let [wfile (:workspace-data state)] (if (= (:id wfile) id) - (vals (get-in wfile [:media])) + (vals (get wfile :media)) (vals (get-in state [:workspace-libraries id :data :media]))))) st/state =)) @@ -1340,7 +1325,7 @@ (l/derived (fn [state] (let [wfile (:workspace-data state)] (if (= (:id wfile) id) - (vals (get-in wfile [:components])) + (vals (get wfile :components)) (vals (get-in state [:workspace-libraries id :data :components]))))) st/state =)) @@ -1349,7 +1334,7 @@ (l/derived (fn [state] (let [wfile (:workspace-data state)] (if (= (:id wfile) id) - (vals (get-in wfile [:typographies])) + (vals (get wfile :typographies)) (vals (get-in state [:workspace-libraries id :data :typographies]))))) st/state =)) @@ -1374,7 +1359,7 @@ comp-fn)))) (mf/defc file-library - [{:keys [file local? default-open? filters locale] :as props}] + [{:keys [file local? default-open? filters] :as props}] (let [open-file (mf/deref (open-file-ref (:id file))) open? (-> open-file :library @@ -1425,23 +1410,23 @@ toggle-sort (mf/use-callback - (fn [event] - (swap! reverse-sort? not))) + (fn [_] + (swap! reverse-sort? not))) toggle-listing (mf/use-callback - (fn [event] - (swap! listing-thumbs? not))) + (fn [_] + (swap! listing-thumbs? not))) toggle-selected-asset (mf/use-callback - (mf/deps @selected-assets) - (fn [asset-type asset-id] - (swap! selected-assets update asset-type - (fn [selected] - (if (contains? selected asset-id) - (disj selected asset-id) - (conj selected asset-id)))))) + (mf/deps @selected-assets) + (fn [asset-type asset-id] + (swap! selected-assets update asset-type + (fn [selected] + (if (contains? selected asset-id) + (disj selected asset-id) + (conj selected asset-id)))))) extend-selected-assets (mf/use-callback @@ -1453,7 +1438,7 @@ (get groups "" []) (reduce concat [] - (->> (filter #(not (empty? (first %))) groups) + (->> (filter #(seq (first %)) groups) (map second) (map flatten-groups)))))] (swap! selected-assets update asset-type @@ -1504,17 +1489,16 @@ (mf/use-callback (mf/deps @selected-assets) (fn [] - (do - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! (map #(dwl/delete-component {:id %}) - (:components @selected-assets))) - (apply st/emit! (map #(dwl/delete-media {:id %}) - (:graphics @selected-assets))) - (apply st/emit! (map #(dwl/delete-color {:id %}) - (:colors @selected-assets))) - (apply st/emit! (map #(dwl/delete-typography %) - (:typographies @selected-assets))) - (st/emit! (dwu/commit-undo-transaction)))))] + (st/emit! (dwu/start-undo-transaction)) + (apply st/emit! (map #(dwl/delete-component {:id %}) + (:components @selected-assets))) + (apply st/emit! (map #(dwl/delete-media {:id %}) + (:graphics @selected-assets))) + (apply st/emit! (map #(dwl/delete-color {:id %}) + (:colors @selected-assets))) + (apply st/emit! (map #(dwl/delete-typography %) + (:typographies @selected-assets))) + (st/emit! (dwu/commit-undo-transaction))))] [:div.tool-window {:on-context-menu #(dom/prevent-default %) :on-click unselect-all} @@ -1526,9 +1510,9 @@ (if local? [:* - [:span (t locale "workspace.assets.file-library")] + [:span (tr "workspace.assets.file-library")] (when shared? - [:span.tool-badge (t locale "workspace.assets.shared")])] + [:span.tool-badge (tr "workspace.assets.shared")])] [:* [:span (:name file)] [:span.tool-link.tooltip.tooltip-left {:alt "Open library file"} @@ -1594,7 +1578,6 @@ (when show-colors? [:& colors-box {:file-id (:id file) :local? local? - :locale locale :colors colors :open? (open-box? :colors) :open-groups (open-groups :colors) @@ -1607,7 +1590,6 @@ [:& typographies-box {:file file :file-id (:id file) :local? local? - :locale locale :typographies typographies :open? (open-box? :typographies) :open-groups (open-groups :typographies) @@ -1618,7 +1600,7 @@ (when (and (not show-components?) (not show-graphics?) (not show-colors?)) [:div.asset-section - [:div.asset-title (t locale "workspace.assets.not-found")]])]))])) + [:div.asset-title (tr "workspace.assets.not-found")]])]))])) (mf/defc assets-toolbox @@ -1627,7 +1609,6 @@ (vals) (remove :is-indirect)) file (mf/deref refs/workspace-file) - locale (mf/deref i18n/locale) team-id (mf/use-ctx ctx/current-team-id) filters (mf/use-state {:term "" :box :all}) @@ -1641,7 +1622,7 @@ on-search-clear-click (mf/use-callback (mf/deps team-id) - (fn [event] + (fn [_] (swap! filters assoc :term ""))) on-box-filter-change @@ -1657,10 +1638,10 @@ [:div.tool-window [:div.tool-window-content [:div.assets-bar-title - (t locale "workspace.assets.assets") + (tr "workspace.assets.assets") [:div.libraries-button {:on-click #(modal/show! :libraries-dialog {})} i/text-align-justify - (t locale "workspace.assets.libraries")]] + (tr "workspace.assets.libraries")]] [:div.search-block [:input.search-input @@ -1677,16 +1658,15 @@ [:select.input-select {:value (:box @filters) :on-change on-box-filter-change} - [:option {:value ":all"} (t locale "workspace.assets.box-filter-all")] - [:option {:value ":components"} (t locale "workspace.assets.components")] - [:option {:value ":graphics"} (t locale "workspace.assets.graphics")] - [:option {:value ":colors"} (t locale "workspace.assets.colors")] - [:option {:value ":typographies"} (t locale "workspace.assets.typography")]]]] + [:option {:value ":all"} (tr "workspace.assets.box-filter-all")] + [:option {:value ":components"} (tr "workspace.assets.components")] + [:option {:value ":graphics"} (tr "workspace.assets.graphics")] + [:option {:value ":colors"} (tr "workspace.assets.colors")] + [:option {:value ":typographies"} (tr "workspace.assets.typography")]]]] [:div.libraries-wrapper [:& file-library {:file file - :locale locale :local? true :default-open? true :filters @filters}] @@ -1697,7 +1677,6 @@ {:key (:id file) :file file :local? false - :locale locale :default-open? false :filters @filters}])]])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/history.cljs b/frontend/src/app/main/ui/workspace/sidebar/history.cljs index 4e44f0e319..86f294d5b2 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/history.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/history.cljs @@ -6,20 +6,15 @@ (ns app.main.ui.workspace.sidebar.history (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] [app.common.data :as d] - [app.main.ui.icons :as i] - [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] - [app.util.data :refer [read-string]] + [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :refer [t] :as i18n] - [app.util.router :as r] - [app.util.time :as dt] + [cuerdas.core :as str] [okulary.core :as l] - [app.main.store :as st])) + [rumext.alpha :as mf])) (def workspace-undo (l/derived :workspace-undo st/state)) @@ -136,7 +131,7 @@ i/layers)) (defn is-shape? [type] - #{:shape :rect :circle :text :path :frame :group}) + (contains? #{:shape :rect :circle :text :path :frame :group} type)) (defn parse-entry [{:keys [redo-changes]}] (->> redo-changes @@ -210,7 +205,7 @@ :modify (->> candidates (filter #(= :modify (:operation %))) (group-by :id) - (d/mapm (fn [k v] (->> v + (d/mapm (fn [_ v] (->> v (mapcat :detail) (map (comp safe-name :attr)) (remove nil?) @@ -279,7 +274,7 @@ (mf/defc history-toolbox [] (let [locale (mf/deref i18n/locale) objects (mf/deref refs/workspace-page-objects) - {:keys [items index transaction]} (mf/deref workspace-undo) + {:keys [items index]} (mf/deref workspace-undo) entries (parse-entries items objects)] [:div.history-toolbox [:div.history-toolbox-title (t locale "workspace.undo.title")] diff --git a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs index 310ece2765..0f147b1f26 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/layers.cljs @@ -16,12 +16,9 @@ [app.main.ui.hooks :as hooks] [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t]] [app.util.keyboard :as kbd] [app.util.object :as obj] - [app.util.perf :as perf] [app.util.timers :as ts] - [beicon.core :as rx] [okulary.core :as l] [rumext.alpha :as mf])) @@ -175,7 +172,7 @@ (st/emit! (dw/select-shape id)))) on-drop - (fn [side {:keys [id] :as data}] + (fn [side _data] (if (= side :center) (st/emit! (dw/relocate-selected-shapes (:id item) 0)) (let [to-index (if (= side :top) (inc index) index) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options.cljs b/frontend/src/app/main/ui/workspace/sidebar/options.cljs index 8f072fcd9e..89248b58b0 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options.cljs @@ -6,13 +6,11 @@ (ns app.main.ui.workspace.sidebar.options (:require - [app.common.spec :as us] [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.icons :as i] [app.main.ui.workspace.sidebar.align :refer [align-options]] [app.main.ui.workspace.sidebar.options.menus.exports :refer [exports-menu]] [app.main.ui.workspace.sidebar.options.menus.interactions :refer [interactions-menu]] @@ -26,9 +24,8 @@ [app.main.ui.workspace.sidebar.options.shapes.rect :as rect] [app.main.ui.workspace.sidebar.options.shapes.svg-raw :as svg-raw] [app.main.ui.workspace.sidebar.options.shapes.text :as text] - [app.util.i18n :as i18n :refer [tr t]] + [app.util.i18n :as i18n :refer [tr]] [app.util.object :as obj] - [beicon.core :as rx] [rumext.alpha :as mf])) ;; --- Options @@ -52,32 +49,30 @@ :page-id page-id :file-id file-id}]]) - (mf/defc options-content {::mf/wrap [mf/memo]} [{:keys [selected section shapes shapes-with-children page-id file-id]}] - (let [locale (mf/deref i18n/locale)] - [:div.tool-window - [:div.tool-window-content - [:& tab-container {:on-change-tab #(st/emit! (udw/set-options-mode %)) - :selected section} - [:& tab-element {:id :design - :title (t locale "workspace.options.design")} - [:div.element-options - [:& align-options] - (case (count selected) - 0 [:& page/options] - 1 [:& shape-options {:shape (first shapes) - :page-id page-id - :file-id file-id - :shapes-with-children shapes-with-children}] - [:& multiple/options {:shapes-with-children shapes-with-children - :shapes shapes}])]] + [:div.tool-window + [:div.tool-window-content + [:& tab-container {:on-change-tab #(st/emit! (udw/set-options-mode %)) + :selected section} + [:& tab-element {:id :design + :title (tr "workspace.options.design")} + [:div.element-options + [:& align-options] + (case (count selected) + 0 [:& page/options] + 1 [:& shape-options {:shape (first shapes) + :page-id page-id + :file-id file-id + :shapes-with-children shapes-with-children}] + [:& multiple/options {:shapes-with-children shapes-with-children + :shapes shapes}])]] - [:& tab-element {:id :prototype - :title (t locale "workspace.options.prototype")} - [:div.element-options - [:& interactions-menu {:shape (first shapes)}]]]]]])) + [:& tab-element {:id :prototype + :title (tr "workspace.options.prototype")} + [:div.element-options + [:& interactions-menu {:shape (first shapes)}]]]]]]) ;; TODO: this need optimizations, selected-objects and From e796c3dfba1cf302ded40b886135e08d7ed0ff9a Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Jun 2021 10:23:03 +0200 Subject: [PATCH 123/204] :sparkles: Fix linter issues on frontend (part 6). --- .clj-kondo/config.edn | 10 +- .clj-kondo/hooks/export.clj | 9 + common/src/app/common/data.cljc | 6 +- common/src/app/common/geom/matrix.cljc | 11 +- common/src/app/common/pages/changes.cljc | 7 +- common/src/app/common/pages/spec.cljc | 10 +- common/src/app/common/spec.cljc | 4 +- common/src/app/common/uuid.cljc | 2 +- frontend/src/app/main/snap.cljs | 2 +- frontend/src/app/main/ui.cljs | 30 +- frontend/src/app/main/ui/auth/register.cljs | 6 +- .../ui/workspace/sidebar/options/common.cljs | 10 +- .../workspace/sidebar/options/menus/blur.cljs | 18 +- .../sidebar/options/menus/component.cljs | 13 +- .../sidebar/options/menus/exports.cljs | 24 +- .../workspace/sidebar/options/menus/fill.cljs | 29 +- .../sidebar/options/menus/frame_grid.cljs | 81 ++-- .../sidebar/options/menus/interactions.cljs | 18 +- .../sidebar/options/menus/layer.cljs | 15 +- .../sidebar/options/menus/measures.cljs | 393 +++++++++--------- .../sidebar/options/menus/shadow.cljs | 58 ++- .../sidebar/options/menus/stroke.cljs | 48 +-- .../sidebar/options/menus/svg_attrs.cljs | 10 +- .../workspace/sidebar/options/menus/text.cljs | 43 +- .../sidebar/options/menus/typography.cljs | 53 +-- .../sidebar/options/rows/input_row.cljs | 6 +- .../sidebar/options/shapes/circle.cljs | 12 +- .../sidebar/options/shapes/frame.cljs | 27 +- .../sidebar/options/shapes/group.cljs | 12 +- .../sidebar/options/shapes/image.cljs | 6 +- .../sidebar/options/shapes/multiple.cljs | 2 +- .../sidebar/options/shapes/path.cljs | 13 +- .../sidebar/options/shapes/rect.cljs | 12 +- .../sidebar/options/shapes/svg_raw.cljs | 39 +- .../main/ui/workspace/sidebar/sitemap.cljs | 10 +- .../src/app/main/ui/workspace/viewport.cljs | 7 +- .../main/ui/workspace/viewport/actions.cljs | 7 +- .../main/ui/workspace/viewport/drawarea.cljs | 19 +- .../ui/workspace/viewport/frame_grid.cljs | 15 +- .../main/ui/workspace/viewport/gradients.cljs | 30 +- .../app/main/ui/workspace/viewport/hooks.cljs | 12 +- .../ui/workspace/viewport/interactions.cljs | 25 +- .../main/ui/workspace/viewport/outline.cljs | 4 +- .../ui/workspace/viewport/path_actions.cljs | 45 +- .../ui/workspace/viewport/pixel_overlay.cljs | 18 +- .../main/ui/workspace/viewport/selection.cljs | 45 +- .../ui/workspace/viewport/snap_distances.cljs | 39 +- .../ui/workspace/viewport/snap_points.cljs | 8 +- .../app/main/ui/workspace/viewport/utils.cljs | 13 +- .../main/ui/workspace/viewport/widgets.cljs | 5 +- frontend/src/app/util/import/parser.cljc | 1 - frontend/src/app/worker/impl.cljs | 1 - frontend/src/app/worker/selection.cljs | 21 +- frontend/src/app/worker/snaps.cljs | 25 +- frontend/src/app/worker/thumbnails.cljs | 11 +- 55 files changed, 629 insertions(+), 771 deletions(-) diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index c1fd2acc7f..7a7b8d6e1a 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -7,14 +7,17 @@ :hooks {:analyze-call {app.common.data/export hooks.export/export - potok.core/reify hooks.export/potok-reify}} + potok.core/reify hooks.export/potok-reify + cljs.core/specify! hooks.export/clojure-specify}} :output {:exclude-files ["data_readers.clj" "app/util/perf.cljs" + "app/common/exceptions.cljc" "app/util/import/.*" - "app/worker/.*" + "app/worker/export.cljs" + "app/worker/import.cljs" "app/libs/.*" "app/main/data/workspace/path/selection.cljs" "app/main/data/workspace/transforms.cljs" @@ -31,6 +34,9 @@ :single-key-in {:level :warning} + :redundant-do + {:level :off} + :unused-binding {:exclude-destructured-as true :exclude-destructured-keys-in-fn-args false diff --git a/.clj-kondo/hooks/export.clj b/.clj-kondo/hooks/export.clj index 5cdb5e4d52..ffa7dceba3 100644 --- a/.clj-kondo/hooks/export.clj +++ b/.clj-kondo/hooks/export.clj @@ -19,3 +19,12 @@ (api/vector-node [])] other))] {:node result})) + +(defn clojure-specify + [{:keys [:node]}] + (let [[rnode rtype & other] (:children node) + result (api/list-node + (into [(api/token-node (symbol "extend-type")) + (api/token-node (gensym (:string-value rtype)))] + other))] + {:node result})) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index acc7bc0633..df18739ea4 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -11,8 +11,8 @@ (:require-macros [app.common.data])) (:require [app.common.math :as mth] + [cljs.analyzer.api :as aapi] [clojure.set :as set] - #?(:clj [cljs.analyzer.api :as aapi]) #?(:cljs [cljs.reader :as r] :clj [clojure.edn :as r]) #?(:cljs [cljs.core :as core] @@ -275,7 +275,7 @@ ;; Data Parsing / Conversion ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- nan? +(defn nan? [v] (not= v v)) @@ -506,7 +506,7 @@ (defn- extract-numeric-suffix [basename] - (if-let [[match p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] + (if-let [[_ p1 p2] (re-find #"(.*)-([0-9]+)$" basename)] [p1 (+ 1 (parse-integer p2))] [basename 1])) diff --git a/common/src/app/common/geom/matrix.cljc b/common/src/app/common/geom/matrix.cljc index decd12595f..c04c05bb3b 100644 --- a/common/src/app/common/geom/matrix.cljc +++ b/common/src/app/common/geom/matrix.cljc @@ -31,7 +31,7 @@ (defn str->matrix [matrix-str] (let [params (->> (re-seq number-regex matrix-str) - (filter #(-> % first empty? not)) + (filter #(-> % first seq)) (map (comp d/parse-double first)))] (apply matrix params))) @@ -52,12 +52,11 @@ "Given two TRANSLATE matrixes (only e and f have significative values), combine them. Quicker than multiplying them, for this precise case." - ([{m1a :a m1b :b m1c :c m1d :d m1e :e m1f :f} - {m2a :a m2b :b m2c :c m2d :d m2e :e m2f :f}] + ([{m1e :e m1f :f} {m2e :e m2f :f}] (Matrix. - 1 - 0 - 0 + 1 + 0 + 0 1 (+ m1e m2e) (+ m1f m2f))) diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index e705619814..47eec12ead 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -32,13 +32,10 @@ (when verify? (us/assert ::spec/changes items)) - (let [pages (into #{} (map :page-id) items) - result (->> items - (reduce #(or (process-change %1 %2) %1) data))] - + (let [result (reduce #(or (process-change %1 %2) %1) data items)] ;; Validate result shapes (only on the backend) #?(:clj - (doseq [page-id pages] + (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])) diff --git a/common/src/app/common/pages/spec.cljc b/common/src/app/common/pages/spec.cljc index 32c010cfd8..9c445ce6de 100644 --- a/common/src/app/common/pages/spec.cljc +++ b/common/src/app/common/pages/spec.cljc @@ -395,11 +395,11 @@ :internal.media-object/mtype])) (s/def ::media-object-update - (s/keys :req-un [::id] - :req-opt [::name - :internal.media-object/width - :internal.media-object/height - :internal.media-object/mtype])) + (s/keys :req-un [::id] + :opt-un [::name + :internal.media-object/width + :internal.media-object/height + :internal.media-object/mtype])) (s/def :internal.file/colors (s/map-of ::uuid ::color)) diff --git a/common/src/app/common/spec.cljc b/common/src/app/common/spec.cljc index 2ab5f6a8ee..ca369fb516 100644 --- a/common/src/app/common/spec.cljc +++ b/common/src/app/common/spec.cljc @@ -15,11 +15,11 @@ ;; NOTE: don't remove this, causes exception on advanced build ;; because of some strange interaction with cljs.spec.alpha and ;; modules spliting. - [expound.alpha] [app.common.exceptions :as ex] [app.common.geom.point :as gpt] [app.common.uuid :as uuid] - [cuerdas.core :as str])) + [cuerdas.core :as str] + [expound.alpha])) (s/check-asserts true) diff --git a/common/src/app/common/uuid.cljc b/common/src/app/common/uuid.cljc index 25f9893815..4d0e483590 100644 --- a/common/src/app/common/uuid.cljc +++ b/common/src/app/common/uuid.cljc @@ -7,7 +7,7 @@ (ns app.common.uuid (:refer-clojure :exclude [next uuid zero?]) (:require - [app.common.data :as d] + #?(:clj [app.common.data :as d]) #?(:clj [clj-uuid :as impl]) #?(:clj [clojure.core :as c]) #?(:cljs [app.common.uuid-impl :as impl]) diff --git a/frontend/src/app/main/snap.cljs b/frontend/src/app/main/snap.cljs index 0479c81e8d..6021730ef5 100644 --- a/frontend/src/app/main/snap.cljs +++ b/frontend/src/app/main/snap.cljs @@ -45,7 +45,7 @@ (apply min-key first) second))) -(defn- snap-frame-id [shapes] +(defn snap-frame-id [shapes] (let [frames (into #{} (map :frame-id shapes))] (cond ;; Only shapes from one frame. The common is the only one diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 3ea44d9ea9..8859dacd35 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -6,15 +6,12 @@ (ns app.main.ui (:require - [app.config :as cf] - [app.common.data :as d] [app.common.exceptions :as ex] [app.common.spec :as us] - [app.common.uuid :as uuid] - [app.config :as cfg] - [app.main.data.users :as du] - [app.main.data.messages :as dm] + [app.config :as cf] [app.main.data.events :as ev] + [app.main.data.messages :as dm] + [app.main.data.users :as du] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.auth :refer [auth]] @@ -32,8 +29,6 @@ [app.main.ui.static :as static] [app.main.ui.viewer :refer [viewer-page]] [app.main.ui.workspace :as workspace] - [app.util.i18n :as i18n :refer [tr t]] - [app.util.router :as rt] [app.util.timers :as ts] [cljs.pprint :refer [pprint]] [cljs.spec.alpha :as s] @@ -60,11 +55,11 @@ (def routes [["/auth" ["/login" :auth-login] - (when cfg/registration-enabled + (when cf/registration-enabled ["/register" :auth-register]) - (when cfg/registration-enabled + (when cf/registration-enabled ["/register/validate" :auth-register-validate]) - (when cfg/registration-enabled + (when cf/registration-enabled ["/register/success" :auth-register-success]) ["/recovery/request" :auth-recovery-request] ["/recovery" :auth-recovery] @@ -102,9 +97,8 @@ (mf/defc on-main-error [{:keys [error] :as props}] - (let [data (ex-data error)] - (mf/use-effect #(ptk/handle-error error)) - [:span "Internal application errror"])) + (mf/use-effect #(ptk/handle-error error)) + [:span "Internal application errror"]) (mf/defc main-page {::mf/wrap [#(mf/catch % {:fallback on-main-error})]} @@ -211,14 +205,14 @@ (derive :service-unavailable ::exceptional-state) (defmethod ptk/handle-error ::exceptional-state - [{:keys [status] :as error}] + [error] (ts/schedule (st/emitf (dm/assign-exception error)))) ;; We receive a explicit authentication error; this explicitly clears ;; all profile data and redirect the user to the login page. (defmethod ptk/handle-error :authentication - [error] + [_] (ts/schedule (st/emitf (du/logout)))) ;; Error that happens on an active bussines model validation does not @@ -245,7 +239,7 @@ ;; Error on parsing an SVG (defmethod ptk/handle-error :svg-parser - [error] + [_] (ts/schedule (st/emitf (dm/show {:content "SVG is invalid or malformed" @@ -261,7 +255,7 @@ context (str/fmt "ns: '%s'\nname: '%s'\nfile: '%s:%s'" (:ns context) (:name context) - (str cfg/public-uri "js/cljs-runtime/" (:file context)) + (str cf/public-uri "js/cljs-runtime/" (:file context)) (:line context))] (ts/schedule (st/emitf diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index d80405a5eb..f6022e2899 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -68,7 +68,7 @@ (st/emit! (dm/error (tr "errors.generic"))))) (defn- handle-prepare-register-success - [form {:keys [token] :as result}] + [_form {:keys [token] :as result}] (st/emit! (rt/nav :auth-register-validate {} {:token token}))) (mf/defc register-form @@ -163,7 +163,7 @@ (st/emit! (dm/error (tr "errors.generic")))))) (defn- handle-register-success - [form data] + [_form data] (cond (some? (:invitation-token data)) (let [token (:invitation-token data)] @@ -197,7 +197,7 @@ on-submit (mf/use-callback - (fn [form event] + (fn [form _event] (reset! submitted? true) (let [params (:clean-data @form)] (->> (rp/mutation :register-profile params) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs index 6468e1b4d1..733fd420b8 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/common.cljs @@ -6,14 +6,10 @@ (ns app.main.ui.workspace.sidebar.options.common (:require - [rumext.alpha :as mf] - [app.util.dom :as dom])) + [rumext.alpha :as mf])) -(mf/defc advanced-options [{:keys [visible? on-close children]}] - (let [ref (mf/use-ref nil) - handle-click (fn [event] (when on-close - (do (dom/stop-propagation event) - (on-close))))] +(mf/defc advanced-options [{:keys [visible? children]}] + (let [ref (mf/use-ref nil)] (mf/use-effect (mf/deps visible?) (fn [] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs index cb2e9fd808..fac3572764 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs @@ -6,16 +6,13 @@ (ns app.main.ui.workspace.sidebar.options.menus.blur (:require - [rumext.alpha :as mf] - [app.common.data :as d] [app.common.uuid :as uuid] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.changes :as dch] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t]])) + [app.util.i18n :as i18n :refer [tr]] + [rumext.alpha :as mf])) (def blur-attrs [:blur]) @@ -27,8 +24,7 @@ :hidden false})) (mf/defc blur-menu [{:keys [ids type values]}] - (let [locale (i18n/use-locale) - blur (:blur values) + (let [blur (:blur values) has-value? (not (nil? blur)) multiple? (= blur :multiple) @@ -59,9 +55,9 @@ [:div.element-set-title [:span (case type - :multiple (t locale "workspace.options.blur-options.title.multiple") - :group (t locale "workspace.options.blur-options.title.group") - (t locale "workspace.options.blur-options.title"))] + :multiple (tr "workspace.options.blur-options.title.multiple") + :group (tr "workspace.options.blur-options.title.group") + (tr "workspace.options.blur-options.title"))] [:div.element-set-title-actions (when (and has-value? (not multiple?)) @@ -78,5 +74,5 @@ :class "pixels" :min 0 :value (:value blur) - :placeholder (t locale "settings.multiple") + :placeholder (tr "settings.multiple") :on-change handle-change}]])])) 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 bc7c393847..024c391d10 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 @@ -6,20 +6,19 @@ (ns app.main.ui.workspace.sidebar.options.menus.component (:require - [rumext.alpha :as mf] [app.common.pages :as cp] [app.main.data.modal :as modal] + [app.main.data.workspace :as dw] + [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.undo :as dwu] [app.main.refs :as refs] [app.main.store :as st] + [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.context :as ctx] [app.main.ui.icons :as i] - [app.main.ui.components.context-menu :refer [context-menu]] - [app.main.data.workspace :as dw] - [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.undo :as dwu] - [app.main.data.workspace.libraries :as dwl] + [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t]] - [app.util.dom :as dom])) + [rumext.alpha :as mf])) (def component-attrs [:component-id :component-file :shape-ref]) 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 09b5a6d8f1..ff24325fcf 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 @@ -6,19 +6,16 @@ (ns app.main.ui.workspace.sidebar.options.menus.exports (:require - [cuerdas.core :as str] - [beicon.core :as rx] - [rumext.alpha :as mf] [app.common.data :as d] - [app.main.repo :as rp] - [app.main.ui.icons :as i] [app.main.data.messages :as dm] [app.main.data.workspace :as udw] + [app.main.repo :as rp] [app.main.store :as st] - [app.util.object :as obj] + [app.main.ui.icons :as i] [app.util.dom :as dom] - [app.util.http :as http] - [app.util.i18n :as i18n :refer [tr t]])) + [app.util.i18n :as i18n :refer [tr]] + [beicon.core :as rx] + [rumext.alpha :as mf])) (defn request-export [shape exports] @@ -31,8 +28,7 @@ (mf/defc exports-menu [{:keys [shape page-id file-id] :as props}] - (let [locale (mf/deref i18n/locale) - exports (:exports shape []) + (let [exports (:exports shape []) loading? (mf/use-state false) filename (cond-> (:name shape) @@ -50,7 +46,7 @@ (rx/subs (fn [body] (dom/trigger-download filename body)) - (fn [error] + (fn [_error] (swap! loading? not) (st/emit! (dm/error (tr "errors.unexpected-error")))) (fn [] @@ -108,7 +104,7 @@ [:div.element-set.exports-options [:div.element-set-title - [:span (t locale "workspace.options.export")] + [:span (tr "workspace.options.export")] [:div.add-page {:on-click add-export} i/close]] (when (seq exports) [:div.element-set-content @@ -141,6 +137,6 @@ :btn-disabled @loading?) :disabled @loading?} (if @loading? - (t locale "workspace.options.exporting-object") - (t locale "workspace.options.export-object"))]])])) + (tr "workspace.options.exporting-object") + (tr "workspace.options.export-object"))]])])) diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs index 47112a3f2b..c13fc2b9a7 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/fill.cljs @@ -8,16 +8,12 @@ (:require [app.common.pages :as cp] [app.main.data.workspace.colors :as dc] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.undo :as dwu] - [app.main.data.workspace.texts :as dwt] - [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [app.util.color :as uc] - [app.util.i18n :as i18n :refer [tr t]] - [app.util.object :as obj] + [app.util.i18n :as i18n :refer [tr]] [rumext.alpha :as mf])) (def fill-attrs @@ -30,14 +26,13 @@ (mf/defc fill-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values"]))]} [{:keys [ids type values] :as props}] - (let [locale (mf/deref i18n/locale) - show? (or (not (nil? (:fill-color values))) - (not (nil? (:fill-color-gradient values)))) + (let [show? (or (not (nil? (:fill-color values))) + (not (nil? (:fill-color-gradient values)))) label (case type - :multiple (t locale "workspace.options.selection-fill") - :group (t locale "workspace.options.group-fill") - (t locale "workspace.options.fill")) + :multiple (tr "workspace.options.selection-fill") + :group (tr "workspace.options.group-fill") + (tr "workspace.options.fill")) color {:color (:fill-color values) :opacity (:fill-opacity values) @@ -48,21 +43,21 @@ on-add (mf/use-callback (mf/deps ids) - (fn [event] + (fn [_] (st/emit! (dc/change-fill ids {:color cp/default-color :opacity 1})))) on-delete (mf/use-callback (mf/deps ids) - (fn [event] + (fn [_] (st/emit! (dc/change-fill ids (into {} uc/empty-color))))) on-change (mf/use-callback (mf/deps ids) (fn [color] - (let [remove-multiple (fn [[key value]] (not= value :multiple)) + (let [remove-multiple (fn [[_ value]] (not= value :multiple)) color (into {} (filter remove-multiple) color)] (st/emit! (dc/change-fill ids color))))) @@ -70,7 +65,7 @@ (mf/use-callback (mf/deps ids) (fn [] - (let [remove-multiple (fn [[key value]] (not= value :multiple)) + (let [remove-multiple (fn [[_ value]] (not= value :multiple)) color (-> (into {} (filter remove-multiple) color) (assoc :id nil :file-id nil))] (st/emit! (dc/change-fill ids color))))) @@ -78,13 +73,13 @@ on-open-picker (mf/use-callback (mf/deps ids) - (fn [value opacity id file-id] + (fn [_value _opacity _id _file-id] (st/emit! (dwu/start-undo-transaction)))) on-close-picker (mf/use-callback (mf/deps ids) - (fn [value opacity id file-id] + (fn [_value _opacity _id _file-id] (st/emit! (dwu/commit-undo-transaction))))] (if show? diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs index 1c30246c86..99f43711ea 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs @@ -6,38 +6,34 @@ (ns app.main.ui.workspace.sidebar.options.menus.frame-grid (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [app.util.dom :as dom] - [app.util.data :as d] [app.common.math :as mth] - [app.common.data :refer [parse-integer]] - [app.main.store :as st] - [app.main.refs :as refs] [app.main.data.workspace.grid :as dw] - [app.util.geom.grid :as gg] + [app.main.refs :as refs] + [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.components.select :refer [select]] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]] - [app.main.ui.components.numeric-input :refer [numeric-input]] - [app.main.ui.components.select :refer [select]] - [app.main.ui.components.editable-select :refer [editable-select]] - [app.main.ui.components.dropdown :refer [dropdown]] - [app.util.i18n :as i18n :refer [tr t]])) + [app.util.data :as d] + [app.util.geom.grid :as gg] + [app.util.i18n :as i18n :refer [tr]] + [okulary.core :as l] + [rumext.alpha :as mf])) (def workspace-saved-grids (l/derived :saved-grids refs/workspace-page-options)) -(defn- get-size-options [locale] - [{:value :auto :label (t locale "workspace.options.grid.auto")} +(defn- get-size-options [] + [{:value :auto :label (tr "workspace.options.grid.auto")} :separator 18 12 10 8 6 4 3 2]) (mf/defc grid-options [{:keys [grid frame default-grid-params on-change on-remove on-save-grid]}] - (let [locale (i18n/use-locale) - size-options (get-size-options locale) + (let [size-options (get-size-options) state (mf/use-state {:show-advanced-options false}) {:keys [type display params]} grid @@ -45,12 +41,12 @@ #(swap! state update :show-advanced-options not) handle-toggle-visibility - (fn [event] + (fn [_] (when on-change (on-change (update grid :display #(if (nil? %) false (not %)))))) handle-remove-grid - (fn [event] + (fn [_] (when on-remove (on-remove))) handle-change-type @@ -82,7 +78,7 @@ handle-change-item-length (fn [item-length] - (let [{:keys [margin gutter size]} (:params grid) + (let [size (get-in grid [:params :size]) size (if (and (nil? item-length) (or (nil? size) (= :auto size))) 12 size)] (when on-change (on-change (-> grid @@ -128,9 +124,9 @@ [:& select {:class "flex-grow" :default-value type - :options [{:value :square :label (t locale "workspace.options.grid.square")} - {:value :column :label (t locale "workspace.options.grid.column")} - {:value :row :label (t locale "workspace.options.grid.row")}] + :options [{:value :square :label (tr "workspace.options.grid.square")} + {:value :column :label (tr "workspace.options.grid.column")} + {:value :row :label (tr "workspace.options.grid.row")}] :on-change handle-change-type}] (if (= type :square) @@ -154,14 +150,14 @@ :on-close toggle-advanced-options} [:button.custom-button {:on-click toggle-advanced-options} i/actions] (when (= :square type) - [:& input-row {:label (t locale "workspace.options.grid.params.size") + [:& input-row {:label (tr "workspace.options.grid.params.size") :class "pixels" :min 1 :value (:size params) :on-change (handle-change :params :size)}]) (when (= :row type) - [:& input-row {:label (t locale "workspace.options.grid.params.rows") + [:& input-row {:label (tr "workspace.options.grid.params.rows") :type :editable-select :options size-options :value (:size params) @@ -170,7 +166,7 @@ :on-change handle-change-size}]) (when (= :column type) - [:& input-row {:label (t locale "workspace.options.grid.params.columns") + [:& input-row {:label (tr "workspace.options.grid.params.columns") :type :editable-select :options size-options :value (:size params) @@ -179,23 +175,23 @@ :on-change handle-change-size}]) (when (#{:row :column} type) - [:& input-row {:label (t locale "workspace.options.grid.params.type") + [:& input-row {:label (tr "workspace.options.grid.params.type") :type :select - :options [{:value :stretch :label (t locale "workspace.options.grid.params.type.stretch")} + :options [{:value :stretch :label (tr "workspace.options.grid.params.type.stretch")} {:value :left :label (if (= type :row) - (t locale "workspace.options.grid.params.type.top") - (t locale "workspace.options.grid.params.type.left"))} - {:value :center :label (t locale "workspace.options.grid.params.type.center")} + (tr "workspace.options.grid.params.type.top") + (tr "workspace.options.grid.params.type.left"))} + {:value :center :label (tr "workspace.options.grid.params.type.center")} {:value :right :label (if (= type :row) - (t locale "workspace.options.grid.params.type.bottom") - (t locale "workspace.options.grid.params.type.right"))}] + (tr "workspace.options.grid.params.type.bottom") + (tr "workspace.options.grid.params.type.right"))}] :value (:type params) :on-change (handle-change :params :type)}]) (when (#{:row :column} type) [:& input-row {:label (if (= :row type) - (t locale "workspace.options.grid.params.height") - (t locale "workspace.options.grid.params.width")) + (tr "workspace.options.grid.params.height") + (tr "workspace.options.grid.params.width")) :class "pixels" :placeholder "Auto" :value (or (:item-length params) "") @@ -203,13 +199,13 @@ (when (#{:row :column} type) [:* - [:& input-row {:label (t locale "workspace.options.grid.params.gutter") + [:& input-row {:label (tr "workspace.options.grid.params.gutter") :class "pixels" :value (:gutter params) :min 0 :placeholder "0" :on-change (handle-change :params :gutter)}] - [:& input-row {:label (t locale "workspace.options.grid.params.margin") + [:& input-row {:label (tr "workspace.options.grid.params.margin") :class "pixels" :min 0 :placeholder "0" @@ -222,13 +218,12 @@ :on-detach handle-detach-color}] [:div.row-flex [:button.btn-options {:disabled is-default - :on-click handle-use-default} (t locale "workspace.options.grid.params.use-default")] + :on-click handle-use-default} (tr "workspace.options.grid.params.use-default")] [:button.btn-options {:disabled is-default - :on-click handle-set-as-default} (t locale "workspace.options.grid.params.set-default")]]]])) + :on-click handle-set-as-default} (tr "workspace.options.grid.params.set-default")]]]])) (mf/defc frame-grid [{:keys [shape]}] - (let [locale (i18n/use-locale) - id (:id shape) + (let [id (:id shape) default-grid-params (merge dw/default-grid-params (mf/deref workspace-saved-grids)) handle-create-grid #(st/emit! (dw/add-frame-grid id)) handle-remove-grid (fn [index] #(st/emit! (dw/remove-frame-grid id index))) @@ -236,10 +231,10 @@ handle-save-grid (fn [grid] (st/emit! (dw/set-default-grid (:type grid) (:params grid))))] [:div.element-set [:div.element-set-title - [:span (t locale "workspace.options.grid.title")] + [:span (tr "workspace.options.grid.title")] [:div.add-page {:on-click handle-create-grid} i/close]] - (when (not (empty? (:grids shape))) + (when (seq (:grids shape)) [:div.element-set-content (for [[index grid] (map-indexed vector (:grids shape))] [:& grid-options {:key (str (:id shape) "-" index) 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 10dad31477..500b5e0388 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 @@ -6,7 +6,6 @@ (ns app.main.ui.workspace.sidebar.options.menus.interactions (:require - [rumext.alpha :as mf] [app.common.data :as d] [app.common.pages :as cp] [app.main.data.workspace :as dw] @@ -14,13 +13,12 @@ [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t]])) + [app.util.i18n :as i18n :refer [tr]] + [rumext.alpha :as mf])) (mf/defc interactions-menu [{:keys [shape] :as props}] - (let [locale (mf/deref i18n/locale) - objects (deref refs/workspace-page-objects) + (let [objects (deref refs/workspace-page-objects) interaction (first (:interactions shape)) ; TODO: in the ; future we may ; have several @@ -48,26 +46,26 @@ (if (not shape) [:* [:div.interactions-help-icon i/interaction] - [:div.interactions-help (t locale "workspace.options.select-a-shape")] + [:div.interactions-help (tr "workspace.options.select-a-shape")] [:div.interactions-help-icon i/play] - [:div.interactions-help (t locale "workspace.options.use-play-button")]] + [:div.interactions-help (tr "workspace.options.use-play-button")]] [:div.element-set {:on-blur on-set-blur} [:div.element-set-title - [:span (t locale "workspace.options.navigate-to")]] + [:span (tr "workspace.options.navigate-to")]] [:div.element-set-content [:div.row-flex [:div.custom-select.flex-grow {:on-click #(reset! show-frames-dropdown? true)} (if destination [:span (:name destination)] - [:span (t locale "workspace.options.select-artboard")]) + [:span (tr "workspace.options.select-artboard")]) [:span.dropdown-button i/arrow-down] [:& dropdown {:show @show-frames-dropdown? :on-close #(reset! show-frames-dropdown? false)} [:ul.custom-select-dropdown [:li.dropdown-separator {:on-click #(on-select-destination nil)} - (t locale "workspace.options.none")] + (tr "workspace.options.none")] (for [frame frames] (when (and (not= (:id frame) (:id shape)) ; A frame cannot navigate to itself diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs index c7be781cf5..68f2e86650 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/layer.cljs @@ -8,7 +8,6 @@ (:require [app.common.data :as d] [app.common.math :as mth] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.changes :as dch] [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] @@ -54,25 +53,25 @@ handle-set-hidden (mf/use-callback (mf/deps change!) - (fn [event] + (fn [_] (change! :hidden true))) handle-set-visible (mf/use-callback (mf/deps change!) - (fn [event] + (fn [_] (change! :hidden false))) handle-set-blocked (mf/use-callback (mf/deps change!) - (fn [event] + (fn [_] (change! :blocked true))) handle-set-unblocked (mf/use-callback (mf/deps change!) - (fn [event] + (fn [_] (change! :blocked false)))] [:div.element-set @@ -92,7 +91,7 @@ [:option {:value "multiple"} "--"]) [:option {:value "normal"} (tr "workspace.options.layer-options.blend-mode.normal")] - + [:option {:value "darken"} (tr "workspace.options.layer-options.blend-mode.darken")] [:option {:value "multiply"} (tr "workspace.options.layer-options.blend-mode.multiply")] [:option {:value "color-burn"} (tr "workspace.options.layer-options.blend-mode.color-burn")] @@ -127,13 +126,13 @@ (cond (or (= :multiple (:hidden values)) (not (:hidden values))) [:div.element-set-actions-button {:on-click handle-set-hidden} i/eye] - + :else [:div.element-set-actions-button {:on-click handle-set-visible} i/eye-closed]) (cond (or (= :multiple (:blocked values)) (not (:blocked values))) [:div.element-set-actions-button {:on-click handle-set-blocked} i/unlock] - + :else [:div.element-set-actions-button {:on-click handle-set-unblocked} i/lock])]]]])) 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 f7e8fa6323..7ac385827a 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 @@ -6,24 +6,21 @@ (ns app.main.ui.workspace.sidebar.options.menus.measures (:require - [cuerdas.core :as str] - [rumext.alpha :as mf] - [app.main.ui.icons :as i] - [app.main.store :as st] - [app.main.refs :as refs] [app.common.data :as d] - [app.util.dom :as dom] - [app.util.data :refer [classnames]] [app.common.geom.shapes :as gsh] - [app.common.geom.point :as gpt] + [app.common.math :as math] [app.common.pages.spec :as spec] [app.common.uuid :as uuid] [app.main.data.workspace :as udw] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.changes :as dch] + [app.main.refs :as refs] + [app.main.store :as st] [app.main.ui.components.numeric-input :refer [numeric-input]] - [app.common.math :as math] - [app.util.i18n :refer [tr] :as i18n])) + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (def measure-attrs [:proportion-lock :width :height @@ -88,7 +85,7 @@ on-proportion-lock-change (mf/use-callback (mf/deps ids) - (fn [event] + (fn [_] (let [new-lock (if (= proportion-lock :multiple) true (not proportion-lock))] (run! #(st/emit! (udw/set-shape-proportion-lock % new-lock)) ids)))) @@ -114,7 +111,7 @@ on-switch-to-radius-1 (mf/use-callback (mf/deps ids) - (fn [value] + (fn [_value] (let [radius-update (fn [shape] (cond-> shape @@ -126,7 +123,7 @@ on-switch-to-radius-4 (mf/use-callback (mf/deps ids) - (fn [value] + (fn [_value] (let [radius-update (fn [shape] (cond-> shape @@ -180,164 +177,162 @@ on-constraint-button-clicked (mf/use-callback - (mf/deps [ids values]) - (fn [button] - (fn [event] - (let [constraints-h (get values :constraints-h :scale) - constraints-v (get values :constraints-v :scale) + (mf/deps [ids values]) + (fn [button] + (fn [_] + (let [constraints-h (get values :constraints-h :scale) + constraints-v (get values :constraints-v :scale) - [constraint new-value] - (case button - :top (case constraints-v - :top [:constraints-v :scale] - :topbottom [:constraints-v :bottom] - :bottom [:constraints-v :topbottom] - [:constraints-v :top]) - :bottom (case constraints-v - :bottom [:constraints-v :scale] - :topbottom [:constraints-v :top] - :top [:constraints-v :topbottom] - [:constraints-v :bottom]) - :left (case constraints-h - :left [:constraints-h :scale] - :leftright [:constraints-h :right] - :right [:constraints-h :leftright] - [:constraints-h :left]) - :right (case constraints-h - :right [:constraints-h :scale] - :leftright [:constraints-h :left] - :left [:constraints-h :leftright] - [:constraints-h :right]) - :centerv (case constraints-v + [constraint new-value] + (case button + :top (case constraints-v + :top [:constraints-v :scale] + :topbottom [:constraints-v :bottom] + :bottom [:constraints-v :topbottom] + [:constraints-v :top]) + :bottom (case constraints-v + :bottom [:constraints-v :scale] + :topbottom [:constraints-v :top] + :top [:constraints-v :topbottom] + [:constraints-v :bottom]) + :left (case constraints-h + :left [:constraints-h :scale] + :leftright [:constraints-h :right] + :right [:constraints-h :leftright] + [:constraints-h :left]) + :right (case constraints-h + :right [:constraints-h :scale] + :leftright [:constraints-h :left] + :left [:constraints-h :leftright] + [:constraints-h :right]) + :centerv (case constraints-v :center [:constraints-v :scale] [:constraints-v :center]) - :centerh (case constraints-h + :centerh (case constraints-h :center [:constraints-h :scale] [:constraints-h :center]))] - (st/emit! (dch/update-shapes - ids - #(assoc % constraint new-value))))))) + (st/emit! (dch/update-shapes + ids + #(assoc % constraint new-value))))))) on-constraint-select-changed (mf/use-callback - (mf/deps [ids values]) - (fn [constraint] + (mf/deps [ids values]) + (fn [constraint] (fn [event] (let [value (-> (dom/get-target-val event) (keyword))] (when-not (str/empty? value) (st/emit! (dch/update-shapes - ids - #(assoc % constraint value)))))))) + ids + #(assoc % constraint value)))))))) on-fixed-scroll-clicked (mf/use-callback - (mf/deps [ids values]) - (fn [event] - (st/emit! (dch/update-shapes - ids - #(update % :fixed-scroll not)))))] + (mf/deps [ids values]) + (fn [_] + (st/emit! (dch/update-shapes ids #(update % :fixed-scroll not)))))] [:* - [:div.element-set - [:div.element-set-content + [:div.element-set + [:div.element-set-content - ;; WIDTH & HEIGHT - (when (options :size) - [:div.row-flex - [:span.element-set-subtitle (tr "workspace.options.size")] - [:div.input-element.width - [:> numeric-input {:min 1 - :no-validate true - :placeholder "--" - :on-click select-all - :on-change on-width-change - :value (attr->string :width values)}]] + ;; WIDTH & HEIGHT + (when (options :size) + [:div.row-flex + [:span.element-set-subtitle (tr "workspace.options.size")] + [:div.input-element.width + [:> numeric-input {:min 1 + :no-validate true + :placeholder "--" + :on-click select-all + :on-change on-width-change + :value (attr->string :width values)}]] - [:div.input-element.height - [:> numeric-input {:min 1 - :no-validate true - :placeholder "--" - :on-click select-all - :on-change on-height-change - :value (attr->string :height values)}]] + [:div.input-element.height + [:> numeric-input {:min 1 + :no-validate true + :placeholder "--" + :on-click select-all + :on-change on-height-change + :value (attr->string :height values)}]] - [:div.lock-size {:class (classnames + [:div.lock-size {:class (dom/classnames :selected (true? proportion-lock) :disabled (= proportion-lock :multiple)) - :on-click on-proportion-lock-change} - (if proportion-lock - i/lock - i/unlock)]]) + :on-click on-proportion-lock-change} + (if proportion-lock + i/lock + i/unlock)]]) - ;; POSITION - (when (options :position) - [:div.row-flex - [:span.element-set-subtitle (tr "workspace.options.position")] - [:div.input-element.Xaxis - [:> numeric-input {:no-validate true - :placeholder "--" - :on-click select-all - :on-change on-pos-x-change - :value (attr->string :x values)}]] - [:div.input-element.Yaxis - [:> numeric-input {:no-validate true - :placeholder "--" - :on-click select-all - :on-change on-pos-y-change - :value (attr->string :y values)}]]]) + ;; POSITION + (when (options :position) + [:div.row-flex + [:span.element-set-subtitle (tr "workspace.options.position")] + [:div.input-element.Xaxis + [:> numeric-input {:no-validate true + :placeholder "--" + :on-click select-all + :on-change on-pos-x-change + :value (attr->string :x values)}]] + [:div.input-element.Yaxis + [:> numeric-input {:no-validate true + :placeholder "--" + :on-click select-all + :on-change on-pos-y-change + :value (attr->string :y values)}]]]) - ;; ROTATION - (when (options :rotation) - [:div.row-flex - [:span.element-set-subtitle (tr "workspace.options.rotation")] - [:div.input-element.degrees - [:> numeric-input - {:no-validate true - :min 0 - :max 359 - :data-wrap true - :placeholder "--" - :on-click select-all - :on-change on-rotation-change - :value (attr->string :rotation values)}]] - #_[:input.slidebar - {:type "range" - :min "0" - :max "359" - :step "10" - :no-validate true - :on-change on-rotation-change - :value (attr->string :rotation values)}]]) + ;; ROTATION + (when (options :rotation) + [:div.row-flex + [:span.element-set-subtitle (tr "workspace.options.rotation")] + [:div.input-element.degrees + [:> numeric-input + {:no-validate true + :min 0 + :max 359 + :data-wrap true + :placeholder "--" + :on-click select-all + :on-change on-rotation-change + :value (attr->string :rotation values)}]] + #_[:input.slidebar + {:type "range" + :min "0" + :max "359" + :step "10" + :no-validate true + :on-change on-rotation-change + :value (attr->string :rotation values)}]]) - ;; RADIUS - (let [radius-1? (some? (:rx values)) - radius-4? (some? (:r1 values))] - (when (and (options :radius) (or radius-1? radius-4?)) - [:div.row-flex - [:div.radius-options + ;; RADIUS + (let [radius-1? (some? (:rx values)) + radius-4? (some? (:r1 values))] + (when (and (options :radius) (or radius-1? radius-4?)) + [:div.row-flex + [:div.radius-options [:div.radius-icon.tooltip.tooltip-bottom - {:class (classnames - :selected - (and radius-1? (not radius-4?))) + {:class (dom/classnames + :selected + (and radius-1? (not radius-4?))) :alt (tr "workspace.options.radius.all-corners") :on-click on-switch-to-radius-1} i/radius-1] [:div.radius-icon.tooltip.tooltip-bottom - {:class (classnames - :selected - (and radius-4? (not radius-1?))) + {:class (dom/classnames + :selected + (and radius-4? (not radius-1?))) :alt (tr "workspace.options.radius.single-corners") :on-click on-switch-to-radius-4} i/radius-4]] - (if radius-1? - [:div.input-element.mini - [:> numeric-input - {:placeholder "--" - :min 0 - :on-click select-all - :on-change on-radius-1-change - :value (attr->string :rx values)}]] + (if radius-1? + [:div.input-element.mini + [:> numeric-input + {:placeholder "--" + :min 0 + :on-click select-all + :on-change on-radius-1-change + :value (attr->string :rx values)}]] - [:* + [:* [:div.input-element.mini [:> numeric-input {:placeholder "--" @@ -367,66 +362,66 @@ :on-change on-radius-r4-change :value (attr->string :r4 values)}]]])]))]] - ;; CONSTRAINTS - (when in-frame? - [:div.element-set - [:div.element-set-title - [:span (tr "workspace.options.constraints")]] + ;; CONSTRAINTS + (when in-frame? + [:div.element-set + [:div.element-set-title + [:span (tr "workspace.options.constraints")]] - [:div.element-set-content - [:div.row-flex.align-top + [:div.element-set-content + [:div.row-flex.align-top - [:div.constraints-widget - [:div.constraints-box] - [:div.constraint-button.top - {:class (classnames :active (or (= constraints-v :top) - (= constraints-v :topbottom))) - :on-click (on-constraint-button-clicked :top)}] - [:div.constraint-button.bottom - {:class (classnames :active (or (= constraints-v :bottom) - (= constraints-v :topbottom))) - :on-click (on-constraint-button-clicked :bottom)}] - [:div.constraint-button.left - {:class (classnames :active (or (= constraints-h :left) - (= constraints-h :leftright))) - :on-click (on-constraint-button-clicked :left)}] - [:div.constraint-button.right - {:class (classnames :active (or (= constraints-h :right) - (= constraints-h :leftright))) - :on-click (on-constraint-button-clicked :right)}] - [:div.constraint-button.centerv - {:class (classnames :active (= constraints-v :center)) - :on-click (on-constraint-button-clicked :centerv)}] - [:div.constraint-button.centerh - {:class (classnames :active (= constraints-h :center)) - :on-click (on-constraint-button-clicked :centerh)}]] + [:div.constraints-widget + [:div.constraints-box] + [:div.constraint-button.top + {:class (dom/classnames :active (or (= constraints-v :top) + (= constraints-v :topbottom))) + :on-click (on-constraint-button-clicked :top)}] + [:div.constraint-button.bottom + {:class (dom/classnames :active (or (= constraints-v :bottom) + (= constraints-v :topbottom))) + :on-click (on-constraint-button-clicked :bottom)}] + [:div.constraint-button.left + {:class (dom/classnames :active (or (= constraints-h :left) + (= constraints-h :leftright))) + :on-click (on-constraint-button-clicked :left)}] + [:div.constraint-button.right + {:class (dom/classnames :active (or (= constraints-h :right) + (= constraints-h :leftright))) + :on-click (on-constraint-button-clicked :right)}] + [:div.constraint-button.centerv + {:class (dom/classnames :active (= constraints-v :center)) + :on-click (on-constraint-button-clicked :centerv)}] + [:div.constraint-button.centerh + {:class (dom/classnames :active (= constraints-h :center)) + :on-click (on-constraint-button-clicked :centerh)}]] - [:div.constraints-form - [:div.row-flex - [:span.left-right i/full-screen] - [:select.input-select {:on-change (on-constraint-select-changed :constraints-h) - :value (d/name constraints-h "scale")} - (when (= constraints-h :multiple) - [:option {:value ""} (tr "settings.multiple")]) - [:option {:value "left"} (tr "workspace.options.constraints.left")] - [:option {:value "right"} (tr "workspace.options.constraints.right")] - [:option {:value "leftright"} (tr "workspace.options.constraints.leftright")] - [:option {:value "center"} (tr "workspace.options.constraints.center")] - [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] - [:div.row-flex - [:span.top-bottom i/full-screen] - [:select.input-select {:on-change (on-constraint-select-changed :constraints-v) - :value (d/name constraints-v "scale")} - (when (= constraints-v :multiple) - [:option {:value ""} (tr "settings.multiple")]) - [:option {:value "top"} (tr "workspace.options.constraints.top")] - [:option {:value "bottom"} (tr "workspace.options.constraints.bottom")] - [:option {:value "topbottom"} (tr "workspace.options.constraints.topbottom")] - [:option {:value "center"} (tr "workspace.options.constraints.center")] - [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] - (when first-level? - [:div.row-flex - [:div.fix-when {:class (classnames :active (:fixed-scroll values)) - :on-click on-fixed-scroll-clicked} - i/pin - [:span (tr "workspace.options.constraints.fix-when-scrolling")]]])]]]])])) + [:div.constraints-form + [:div.row-flex + [:span.left-right i/full-screen] + [:select.input-select {:on-change (on-constraint-select-changed :constraints-h) + :value (d/name constraints-h "scale")} + (when (= constraints-h :multiple) + [:option {:value ""} (tr "settings.multiple")]) + [:option {:value "left"} (tr "workspace.options.constraints.left")] + [:option {:value "right"} (tr "workspace.options.constraints.right")] + [:option {:value "leftright"} (tr "workspace.options.constraints.leftright")] + [:option {:value "center"} (tr "workspace.options.constraints.center")] + [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] + [:div.row-flex + [:span.top-bottom i/full-screen] + [:select.input-select {:on-change (on-constraint-select-changed :constraints-v) + :value (d/name constraints-v "scale")} + (when (= constraints-v :multiple) + [:option {:value ""} (tr "settings.multiple")]) + [:option {:value "top"} (tr "workspace.options.constraints.top")] + [:option {:value "bottom"} (tr "workspace.options.constraints.bottom")] + [:option {:value "topbottom"} (tr "workspace.options.constraints.topbottom")] + [:option {:value "center"} (tr "workspace.options.constraints.center")] + [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] + (when first-level? + [:div.row-flex + [:div.fix-when {:class (dom/classnames :active (:fixed-scroll values)) + :on-click on-fixed-scroll-clicked} + i/pin + [:span (tr "workspace.options.constraints.fix-when-scrolling")]]])]]]])])) 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 9b6e487353..5cd11f6f3a 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 @@ -6,19 +6,18 @@ (ns app.main.ui.workspace.sidebar.options.menus.shadow (:require - [rumext.alpha :as mf] [app.common.data :as d] [app.common.uuid :as uuid] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.undo :as dwu] [app.main.store :as st] - [app.main.ui.icons :as i] [app.main.ui.components.numeric-input :refer [numeric-input]] + [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t]])) + [app.util.i18n :as i18n :refer [tr]] + [rumext.alpha :as mf])) (def shadow-attrs [:shadow]) @@ -38,8 +37,7 @@ (mf/defc shadow-entry [{:keys [ids index value]}] - (let [locale (i18n/use-locale) - open-shadow (mf/use-state false) + (let [open-shadow (mf/use-state false) basic-offset-x-ref (mf/use-ref nil) basic-offset-y-ref (mf/use-ref nil) @@ -52,7 +50,7 @@ remove-shadow-by-index (fn [values index] (->> (d/enumerate values) - (filterv (fn [[idx s]] (not= idx index))) + (filterv (fn [[idx _]] (not= idx index))) (mapv second))) on-remove-shadow @@ -61,7 +59,7 @@ (st/emit! (dch/update-shapes ids #(update % :shadow remove-shadow-by-index index) )))) select-text - (fn [ref] (fn [event] (dom/select-text! (mf/ref-val ref)))) + (fn [ref] (fn [_] (dom/select-text! (mf/ref-val ref)))) update-attr (fn update-attr @@ -86,12 +84,12 @@ detach-color (fn [index] - (fn [color opacity] - (if-not (string? (:color value)) + (fn [_color _opacity] + (when-not (string? (:color value)) (st/emit! (dch/update-shapes - ids - #(assoc-in % [:shadow index :color] - (dissoc (:color value) :id :file-id))))))) + ids + #(assoc-in % [:shadow index :color] + (dissoc (:color value) :id :file-id))))))) toggle-visibility (fn [index] @@ -122,8 +120,8 @@ :on-change (fn [event] (let [value (-> event dom/get-target dom/get-value d/read-string)] (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))} - [:option {:value ":drop-shadow"} (t locale "workspace.options.shadow-options.drop-shadow")] - [:option {:value ":inner-shadow"} (t locale "workspace.options.shadow-options.inner-shadow")]] + [:option {:value ":drop-shadow"} (tr "workspace.options.shadow-options.drop-shadow")] + [:option {:value ":inner-shadow"} (tr "workspace.options.shadow-options.inner-shadow")]] [:div.element-set-actions [:div.element-set-actions-button {:on-click (toggle-visibility index)} @@ -142,8 +140,8 @@ :on-change (fn [event] (let [value (-> event dom/get-target dom/get-value d/read-string)] (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index :style] value)))))} - [:option {:value ":drop-shadow"} (t locale "workspace.options.shadow-options.drop-shadow")] - [:option {:value ":inner-shadow"} (t locale "workspace.options.shadow-options.inner-shadow")]]] + [:option {:value ":drop-shadow"} (tr "workspace.options.shadow-options.drop-shadow")] + [:option {:value ":inner-shadow"} (tr "workspace.options.shadow-options.inner-shadow")]]] [:div.row-grid-2 [:div.input-element @@ -153,7 +151,7 @@ :on-click (select-text adv-offset-x-ref) :on-change (update-attr index :offset-x valid-number? basic-offset-x-ref) :value (:offset-x value)}] - [:span.after (t locale "workspace.options.shadow-options.offsetx")]] + [:span.after (tr "workspace.options.shadow-options.offsetx")]] [:div.input-element [:> numeric-input {:ref adv-offset-y-ref @@ -162,7 +160,7 @@ :on-click (select-text adv-offset-y-ref) :on-change (update-attr index :offset-y valid-number? basic-offset-y-ref) :value (:offset-y value)}] - [:span.after (t locale "workspace.options.shadow-options.offsety")]]] + [:span.after (tr "workspace.options.shadow-options.offsety")]]] [:div.row-grid-2 [:div.input-element @@ -173,7 +171,7 @@ :on-change (update-attr index :blur valid-number? basic-blur-ref) :min 0 :value (:blur value)}] - [:span.after (t locale "workspace.options.shadow-options.blur")]] + [:span.after (tr "workspace.options.shadow-options.blur")]] [:div.input-element [:> numeric-input {:ref adv-spread-ref @@ -183,7 +181,7 @@ :on-change (update-attr index :spread valid-number?) :min 0 :value (:spread value)}] - [:span.after (t locale "workspace.options.shadow-options.spread")]]] + [:span.after (tr "workspace.options.shadow-options.spread")]]] [:div.color-row-wrap [:& color-row {:color (if (string? (:color value)) @@ -197,10 +195,8 @@ :on-close #(st/emit! (dwu/commit-undo-transaction))}]]]])) (mf/defc shadow-menu [{:keys [ids type values] :as props}] - (let [locale (i18n/use-locale) - on-remove-all-shadows - (fn [event] - (st/emit! (dch/update-shapes ids #(dissoc % :shadow) ))) + (let [on-remove-all-shadows + (fn [_] (st/emit! (dch/update-shapes ids #(dissoc % :shadow)))) on-add-shadow (fn [] @@ -209,9 +205,9 @@ [:div.element-set-title [:span (case type - :multiple (t locale "workspace.options.shadow-options.title.multiple") - :group (t locale "workspace.options.shadow-options.title.group") - (t locale "workspace.options.shadow-options.title"))] + :multiple (tr "workspace.options.shadow-options.title.multiple") + :group (tr "workspace.options.shadow-options.title.group") + (tr "workspace.options.shadow-options.title"))] (when-not (= :multiple (:shadow values)) [:div.add-page {:on-click on-add-shadow} i/close])] @@ -220,14 +216,14 @@ (= :multiple (:shadow values)) [:div.element-set-content [:div.element-set-options-group - [:div.element-set-label (t locale "settings.multiple")] + [:div.element-set-label (tr "settings.multiple")] [:div.element-set-actions [:div.element-set-actions-button {:on-click on-remove-all-shadows} i/minus]]]] - (not (empty? (:shadow values))) + (seq (:shadow values)) [:div.element-set-content - (for [[index {:keys [id] :as value}] (d/enumerate (:shadow values []))] + (for [[index value] (d/enumerate (:shadow values []))] [:& shadow-entry {:key (str "shadow-" index) :ids ids :value value diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs index 14ae0a784c..beee39a191 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs @@ -6,21 +6,18 @@ (ns app.main.ui.workspace.sidebar.options.menus.stroke (:require - [cuerdas.core :as str] - [rumext.alpha :as mf] [app.common.data :as d] [app.common.math :as math] - [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.undo :as dwu] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.colors :as dc] + [app.main.data.workspace.undo :as dwu] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]] - [app.util.data :refer [classnames]] [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [tr t]] - [app.util.object :as obj])) + [app.util.i18n :as i18n :refer [tr]] + [cuerdas.core :as str] + [rumext.alpha :as mf])) (def stroke-attrs [:stroke-style @@ -47,11 +44,10 @@ (mf/defc stroke-menu {::mf/wrap [#(mf/memo' % (mf/check-props ["ids" "values" "type"]))]} [{:keys [ids type values] :as props}] - (let [locale (i18n/use-locale) - label (case type - :multiple (t locale "workspace.options.selection-stroke") - :group (t locale "workspace.options.group-stroke") - (t locale "workspace.options.stroke")) + (let [label (case type + :multiple (tr "workspace.options.selection-stroke") + :group (tr "workspace.options.group-stroke") + (tr "workspace.options.stroke")) show-options (not= (:stroke-style values :none) :none) @@ -65,7 +61,7 @@ (mf/use-callback (mf/deps ids) (fn [color] - (let [remove-multiple (fn [[key value]] (not= value :multiple)) + (let [remove-multiple (fn [[_ value]] (not= value :multiple)) color (into {} (filter remove-multiple) color)] (st/emit! (dc/change-stroke ids color))))) @@ -99,7 +95,7 @@ (st/emit! (dch/update-shapes ids #(assoc % :stroke-width value)))))) on-add-stroke - (fn [event] + (fn [_] (st/emit! (dch/update-shapes ids #(assoc % :stroke-style :solid :stroke-color "#000000" @@ -107,19 +103,19 @@ :stroke-width 1)))) on-del-stroke - (fn [event] + (fn [_] (st/emit! (dch/update-shapes ids #(assoc % :stroke-style :none)))) on-open-picker (mf/use-callback (mf/deps ids) - (fn [value opacity id file-id] + (fn [_value _opacity _id _file-id] (st/emit! (dwu/start-undo-transaction)))) on-close-picker (mf/use-callback (mf/deps ids) - (fn [value opacity id file-id] + (fn [_value _opacity _id _file-id] (st/emit! (dwu/commit-undo-transaction))))] (if show-options @@ -139,29 +135,29 @@ ;; Stroke Width, Alignment & Style [:div.row-flex [:div.input-element - {:class (classnames :pixels (not= (:stroke-width values) :multiple))} + {:class (dom/classnames :pixels (not= (:stroke-width values) :multiple))} [:input.input-text {:type "number" :min "0" :value (-> (:stroke-width values) width->string) - :placeholder (t locale "settings.multiple") + :placeholder (tr "settings.multiple") :on-change on-stroke-width-change}]] [:select#style.input-select {:value (enum->string (:stroke-alignment values)) :on-change on-stroke-alignment-change} (when (= (:stroke-alignment values) :multiple) [:option {:value ""} "--"]) - [:option {:value ":center"} (t locale "workspace.options.stroke.center")] - [:option {:value ":inner"} (t locale "workspace.options.stroke.inner")] - [:option {:value ":outer"} (t locale "workspace.options.stroke.outer")]] + [:option {:value ":center"} (tr "workspace.options.stroke.center")] + [:option {:value ":inner"} (tr "workspace.options.stroke.inner")] + [:option {:value ":outer"} (tr "workspace.options.stroke.outer")]] [:select#style.input-select {:value (enum->string (:stroke-style values)) :on-change on-stroke-style-change} (when (= (:stroke-style values) :multiple) [:option {:value ""} "--"]) - [:option {:value ":solid"} (t locale "workspace.options.stroke.solid")] - [:option {:value ":dotted"} (t locale "workspace.options.stroke.dotted")] - [:option {:value ":dashed"} (t locale "workspace.options.stroke.dashed")] - [:option {:value ":mixed"} (t locale "workspace.options.stroke.mixed")]]]]] + [:option {:value ":solid"} (tr "workspace.options.stroke.solid")] + [:option {:value ":dotted"} (tr "workspace.options.stroke.dotted")] + [:option {:value ":dashed"} (tr "workspace.options.stroke.dashed")] + [:option {:value ":mixed"} (tr "workspace.options.stroke.mixed")]]]]] ;; NO STROKE [:div.element-set diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs index bd6dc955ae..3d953a699b 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/svg_attrs.cljs @@ -6,16 +6,14 @@ (ns app.main.ui.workspace.sidebar.options.menus.svg-attrs (:require - [cuerdas.core :as str] [app.common.data :as d] - [app.main.data.workspace.common :as dwc] [app.main.data.workspace.changes :as dch] [app.main.store :as st] + [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.rows.input-row :refer [input-row]] [app.util.dom :as dom] [app.util.i18n :refer [tr]] - [rumext.alpha :as mf] - [app.main.ui.icons :as i])) + [rumext.alpha :as mf])) (mf/defc attribute-value [{:keys [attr value on-change on-delete] :as props}] (let [handle-change @@ -55,7 +53,7 @@ :on-change on-change :on-delete on-delete}])])])) -(mf/defc svg-attrs-menu [{:keys [ids type values]}] +(mf/defc svg-attrs-menu [{:keys [ids values]}] (let [handle-change (mf/use-callback (mf/deps ids) @@ -86,7 +84,7 @@ [:div.element-set-title [:span (tr "workspace.sidebar.options.svg-attrs.title")]] - (for [[index [attr-key attr-value]] (d/enumerate (:svg-attrs values))] + (for [[attr-key attr-value] (:svg-attrs values)] [:& attribute-value {:key attr-key :attr [attr-key] :value attr-value 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 3cda61c33a..d9f9ec455b 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 @@ -7,9 +7,8 @@ (ns app.main.ui.workspace.sidebar.options.menus.text (:require [app.common.data :as d] - [app.common.uuid :as uuid] [app.common.text :as txt] - [app.main.data.workspace.common :as dwc] + [app.common.uuid :as uuid] [app.main.data.workspace.changes :as dch] [app.main.data.workspace.libraries :as dwl] [app.main.data.workspace.texts :as dwt] @@ -81,10 +80,10 @@ (def attrs (d/concat #{} shape-attrs root-attrs paragraph-attrs text-attrs)) (mf/defc text-align-options - [{:keys [ids values on-change] :as props}] + [{:keys [values on-change] :as props}] (let [{:keys [text-align]} values handle-change - (fn [event new-align] + (fn [_ new-align] (on-change {:text-align new-align}))] ;; --- Align @@ -111,9 +110,9 @@ i/text-align-justify]])) (mf/defc text-direction-options - [{:keys [ids values on-change] :as props}] + [{:keys [values on-change] :as props}] (let [direction (:text-direction values) - handle-change (fn [event val] + handle-change (fn [_ val] (on-change {:text-direction val}))] ;; --- Align [:div.align-icons @@ -129,11 +128,11 @@ i/text-direction-rtl]])) (mf/defc vertical-align - [{:keys [shapes ids values on-change] :as props}] + [{:keys [values on-change] :as props}] (let [{:keys [vertical-align]} values vertical-align (or vertical-align "top") handle-change - (fn [event new-align] + (fn [_ new-align] (on-change {:vertical-align new-align}))] [:div.align-icons @@ -154,11 +153,10 @@ i/align-bottom]])) (mf/defc grow-options - [{:keys [ids values on-change] :as props}] - (let [to-single-value (fn [coll] (if (> (count coll) 1) nil (first coll))) - grow-type (->> values :grow-type) + [{:keys [ids values] :as props}] + (let [grow-type (:grow-type values) handle-change-grow - (fn [event grow-type] + (fn [_ grow-type] (st/emit! (dch/update-shapes ids #(assoc % :grow-type grow-type))))] [:div.align-icons @@ -179,13 +177,10 @@ i/auto-height]])) (mf/defc text-decoration-options - [{:keys [ids values on-change] :as props}] - (let [{:keys [text-decoration]} values - - text-decoration (or text-decoration "none") - + [{:keys [values on-change] :as props}] + (let [text-decoration (or (:text-decoration values) "none") handle-change - (fn [event type] + (fn [_ type] (on-change {:text-decoration type}))] [:div.align-icons [:span.tooltip.tooltip-bottom @@ -262,18 +257,18 @@ (get typographies (:typography-ref-id values))))) on-convert-to-typography - (fn [event] + (fn [_] (let [setted-values (-> (d/without-nils values) (select-keys (d/concat text-font-attrs text-spacing-attrs text-transform-attrs))) typography (merge txt/default-typography setted-values) - typography (generate-typography-name typography)] - (let [id (uuid/next)] - (st/emit! (dwl/add-typography (assoc typography :id id) false)) - (run! #(emit-update! % {:typography-ref-id id - :typography-ref-file file-id}) ids)))) + typography (generate-typography-name typography) + id (uuid/next)] + (st/emit! (dwl/add-typography (assoc typography :id id) false)) + (run! #(emit-update! % {:typography-ref-id id + :typography-ref-file file-id}) ids))) handle-detach-typography (mf/use-callback 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 36e8acdf0b..ca5e77631a 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 @@ -7,30 +7,24 @@ (ns app.main.ui.workspace.sidebar.options.menus.typography (:require ["react-virtualized" :as rvt] - [app.common.exceptions :as ex] [app.common.data :as d] + [app.common.exceptions :as ex] [app.common.pages :as cp] [app.common.text :as txt] - [app.main.data.workspace.texts :as dwt] [app.main.data.shortcuts :as dsc] - [app.main.data.fonts :as df] - [app.main.data.workspace :as dw] [app.main.fonts :as fonts] - [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.editable-select :refer [editable-select]] [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.common :refer [advanced-options]] [app.util.dom :as dom] - [app.util.object :as obj] - [app.util.timers :as tm] - [app.util.keyboard :as kbd] [app.util.i18n :as i18n :refer [tr]] + [app.util.keyboard :as kbd] + [app.util.object :as obj] [app.util.router :as rt] - [app.util.timers :as ts] - [goog.events :as events] + [app.util.timers :as tm] [cuerdas.core :as str] + [goog.events :as events] [rumext.alpha :as mf])) (defn- attr->string [value] @@ -88,11 +82,11 @@ (comp (filter #(contains? backends (:backend %)))))] (into [] xform fonts))) -(defn- toggle-backend - [backends id] - (if (contains? backends id) - (disj backends id) - (conj backends id))) +;; (defn- toggle-backend +;; [backends id] +;; (if (contains? backends id) +;; (disj backends id) +;; (conj backends id))) (mf/defc font-selector [{:keys [on-select on-close current-font] :as props}] @@ -101,7 +95,6 @@ flist (mf/use-ref) input (mf/use-ref) - ddown (mf/use-ref) fonts (mf/use-memo (mf/deps @state) #(filter-fonts @state @fonts/fonts)) @@ -237,7 +230,7 @@ :current? (= (:id font) (:id selected))}]))) (mf/defc font-options - [{:keys [editor ids values on-change] :as props}] + [{:keys [values on-change] :as props}] (let [{:keys [font-id font-size font-variant-id]} values font-id (or font-id (:font-id txt/default-text-attrs)) @@ -261,15 +254,6 @@ :font-weight weight :font-style style})))) - on-font-family-change - (mf/use-callback - (mf/deps fonts change-font) - (fn [event] - (let [new-font-id (dom/get-target-val event)] - (when-not (str/empty? new-font-id) - (let [font (get fonts new-font-id)] - (fonts/ensure-loaded! new-font-id (partial change-font new-font-id))))))) - on-font-size-change (mf/use-callback (mf/deps on-change) @@ -345,7 +329,7 @@ (mf/defc spacing-options - [{:keys [editor ids values on-change] :as props}] + [{:keys [values on-change] :as props}] (let [{:keys [line-height letter-spacing]} values @@ -385,13 +369,10 @@ :on-change #(handle-change % :letter-spacing)}]]])) (mf/defc text-transform-options - [{:keys [editor ids values on-change] :as props}] - (let [{:keys [text-transform]} values - - text-transform (or text-transform "none") - + [{:keys [values on-change] :as props}] + (let [text-transform (or (:text-transform values) "none") handle-change - (fn [event type] + (fn [_ type] (on-change {:text-transform type}))] [:div.align-icons [:span.tooltip.tooltip-bottom @@ -461,7 +442,7 @@ (mf/deps focus-name?) (fn [] (when focus-name? - (ts/schedule + (tm/schedule #(when-let [node (mf/ref-val name-input-ref)] (dom/focus! node) (dom/select-text! node)))))) @@ -539,7 +520,7 @@ :ref name-input-ref :default-value (cp/merge-path-item (:path typography) (:name typography)) :on-blur on-name-blur}] - + [:div.element-set-actions-button {:on-click #(reset! open? false)} i/actions]]] diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs index b42996c5de..c44d93ad28 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/input_row.cljs @@ -6,12 +6,10 @@ (ns app.main.ui.workspace.sidebar.options.rows.input-row (:require - [rumext.alpha :as mf] - [app.common.data :as d] + [app.main.ui.components.editable-select :refer [editable-select]] [app.main.ui.components.numeric-input :refer [numeric-input]] [app.main.ui.components.select :refer [select]] - [app.main.ui.components.editable-select :refer [editable-select]] - [app.util.dom :as dom])) + [rumext.alpha :as mf])) (mf/defc input-row [{:keys [label options value class min max on-change type placeholder]}] [:div.row-flex.input-row 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 8c8888aaa1..df658997dd 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 @@ -6,14 +6,14 @@ (ns app.main.ui.workspace.sidebar.options.shapes.circle (:require - [rumext.alpha :as mf] - [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] - [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] - [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] - [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-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.measures :refer [measure-attrs measures-menu]] + [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]] - [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]])) + [rumext.alpha :as mf])) (mf/defc options [{:keys [shape] :as props}] 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 3990546eef..6f7280e688 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,23 +6,22 @@ (ns app.main.ui.workspace.sidebar.options.shapes.frame (:require - [rumext.alpha :as mf] [app.common.data :as d] - [app.util.dom :as dom] - [app.common.geom.point :as gpt] - [app.util.i18n :refer [tr]] [app.common.math :as math] - [app.main.store :as st] [app.main.data.workspace :as udw] - [app.main.ui.icons :as i] + [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.components.numeric-input :refer [numeric-input]] - [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] - [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] - [app.main.ui.workspace.sidebar.options.menus.frame-grid :refer [frame-grid]] - [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] + [app.main.ui.icons :as i] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] - [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]])) + [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs 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.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] + [app.util.dom :as dom] + [app.util.i18n :refer [tr]] + [rumext.alpha :as mf])) (declare +size-presets+) @@ -50,7 +49,7 @@ (st/emit! (udw/update-dimensions [(:id shape)] attr value))) on-proportion-lock-change - (fn [event] + (fn [_] (st/emit! (udw/set-shape-proportion-lock (:id shape) (not (:proportion-lock shape))))) on-position-change @@ -82,8 +81,8 @@ :on-click #(on-preset-selected (:width size-preset) (:height size-preset))} (:name size-preset) [:span (:width size-preset) " x " (:height size-preset)]]))]]] - [:span.orientation-icon {on-click #(on-orientation-clicked :vert)} i/size-vert] - [:span.orientation-icon {on-click #(on-orientation-clicked :horiz)} i/size-horiz]] + [:span.orientation-icon {:on-click #(on-orientation-clicked :vert)} i/size-vert] + [:span.orientation-icon {:on-click #(on-orientation-clicked :horiz)} i/size-horiz]] ;; WIDTH & HEIGHT [:div.row-flex 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 49d87ddebd..eae1f87461 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 @@ -6,18 +6,18 @@ (ns app.main.ui.workspace.sidebar.options.shapes.group (:require - [rumext.alpha :as mf] [app.common.data :as d] - [app.main.ui.workspace.sidebar.options.shapes.multiple :refer [get-attrs]] - [app.main.ui.workspace.sidebar.options.menus.measures :refer [measures-menu]] + [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-attrs component-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]] - [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-menu]] + [app.main.ui.workspace.sidebar.options.menus.measures :refer [measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-menu]] - [app.main.ui.workspace.sidebar.options.menus.text :as ot] [app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]] - [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]])) + [app.main.ui.workspace.sidebar.options.menus.text :as ot] + [app.main.ui.workspace.sidebar.options.shapes.multiple :refer [get-attrs]] + [rumext.alpha :as mf])) (mf/defc options {::mf/wrap [mf/memo] 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 4db2bb0290..a506b56b80 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 @@ -6,11 +6,11 @@ (ns app.main.ui.workspace.sidebar.options.shapes.image (:require - [rumext.alpha :as mf] + [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] - [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] - [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]])) + [rumext.alpha :as mf])) (mf/defc options [{:keys [shape] :as props}] 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 86a0490531..42d822850f 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 @@ -147,7 +147,7 @@ :else (attrs/get-attrs-multi [v1 v2] attrs))) extract-attrs - (fn [[ids values] {:keys [id type shapes content] :as shape}] + (fn [[ids values] {:keys [id type content] :as shape}] (let [props (get-in type->props [type attr-type])] (case props :ignore [ids values] 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 00d09a17ec..1139f19b92 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 @@ -6,15 +6,14 @@ (ns app.main.ui.workspace.sidebar.options.shapes.path (:require - [rumext.alpha :as mf] - [app.common.data :as d] - [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] - [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] - [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] - [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-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.measures :refer [measure-attrs measures-menu]] + [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]] - [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]])) + [rumext.alpha :as mf])) (mf/defc options [{:keys [shape] :as props}] 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 bf8a4ac47e..17d1c991a4 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 @@ -6,14 +6,14 @@ (ns app.main.ui.workspace.sidebar.options.shapes.rect (:require - [rumext.alpha :as mf] - [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] - [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] - [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] - [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-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.measures :refer [measure-attrs measures-menu]] + [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] [app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]] - [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]])) + [rumext.alpha :as mf])) (mf/defc options {::mf/wrap [mf/memo]} 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 01494340fc..6d6ae07356 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 @@ -6,22 +6,23 @@ (ns app.main.ui.workspace.sidebar.options.shapes.svg-raw (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] - [app.util.data :as d] - [app.util.color :as uc] - [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] - [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] - [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] - [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] - [app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]])) + [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] + [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] + [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] + [app.main.ui.workspace.sidebar.options.menus.stroke :refer [stroke-attrs stroke-menu]] + [app.main.ui.workspace.sidebar.options.menus.svg-attrs :refer [svg-attrs-menu]] + [app.util.color :as uc] + [app.util.data :as d] + [cuerdas.core :as str] + [rumext.alpha :as mf])) ;; This is a list of svg tags that can be grouped in shape-container ;; this allows them to have gradients, shadows and masks (def svg-elements #{:svg :g :circle :ellipse :image :line :path :polygon :polyline :rect :symbol :text :textPath}) -(defn hex->number [hex] 1) +(defn hex->number [_] 1) + (defn shorthex->longhex [hex] (let [[_ r g b] hex] (str "#" r r g g b b))) @@ -45,10 +46,10 @@ (defn get-fill-values [shape] - (let [fill-values (or (select-keys shape fill-attrs)) - color (-> (or (get-in shape [:content :attrs :fill]) - (get-in shape [:content :attrs :style :fill])) - (parse-color)) + (let [fill-values (select-keys shape fill-attrs) + color (-> (or (get-in shape [:content :attrs :fill]) + (get-in shape [:content :attrs :style :fill])) + (parse-color)) fill-values (if (and (empty? fill-values) color) {:fill-color (:color color) @@ -57,10 +58,10 @@ fill-values)) (defn get-stroke-values [shape] - (let [stroke-values (or (select-keys shape stroke-attrs)) - color (-> (or (get-in shape [:content :attrs :stroke]) - (get-in shape [:content :attrs :style :stroke])) - (parse-color)) + (let [stroke-values (select-keys shape stroke-attrs) + color (-> (or (get-in shape [:content :attrs :stroke]) + (get-in shape [:content :attrs :style :stroke])) + (parse-color)) stroke-color (:color color "#000000") stroke-opacity (:opacity color 1) @@ -90,7 +91,7 @@ (let [ids [(:id shape)] type (:type shape) - {:keys [tag attrs] :as content} (:content shape) + {:keys [tag] :as content} (:content shape) measure-values (select-keys shape measure-attrs) fill-values (get-fill-values shape) stroke-values (get-stroke-values shape)] diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs index ae5ac0ed8c..65cf1c0fef 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs @@ -18,8 +18,6 @@ [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] - [app.util.router :as rt] - [cuerdas.core :as str] [okulary.core :as l] [rumext.alpha :as mf])) @@ -86,12 +84,12 @@ on-drop (mf/use-callback (mf/deps id) - (fn [side {:keys [id name] :as data}] + (fn [side {:keys [id] :as data}] (let [index (if (= :bot side) (inc index) index)] (st/emit! (dw/relocate-page id index))))) on-duplicate - (fn [event] + (fn [_] (st/emit! (dw/duplicate-page id))) [dprops dref] @@ -169,7 +167,7 @@ st/state =)) (mf/defc page-item-wrapper - [{:keys [file-id page-id index deletable? selected?] :as props}] + [{:keys [page-id index deletable? selected?] :as props}] (let [page-ref (mf/use-memo (mf/deps page-id) #(make-page-ref page-id)) page (mf/deref page-ref)] [:& page-item {:page page @@ -197,7 +195,7 @@ ;; --- Sitemap Toolbox (mf/defc sitemap - [{:keys [layout] :as props}] + [] (let [file (mf/deref refs/workspace-file) create (mf/use-callback (mf/deps file) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 6a79f9c1e1..a56430b5f0 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -7,11 +7,9 @@ (ns app.main.ui.workspace.viewport (:require [app.common.data :as d] - [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.main.refs :as refs] [app.main.ui.context :as ctx] - [app.main.ui.context :as muc] [app.main.ui.measurements :as msr] [app.main.ui.shapes.embed :as embed] [app.main.ui.shapes.export :as use] @@ -79,7 +77,6 @@ ;; REFS viewport-ref (mf/use-ref nil) - zoom-view-ref (mf/use-ref nil) render-ref (mf/use-ref nil) ;; VARS @@ -139,7 +136,9 @@ show-presence? page-id show-prototypes? (= options-mode :prototype) show-selection-handlers? (seq selected) - show-snap-distance? (and (contains? layout :dynamic-alignment) (= transform :move) (not (empty? selected))) + show-snap-distance? (and (contains? layout :dynamic-alignment) + (= transform :move) + (seq selected)) show-snap-points? (and (or (contains? layout :dynamic-alignment) (contains? layout :snap-grid)) (or drawing-obj transform)) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index a32ae106e3..9fdcdbbf55 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -12,10 +12,10 @@ [app.main.data.workspace :as dw] [app.main.data.workspace.drawing :as dd] [app.main.data.workspace.libraries :as dwl] + [app.main.data.workspace.path :as dwdp] [app.main.store :as st] [app.main.streams :as ms] [app.main.ui.workspace.viewport.utils :as utils] - [app.main.data.workspace.path :as dwdp] [app.util.dom :as dom] [app.util.dom.dnd :as dnd] [app.util.keyboard :as kbd] @@ -290,8 +290,7 @@ (st/emit! (ms/->KeyboardEvent :up key shift? ctrl? alt? meta?)))))) (defn on-mouse-move [viewport-ref zoom] - (let [last-position (mf/use-var nil) - viewport (mf/ref-val viewport-ref)] + (let [last-position (mf/use-var nil)] (mf/use-callback (mf/deps zoom) (fn [event] @@ -477,7 +476,7 @@ (defn on-resize [viewport-ref] (mf/use-callback - (fn [event] + (fn [_] (let [node (mf/ref-val viewport-ref) prnt (dom/get-parent node) size (dom/get-client-size prnt)] diff --git a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs index 20d6e49600..740c6956c3 100644 --- a/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/drawarea.cljs @@ -7,17 +7,12 @@ (ns app.main.ui.workspace.viewport.drawarea "Drawing components." (:require - [rumext.alpha :as mf] - [app.main.data.workspace :as dw] - [app.main.data.workspace.drawing :as dd] - [app.main.store :as st] - [app.main.ui.workspace.shapes :as shapes] - [app.main.ui.shapes.path :refer [path-shape]] - [app.main.ui.workspace.shapes.path.editor :refer [path-editor]] [app.common.geom.shapes :as gsh] - [app.common.data :as d] - [app.util.dom :as dom] - [app.util.i18n :as i18n :refer [t]])) + [app.common.math :as mth] + [app.main.ui.shapes.path :refer [path-shape]] + [app.main.ui.workspace.shapes :as shapes] + [app.main.ui.workspace.shapes.path.editor :refer [path-editor]] + [rumext.alpha :as mf])) (declare generic-draw-area) (declare path-draw-area) @@ -38,8 +33,8 @@ [{:keys [shape zoom]}] (let [{:keys [x y width height]} (:selrect (gsh/transform-shape shape))] (when (and x y - (not (d/nan? x)) - (not (d/nan? y))) + (not (mth/nan? x)) + (not (mth/nan? y))) [:rect.main {:x x :y y :width width 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 d9b147bbc3..5cd49cc681 100644 --- a/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/frame_grid.cljs @@ -6,18 +6,17 @@ (ns app.main.ui.workspace.viewport.frame-grid (:require - [rumext.alpha :as mf] - [okulary.core :as l] - [app.main.refs :as refs] - [app.common.math :as mth] - [app.common.pages :as cp] [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.common.uuid :as uuid] + [app.main.refs :as refs] [app.util.geom.grid :as gg] - [app.common.uuid :as uuid])) + [okulary.core :as l] + [rumext.alpha :as mf])) (mf/defc square-grid [{:keys [frame zoom grid] :as props}] (let [grid-id (mf/use-memo #(uuid/next)) - {:keys [color size] :as params} (-> grid :params) + {:keys [size] :as params} (-> grid :params) {color-value :color color-opacity :opacity} (-> grid :params :color) ;; Support for old color format color-value (or color-value (:value (get-in grid [:params :color :value])))] @@ -43,7 +42,7 @@ :height (:height frame) :fill (str "url(#" grid-id ")")}]])) -(mf/defc layout-grid [{:keys [key frame zoom grid]}] +(mf/defc layout-grid [{:keys [key frame grid]}] (let [{color-value :color color-opacity :opacity} (-> grid :params :color) ;; Support for old color format color-value (or color-value (:value (get-in grid [:params :color :value]))) diff --git a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs index e65bc12d05..48a8668754 100644 --- a/frontend/src/app/main/ui/workspace/viewport/gradients.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/gradients.cljs @@ -7,21 +7,19 @@ (ns app.main.ui.workspace.viewport.gradients "Gradients handlers and renders" (:require - [rumext.alpha :as mf] - [cuerdas.core :as str] - [beicon.core :as rx] - [okulary.core :as l] - [app.common.math :as mth] - [app.common.geom.shapes :as gsh] - [app.common.geom.point :as gpt] [app.common.geom.matrix :as gmt] - [app.util.dom :as dom] - [app.main.store :as st] + [app.common.geom.point :as gpt] + [app.common.geom.shapes :as gsh] + [app.common.math :as mth] + [app.main.data.workspace.colors :as dc] [app.main.refs :as refs] + [app.main.store :as st] [app.main.streams :as ms] - [app.main.data.modal :as modal] - [app.main.data.workspace.common :as dwc] - [app.main.data.workspace.colors :as dc])) + [app.util.dom :as dom] + [beicon.core :as rx] + [cuerdas.core :as str] + [okulary.core :as l] + [rumext.alpha :as mf])) (def gradient-line-stroke-width 2) (def gradient-line-stroke-color "white") @@ -94,7 +92,7 @@ on-click on-mouse-down on-mouse-up]}] [:g {:filter (str/fmt "url(#%s)" filter-id) :transform (gmt/rotate-matrix angle point)} - + [:image {:href checkboard :x (- (:x point) (/ gradient-square-width 2 zoom)) :y (- (:y point) (/ gradient-square-width 2 zoom)) @@ -127,7 +125,7 @@ (mf/defc gradient-handler-transformed [{:keys [from-p to-p width-p from-color to-color zoom editing - on-change-start on-change-finish on-change-width on-change-stop-color]}] + on-change-start on-change-finish on-change-width]}] (let [moving-point (mf/use-var nil) angle (+ 90 (gpt/angle from-p to-p)) @@ -138,7 +136,7 @@ (st/emit! (dc/select-gradient-stop (case position :from-p 0 :to-p 1))))) - + on-mouse-down (fn [position event] (dom/stop-propagation event) (dom/prevent-default event) @@ -148,7 +146,7 @@ :from-p 0 :to-p 1))))) - on-mouse-up (fn [position event] + on-mouse-up (fn [_position event] (dom/stop-propagation event) (dom/prevent-default event) (reset! moving-point nil))] diff --git a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs index bbda478b0c..1d9c98b1a6 100644 --- a/frontend/src/app/main/ui/workspace/viewport/hooks.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/hooks.cljs @@ -37,8 +37,6 @@ (mf/deps on-key-down on-key-up on-mouse-move on-mouse-wheel on-resize on-paste) (fn [] (let [node (mf/ref-val viewport-ref) - prnt (dom/get-parent node) - keys [(events/listen js/document EventType.KEYDOWN on-key-down) (events/listen js/document EventType.KEYUP on-key-up) (events/listen node EventType.MOUSEMOVE on-mouse-move) @@ -90,7 +88,8 @@ (hooks/use-stream ms/keyboard-alt #(reset! alt? %)) (hooks/use-stream ms/keyboard-ctrl #(reset! ctrl? %))) -(defn setup-hover-shapes [page-id move-stream selected objects transform selected ctrl? hover hover-ids zoom] +;; TODO: revisit the arguments, looks like `selected` is not necessary here +(defn setup-hover-shapes [page-id move-stream _selected objects transform selected ctrl? hover hover-ids zoom] (let [query-point (mf/use-callback (mf/deps page-id) @@ -111,12 +110,7 @@ ;; When transforming shapes we stop querying the worker (rx/filter #(not (some? (mf/ref-val transform-ref)))) (rx/switch-map query-point)) - - roots (mf/use-memo - (mf/deps selected objects) - (fn [] - (let [roots-ids (cp/clean-loops objects selected)] - (->> roots-ids (mapv #(get objects %))))))] + ] (mf/use-effect (mf/deps transform) diff --git a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs index 4e126fe878..ca0d1b0e2e 100644 --- a/frontend/src/app/main/ui/workspace/viewport/interactions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/interactions.cljs @@ -7,15 +7,11 @@ (ns app.main.ui.workspace.viewport.interactions "Visually show shape interactions in workspace" (:require - [app.common.geom.point :as gpt] - [app.common.geom.shapes :as geom] [app.main.data.workspace :as dw] [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.workspace.viewport.outline :refer [outline]] - [app.util.data :as dt] [app.util.dom :as dom] - [app.util.keyboard :as kbd] [cuerdas.core :as str] [rumext.alpha :as mf] )) @@ -24,14 +20,11 @@ [shape] (first (filter #(= (:event-type %) :click) (:interactions shape)))) - (defn- on-mouse-down - [event {:keys [id type] :as shape} selected] - (do - (dom/stop-propagation event) - (st/emit! (dw/select-shape id)) - (st/emit! (dw/start-create-interaction)))) - + [event {:keys [id] :as shape}] + (dom/stop-propagation event) + (st/emit! (dw/select-shape id)) + (st/emit! (dw/start-create-interaction))) (defn connect-to-shape "Calculate the best position to draw an interaction line @@ -118,7 +111,7 @@ (mf/defc interaction-path - [{:keys [orig-shape dest-shape dest-point selected selected? zoom] :as props}] + [{:keys [orig-shape dest-shape dest-point selected? zoom] :as props}] (let [[orig-pos orig-x orig-y dest-pos dest-x dest-y] (if dest-shape (connect-to-shape orig-shape dest-shape) @@ -138,9 +131,9 @@ :pointer-events "visible" :stroke-width (/ 2 zoom) :d pdata - :on-mouse-down #(on-mouse-down % orig-shape selected)}] + :on-mouse-down #(on-mouse-down % orig-shape)}] - [:g {:on-mouse-down #(on-mouse-down % orig-shape selected)} + [:g {:on-mouse-down #(on-mouse-down % orig-shape)} [:path {:stroke "#31EFB8" :fill "none" :pointer-events "visible" @@ -161,11 +154,11 @@ (mf/defc interaction-handle - [{:keys [shape selected zoom] :as props}] + [{:keys [shape zoom] :as props}] (let [shape-rect (:selrect shape) handle-x (+ (:x shape-rect) (:width shape-rect)) handle-y (+ (:y shape-rect) (/ (:height shape-rect) 2))] - [:g {:on-mouse-down #(on-mouse-down % shape selected)} + [:g {:on-mouse-down #(on-mouse-down % shape)} [:& interaction-marker {:x handle-x :y handle-y :arrow-dir :right diff --git a/frontend/src/app/main/ui/workspace/viewport/outline.cljs b/frontend/src/app/main/ui/workspace/viewport/outline.cljs index a024a7d3a9..44d644f6b0 100644 --- a/frontend/src/app/main/ui/workspace/viewport/outline.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/outline.cljs @@ -9,8 +9,8 @@ [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.main.refs :as refs] - [app.util.path.format :as upf] [app.util.object :as obj] + [app.util.path.format :as upf] [clojure.set :as set] [rumext.alpha :as mf] [rumext.util :refer [map->obj]])) @@ -29,7 +29,7 @@ (mf/deps shape) #(when path? (upf/format-path (:content shape)))) - {:keys [id x y width height selrect]} shape + {:keys [x y width height selrect]} shape outline-type (case (:type shape) :circle "ellipse" diff --git a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs index d504faf56b..f6d85e32cf 100644 --- a/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/path_actions.cljs @@ -7,9 +7,7 @@ (ns app.main.ui.workspace.viewport.path-actions (:require [app.main.data.workspace.path :as drp] - [app.main.data.workspace.path.helpers :as wph] [app.main.data.workspace.path.shortcuts :as sc] - [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.icons :as i] [app.main.ui.workspace.shapes.path.common :as pc] @@ -20,8 +18,8 @@ (defn check-enabled [content selected-points] (let [segments (upt/get-segments content selected-points) num-points (count selected-points) - points-selected? (not (empty? selected-points)) - segments-selected? (not (empty? segments))] + points-selected? (seq selected-points) + segments-selected? (seq segments)] {:make-corner points-selected? :make-curve points-selected? :add-node segments-selected? @@ -31,8 +29,7 @@ :separate-nodes segments-selected?})) (mf/defc path-actions [{:keys [shape]}] - (let [id (mf/deref refs/selected-edition) - {:keys [edit-mode selected-points snap-toggled] :as all} (mf/deref pc/current-edit-path-ref) + (let [{:keys [edit-mode selected-points snap-toggled] :as all} (mf/deref pc/current-edit-path-ref) content (:content shape) enabled-buttons @@ -42,66 +39,66 @@ on-select-draw-mode (mf/use-callback - (fn [event] + (fn [_] (st/emit! (drp/change-edit-mode :draw)))) - + on-select-edit-mode (mf/use-callback - (fn [event] + (fn [_] (st/emit! (drp/change-edit-mode :move)))) - + on-add-node (mf/use-callback (mf/deps (:add-node enabled-buttons)) - (fn [event] + (fn [_] (when (:add-node enabled-buttons) (st/emit! (drp/add-node))))) - + on-remove-node (mf/use-callback (mf/deps (:remove-node enabled-buttons)) - (fn [event] + (fn [_] (when (:remove-node enabled-buttons) (st/emit! (drp/remove-node))))) - + on-merge-nodes (mf/use-callback (mf/deps (:merge-nodes enabled-buttons)) - (fn [event] + (fn [_] (when (:merge-nodes enabled-buttons) (st/emit! (drp/merge-nodes))))) - + on-join-nodes (mf/use-callback (mf/deps (:join-nodes enabled-buttons)) - (fn [event] + (fn [_] (when (:join-nodes enabled-buttons) (st/emit! (drp/join-nodes))))) - + on-separate-nodes (mf/use-callback (mf/deps (:separate-nodes enabled-buttons)) - (fn [event] + (fn [_] (when (:separate-nodes enabled-buttons) (st/emit! (drp/separate-nodes))))) on-make-corner (mf/use-callback (mf/deps (:make-corner enabled-buttons)) - (fn [event] + (fn [_] (when (:make-corner enabled-buttons) (st/emit! (drp/make-corner))))) - + on-make-curve (mf/use-callback (mf/deps (:make-curve enabled-buttons)) - (fn [event] + (fn [_] (when (:make-curve enabled-buttons) (st/emit! (drp/make-curve))))) on-toggle-snap (mf/use-callback - (fn [event] + (fn [_] (st/emit! (drp/toggle-snap)))) ] @@ -121,7 +118,7 @@ :alt (tr "workspace.path.actions.draw-nodes" (sc/get-tooltip :draw-nodes)) :on-click on-select-edit-mode} i/pointer-inner]] - + [:div.viewport-actions-group ;; Add Node [:div.viewport-actions-entry.tooltip.tooltip-bottom 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 9601b2b982..1373a151e0 100644 --- a/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/pixel_overlay.cljs @@ -7,21 +7,18 @@ (ns app.main.ui.workspace.viewport.pixel-overlay (:require [app.common.uuid :as uuid] - [app.main.data.workspace.colors :as dwc] [app.main.data.modal :as modal] + [app.main.data.workspace.colors :as dwc] [app.main.refs :as refs] [app.main.store :as st] - [app.main.ui.context :as muc] [app.main.ui.cursors :as cur] [app.main.ui.workspace.shapes :refer [shape-wrapper frame-wrapper]] [app.util.dom :as dom] [app.util.keyboard :as kbd] [app.util.object :as obj] - [app.util.timers :as timers] [beicon.core :as rx] [cuerdas.core :as str] [goog.events :as events] - [okulary.core :as l] [promesa.core :as p] [rumext.alpha :as mf]) (:import goog.events.EventType)) @@ -54,11 +51,8 @@ {::mf/wrap-props false} [props] (let [vport (unchecked-get props "vport") - vbox (unchecked-get props "vbox") viewport-ref (unchecked-get props "viewport-ref") viewport-node (mf/ref-val viewport-ref) - options (unchecked-get props "options") - svg-ref (mf/use-ref nil) canvas-ref (mf/use-ref nil) img-ref (mf/use-ref nil) @@ -67,11 +61,11 @@ handle-keydown (mf/use-callback (fn [event] - (when (and (kbd/esc? event)) - (do (dom/stop-propagation event) - (dom/prevent-default event) - (st/emit! (dwc/stop-picker)) - (modal/disallow-click-outside!))))) + (when (kbd/esc? event) + (dom/stop-propagation event) + (dom/prevent-default event) + (st/emit! (dwc/stop-picker)) + (modal/disallow-click-outside!)))) handle-mouse-move-picker (mf/use-callback diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 3b18129437..ad547c9e51 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -10,23 +10,15 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as geom] - [app.common.math :as mth] - [app.common.uuid :as uuid] [app.main.data.workspace :as dw] - [app.main.data.workspace.common :as dwc] [app.main.refs :as refs] [app.main.store :as st] - [app.main.streams :as ms] [app.main.ui.cursors :as cur] - [app.main.ui.hooks :as hooks] [app.main.ui.workspace.shapes.path.editor :refer [path-editor]] - [app.util.data :as d] [app.util.debug :refer [debug?]] [app.util.dom :as dom] [app.util.object :as obj] - [beicon.core :as rx] [cuerdas.core :as str] - [potok.core :as ptk] [rumext.alpha :as mf] [rumext.util :refer [map->obj]])) @@ -178,17 +170,12 @@ :cursor cursor} :on-mouse-down #(on-resize {:x cx' :y cy'} %)}]) - (let [rot-square (case position - :top-left 0 - :top-right 90 - :bottom-right 180 - :bottom-left 270)] - [:circle {:on-mouse-down #(on-resize {:x cx' :y cy'} %) - :r (/ resize-point-circle-radius zoom) - :cx cx' - :cy cy' - :style {:fill (if (debug? :resize-handler) "red" "none") - :cursor cursor}}]) + [:circle {:on-mouse-down #(on-resize {:x cx' :y cy'} %) + :r (/ resize-point-circle-radius zoom) + :cx cx' + :cy cy' + :style {:fill (if (debug? :resize-handler) "red" "none") + :cursor cursor}}] )])) (mf/defc resize-side-handler @@ -232,7 +219,7 @@ (mf/defc controls {::mf/wrap-props false} [props] - (let [{:keys [overflow-text type] :as shape} (obj/get props "shape") + (let [{:keys [overflow-text] :as shape} (obj/get props "shape") zoom (obj/get props "zoom") color (obj/get props "color") on-move-selected (obj/get props "on-move-selected") @@ -275,7 +262,7 @@ ;; TODO: add specs for clarity (mf/defc text-edition-selection-handlers - [{:keys [shape zoom color] :as props}] + [{:keys [shape color] :as props}] (let [{:keys [x y width height]} shape] [:g.controls [:rect.main {:x x :y y @@ -299,12 +286,6 @@ shape-center (geom/center-shape shape) - hover-id (-> (mf/deref refs/current-hover) first) - hover-id (when-not (d/seek #(= hover-id (:id %)) shapes) hover-id) - hover-shape (mf/deref (refs/object-by-id hover-id)) - - vbox (mf/deref refs/vbox) - on-resize (fn [current-position initial-position event] (dom/stop-propagation event) (st/emit! (dw/start-resize current-position initial-position selected shape))) @@ -329,14 +310,6 @@ (let [shape-id (:id shape) shape (geom/transform-shape shape {:round-coords? false}) - frame (mf/deref (refs/object-by-id (:frame-id shape))) - frame (when-not (= (:id frame) uuid/zero) frame) - vbox (mf/deref refs/vbox) - - hover-id (-> (mf/deref refs/current-hover) first) - hover-id (when-not (= shape-id hover-id) hover-id) - hover-shape (mf/deref (refs/object-by-id hover-id)) - shape' (if (debug? :simple-selection) (geom/setup {:type :rect} (geom/selection-rect [shape])) shape) on-resize (fn [current-position initial-position event] (dom/stop-propagation event) @@ -357,7 +330,7 @@ {::mf/wrap [mf/memo]} [{:keys [shapes selected edition zoom disable-handlers on-move-selected] :as props}] (let [num (count shapes) - {:keys [id type] :as shape} (first shapes) + {:keys [type] :as shape} (first shapes) color (if (or (> num 1) (nil? (:shape-ref shape))) selection-rect-color-normal 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 ae6c809ad3..a5c85499f9 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_distances.cljs @@ -7,15 +7,11 @@ (ns app.main.ui.workspace.viewport.snap-distances (:require [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 :as cp] - [app.common.uuid :as uuid] [app.main.refs :as refs] - [app.main.snap :as snap] [app.main.worker :as uw] - [app.util.geom.snap-points :as sp] [beicon.core :as rx] [clojure.set :as set] [cuerdas.core :as str] @@ -275,21 +271,20 @@ update-shape (fn [shape] (-> shape (update :modifiers merge (:modifiers local)) - gsh/transform-shape))] - (let [selrect (->> selected-shapes (map update-shape) gsh/selection-rect) - key (->> selected (map str) (str/join "-"))] - [: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}]]))) + gsh/transform-shape)) + selrect (->> selected-shapes (map update-shape) gsh/selection-rect)] + [: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 f9cc2707c7..5fad2a5c1d 100644 --- a/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/snap_points.cljs @@ -6,12 +6,10 @@ (ns app.main.ui.workspace.viewport.snap-points (:require - [app.common.pages :as cp] - [app.common.math :as mth] [app.common.data :as d] - [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] - [app.main.refs :as refs] + [app.common.math :as mth] + [app.common.pages :as cp] [app.main.snap :as snap] [app.util.geom.snap-points :as sp] [beicon.core :as rx] @@ -106,7 +104,7 @@ (hash-map coord fixedv (flip coord) maxv)])))) (mf/defc snap-feedback - [{:keys [shapes page-id filter-shapes zoom modifiers] :as props}] + [{:keys [shapes filter-shapes zoom modifiers] :as props}] (let [state (mf/use-state []) subject (mf/use-memo #(rx/subject)) diff --git a/frontend/src/app/main/ui/workspace/viewport/utils.cljs b/frontend/src/app/main/ui/workspace/viewport/utils.cljs index 092da6bbce..384f381177 100644 --- a/frontend/src/app/main/ui/workspace/viewport/utils.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/utils.cljs @@ -6,14 +6,14 @@ (ns app.main.ui.workspace.viewport.utils (:require - [app.util.dom :as dom] - [app.common.geom.point :as gpt] - [cuerdas.core :as str] [app.common.data :as d] + [app.common.geom.point :as gpt] [app.main.ui.cursors :as cur] - )) + [app.util.dom :as dom] + [cuerdas.core :as str])) -(defn update-transform [node shapes modifiers] +;; TODO: looks like first argument is not necessary. +(defn update-transform [_node shapes modifiers] (doseq [{:keys [id type]} shapes] (let [shape-node (dom/get-element (str "shape-" id)) @@ -30,7 +30,8 @@ shape-node)] (dom/set-attribute node "transform" (str (:displacement modifiers))))))) -(defn remove-transform [node shapes] +;; TODO: looks like first argument is not necessary. +(defn remove-transform [_node shapes] (doseq [{:keys [id type]} shapes] (when-let [node (dom/get-element (str "shape-" id))] (let [node (if (= :frame type) (.-parentNode node) node)] diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index 3faa465556..b2851340f3 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -16,7 +16,6 @@ [app.main.ui.hooks :as hooks] [app.main.ui.workspace.viewport.path-actions :refer [path-actions]] [app.util.dom :as dom] - [app.util.object :as obj] [rumext.alpha :as mf])) (mf/defc pixel-grid @@ -110,13 +109,13 @@ on-pointer-enter (mf/use-callback (mf/deps (:id frame) on-frame-enter) - (fn [event] + (fn [_] (on-frame-enter (:id frame)))) on-pointer-leave (mf/use-callback (mf/deps (:id frame) on-frame-leave) - (fn [event] + (fn [_] (on-frame-leave (:id frame))))] [:text {:x 0 diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 7855017a1b..1ffc490d46 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -8,7 +8,6 @@ (:require [app.common.data :as d] [app.common.geom.matrix :as gmt] - [app.common.geom.shapes :as gsh] [app.common.geom.point :as gpt] [app.common.uuid :as uuid] [app.util.color :as uc] diff --git a/frontend/src/app/worker/impl.cljs b/frontend/src/app/worker/impl.cljs index cfa69ac234..9dfb3bba26 100644 --- a/frontend/src/app/worker/impl.cljs +++ b/frontend/src/app/worker/impl.cljs @@ -7,7 +7,6 @@ (ns app.worker.impl (:require [app.common.pages.changes :as ch] - [app.common.transit :as t] [okulary.core :as l])) (enable-console-print!) diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs index 09cc4041a1..d0a03d6319 100644 --- a/frontend/src/app/worker/selection.cljs +++ b/frontend/src/app/worker/selection.cljs @@ -6,17 +6,14 @@ (ns app.worker.selection (:require - [cljs.spec.alpha :as s] - [okulary.core :as l] [app.common.data :as d] - [app.common.exceptions :as ex] [app.common.geom.shapes :as gsh] [app.common.pages :as cp] - [app.common.spec :as us] [app.common.uuid :as uuid] [app.util.quadtree :as qdt] [app.worker.impl :as impl] - [clojure.set :as set])) + [clojure.set :as set] + [okulary.core :as l])) (defonce state (l/atom {})) @@ -66,8 +63,8 @@ changed-ids (into #{} (comp (filter changes?) (filter #(not= % uuid/zero))) - (set/union (keys old-objects) - (keys new-objects))) + (set/union (set (keys old-objects)) + (set (keys new-objects)))) shapes (->> changed-ids (mapv #(get new-objects %)) (filterv (comp not nil?))) parents-index (cp/generate-child-all-parents-index new-objects shapes) @@ -87,7 +84,7 @@ (create-index new-objects))) (defn- query-index - [{index :index z-index :z-index} rect frame-id include-frames? include-groups? disabled-masks reverse?] + [{index :index z-index :z-index} rect frame-id include-frames? include-groups? reverse?] (let [result (-> (qdt/search index (clj->js rect)) (es6-iterator-seq)) @@ -137,7 +134,7 @@ (defmethod impl/handler :selection/initialize-index - [{:keys [file-id data] :as message}] + [{:keys [data] :as message}] (letfn [(index-page [state page] (let [id (:id page) objects (:objects page)] @@ -154,10 +151,10 @@ nil) (defmethod impl/handler :selection/query - [{:keys [page-id rect frame-id include-frames? include-groups? disabled-masks reverse?] - :or {include-groups? true disabled-masks #{} reverse? false} :as message}] + [{:keys [page-id rect frame-id include-frames? include-groups? reverse?] + :or {include-groups? true reverse? false} :as message}] (when-let [index (get @state page-id)] - (query-index index rect frame-id include-frames? include-groups? disabled-masks reverse?))) + (query-index index rect frame-id include-frames? include-groups? reverse?))) (defmethod impl/handler :selection/query-z-index [{:keys [page-id objects ids]}] diff --git a/frontend/src/app/worker/snaps.cljs b/frontend/src/app/worker/snaps.cljs index a2c3954483..dadd825eef 100644 --- a/frontend/src/app/worker/snaps.cljs +++ b/frontend/src/app/worker/snaps.cljs @@ -7,7 +7,6 @@ (ns app.worker.snaps (:require [app.common.data :as d] - [app.common.pages :as cp] [app.common.uuid :as uuid] [app.util.geom.grid :as gg] [app.util.geom.snap-points :as snap] @@ -99,7 +98,8 @@ changed-ids (into #{} (filter changed?) - (set/union (keys old-objects) (keys new-objects))) + (set/union (set (keys old-objects)) + (set (keys new-objects)))) to-delete (aggregate-data old-objects changed-ids) to-add (aggregate-data new-objects changed-ids) @@ -134,30 +134,25 @@ (reduce add-data $ to-add) (reduce delete-frames $ frames-to-delete)))) -(defn- log-state - "Helper function to print a friendly version of the snap tree. Debugging purposes" - [] - (let [process-frame-data #(d/mapm rt/as-map %) - process-page-data #(d/mapm process-frame-data %)] - (js/console.log "STATE" (clj->js (d/mapm process-page-data @state))))) +;; (defn- log-state +;; "Helper function to print a friendly version of the snap tree. Debugging purposes" +;; [] +;; (let [process-frame-data #(d/mapm rt/as-map %) +;; process-page-data #(d/mapm process-frame-data %)] +;; (js/console.log "STATE" (clj->js (d/mapm process-page-data @state))))) (defn- index-page [state page-id objects] (let [snap-data (initialize-snap-data objects)] (assoc state page-id snap-data))) (defn- update-page [state page-id old-objects new-objects] - (let [changed? #(not= (get old-objects %) (get new-objects %)) - changed-ids (into #{} - (filter changed?) - (set/union (keys old-objects) (keys new-objects))) - - snap-data (get state page-id) + (let [snap-data (get state page-id) snap-data (update-snap-data snap-data old-objects new-objects)] (assoc state page-id snap-data))) ;; Public API (defmethod impl/handler :snaps/initialize-index - [{:keys [file-id data] :as message}] + [{:keys [data] :as message}] ;; Create the index (letfn [(process-page [state page] (let [id (:id page) diff --git a/frontend/src/app/worker/thumbnails.cljs b/frontend/src/app/worker/thumbnails.cljs index bf010d56a9..210dfe31b9 100644 --- a/frontend/src/app/worker/thumbnails.cljs +++ b/frontend/src/app/worker/thumbnails.cljs @@ -6,14 +6,13 @@ (ns app.worker.thumbnails (:require - [rumext.alpha :as mf] - [beicon.core :as rx] - [promesa.core :as p] - [app.main.fonts :as fonts] + ["react-dom/server" :as rds] [app.main.exports :as exports] - [app.worker.impl :as impl] + [app.main.fonts :as fonts] [app.util.http :as http] - ["react-dom/server" :as rds])) + [app.worker.impl :as impl] + [beicon.core :as rx] + [rumext.alpha :as mf])) (defn- handle-response [response] From c937ccc92b9bf3ad3f09597b4c16452769daebbf Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Jun 2021 10:25:17 +0200 Subject: [PATCH 124/204] :paperclip: Activate frontend and common linter on CI. --- .circleci/config.yml | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fc6f86d2e6..09e3e6a893 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -33,11 +33,20 @@ jobs: # fallback to using the latest cache if no exact match is found - v1-dependencies- - # run backend lint + - run: + name: common lint + working_directory: "./common" + command: "clj-kondo --parallel --lint src/" + + - run: + name: frontend lint + working_directory: "./frontend" + command: "clj-kondo --parallel --lint src/" + - run: name: backend lint working_directory: "./backend" - command: "clj-kondo --lint src/" + command: "clj-kondo --parallel --lint src/" # run backend test - run: @@ -62,11 +71,6 @@ jobs: JAVA_HOME: /usr/lib/jvm/openjdk16 PATH: /usr/local/nodejs/bin/:/usr/local/bin:/bin:/usr/bin:/usr/lib/jvm/openjdk16/bin - # - run: - # name: common lint - # working_directory: "./common" - # command: "clj-kondo --lint src/" - - run: working_directory: "./common" name: common tests From 358fa7b20f9c6bb065ec714ab5c698ada1651359 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Jun 2021 10:53:03 +0200 Subject: [PATCH 125/204] :paperclip: Add specific linter for service defmethod (on backend). --- .clj-kondo/config.edn | 14 +++++++------- .clj-kondo/hooks/export.clj | 19 +++++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 7a7b8d6e1a..ec3b66f776 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -6,9 +6,12 @@ app.db/with-atomic clojure.core/with-open} :hooks - {:analyze-call {app.common.data/export hooks.export/export - potok.core/reify hooks.export/potok-reify - cljs.core/specify! hooks.export/clojure-specify}} + {:analyze-call + {app.common.data/export hooks.export/export + potok.core/reify hooks.export/potok-reify + cljs.core/specify! hooks.export/clojure-specify + app.util.services/defmethod hooks.export/service-defmethod + }} :output {:exclude-files @@ -41,8 +44,5 @@ {:exclude-destructured-as true :exclude-destructured-keys-in-fn-args false } - - :unresolved-symbol - {:exclude ['(app.util.services/defmethod) - ]}}} + }} diff --git a/.clj-kondo/hooks/export.clj b/.clj-kondo/hooks/export.clj index ffa7dceba3..f29cc1a156 100644 --- a/.clj-kondo/hooks/export.clj +++ b/.clj-kondo/hooks/export.clj @@ -28,3 +28,22 @@ (api/token-node (gensym (:string-value rtype)))] other))] {:node result})) + + +(defn service-defmethod + [{:keys [:node]}] + (let [[rnode rtype & other] (:children node) + rsym (gensym (name (:k rtype))) + result (api/list-node + [(api/token-node (symbol "do")) + (api/list-node + [(api/token-node (symbol "declare")) + (api/token-node rsym)]) + (api/list-node + (into [(api/token-node (symbol "defmethod")) + (api/token-node rsym) + rtype] + other))])] + {:node result})) + + From 047791413e11e5e356dfa9b9a79812e4f57cbba1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Jun 2021 11:02:31 +0200 Subject: [PATCH 126/204] :sparkles: Fix linter issues on backend. --- backend/src/app/rpc/mutations/files.clj | 6 +++--- backend/src/app/rpc/mutations/ldap.clj | 2 +- backend/src/app/rpc/mutations/management.clj | 4 ++-- backend/src/app/rpc/mutations/profile.clj | 9 ++++----- backend/src/app/rpc/queries/files.clj | 6 +++--- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/backend/src/app/rpc/mutations/files.clj b/backend/src/app/rpc/mutations/files.clj index 0b6da5b516..3c266268d3 100644 --- a/backend/src/app/rpc/mutations/files.clj +++ b/backend/src/app/rpc/mutations/files.clj @@ -169,7 +169,7 @@ (s/keys :req-un [::profile-id ::file-id ::library-id])) (sv/defmethod ::unlink-file-from-library - [{:keys [pool] :as cfg} {:keys [profile-id file-id library-id] :as params}] + [{: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))) @@ -189,7 +189,7 @@ (s/keys :req-un [::profile-id ::file-id ::library-id])) (sv/defmethod ::update-sync - [{:keys [pool] :as cfg} {:keys [profile-id file-id library-id] :as params}] + [{: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))) @@ -209,7 +209,7 @@ (s/keys :req-un [::profile-id ::file-id ::date])) (sv/defmethod ::ignore-sync - [{:keys [pool] :as cfg} {:keys [profile-id file-id date] :as params}] + [{: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))) diff --git a/backend/src/app/rpc/mutations/ldap.clj b/backend/src/app/rpc/mutations/ldap.clj index ec9c7f0c51..d327589d89 100644 --- a/backend/src/app/rpc/mutations/ldap.clj +++ b/backend/src/app/rpc/mutations/ldap.clj @@ -48,7 +48,7 @@ :opt-un [::invitation-token])) (sv/defmethod ::login-with-ldap {:auth false :rlimit :password} - [{:keys [pool session tokens] :as cfg} {:keys [email password invitation-token] :as params}] + [{:keys [pool session tokens] :as cfg} params] (db/with-atomic [conn pool] (let [info (authenticate params) cfg (assoc cfg :conn conn)] diff --git a/backend/src/app/rpc/mutations/management.clj b/backend/src/app/rpc/mutations/management.clj index 7ef6207263..1cf1117292 100644 --- a/backend/src/app/rpc/mutations/management.clj +++ b/backend/src/app/rpc/mutations/management.clj @@ -167,7 +167,7 @@ :opt-un [::name])) (sv/defmethod ::duplicate-file - [{:keys [pool] :as cfg} {:keys [profile-id file-id name] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] (let [file (db/get-by-id conn :file file-id) index {file-id (uuid/next)} @@ -187,7 +187,7 @@ :opt-un [::name])) (sv/defmethod ::duplicate-project - [{:keys [pool] :as cfg} {:keys [profile-id project-id name] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] (db/with-atomic [conn pool] (let [project (db/get-by-id conn :project project-id)] (teams/check-edition-permissions! conn profile-id (:team-id project)) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 1aa3a8ae47..b40cf802a6 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -131,8 +131,7 @@ :opt-un [::accept-newsletter-subscription])) (sv/defmethod ::register-profile {:auth false :rlimit :password} - [{:keys [pool tokens session] :as cfg} params] - + [{:keys [pool] :as cfg} params] (when-not (:accept-terms-and-privacy params) (ex/raise :type :validation :code :invalid-terms-and-privacy)) @@ -292,7 +291,7 @@ :opt-un [::scope ::invitation-token])) (sv/defmethod ::login {:auth false :rlimit :password} - [{:keys [pool session tokens] :as cfg} {:keys [email password scope] :as params}] + [{:keys [pool session tokens] :as cfg} {:keys [email password] :as params}] (letfn [(check-password [profile password] (when (= (:password profile) "!") (ex/raise :type :validation @@ -343,7 +342,7 @@ (s/keys :req-un [::profile-id])) (sv/defmethod ::logout - [{:keys [pool session] :as cfg} {:keys [profile-id] :as params}] + [{:keys [session] :as cfg} _] (with-meta {} {:transform-response (:delete session)})) @@ -376,7 +375,7 @@ (s/keys :req-un [::profile-id ::password ::old-password])) (sv/defmethod ::update-profile-password {:rlimit :password} - [{:keys [pool] :as cfg} {:keys [password profile-id] :as params}] + [{:keys [pool] :as cfg} {:keys [password] :as params}] (db/with-atomic [conn pool] (let [profile (validate-password! conn params)] (update-profile-password! conn (assoc profile :password password)) diff --git a/backend/src/app/rpc/queries/files.clj b/backend/src/app/rpc/queries/files.clj index 730efe8d5e..74938feb23 100644 --- a/backend/src/app/rpc/queries/files.clj +++ b/backend/src/app/rpc/queries/files.clj @@ -232,7 +232,7 @@ (update data :objects update-objects))) (sv/defmethod ::page - [{:keys [pool] :as cfg} {:keys [profile-id file-id id strip-thumbnails]}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id strip-thumbnails]}] (db/with-atomic [conn pool] (check-edition-permissions! conn profile-id file-id) (let [cfg (assoc cfg :conn conn) @@ -260,7 +260,7 @@ (s/keys :req-un [::profile-id ::team-id])) (sv/defmethod ::shared-files - [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] + [{:keys [pool] :as cfg} {:keys [team-id] :as params}] (into [] decode-row-xf (db/exec! pool [sql:shared-files team-id]))) @@ -285,7 +285,7 @@ (s/keys :req-un [::profile-id ::team-id])) (sv/defmethod ::team-shared-files - [{:keys [pool] :as cfg} {:keys [profile-id team-id] :as params}] + [{:keys [pool] :as cfg} {:keys [team-id] :as params}] (db/exec! pool [sql:team-shared-files team-id])) From dd7f5fd22815a6cfdaff7af45bb32aa89eb65032 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Jun 2021 11:24:54 +0200 Subject: [PATCH 127/204] Revert ":paperclip: Sort & validate translation files." This reverts commit 09314c892624d4e4bf838685dd967de7a856bd6b. --- frontend/translations/ar.po | 900 +++++----- frontend/translations/ca.po | 679 ++++---- frontend/translations/de.po | 61 +- frontend/translations/id.po | 321 ++-- frontend/translations/pt_BR.po | 602 +++---- frontend/translations/tr.po | 2791 ++++++++++++++++---------------- 6 files changed, 2676 insertions(+), 2678 deletions(-) diff --git a/frontend/translations/ar.po b/frontend/translations/ar.po index fd849b184a..4783704d6b 100644 --- a/frontend/translations/ar.po +++ b/frontend/translations/ar.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-06-15 18:34+0000\n" "Last-Translator: Amine Gdoura \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" @@ -96,14 +96,6 @@ msgstr "رمز الاسترداد غير صالح." msgid "auth.notifications.password-changed-succesfully" 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 msgid "auth.notifications.team-invitation-accepted" msgstr "تم الانضمام إلى الفريق بنجاح" @@ -161,45 +153,6 @@ msgstr "عند إنشاء حساب جديد ، فإنك توافق على شرو msgid "auth.verification-email-sent" msgstr "لقد أرسلنا رسالة تحقق إلى بريدك الالكتروني" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.add-shared" -msgstr "أضف كمكتبة مشتركة" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.change-email" -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 "+ إنشاء فريق جديد" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.default-team-name" -msgstr "Penpot الخاص بك" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.delete-team" -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/dashboard/file_menu.cljs -msgid "dashboard.duplicate-multi" -msgstr "تكرير ٪s الملفات" - -#: src/app/main/ui/dashboard/grid.cljs -msgid "dashboard.empty-files" -msgstr "لا يزال لديك 0 ملفات هنا" - #, fuzzy, markdown msgid "dashboard.fonts.hero-text1" msgstr "" @@ -216,406 +169,6 @@ msgstr "" "(https://penpot.app/terms.html). قد ترغب أيضًا في القراءة عن [ترخيص الخطوط] " "(2)." -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.invite-profile" -msgstr "قم بدعوة فريق" - -#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.leave-team" -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 "تحميل ملفاتك …" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to" -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/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs -msgid "dashboard.new-file" -msgstr "+ ملف جديد" - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-file-prefix" -msgstr "ملف جديد" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "dashboard.new-project" -msgstr "+ مشروع جديد" - -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-project-prefix" -msgstr "مشروع جديد" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.no-matches-for" -msgstr "لم يتم العثور على مطابقات ل \"٪s\"" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.no-projects-placeholder" -msgstr "ستظهر المشاريع المثبتة هنا" - -#: src/app/main/ui/auth/verify_token.cljs -msgid "dashboard.notifications.email-changed-successfully" -msgstr "تم تحديث عنوان بريدك الإلكتروني بنجاح" - -#: src/app/main/ui/auth/verify_token.cljs -msgid "dashboard.notifications.email-verified-successfully" -msgstr "تم التحقق من عنوان بريدك الإلكتروني بنجاح" - -#: src/app/main/ui/settings/password.cljs -msgid "dashboard.notifications.password-saved" -msgstr "تم حفظ كلمة المرور بنجاح!" - -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.num-of-members" -msgstr "٪s الأعضاء" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.open-in-new-tab" -msgstr "فتح ملف في علامة تبويب جديدة" - -#: src/app/main/ui/settings/password.cljs -msgid "dashboard.password-change" -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/team.cljs -msgid "dashboard.promote-to-owner" -msgstr "الترقية إلى مالك" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.remove-account" -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.searching-for" -msgstr "البحث عن \"٪s\"…" - -#: src/app/main/ui/settings/options.cljs -msgid "dashboard.select-ui-language" -msgstr "حدد لغة واجهة المستخدم" - -#: src/app/main/ui/settings/options.cljs -msgid "dashboard.select-ui-theme" -msgstr "اختر نمطا" - -#: src/app/main/ui/dashboard/grid.cljs -msgid "dashboard.show-all-files" -msgstr "إظهار كافة الملفات" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-delete-file" -msgstr "تم حذف ملفك بنجاح" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-delete-project" -msgstr "تم حذف مشروعك بنجاح" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-duplicate-file" -msgstr "تم تكرار ملفك بنجاح" - -#: src/app/main/ui/dashboard/project_menu.cljs -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 -msgid "dashboard.success-move-file" -msgstr "تم نقل ملفك بنجاح" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-move-files" -msgstr "تم نقل الملفات بنجاح" - -#: src/app/main/ui/dashboard/project_menu.cljs -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 "معلومات الفريق" - -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.team-members" -msgstr "أعضاء الفريق" - -#: src/app/main/ui/dashboard/team.cljs -msgid "dashboard.team-projects" -msgstr "مشاريع الفريق" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.title-search" -msgstr "نتائج البحث" - -#: src/app/main/ui/dashboard/search.cljs -msgid "dashboard.type-something" -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 "تحديث الإعدادات" - -#: src/app/main/ui/settings.cljs -#, fuzzy -msgid "dashboard.your-account-title" -msgstr "حسابك الخاص" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.your-email" -msgstr "البريد الالكتروني" - -#: src/app/main/ui/settings/profile.cljs -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 -msgid "dashboard.your-penpot" -msgstr "Penpot الخاص بك" - -#: src/app/main/ui/confirm.cljs -msgid "ds.confirm-cancel" -msgstr "إلغاء الأمر" - -#: src/app/main/ui/confirm.cljs -msgid "ds.confirm-ok" -msgstr "حسنا" - -#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs -msgid "ds.confirm-title" -msgstr "هل أنت متأكد؟" - -#: src/app/main/ui/dashboard/grid.cljs -#, fuzzy -msgid "ds.updated-at" -msgstr "محدث: ٪s" - -#: src/app/main/data/workspace.cljs -msgid "errors.clipboard-not-implemented" -msgstr "لا يمكن للمتصفح إجراء هذه العملية" - -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs -msgid "errors.email-already-exists" -msgstr "البريد الإلكتروني مستخدم بالفعل" - -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.email-already-validated" -msgstr "تم التحقق من صحة البريد الإلكتروني." - -#: src/app/main/ui/settings/change_email.cljs -msgid "errors.email-invalid-confirmation" -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" -msgstr "حدث خطأ ما." - -#: src/app/main/ui/auth/login.cljs -msgid "errors.google-auth-not-enabled" -msgstr "المصادقة مع جوجل تعطلت في الخلفية" - -#: src/app/main/ui/components/color_input.cljs -msgid "errors.invalid-color" -msgstr "لون غير صالح" - -#: src/app/main/ui/auth/login.cljs -msgid "errors.ldap-disabled" -msgstr "تم تعطيل مصادقة LDAP." - -msgid "errors.media-format-unsupported" -msgstr "تنسيق الصورة غير مدعوم (يجب أن يكون svg أو jpg أو png)." - -#: src/app/main/data/workspace/persistence.cljs -msgid "errors.media-too-large" -msgstr "الصورة كبيرة جدا بحيث لا يمكن إدراجها (يجب أن تكون أقل من 5mb)." - -#: 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 "يبدو أن هذه ليست صورة صالحة." - -msgid "errors.network" -msgstr "تعذر الاتصال بخادم الواجهة الخلفية." - -#: src/app/main/ui/settings/password.cljs -msgid "errors.password-invalid-confirmation" -msgstr "يجب أن تتطابق كلمة مرور التأكيد" - -#: src/app/main/ui/settings/password.cljs -msgid "errors.password-too-short" -msgstr "يجب ألا تقل كلمة المرور عن 8 أحرف" - -#: src/app/main/ui/settings/password.cljs -msgid "errors.wrong-old-password" -msgstr "كلمة المرور القديمة غير صحيحة" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.chat-start" -msgstr "انضم إلى الدردشة" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.chat-subtitle" -msgstr "ترغب في الكلام؟ تحدث معنا في Gitter" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.description" -msgstr "وصف" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-go-to" -msgstr "اذهب إلى المناقشات" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-subtitle1" -msgstr "انضم إلى منتدى التواصل التعاوني لفريق Penpot." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-subtitle2" -msgstr "" -"يمكنك طرح الأسئلة والإجابة عليها، إجراء محادثات مفتوحة، ومتابعة القرارات " -"التي تؤثر على المشروع." - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-title" -msgstr "مناقشات الفريق" - -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.subject" -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/password.cljs -msgid "generic.error" -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/image.cljs -msgid "handoff.attributes.image.download" -msgstr "تحميل صورة المصدر" - -#: src/app/main/ui/handoff/attributes/image.cljs -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 "دوران" - -#: 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/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow" -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 "عرض" - msgid "labels.custom-fonts" msgstr "خطوط مخصصة" @@ -675,4 +228,451 @@ msgid "title.dashboard.fonts" msgstr "الخطوط -٪ s - Penpot" msgid "workspace.viewport.click-to-close-path" -msgstr "انقر لإغلاق المسار" \ No newline at end of file +msgstr "انقر لإغلاق المسار" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.notifications.password-saved" +msgstr "تم حفظ كلمة المرور بنجاح!" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-verified-successfully" +msgstr "تم التحقق من عنوان بريدك الإلكتروني بنجاح" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "dashboard.notifications.email-changed-successfully" +msgstr "تم تحديث عنوان بريدك الإلكتروني بنجاح" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.no-projects-placeholder" +msgstr "ستظهر المشاريع المثبتة هنا" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.no-matches-for" +msgstr "لم يتم العثور على مطابقات ل \"٪s\"" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "مشروع جديد" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.new-project" +msgstr "+ مشروع جديد" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "ملف جديد" + +#: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs +msgid "dashboard.new-file" +msgstr "+ ملف جديد" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "الانتقال إلى فريق آخر" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "أنقل ٪s الملفات إلى" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "الانتقال إلى" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.loading-files" +msgstr "تحميل ملفاتك …" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "dashboard.libraries-title" +msgstr "المكتبات المشتركة" + +#: src/app/main/ui/dashboard/sidebar.cljs, src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.leave-team" +msgstr "ترك الفريق" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.invite-profile" +msgstr "قم بدعوة فريق" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "لا يزال لديك 0 ملفات هنا" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "تكرير ٪s الملفات" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "تكرير" + +msgid "dashboard.draft-title" +msgstr "مسودة" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "حذف الفريق" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Penpot الخاص بك" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ إنشاء فريق جديد" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(نسخة)" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "تغيير البريد الإلكتروني" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "أضف كمكتبة مشتركة" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "تم إرسال رابط استعادة كلمة المرور إلى صندوق البريد الخاص بك." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "لم يتم التعرف على الحساب الشخصي ، يرجى التحقق قبل المتابعة." + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.email-already-validated" +msgstr "تم التحقق من صحة البريد الإلكتروني." + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/settings/change_email.cljs +msgid "errors.email-already-exists" +msgstr "البريد الإلكتروني مستخدم بالفعل" + +#: src/app/main/data/workspace.cljs +msgid "errors.clipboard-not-implemented" +msgstr "لا يمكن للمتصفح إجراء هذه العملية" + +#: src/app/main/ui/dashboard/grid.cljs +#, fuzzy +msgid "ds.updated-at" +msgstr "محدث: ٪s" + +#: src/app/main/ui/confirm.cljs, src/app/main/ui/confirm.cljs +msgid "ds.confirm-title" +msgstr "هل أنت متأكد؟" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-ok" +msgstr "حسنا" + +#: src/app/main/ui/confirm.cljs +msgid "ds.confirm-cancel" +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 +msgid "dashboard.your-penpot" +msgstr "Penpot الخاص بك" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-name" +msgstr "اسمك" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.your-email" +msgstr "البريد الالكتروني" + +#: src/app/main/ui/settings.cljs +#, fuzzy +msgid "dashboard.your-account-title" +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 "تحديث الإعدادات" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.type-something" +msgstr "اكتب لإظهار نتائج البحث" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.title-search" +msgstr "نتائج البحث" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-projects" +msgstr "مشاريع الفريق" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-members" +msgstr "أعضاء الفريق" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.team-info" +msgstr "معلومات الفريق" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.switch-team" +msgstr "تبديل الفريق" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "تم نقل مشروعك بنجاح" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +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 "تم نقل ملفك بنجاح" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "تم نسخ مشروعك بنجاح" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "تم تكرار ملفك بنجاح" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "تم حذف مشروعك بنجاح" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "تم حذف ملفك بنجاح" + +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.show-all-files" +msgstr "إظهار كافة الملفات" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-theme" +msgstr "اختر نمطا" + +#: src/app/main/ui/settings/options.cljs +msgid "dashboard.select-ui-language" +msgstr "حدد لغة واجهة المستخدم" + +#: src/app/main/ui/dashboard/search.cljs +msgid "dashboard.searching-for" +msgstr "البحث عن \"٪s\"…" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.search-placeholder" +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/settings/profile.cljs +msgid "dashboard.remove-account" +msgstr "هل تريد إزالة حسابك؟" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.promote-to-owner" +msgstr "الترقية إلى مالك" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "dashboard.projects-title" +msgstr "المشاريع" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "تثبيت / إلغاء التثبيت" + +#: src/app/main/ui/settings/password.cljs +msgid "dashboard.password-change" +msgstr "تغيير كلمة المرور" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "فتح ملف في علامة تبويب جديدة" + +#: src/app/main/ui/dashboard/team.cljs +msgid "dashboard.num-of-members" +msgstr "٪s الأعضاء" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "عرض" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "صلب" + +msgid "handoff.attributes.stroke.style.none" +msgstr "لا أحد" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "مختلط" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "منقط" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "خارج" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "داخل" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "مركز" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "ظل" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +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.rotation" +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.left" +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" +msgstr "تخطيط" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "عرض" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "ارتفاع" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "تحميل صورة المصدر" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.rgba" +msgstr "RGBA" + +#: 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.hex" +msgstr "HEX" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "قيمة" + +#: src/app/main/ui/settings/password.cljs +msgid "generic.error" +msgstr "حدث خطأ" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.title" +msgstr "البريد الإلكتروني" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" +msgstr "" +"يرجى وصف سبب بريدك الإلكتروني ، وتحديد ما إذا كانت مشكلة أم فكرة أم شك. سيرد " +"أحد أعضاء فريقنا في أسرع وقت ممكن." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subject" +msgstr "موضوع" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-title" +msgstr "مناقشات الفريق" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle2" +msgstr "" +"يمكنك طرح الأسئلة والإجابة عليها، إجراء محادثات مفتوحة، ومتابعة القرارات " +"التي تؤثر على المشروع." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle1" +msgstr "انضم إلى منتدى التواصل التعاوني لفريق Penpot." + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-go-to" +msgstr "اذهب إلى المناقشات" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.description" +msgstr "وصف" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-subtitle" +msgstr "ترغب في الكلام؟ تحدث معنا في Gitter" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.chat-start" +msgstr "انضم إلى الدردشة" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.wrong-old-password" +msgstr "كلمة المرور القديمة غير صحيحة" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-too-short" +msgstr "يجب ألا تقل كلمة المرور عن 8 أحرف" + +#: src/app/main/ui/settings/password.cljs +msgid "errors.password-invalid-confirmation" +msgstr "يجب أن تتطابق كلمة مرور التأكيد" + +msgid "errors.network" +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 +msgid "errors.media-type-not-allowed" +msgstr "يبدو أن هذه ليست صورة صالحة." + +#: src/app/main/data/workspace/persistence.cljs +msgid "errors.media-too-large" +msgstr "الصورة كبيرة جدا بحيث لا يمكن إدراجها (يجب أن تكون أقل من 5mb)." + +msgid "errors.media-format-unsupported" +msgstr "تنسيق الصورة غير مدعوم (يجب أن يكون svg أو jpg أو png)." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "تم تعطيل مصادقة LDAP." + +#: src/app/main/ui/components/color_input.cljs +msgid "errors.invalid-color" +msgstr "لون غير صالح" + +#: src/app/main/ui/auth/login.cljs +msgid "errors.google-auth-not-enabled" +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" +msgstr "حدث خطأ ما." + +#: src/app/main/ui/settings/change_email.cljs +msgid "errors.email-invalid-confirmation" +msgstr "يجب أن يتطابق البريد الإلكتروني للتأكيد" diff --git a/frontend/translations/ca.po b/frontend/translations/ca.po index f2f53221be..44ac3aa5d3 100644 --- a/frontend/translations/ca.po +++ b/frontend/translations/ca.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-06-01 00:38+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" @@ -18,8 +18,8 @@ msgstr "Ja teniu un compte?" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" msgstr "" -"Reviseu el correu i cliqueu l'enllaç per verificar i començar a utilitzar " -"el Penpot." +"Reviseu el correu i cliqueu l'enllaç per verificar i començar a utilitzar el " +"Penpot." #: src/app/main/ui/auth/recovery.cljs msgid "auth.confirm-password" @@ -75,18 +75,10 @@ msgstr "Entreu amb GitHub" msgid "auth.login-with-gitlab-submit" msgstr "Entreu amb GitLab" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-google-submit" -msgstr "Entreu amb Google" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" msgstr "Identifiqueu-vos amb LDAP" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-oidc-submit" -msgstr "Entreu amb OpenID (SSO)" - #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" msgstr "Escriviu la nova contrasenya" @@ -101,7 +93,8 @@ msgstr "La contrasenya s'ha canviat correctament" #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.profile-not-verified" -msgstr "El perfil encara no ha estat verificat. Verifiqueu-lo abans de continuar." +msgstr "" +"El perfil encara no ha estat verificat. Verifiqueu-lo abans de continuar." #: src/app/main/ui/auth/recovery_request.cljs msgid "auth.notifications.recovery-token-sent" @@ -155,12 +148,6 @@ msgstr "Creeu un compte" msgid "auth.sidebar-tagline" msgstr "La solució de codi obert per dissenyar i prototipar." -#: src/app/main/ui/auth/register.cljs -msgid "auth.terms-privacy-agreement" -msgstr "" -"En crear un nou compte, accepteu les nostres condicions del servei i la " -"política de privacitat." - #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "S'ha enviat un correu de verificació a" @@ -173,10 +160,6 @@ msgstr "Afegeix una biblioteca compartida" msgid "dashboard.change-email" msgstr "Canvia el correu" -#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs -msgid "dashboard.copy-suffix" -msgstr "(còpia)" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.create-new-team" msgstr "+ Crea un nou equip" @@ -192,36 +175,10 @@ msgstr "Suprimeix l'equip" msgid "dashboard.draft-title" msgstr "Esborrany" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate" -msgstr "Duplica" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate-multi" -msgstr "Duplica %s fitxers" - #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.empty-files" msgstr "Encara no teniu cap arxiu aquí" -#, markdown -msgid "dashboard.fonts.hero-text1" -msgstr "" -"Els tipus de lletra web que pengeu aquí s'afegiran a la llista de famílies " -"tipogràfiques disponibles a les propietats del text dels fitxers d'aquest " -"equip. Els tipus de lletra amb el mateix nom de família s'agruparan en " -"**una sola família tipogràfica**. Podeu pujar tipus de lletra amb aquests " -"formats: **TTF, OTF i WOFF** (només és necessari un)." - -#, markdown -msgid "dashboard.fonts.hero-text2" -msgstr "" -"Només podeu pujar tipus de lletra de la vostra propietat o dels que tingueu " -"una llicència que us permeti utilitzar-los al Penpot. Teniu més informació " -"a la secció de drets de contingut de les [Condicions del servei del " -"Penpot](https://penpot.app/terms.html). També podeu llegir sobre " -"[llicenciament de tipus de lletra](https://www.typography.com/faq)." - #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Convida a l'equip" @@ -238,34 +195,14 @@ msgstr "Biblioteques compartides" msgid "dashboard.loading-files" msgstr "S'estan carregant els fitxers…" -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to" -msgstr "Mou a" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to-multi" -msgstr "Mou %s fitxers a" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.move-to-other-team" -msgstr "Mou a un altre equip" - #: src/app/main/ui/dashboard/projects.cljs, src/app/main/ui/dashboard/files.cljs msgid "dashboard.new-file" msgstr "+ Fitxer nou" -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-file-prefix" -msgstr "Fitxer nou" - #: src/app/main/ui/dashboard/projects.cljs msgid "dashboard.new-project" msgstr "+ Projecte nou" -#: src/app/main/data/dashboard.cljs -msgid "dashboard.new-project-prefix" -msgstr "Projecte nou" - #: src/app/main/ui/dashboard/search.cljs msgid "dashboard.no-matches-for" msgstr "No s'ha trobat cap coincidència amb “%s“" @@ -290,18 +227,10 @@ msgstr "La contrasenya s'ha desat correctament" msgid "dashboard.num-of-members" msgstr "%s membres" -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.open-in-new-tab" -msgstr "Obre el fitxer en una pestanya nova" - #: src/app/main/ui/settings/password.cljs msgid "dashboard.password-change" msgstr "Canvia la contrasenya" -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.pin-unpin" -msgstr "Fixa/Deixa de fixar" - #: src/app/main/ui/dashboard/projects.cljs msgid "dashboard.projects-title" msgstr "Projectes" @@ -338,34 +267,6 @@ msgstr "Selecciona un tema" msgid "dashboard.show-all-files" msgstr "Mostra tots els fitxers" -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-delete-file" -msgstr "S'ha eliminat el fitxer" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-delete-project" -msgstr "S'ha eliminat el projecte" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-duplicate-file" -msgstr "S'ha duplicat el fitxer" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-duplicate-project" -msgstr "S'ha eliminat el projecte" - -#: 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 "S'ha mogut el fitxer" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.success-move-files" -msgstr "S'han mogut els fitxers" - -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "dashboard.success-move-project" -msgstr "S'ha mogut el projecte" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "dashboard.switch-team" msgstr "Canvia d'equip" @@ -458,14 +359,6 @@ msgstr "Alguna cosa ha anat malament" msgid "errors.google-auth-not-enabled" msgstr "L'autenticació amb Google està desactivada en aquest servidor" -#: src/app/main/ui/components/color_input.cljs -msgid "errors.invalid-color" -msgstr "El color no és vàlid" - -#: src/app/main/ui/auth/login.cljs -msgid "errors.ldap-disabled" -msgstr "L'autenticació LDAP està inhabilitada." - msgid "errors.media-format-unsupported" msgstr "El format d'imatge no està suportat (ha de ser SVG, JPG o PNG)." @@ -476,8 +369,7 @@ msgstr "La imatge és massa gran (ha de ser inferior a 5 MB)." #: src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs msgid "errors.media-type-mismatch" msgstr "" -"Sembla que el contingut de la imatge no coincideix amb l'extensió del " -"fitxer." +"Sembla que el contingut de la imatge no coincideix amb l'extensió del fitxer." #: 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" @@ -510,15 +402,6 @@ msgstr "" msgid "errors.registration-disabled" msgstr "El registre està desactivat" -msgid "errors.terms-privacy-agreement-invalid" -msgstr "" -"Heu d'acceptar les nostres condicions del servei i la política de " -"privacitat." - -#: src/app/main/ui/auth/verify_token.cljs -msgid "errors.token-expired" -msgstr "El codi ha caducat" - #: 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 "S'ha produït un error inesperat." @@ -584,224 +467,9 @@ msgstr "Correu electrònic" msgid "generic.error" msgstr "S'ha produït un error" -#: src/app/main/ui/handoff/attributes/blur.cljs -msgid "handoff.attributes.blur" -msgstr "Difuminat" - -#: 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 "Emplenat" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.download" -msgstr "Baixa la imatge original" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.height" -msgstr "Alçada" - -#: src/app/main/ui/handoff/attributes/image.cljs -msgid "handoff.attributes.image.width" -msgstr "Amplada" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout" -msgstr "Disposició" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.height" -msgstr "Alçada" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.left" -msgstr "Esquerra" - -#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.radius" -msgstr "Radi" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.rotation" -msgstr "Rotació" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.top" -msgstr "Superior" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.width" -msgstr "Amplada" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow" -msgstr "Ombra" - -#: 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ç" - -#, permanent -msgid "handoff.attributes.stroke.alignment.center" -msgstr "Centre" - -#, 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 "Puntejat" - -msgid "handoff.attributes.stroke.style.mixed" -msgstr "Mesclat" - -msgid "handoff.attributes.stroke.style.none" -msgstr "Cap" - -msgid "handoff.attributes.stroke.style.solid" -msgstr "Sòlid" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke.width" -msgstr "Amplada" - -#: 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 tipogràfica" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-size" -msgstr "Mida de la lletra" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-style" -msgstr "Estil de la lletra" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.letter-spacing" -msgstr "Espaiat de la lletra" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.line-height" -msgstr "Alçada de la línia" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-decoration" -msgstr "Decoració del text" - -msgid "handoff.attributes.typography.text-decoration.none" -msgstr "Cap" - -msgid "handoff.attributes.typography.text-decoration.strikethrough" -msgstr "Barrat" - -msgid "handoff.attributes.typography.text-decoration.underline" -msgstr "Subratllat" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-transform" -msgstr "Transformació del text" - -msgid "handoff.attributes.typography.text-transform.lowercase" -msgstr "Minúscules" - -msgid "handoff.attributes.typography.text-transform.none" -msgstr "Cap" - -msgid "handoff.attributes.typography.text-transform.titlecase" -msgstr "Inicials en majúscules" - -msgid "handoff.attributes.typography.text-transform.uppercase" -msgstr "Majúscules" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code" -msgstr "Codi" - -msgid "handoff.tabs.code.selected.circle" -msgstr "Cercle" - -msgid "handoff.tabs.code.selected.curve" -msgstr "Corba" - -msgid "handoff.tabs.code.selected.frame" -msgstr "Taula de treball" - -msgid "handoff.tabs.code.selected.group" -msgstr "Grup" - -msgid "handoff.tabs.code.selected.image" -msgstr "Imatge" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code.selected.multiple" -msgstr "%s seleccionats" - -msgid "handoff.tabs.code.selected.path" -msgstr "Camí" - -msgid "handoff.tabs.code.selected.rect" -msgstr "Rectangle" - -msgid "handoff.tabs.code.selected.svg-raw" -msgstr "SVG" - -msgid "handoff.tabs.code.selected.text" -msgstr "Text" - -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.info" -msgstr "Informació" - -msgid "history.alert-message" -msgstr "Esteu veient la versió %s" - msgid "labels.accept" msgstr "Acceptar" -#: 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 "Administració" - msgid "labels.go-back" msgstr "Enrere" @@ -817,4 +485,335 @@ msgstr "Projectes - %s - Penpot" #: src/app/main/ui/dashboard/search.cljs msgid "title.dashboard.search" -msgstr "Cerca - %s - Penpot" \ No newline at end of file +msgstr "Cerca - %s - Penpot" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "errors.token-expired" +msgstr "El codi ha caducat" + +msgid "errors.terms-privacy-agreement-invalid" +msgstr "" +"Heu d'acceptar les nostres condicions del servei i la política de privacitat." + +#: src/app/main/ui/auth/login.cljs +msgid "errors.ldap-disabled" +msgstr "L'autenticació LDAP està inhabilitada." + +#: src/app/main/ui/components/color_input.cljs +msgid "errors.invalid-color" +msgstr "El color no és vàlid" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-move-project" +msgstr "S'ha mogut el projecte" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-move-files" +msgstr "S'han mogut els fitxers" + +#: 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 "S'ha mogut el fitxer" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-duplicate-project" +msgstr "S'ha eliminat el projecte" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-duplicate-file" +msgstr "S'ha duplicat el fitxer" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.success-delete-project" +msgstr "S'ha eliminat el projecte" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.success-delete-file" +msgstr "S'ha eliminat el fitxer" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "dashboard.pin-unpin" +msgstr "Fixa/Deixa de fixar" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.open-in-new-tab" +msgstr "Obre el fitxer en una pestanya nova" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-project-prefix" +msgstr "Projecte nou" + +#: src/app/main/data/dashboard.cljs +msgid "dashboard.new-file-prefix" +msgstr "Fitxer nou" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-other-team" +msgstr "Mou a un altre equip" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to-multi" +msgstr "Mou %s fitxers a" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.move-to" +msgstr "Mou a" + +#, markdown +msgid "dashboard.fonts.hero-text2" +msgstr "" +"Només podeu pujar tipus de lletra de la vostra propietat o dels que tingueu " +"una llicència que us permeti utilitzar-los al Penpot. Teniu més informació a " +"la secció de drets de contingut de les [Condicions del servei del " +"Penpot](https://penpot.app/terms.html). També podeu llegir sobre [" +"llicenciament de tipus de lletra](https://www.typography.com/faq)." + +#, markdown +msgid "dashboard.fonts.hero-text1" +msgstr "" +"Els tipus de lletra web que pengeu aquí s'afegiran a la llista de famílies " +"tipogràfiques disponibles a les propietats del text dels fitxers d'aquest " +"equip. Els tipus de lletra amb el mateix nom de família s'agruparan en **una " +"sola família tipogràfica**. Podeu pujar tipus de lletra amb aquests formats: " +"**TTF, OTF i WOFF** (només és necessari un)." + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Duplica %s fitxers" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplica" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(còpia)" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"En crear un nou compte, accepteu les nostres condicions del servei i la " +"política de privacitat." + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Entreu amb OpenID (SSO)" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Entreu amb Google" + +#: 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 "Administració" + +msgid "history.alert-message" +msgstr "Esteu veient la versió %s" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.info" +msgstr "Informació" + +msgid "handoff.tabs.code.selected.text" +msgstr "Text" + +msgid "handoff.tabs.code.selected.svg-raw" +msgstr "SVG" + +msgid "handoff.tabs.code.selected.rect" +msgstr "Rectangle" + +msgid "handoff.tabs.code.selected.path" +msgstr "Camí" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code.selected.multiple" +msgstr "%s seleccionats" + +msgid "handoff.tabs.code.selected.image" +msgstr "Imatge" + +msgid "handoff.tabs.code.selected.group" +msgstr "Grup" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Taula de treball" + +msgid "handoff.tabs.code.selected.curve" +msgstr "Corba" + +msgid "handoff.tabs.code.selected.circle" +msgstr "Cercle" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Espaiat de la lletra" + +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Codi" + +msgid "handoff.attributes.typography.text-transform.uppercase" +msgstr "Majúscules" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "Inicials en majúscules" + +msgid "handoff.attributes.typography.text-transform.none" +msgstr "Cap" + +msgid "handoff.attributes.typography.text-transform.lowercase" +msgstr "Minúscules" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Transformació del text" + +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Subratllat" + +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Barrat" + +msgid "handoff.attributes.typography.text-decoration.none" +msgstr "Cap" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Decoració del text" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Alçada de la línia" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Estil de la lletra" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Mida de la lletra" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-family" +msgstr "Família tipogràfica" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografia" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Amplada" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Sòlid" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Cap" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Mesclat" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Puntejat" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Exterior" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "Interior" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Centre" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Traç" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.spread" +msgstr "S" + +#: 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.offset-x" +msgstr "X" + +#: 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" +msgstr "Ombra" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Amplada" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Superior" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Rotació" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Radi" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Esquerra" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Alçada" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout" +msgstr "Disposició" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.width" +msgstr "Amplada" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.height" +msgstr "Alçada" + +#: src/app/main/ui/handoff/attributes/image.cljs +msgid "handoff.attributes.image.download" +msgstr "Baixa la imatge original" + +#: src/app/main/ui/handoff/attributes/fill.cljs +msgid "handoff.attributes.fill" +msgstr "Emplenat" + +#: src/app/main/ui/handoff/attributes/common.cljs +msgid "handoff.attributes.color.rgba" +msgstr "RGBA" + +#: 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.hex" +msgstr "HEX" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur.value" +msgstr "Valor" + +#: src/app/main/ui/handoff/attributes/blur.cljs +msgid "handoff.attributes.blur" +msgstr "Difuminat" diff --git a/frontend/translations/de.po b/frontend/translations/de.po index 0d14a32660..4763752014 100644 --- a/frontend/translations/de.po +++ b/frontend/translations/de.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-05-25 12:31+0000\n" "Last-Translator: Yannik Rödel \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" @@ -75,18 +75,10 @@ msgstr "Einloggen mit Github" msgid "auth.login-with-gitlab-submit" msgstr "Einloggen mit Gitlab" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-google-submit" -msgstr "Einloggen mit Google" - #: src/app/main/ui/auth/login.cljs msgid "auth.login-with-ldap-submit" msgstr "Anmelden mit LDAP" -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-oidc-submit" -msgstr "Einloggen mit OpenID (SSO)" - #: src/app/main/ui/auth/recovery.cljs msgid "auth.new-password" msgstr "Geben Sie ein neues Passwort ein" @@ -157,12 +149,6 @@ msgstr "Konto erstellen" msgid "auth.sidebar-tagline" msgstr "Die Open-Source-Lösung für Design und Prototyping." -#: src/app/main/ui/auth/register.cljs -msgid "auth.terms-privacy-agreement" -msgstr "" -"Wenn Sie ein neues Konto erstellen, stimmen Sie unseren Nutzungsbedingungen " -"und Datenschutzrichtlinien zu." - #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "Wir haben eine Bestätigungs-E-Mail gesendet an" @@ -198,24 +184,10 @@ msgstr "Entwurf" msgid "dashboard.duplicate" msgstr "Duplizieren" -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate-multi" -msgstr "%s Dateien duplizieren" - #: src/app/main/ui/dashboard/grid.cljs msgid "dashboard.empty-files" msgstr "Sie haben hier noch keine Dateien" -#, markdown -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)." - #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Zum Team einladen" @@ -2440,4 +2412,31 @@ 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" + +#, markdown +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)." + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "%s Dateien duplizieren" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Wenn Sie ein neues Konto erstellen, stimmen Sie unseren Nutzungsbedingungen " +"und Datenschutzrichtlinien zu." + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Einloggen mit OpenID (SSO)" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Einloggen mit Google" diff --git a/frontend/translations/id.po b/frontend/translations/id.po index 800120d39f..5210b494db 100644 --- a/frontend/translations/id.po +++ b/frontend/translations/id.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-05-23 21:33+0000\n" "Last-Translator: luthfi azhari \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" @@ -11,9 +11,84 @@ msgstr "" "Plural-Forms: nplurals=1; plural=0;\n" "X-Generator: Weblate 4.7-dev\n" +#: src/app/main/ui/dashboard/grid.cljs +msgid "dashboard.empty-files" +msgstr "Anda belum memiliki file disini" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate-multi" +msgstr "Duplikasi % file" + +#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.duplicate" +msgstr "Duplikat" + +msgid "dashboard.draft-title" +msgstr "Konsep" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.delete-team" +msgstr "Hapus tim" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.default-team-name" +msgstr "Penpot anda" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "dashboard.create-new-team" +msgstr "+ Buat tim baru" + +#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs +msgid "dashboard.copy-suffix" +msgstr "(salin)" + +#: src/app/main/ui/settings/profile.cljs +msgid "dashboard.change-email" +msgstr "Ubah email" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "dashboard.add-shared" +msgstr "Tambahkan sebagai pustaka bersama" + #: src/app/main/ui/auth/register.cljs -msgid "auth.already-have-account" -msgstr "Sudah memiliki akun?" +msgid "auth.verification-email-sent" +msgstr "Kami mengirim verifikasi ke surel anda" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-title" +msgstr "Buat akun baru" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.register-subtitle" +msgstr "Ini gratis, Open Source" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.register-submit" +msgstr "Buat akun baru" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.register" +msgstr "Tidak ada akun?" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.recovery-submit" +msgstr "Ubah kata sandi anda" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-title" +msgstr "Lupa kata sandi?" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-subtitle" +msgstr "Kami akan mengirimi anda surel dengan intruksi" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.recovery-request-submit" +msgstr "Pemulihan Kata sandi" + +#: src/app/main/ui/auth/register.cljs +msgid "auth.password-length-hint" +msgstr "Paling tidak 8 karakter" #: src/app/main/ui/auth/register.cljs msgid "auth.check-your-email" @@ -25,13 +100,72 @@ msgstr "" msgid "auth.confirm-password" msgstr "Konfirmasi kata sandi" -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.create-demo-account" -msgstr "Buat akun demo" +#: src/app/main/ui/auth/register.cljs +msgid "auth.terms-privacy-agreement" +msgstr "" +"Ketika membuat akun baru, anda menyetujui persyaratan layanan dan kebijakan " +"privasi kami." + +#: src/app/main/ui/auth.cljs +msgid "auth.sidebar-tagline" +msgstr "Solusi open-source untuk desain dan pembuatan prototype." #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.create-demo-profile" -msgstr "Ingin mencobanya?" +msgid "auth.password" +msgstr "Kata sandi" + +#: src/app/main/ui/auth/verify_token.cljs +msgid "auth.notifications.team-invitation-accepted" +msgstr "Berhasil bergabung dengan tim" + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.recovery-token-sent" +msgstr "Link pemulihan kata sandi berhasil dikirim ke kotak masuk anda." + +#: src/app/main/ui/auth/recovery_request.cljs +msgid "auth.notifications.profile-not-verified" +msgstr "" +"Akun belum terverifikasi, harap verifikasi profile anda sebelum melanjutkan." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.password-changed-succesfully" +msgstr "Kata sandi berhasil diubah" + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.notifications.invalid-token-error" +msgstr "Token pemulihan tidak valid." + +#: src/app/main/ui/auth/recovery.cljs +msgid "auth.new-password" +msgstr "Ketikkan kata sandi baru" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-oidc-submit" +msgstr "Masuk dengan OpenID (SSO)" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-ldap-submit" +msgstr "Masuk dengan LDAP" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-google-submit" +msgstr "Masuk dengan Google" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-gitlab-submit" +msgstr "Masuk dengan Gitlab" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-with-github-submit" +msgstr "Masuk dengan Github" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-title" +msgstr "Senang bertemu denganmu lagi!" + +#: src/app/main/ui/auth/login.cljs +msgid "auth.login-subtitle" +msgstr "Masukkan detail anda di bawah ini" #: src/app/main/ui/auth/register.cljs msgid "auth.demo-warning" @@ -39,171 +173,38 @@ msgstr "" "Ini layanan DEMO, JANGAN GUNAKAN untuk pekerjaan nyata, project ini akan di " "hapus secara berkala." -#: 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 "Surel" - #: src/app/main/ui/auth/login.cljs -msgid "auth.forgot-password" -msgstr "Lupa kata sandi?" +msgid "auth.login-submit" +msgstr "Masuk" #: src/app/main/ui/auth/register.cljs -msgid "auth.fullname" -msgstr "Nama Lengkap" +msgid "auth.login-here" +msgstr "Masuk disini" #: 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 disini" +msgid "auth.fullname" +msgstr "Nama Lengkap" #: src/app/main/ui/auth/login.cljs -msgid "auth.login-submit" -msgstr "Masuk" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-subtitle" -msgstr "Masukkan detail anda di bawah ini" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-title" -msgstr "Senang bertemu denganmu lagi!" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-github-submit" -msgstr "Masuk dengan Github" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-gitlab-submit" -msgstr "Masuk dengan Gitlab" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-google-submit" -msgstr "Masuk dengan Google" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-ldap-submit" -msgstr "Masuk dengan LDAP" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.login-with-oidc-submit" -msgstr "Masuk dengan OpenID (SSO)" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.new-password" -msgstr "Ketikkan kata sandi baru" - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.invalid-token-error" -msgstr "Token pemulihan tidak valid." - -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.notifications.password-changed-succesfully" -msgstr "Kata sandi berhasil diubah" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.notifications.profile-not-verified" -msgstr "Akun belum terverifikasi, harap verifikasi profile anda sebelum melanjutkan." - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.notifications.recovery-token-sent" -msgstr "Link pemulihan kata sandi berhasil dikirim ke kotak masuk anda." - -#: src/app/main/ui/auth/verify_token.cljs -msgid "auth.notifications.team-invitation-accepted" -msgstr "Berhasil bergabung dengan tim" - -#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.password" -msgstr "Kata sandi" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.password-length-hint" -msgstr "Paling tidak 8 karakter" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.recovery-request-submit" -msgstr "Pemulihan Kata sandi" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.recovery-request-subtitle" -msgstr "Kami akan mengirimi anda surel dengan intruksi" - -#: src/app/main/ui/auth/recovery_request.cljs -msgid "auth.recovery-request-title" +msgid "auth.forgot-password" msgstr "Lupa kata sandi?" -#: src/app/main/ui/auth/recovery.cljs -msgid "auth.recovery-submit" -msgstr "Ubah kata sandi anda" - -#: src/app/main/ui/auth/login.cljs -msgid "auth.register" -msgstr "Tidak ada akun?" +#: 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 "Surel" #: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs -msgid "auth.register-submit" -msgstr "Buat akun baru" +msgid "auth.create-demo-profile" +msgstr "Ingin mencobanya?" + +#: src/app/main/ui/auth/register.cljs, src/app/main/ui/auth/login.cljs +msgid "auth.create-demo-account" +msgstr "Buat akun demo" #: src/app/main/ui/auth/register.cljs -msgid "auth.register-subtitle" -msgstr "Ini gratis, Open Source" - -#: src/app/main/ui/auth/register.cljs -msgid "auth.register-title" -msgstr "Buat akun baru" - -#: src/app/main/ui/auth.cljs -msgid "auth.sidebar-tagline" -msgstr "Solusi open-source untuk desain dan pembuatan prototype." - -#: src/app/main/ui/auth/register.cljs -msgid "auth.terms-privacy-agreement" -msgstr "" -"Ketika membuat akun baru, anda menyetujui persyaratan layanan dan kebijakan " -"privasi kami." - -#: src/app/main/ui/auth/register.cljs -msgid "auth.verification-email-sent" -msgstr "Kami mengirim verifikasi ke surel anda" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.add-shared" -msgstr "Tambahkan sebagai pustaka bersama" - -#: src/app/main/ui/settings/profile.cljs -msgid "dashboard.change-email" -msgstr "Ubah email" - -#: src/app/main/data/dashboard.cljs, src/app/main/data/dashboard.cljs -msgid "dashboard.copy-suffix" -msgstr "(salin)" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.create-new-team" -msgstr "+ Buat tim baru" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.default-team-name" -msgstr "Penpot anda" - -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "dashboard.delete-team" -msgstr "Hapus tim" - -msgid "dashboard.draft-title" -msgstr "Konsep" - -#: src/app/main/ui/dashboard/project_menu.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate" -msgstr "Duplikat" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "dashboard.duplicate-multi" -msgstr "Duplikasi % file" - -#: src/app/main/ui/dashboard/grid.cljs -msgid "dashboard.empty-files" -msgstr "Anda belum memiliki file disini" \ No newline at end of file +msgid "auth.already-have-account" +msgstr "Sudah memiliki akun?" diff --git a/frontend/translations/pt_BR.po b/frontend/translations/pt_BR.po index 5541abdb76..ae9e3d988b 100644 --- a/frontend/translations/pt_BR.po +++ b/frontend/translations/pt_BR.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-06-15 18:34+0000\n" "Last-Translator: Eranot \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" @@ -849,14 +849,6 @@ msgstr "" msgid "modals.delete-font.title" msgstr "Excluindo fonte" -#: 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?" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.title" -msgstr "Excluir membro da equipe" - #: src/app/main/ui/dashboard/team.cljs msgid "modals.invite-member-confirm.accept" msgstr "Enviar convite" @@ -870,10 +862,6 @@ msgstr "" "Você não pode deixar a equipe se não houver outro membro para promover a " "proprietário. Você pode excluir a equipe." -#: src/app/main/ui/dashboard/sidebar.cljs -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" @@ -918,13 +906,6 @@ msgstr "Promover a proprietário" msgid "modals.remove-shared-confirm.accept" msgstr "Remover como Biblioteca Compartilhada" -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.remove-shared-confirm.hint" -msgstr "" -"Depois de removida como Biblioteca Compartilhada, os Componentes deste " -"arquivo deixarão de estar disponível para serem usados com o resto de seus " -"arquivos." - #: 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" @@ -937,46 +918,6 @@ msgstr "Atualizar componente" 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 "" -"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" - -#: 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 "" -"Você não pode deletar seu perfil. Designe um novo proprietário para suas " -"equipes antes de continuar." - -#: 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. Verifique seu e-mail!" - -#: src/app/main/ui/auth/recovery.cljs -msgid "profile.recovery.go-to-login" -msgstr "Ir para a página de 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 "Misto" - -#: 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" @@ -985,271 +926,330 @@ msgstr "Provedores de fonte - %s - Penpot" msgid "title.dashboard.fonts" msgstr "Fontes - %s - 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 "Pesquisar - %s - Penpot" - -#: src/app/main/ui/dashboard/libraries.cljs -msgid "title.dashboard.shared-libraries" -msgstr "Bibliotecas Compartilhadas - %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 equipes" - -#: src/app/main/ui/settings/feedback.cljs -msgid "title.settings.feedback" -msgstr "Dê sua opinião - Penpot" - -#: src/app/main/ui/settings/options.cljs -msgid "title.settings.options" -msgstr "Configurações - Penpot" - -#: src/app/main/ui/settings/password.cljs -msgid "title.settings.password" -msgstr "Senha - 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 "Configurações - %s - Penpot" #: src/app/main/ui/dashboard/team.cljs msgid "title.team-members" msgstr "Membros - %s - Penpot" -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-settings" -msgstr "Configurações - %s - Penpot" +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Perfil - Penpot" -msgid "workspace.library.all" -msgstr "Todas bibliotecas" +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Senha - Penpot" -msgid "workspace.library.libraries" -msgstr "Bibliotecas" +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Configurações - Penpot" -msgid "workspace.library.own" -msgstr "Minhas bibliotecas" +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Dê sua opinião - Penpot" -msgid "workspace.library.store" -msgstr "Bibliotecas da loja" +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot - Liberdade de design para equipes" -msgid "workspace.options.blur-options.background-blur" -msgstr "Fundo" +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Bibliotecas Compartilhadas - %s - Penpot" -msgid "workspace.options.blur-options.layer-blur" -msgstr "Camada" +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Pesquisar - %s - Penpot" -#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs -msgid "workspace.options.blur-options.title" +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Projetos - %s - Penpot" + +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - 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 "Misto" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Ir para a página de login" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "E-mail de verificação enviado para %s. Verifique seu e-mail!" + +#: 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/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" msgstr "" +"Você não pode deletar seu perfil. Designe um novo proprietário para suas " +"equipes antes de continuar." -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs -msgid "workspace.options.component" -msgstr "Componente" +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Convite enviado com sucesso" -#: src/app/main/ui/workspace/sidebar/options.cljs -msgid "workspace.options.design" -msgstr "Design" +#: 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" -#: 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/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." -#: 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" +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.hint" +msgstr "" +"Depois de removida como Biblioteca Compartilhada, os Componentes deste " +"arquivo deixarão de estar disponível para serem usados com o resto de seus " +"arquivos." -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs -msgid "workspace.options.export.suffix" -msgstr "Sufixo" +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "Você é o proprietário de %s." -#: 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/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Excluir membro da equipe" -#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs -msgid "workspace.options.fill" -msgstr "Preencher" - -#: 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.params.columns" -msgstr "Colunas" - -#: 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.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/frame_grid.cljs -msgid "workspace.options.grid.params.type.bottom" -msgstr "Inferior" - -#: 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" -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.type.top" -msgstr "Superior" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.use-default" -msgstr "Usar padrão" - -#: 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/frame_grid.cljs -msgid "workspace.options.grid.title" -msgstr "Grades & Layouts" - -#: 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.title.multiple" -msgstr "Camadas selecionadas" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.navigate-to" -msgstr "Navegar para" - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.none" -msgstr "Nenhum" - -#: 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 cantos" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.radius.single-corners" -msgstr "Cantos individuais" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.rotation" -msgstr "Rotação" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.blur" -msgstr "Borrar" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.drop-shadow" -msgstr "Sombra projetada" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.inner-shadow" -msgstr "Sombra interior" - -#: 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.title" -msgstr "Sombra" - -#: 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 "Predefinições de tamanho" +#: 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?" #: 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 "Tracejada" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dotted" -msgstr "Pontilhada" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.inner" -msgstr "Dentro" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.mixed" -msgstr "Misturado" +msgid "workspace.options.stroke.solid" +msgstr "Sólido" #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs msgid "workspace.options.stroke.outer" msgstr "Fora" #: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.solid" -msgstr "Sólido" \ No newline at end of file +msgid "workspace.options.stroke.mixed" +msgstr "Misturado" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.inner" +msgstr "Dentro" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Pontilhada" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Tracejada" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/shapes/frame.cljs +msgid "workspace.options.size-presets" +msgstr "Predefinições de tamanho" + +#: 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/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.offsety" +msgstr "Y" + +#: 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 interior" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Sombra projetada" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.blur" +msgstr "Borrar" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Rotação" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.single-corners" +msgstr "Cantos individuais" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Todos cantos" + +msgid "workspace.options.radius" +msgstr "Raio" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Protótipo" + +#: 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/menus/interactions.cljs +msgid "workspace.options.none" +msgstr "Nenhum" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.navigate-to" +msgstr "Navegar para" + +#: 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/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Cor" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.title" +msgstr "Grades & Layouts" + +#: 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/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.params.width" +msgstr "Largura" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Usar padrão" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Superior" + +#: 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.type.right" +msgstr "Direita" + +#: 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.center" +msgstr "Centro" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Inferior" + +#: 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/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.set-default" +msgstr "Definir como padrão" + +#: 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.margin" +msgstr "Margem" + +#: 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.columns" +msgstr "Colunas" + +#: 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.auto" +msgstr "Automático" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Preencher" + +#: 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 +msgid "workspace.options.export.suffix" +msgstr "Sufixo" + +#: 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" + +#: 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.cljs +msgid "workspace.options.design" +msgstr "Design" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Componente" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Camada" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Fundo" + +msgid "workspace.library.store" +msgstr "Bibliotecas da loja" + +msgid "workspace.library.own" +msgstr "Minhas bibliotecas" + +msgid "workspace.library.libraries" +msgstr "Bibliotecas" + +msgid "workspace.library.all" +msgstr "Todas bibliotecas" diff --git a/frontend/translations/tr.po b/frontend/translations/tr.po index 176064b54a..55e5587d08 100644 --- a/frontend/translations/tr.po +++ b/frontend/translations/tr.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-06-01 00:38+0000\n" "Last-Translator: Çağlar Yeşilyurt \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" @@ -209,10 +209,9 @@ 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ı] içindeki " -"İçerik hakları bölümünden detaylı bilgi alabilirsiniz " -"(https://penpot.app/terms.html). Ayrıca [yazı tipi " -"lisanslama](https://www.typography.com/faq) hakkında daha fazla bilgi almak " -"isteyebilirsiniz." +"İçerik hakları bölümünden detaylı bilgi alabilirsiniz (https://penpot.app/" +"terms.html). Ayrıca [yazı tipi lisanslama](https://www.typography.com/faq) " +"hakkında daha fazla bilgi almak isteyebilirsiniz." #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" @@ -539,12 +538,6 @@ msgstr "Açıklama" msgid "feedback.discussions-go-to" msgstr "Tartışmalar bölümüne git" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.discussions-subtitle2" -msgstr "" -"Soru sorabilir ve soruları cevaplayabilir, açık uçlu tartışmalar yapabilir " -"ve projeyi etkileyen kararları takip edebilirsin." - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.discussions-title" msgstr "Takım tartışmaları" @@ -553,12 +546,6 @@ msgstr "Takım tartışmaları" msgid "feedback.subject" msgstr "Konu" -#: src/app/main/ui/settings/feedback.cljs -msgid "feedback.subtitle" -msgstr "" -"Lütfen bir sorun, fikir ya da kuşkunuzu açıklayarak e-postanızın nedenini " -"belirtin. Ekibimizin bir üyesi en kısa sürede yanıt verecektir." - #: src/app/main/ui/settings/feedback.cljs msgid "feedback.title" msgstr "E-posta" @@ -603,144 +590,24 @@ msgstr "Yükseklik" msgid "handoff.attributes.image.width" msgstr "Genişlik" -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.height" -msgstr "Yükseklik" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.left" -msgstr "Sol" - -#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.radius" -msgstr "Yarı Çap" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.rotation" -msgstr "Döndür" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.top" -msgstr "Üst" - -#: src/app/main/ui/handoff/attributes/layout.cljs -msgid "handoff.attributes.layout.width" -msgstr "Genişlik" - -#: src/app/main/ui/handoff/attributes/shadow.cljs -msgid "handoff.attributes.shadow" -msgstr "Gölge" - -#: 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 "Y" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke" -msgstr "Çerçeve" - -#, permanent -msgid "handoff.attributes.stroke.alignment.center" -msgstr "Merkezi" - -#, permanent -msgid "handoff.attributes.stroke.alignment.inner" -msgstr "İçinde" - -#, permanent -msgid "handoff.attributes.stroke.alignment.outer" -msgstr "Dışarıda" - -msgid "handoff.attributes.stroke.style.dotted" -msgstr "Noktalı" - -msgid "handoff.attributes.stroke.style.mixed" -msgstr "Karışık" - -msgid "handoff.attributes.stroke.style.none" -msgstr "Hiçbiri" - -msgid "handoff.attributes.stroke.style.solid" -msgstr "Düz" - -#: src/app/main/ui/handoff/attributes/stroke.cljs -msgid "handoff.attributes.stroke.width" -msgstr "Genişlik" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography" -msgstr "Tipografi" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-family" -msgstr "Font Ailesi" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-size" -msgstr "Font Boyutu" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.font-style" -msgstr "Font Stili" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.letter-spacing" -msgstr "Harf Aralığı" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.line-height" -msgstr "Satır Yüksekliği" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-decoration" -msgstr "Metin Süsleme" - msgid "handoff.attributes.typography.text-decoration.none" msgstr "Hiçbiri" -msgid "handoff.attributes.typography.text-decoration.strikethrough" -msgstr "Üstü Çizili" - -msgid "handoff.attributes.typography.text-decoration.underline" -msgstr "Altı Çizili" - -#: src/app/main/ui/handoff/attributes/text.cljs -msgid "handoff.attributes.typography.text-transform" -msgstr "Metin Dönüşümü" - msgid "handoff.attributes.typography.text-transform.lowercase" msgstr "Küçük Harf" msgid "handoff.attributes.typography.text-transform.none" msgstr "Hiçbiri" -msgid "handoff.attributes.typography.text-transform.titlecase" -msgstr "İlk Harfleri Büyük" - msgid "handoff.attributes.typography.text-transform.uppercase" msgstr "Büyük Harf" -#: src/app/main/ui/handoff/right_sidebar.cljs -msgid "handoff.tabs.code" -msgstr "Kod" - msgid "handoff.tabs.code.selected.circle" msgstr "Daire" msgid "handoff.tabs.code.selected.curve" msgstr "Eğri" -msgid "handoff.tabs.code.selected.frame" -msgstr "Çalışma yüzeyi" - msgid "handoff.tabs.code.selected.group" msgstr "Grup" @@ -781,16 +648,6 @@ msgstr "Yönetici" msgid "labels.all" msgstr "Hepsi" -#: src/app/main/ui/static.cljs -msgid "labels.bad-gateway.desc-message" -msgstr "" -"Görünüşe göre biraz beklemen ve yeniden denemen gerekiyor; sunucularımızda " -"küçük bir bakım yapıyoruz." - -#: src/app/main/ui/static.cljs -msgid "labels.bad-gateway.main-message" -msgstr "Hatalı Ağ Geçidi" - #: src/app/main/ui/dashboard/sidebar.cljs msgid "labels.cancel" msgstr "İptal" @@ -821,9 +678,6 @@ msgstr "Yeni takım oluştur" msgid "labels.create-team.placeholder" msgstr "Yeni takım adı gir" -msgid "labels.custom-fonts" -msgstr "Özel Fontlar" - #: src/app/main/ui/settings/sidebar.cljs msgid "labels.dashboard" msgstr "Kontrol paneli" @@ -871,9 +725,6 @@ msgstr "Geri bildirim gönderildi" msgid "labels.font-family" msgstr "Font Ailesi" -msgid "labels.font-providers" -msgstr "Font sağlayıcısı" - msgid "labels.font-variant" msgstr "Stil" @@ -891,9 +742,6 @@ msgstr "Geri dön" msgid "labels.hide-resolved-comments" msgstr "Çözülmüş yorumları gizle" -msgid "labels.icons" -msgstr "Simgeler" - msgid "labels.images" msgstr "Görseller" @@ -906,10 +754,6 @@ msgstr "" "Kötü bir şey oldu. Lütfen işlemi yeniden deneyin ve sorun devam ederse " "destek ile iletişime geçin." -#: src/app/main/ui/static.cljs -msgid "labels.internal-error.main-message" -msgstr "İç Hata" - #: src/app/main/ui/settings/options.cljs msgid "labels.language" msgstr "Dil" @@ -918,9 +762,6 @@ msgstr "Dil" msgid "labels.logout" msgstr "Çıkış Yap" -msgid "labels.manage-fonts" -msgstr "Fontları yönet" - #: 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 "Üyeler" @@ -1062,121 +903,12 @@ msgstr "Çıkış yap" msgid "labels.update" msgstr "Güncelle" -#: src/app/main/ui/dashboard/team_form.cljs -msgid "labels.update-team" -msgstr "Takımı güncelle" - msgid "labels.upload" msgstr "Yükle" -msgid "labels.upload-custom-fonts" -msgstr "Özel yazı tipi yükle" - msgid "labels.uploading" msgstr "Yükleniyor…" -#: src/app/main/ui/dashboard/team.cljs -msgid "labels.viewer" -msgstr "Görüntüler" - -#: src/app/main/ui/comments.cljs -msgid "labels.write-new-comment" -msgstr "Yeni yorum yaz" - -#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs -msgid "media.loading" -msgstr "Resim yükleniyor…" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.add-shared-confirm.accept" -msgstr "Paylaşılmış Kütüphane olarak Ekle" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.add-shared-confirm.hint" -msgstr "" -"Paylaşılmış Kütüphane olarak eklenince, bu dosya kütüphanesindeki varlıklar " -"diğer dosyalarınızdan da ulaşılabilecek." - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.add-shared-confirm.message" -msgstr "Paylaşılmış Kütüphane olarak “%s” Ekle" - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.confirm-email" -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." - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.new-email" -msgstr "Yeni e-posta" - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.submit" -msgstr "E-postayı değiştir" - -#: src/app/main/ui/settings/change_email.cljs -msgid "modals.change-email.title" -msgstr "E-postanızı değiştirin" - -#: src/app/main/ui/settings/delete_account.cljs -msgid "modals.delete-account.cancel" -msgstr "İptal et ve hesabımı koru" - -#: src/app/main/ui/settings/delete_account.cljs -msgid "modals.delete-account.confirm" -msgstr "Evet, hesabımı sil" - -#: src/app/main/ui/settings/delete_account.cljs -msgid "modals.delete-account.info" -msgstr "Hesabını silerek tüm projelerini ve arşivlerini kaybedeceksin." - -#: src/app/main/ui/settings/delete_account.cljs -msgid "modals.delete-account.title" -msgstr "Hesabını silmek istediğinden emin misin?" - -#: src/app/main/ui/comments.cljs -msgid "modals.delete-comment-thread.accept" -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." - -#: src/app/main/ui/comments.cljs -msgid "modals.delete-comment-thread.title" -msgstr "Konuşmayı sil" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-confirm.accept" -msgstr "Dosyayı sil" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-confirm.message" -msgstr "Bu dosyayı silmek istediğinden emin misin?" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-confirm.title" -msgstr "Dosya siliniyor" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.accept" -msgstr "Dosyalar sil" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.message" -msgstr "%s dosyayı silmek istediğinden emin misin?" - -#: src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.delete-file-multi-confirm.title" -msgstr "%s dosyanın silinmesi" - msgid "modals.delete-font.message" msgstr "" "Bu fontu silmek istediğine emin misin? Bir dosyada kullanılıyorsa " @@ -1185,120 +917,174 @@ msgstr "" msgid "modals.delete-font.title" msgstr "Fontu sil" -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "modals.delete-page.body" -msgstr "Bu sayfayı silmek istediğinden emin misin?" +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.fonts" +msgstr "Fontlar - %s - Penpot" -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "modals.delete-page.title" -msgstr "Sayfayı sil" +msgid "labels.manage-fonts" +msgstr "Fontları yönet" -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.accept" -msgstr "Projeyi sil" +#: src/app/main/ui/static.cljs +msgid "labels.internal-error.main-message" +msgstr "İç Hata" -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.message" -msgstr "Bu projeyi silmek istediğinden emin misin?" +msgid "labels.font-providers" +msgstr "Font sağlayıcısı" -#: src/app/main/ui/dashboard/project_menu.cljs -msgid "modals.delete-project-confirm.title" -msgstr "Projeyi sil" +msgid "labels.custom-fonts" +msgstr "Özel Fontlar" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.accept" -msgstr "Takımı sil" +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.main-message" +msgstr "Hatalı Ağ Geçidi" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.message" +#: src/app/main/ui/static.cljs +msgid "labels.bad-gateway.desc-message" msgstr "" -"Bu takımı silmek istediğinden emin misin? Takımla ilişkili dosyalar ve " -"projeler kalıcı olarak silinecektir." +"Görünüşe göre biraz beklemen ve yeniden denemen gerekiyor; sunucularımızda " +"küçük bir bakım yapıyoruz." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.delete-team-confirm.title" -msgstr "Takımın silinmesi" +#: src/app/main/ui/handoff/right_sidebar.cljs +msgid "handoff.tabs.code" +msgstr "Kod" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.accept" -msgstr "Üyeyi sil" +msgid "handoff.attributes.typography.text-decoration.underline" +msgstr "Altı Çizili" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.message" -msgstr "Bu üyeyi takımdan silmek istediğinden emin misin?" +msgid "handoff.attributes.typography.text-decoration.strikethrough" +msgstr "Üstü Çizili" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.delete-team-member-confirm.title" -msgstr "Takım üyesini sil" +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.line-height" +msgstr "Satır Yüksekliği" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-member-confirm.accept" -msgstr "Davet gönder" +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.letter-spacing" +msgstr "Harf Aralığı" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.invite-member.title" -msgstr "Takıma katılma daveti gönder" +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-style" +msgstr "Font Stili" -msgid "modals.leave-and-reassign.forbiden" +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-size" +msgstr "Font Boyutu" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.font-family" +msgstr "Font Ailesi" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography" +msgstr "Tipografi" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke.width" +msgstr "Genişlik" + +msgid "handoff.attributes.stroke.style.solid" +msgstr "Düz" + +msgid "handoff.attributes.stroke.style.none" +msgstr "Hiçbiri" + +msgid "handoff.attributes.stroke.style.mixed" +msgstr "Karışık" + +msgid "handoff.attributes.stroke.style.dotted" +msgstr "Noktalı" + +#, permanent +msgid "handoff.attributes.stroke.alignment.center" +msgstr "Merkezi" + +#, permanent +msgid "handoff.attributes.stroke.alignment.outer" +msgstr "Dışarıda" + +#, permanent +msgid "handoff.attributes.stroke.alignment.inner" +msgstr "İçinde" + +#: src/app/main/ui/handoff/attributes/stroke.cljs +msgid "handoff.attributes.stroke" +msgstr "Çerçeve" + +#: 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.offset-x" +msgstr "X" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow" +msgstr "Gölge" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.width" +msgstr "Genişlik" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.top" +msgstr "Üst" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.rotation" +msgstr "Döndür" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.left" +msgstr "Sol" + +#: src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.height" +msgstr "Yükseklik" + +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.subtitle" msgstr "" -"Birisini takımın sahibi yapmadan takımı bırakamazsın. Takımı silmek " -"isteyebilirsin." +"Lütfen bir sorun, fikir ya da kuşkunuzu açıklayarak e-postanızın nedenini " +"belirtin. Ekibimizin bir üyesi en kısa sürede yanıt verecektir." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint1" -msgstr "%s sahibisiniz." +#: src/app/main/ui/settings/feedback.cljs +msgid "feedback.discussions-subtitle2" +msgstr "" +"Soru sorabilir ve soruları cevaplayabilir, açık uçlu tartışmalar yapabilir " +"ve projeyi etkileyen kararları takip edebilirsin." -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.hint2" -msgstr "Ayrılmadan önce terfi etmek için başka bir üye seçin" +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.typography" +msgstr "%s tipografi" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.promote-and-leave" -msgstr "Terfi et ve ayrıl" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography-tooltip" +msgstr "Tüm tipografileri ayır" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.select-memeber-to-promote" -msgstr "Terfi etmek için bir üye seçin" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.libraries.text.multiple-typography" +msgstr "Çoklu tipografiler" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-and-reassign.title" -msgstr "Terfi etmek için bir üye seçin" +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.small-thumbnails" +msgstr "Küçük önizlemeler" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-confirm.accept" -msgstr "Takımdan ayrıl" +#: src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.big-thumbnails" +msgstr "Büyük önizlemeler" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-confirm.message" -msgstr "Bu takımdan ayrılmak istediğinden emin misin?" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-variant-id" +msgstr "Çeşit" -#: src/app/main/ui/dashboard/sidebar.cljs -msgid "modals.leave-confirm.title" -msgstr "Takımdan ayrılmak" +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.typography" +msgstr "Tipografiler" -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.accept" -msgstr "Terfi et" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.message" -msgstr "Bu kullanıcıyı sahip olarak terfi etmek istediğinden emin misin?" - -#: src/app/main/ui/dashboard/team.cljs -msgid "modals.promote-owner-confirm.title" -msgstr "Sahip olarak terfi et" - -#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs -msgid "modals.remove-shared-confirm.accept" -msgstr "Paylaşılmış Kütüphane olarak 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.accept" -msgstr "Bileşeni güncelle" - -#: 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 "İptal" +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "title.viewer" +msgstr "%s - Görünüm modu - Penpot" #: src/app/main/ui/workspace/sidebar/options/menus/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "modals.update-remote-component.hint" @@ -1307,1109 +1093,740 @@ msgstr "" "diğer dosyalar etkilenebilir." #: 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 "Paylaşılmış bir kütüphanede bir bileşen güncelle" - -#: src/app/main/ui/dashboard/team.cljs -msgid "notifications.invitation-email-sent" -msgstr "Davet başarıyla iletildi" - -#: src/app/main/ui/settings/delete_account.cljs -msgid "notifications.profile-deletion-not-allowed" -msgstr "Profilini silemezsin. Önce takımlarını birine atamalsın." - -#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs -msgid "notifications.profile-saved" -msgstr "Profil başarıyla kaydedildi!" - -#: src/app/main/ui/settings/change_email.cljs -msgid "notifications.validation-email-sent" -msgstr "%s adresine doğrulama e-postası gönderildi. E-postalarınızı kontrol edin!" - -#: src/app/main/ui/auth/recovery.cljs -msgid "profile.recovery.go-to-login" -msgstr "Giriş yap" - -#: 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 "Karışık" - -#: 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 "Yazıtipi Sağlayıcıları - %s - Penpot" - -#: src/app/main/ui/dashboard/fonts.cljs -msgid "title.dashboard.fonts" -msgstr "Fontlar - %s - Penpot" - -#: src/app/main/ui/dashboard/projects.cljs -msgid "title.dashboard.projects" -msgstr "Projeler - %s - Penpot" - -#: src/app/main/ui/dashboard/search.cljs -msgid "title.dashboard.search" -msgstr "Ara - %s - Penpot" - -#: src/app/main/ui/dashboard/libraries.cljs -msgid "title.dashboard.shared-libraries" -msgstr "Paylaşılmış Kütüphaneler - %s - Penpot" - -#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs -msgid "title.default" -msgstr "Penpot * Takımlar için Özgür Tasarım" - -#: src/app/main/ui/settings/feedback.cljs -msgid "title.settings.feedback" -msgstr "Geri bildirimde bulun - Penpot" - -#: src/app/main/ui/settings/options.cljs -msgid "title.settings.options" -msgstr "Ayarlar - Penpot" - -#: src/app/main/ui/settings/password.cljs -msgid "title.settings.password" -msgstr "Parola - 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-members" -msgstr "Üyeler - %s - Penpot" - -#: src/app/main/ui/dashboard/team.cljs -msgid "title.team-settings" -msgstr "Ayarlar * %s - Penpot" - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "title.viewer" -msgstr "%s - Görünüm modu - Penpot" - -#: src/app/main/ui/workspace.cljs -msgid "title.workspace" -msgstr "%s - Penpot" - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "viewer.empty-state" -msgstr "Sayfada çerçeve bulunmuyor." - -#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs -msgid "viewer.frame-not-found" -msgstr "Çerçeve bulunmadı." - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.dont-show-interactions" -msgstr "Etkileşimleri gösterme" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.edit-page" -msgstr "Sayfayı düzenle" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.fullscreen" -msgstr "Tam Ekran" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.copy-link" -msgstr "Bağlantıyı kopyala" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.create-link" -msgstr "Bağlantı oluştur" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.placeholder" -msgstr "Paylaşım adresi burada görünecek" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.remove-link" -msgstr "Bağlantıyı kaldır" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.share.subtitle" -msgstr "Bağlantıya sahip herkes erişebilecek" - -#: 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 "Bağlantıyı paylaş" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.show-interactions" -msgstr "Etkileşimleri göster" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.show-interactions-on-click" -msgstr "Tıklamada etkileşimleri göster" - -#: src/app/main/ui/viewer/header.cljs -msgid "viewer.header.sitemap" -msgstr "Site haritası" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.hcenter" -msgstr "Yatay olarak ortaya hizala" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.hdistribute" -msgstr "Yatayda dağıt" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.hleft" -msgstr "Sola hizala" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.hright" -msgstr "Sağa hizala" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.vbottom" -msgstr "Alta hizala" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.vcenter" -msgstr "Dikey olarak ortaya hizala" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.vdistribute" -msgstr "Dikeyde dağıt" - -#: src/app/main/ui/workspace/sidebar/align.cljs -msgid "workspace.align.vtop" -msgstr "Üste hizala" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.assets" -msgstr "Varlıklar" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.box-filter-all" -msgstr "Tüm varlıklar" - -msgid "workspace.assets.box-filter-graphics" -msgstr "Grafikler" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.colors" -msgstr "Renkler" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.components" -msgstr "Bileşenler" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.create-group" -msgstr "Grup oluştur" - -#: 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 "Sil" - -#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.duplicate" -msgstr "Çoğalt" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -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" -msgstr "Grafikler" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.group" -msgstr "Grup" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.group-name" -msgstr "Grup adı" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.libraries" -msgstr "Kütüphaneler" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.not-found" -msgstr "Varlık bulunmadı" - -#: 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 "Yeniden adlandır" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.search" -msgstr "Varlık ara" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.selected-count" -msgid_plural "workspace.assets.selected-count" -msgstr[0] "Tek öge seçildi" -msgstr[1] "%s öge seçildi" - -#: src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.shared" -msgstr "PAYLAŞILDI" - -#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs -msgid "workspace.assets.typography" -msgstr "Tipografiler" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-id" -msgstr "Yazı tipi" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-size" -msgstr "Boyut" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.font-variant-id" -msgstr "Çeşit" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.go-to-edit" -msgstr "Düzenlemek için biçim kütüphane dosyasına gidin" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.letter-spacing" -msgstr "Harf Boşluğu" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.line-height" -msgstr "Satır Yüksekliği" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.assets.typography.text-transform" -msgstr "Metin Dönüşümü" - -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs -msgid "workspace.gradients.linear" -msgstr "Doğrusal degrade" - -#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs -msgid "workspace.gradients.radial" -msgstr "Dairesel degrade" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-dynamic-alignment" -msgstr "Dinamik hizalamayı kapat" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-scale-text" -msgstr "Metin ölçeklendirmeyi kapat" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.disable-snap-grid" -msgstr "Izgaraya tutturmayı kapat" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-dynamic-alignment" -msgstr "Dinamik hizalamayı etkinleştir" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-scale-text" -msgstr "Metin ölçeklendirmeyi etkinleştir" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.enable-snap-grid" -msgstr "Izgaraya tuttur" - -#: 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" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.hide-rules" -msgstr "Cetveli gizle" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.select-all" -msgstr "Tümünü seç" - -#: 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" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.menu.show-rules" -msgstr "Cetveli göster" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.save-error" -msgstr "Kaydetmede hata" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.saved" -msgstr "Kaydedildi" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.saving" -msgstr "Kaydediliyor" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.unsaved" -msgstr "Kaydedilmemiş değişiklikler" - -#: src/app/main/ui/workspace/header.cljs -msgid "workspace.header.viewer" -msgstr "Görünüm modu (%s)" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.add" -msgstr "Ekle" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.colors" -msgstr "%s renk" - -#: src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.big-thumbnails" -msgstr "Büyük önizlemeler" - -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.file-library" -msgstr "Dosya kütüphanesi" - -#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.recent-colors" -msgstr "Son renkler" - -#: src/app/main/ui/workspace/colorpicker.cljs -msgid "workspace.libraries.colors.save-color" -msgstr "Renk biçimini kaydet" - -#: src/app/main/ui/workspace/colorpalette.cljs -msgid "workspace.libraries.colors.small-thumbnails" -msgstr "Küçük önizlemeler" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.components" -msgstr "%s bileşen" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.file-library" -msgstr "Dosya kütüphanesi" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.graphics" -msgstr "%s grafik" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.in-this-file" -msgstr "BU DOSYADAKİ KÜTÜPHANELER" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.libraries" -msgstr "KÜTÜPHANELER" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.library" -msgstr "KÜTÜPHANE" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-libraries-need-sync" -msgstr "Güncelleme gerektiren Paylaşılmış Kütüphane bulunmuyor" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-matches-for" -msgstr "“%s“ için eşleşme bulunmadı" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.no-shared-libraries-available" -msgstr "Paylaşılmış Kütüphane bulunmuyor" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.search-shared-libraries" -msgstr "Paylaşılmış kütüphane ara" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.shared-libraries" -msgstr "PAYLAŞILMIŞ KÜTÜPHANELER" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.libraries.text.multiple-typography" -msgstr "Çoklu tipografiler" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.libraries.text.multiple-typography-tooltip" -msgstr "Tüm tipografileri ayır" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.typography" -msgstr "%s tipografi" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.update" -msgstr "Güncelle" - -#: src/app/main/ui/workspace/libraries.cljs -msgid "workspace.libraries.updates" -msgstr "GÜNCELLEMELER" - -msgid "workspace.library.all" -msgstr "Tüm kütüphaneler" - -msgid "workspace.library.libraries" -msgstr "Kütüphaneler" - -msgid "workspace.library.own" -msgstr "Kütüphanelerim" - -msgid "workspace.options.blur-options.background-blur" -msgstr "Arkaplan" - -msgid "workspace.options.blur-options.layer-blur" -msgstr "Katman" - -#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs -msgid "workspace.options.blur-options.title" -msgstr "Bulanıklık" - -#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs -msgid "workspace.options.component" -msgstr "Bileşen" - -#: src/app/main/ui/workspace/sidebar/options.cljs -msgid "workspace.options.design" -msgstr "Tasarım" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.export" -msgstr "Dışarı Aktar" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.export-object" -msgstr "Şekli dışarı aktar" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs -msgid "workspace.options.export.suffix" -msgstr "Son ek" - -#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs -msgid "workspace.options.exporting-object" -msgstr "Dışarı aktarılıyor…" - -#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs -msgid "workspace.options.fill" -msgstr "Doldur" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.auto" -msgstr "Otomatik" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.column" -msgstr "Sütunlar" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.columns" -msgstr "Sütunlar" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.height" -msgstr "Yükseklik" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.margin" -msgstr "Kenar Boşluğu" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.rows" -msgstr "Satırlar" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.set-default" -msgstr "Varsayılan olarak belirle" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.size" -msgstr "Boyut" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type" -msgstr "Tür" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.bottom" -msgstr "Alt" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.center" -msgstr "Orta" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.left" -msgstr "Sol" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.right" -msgstr "Sağ" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.stretch" -msgstr "Ger" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.type.top" -msgstr "Üst" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.use-default" -msgstr "Varsayılanı kullan" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.params.width" -msgstr "Genişlik" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.row" -msgstr "Satırlar" - -#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs -msgid "workspace.options.grid.square" -msgstr "Kare" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.color" -msgstr "Renk" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.color-burn" -msgstr "Renk yanması" - -#: 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 "Üst katman" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.blend-mode.saturation" -msgstr "Doygunluk" - -#: 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.title" -msgstr "Katman" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.title.group" -msgstr "Katman grubu" - -#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs -msgid "workspace.options.layer-options.title.multiple" -msgstr "Seçili katmanlar" +msgid "modals.update-remote-component.accept" +msgstr "Bileşeni güncelle" #: 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" - -#: 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 "Konum" - -#: src/app/main/ui/workspace/sidebar/options.cljs -msgid "workspace.options.prototype" -msgstr "Prototip" - -msgid "workspace.options.radius" -msgstr "Yarı çap" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.radius.all-corners" -msgstr "Tüm köşeler" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.radius.single-corners" -msgstr "Tek köşe" - -#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs -msgid "workspace.options.rotation" -msgstr "Döndür" - #: 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." - -#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs -msgid "workspace.options.select-artboard" -msgstr "Çalışma yüzeyi seç" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.drop-shadow" -msgstr "Kabartı gölgesi" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.inner-shadow" -msgstr "İç gölge" - -#: 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 "Yayılma" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title" -msgstr "Gölge" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title.group" -msgstr "Gölge grubu" - -#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs -msgid "workspace.options.shadow-options.title.multiple" -msgstr "Gölge seçimi" - -#: 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 "Boyut" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.center" -msgstr "Merkez" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dashed" -msgstr "Çizgili" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.dotted" -msgstr "Noktalı" - -#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs -msgid "workspace.options.stroke.mixed" -msgstr "Karışık" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-bottom" -msgstr "Alta hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-center" -msgstr "Ortaya hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-justify" -msgstr "İki yana yasla" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-left" -msgstr "Sola hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-middle" -msgstr "Merkeze hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-right" -msgstr "Sağa hizala" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.align-top" -msgstr "Üste hizala" - -msgid "workspace.options.text-options.decoration" -msgstr "Süsleme" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.direction-ltr" -msgstr "Soldan sağa" +"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/text.cljs msgid "workspace.options.text-options.direction-rtl" msgstr "Sağdan sola" -#: 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 "Otomatik yükseklik" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.grow-auto-width" -msgstr "Otomatik genişlik" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.grow-fixed" -msgstr "Sabit" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.letter-spacing" -msgstr "Harf Aralıkları" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.line-height" -msgstr "Satır yüksekliği" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.lowercase" -msgstr "Küçük harf" - -#: 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 "Hiçbiri" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.strikethrough" -msgstr "Üstü çizili" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.title" -msgstr "Metin" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.title-group" -msgstr "Grup metni" - -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -#, fuzzy -msgid "workspace.options.text-options.title-selection" -msgstr "Metin seçimi" +msgid "workspace.options.text-options.direction-ltr" +msgstr "Soldan sağa" #: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs msgid "workspace.options.text-options.titlecase" msgstr "İlk Harfi Büyük" -#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs -msgid "workspace.options.text-options.underline" -msgstr "Altı Çizili" - -#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs -msgid "workspace.options.text-options.uppercase" -msgstr "Büyük Harf" - -msgid "workspace.options.text-options.vertical-align" -msgstr "Düşey 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." - -msgid "workspace.path.actions.add-node" -msgstr "Düğüm ekle (%s)" - -msgid "workspace.path.actions.delete-node" -msgstr "Düğüm sil (%s)" - -msgid "workspace.path.actions.draw-nodes" -msgstr "Düğüm çiz (%s)" - -msgid "workspace.path.actions.join-nodes" -msgstr "Düğümleri birleştir (%s)" - -msgid "workspace.path.actions.make-corner" -msgstr "Köşeye (%s)" - -msgid "workspace.path.actions.make-curve" -msgstr "Eğriye (%s)" - -msgid "workspace.path.actions.merge-nodes" -msgstr "Düğümleri birleştir (%s)" - -msgid "workspace.path.actions.move-nodes" -msgstr "Düğümleri taşı (%s)" - -msgid "workspace.path.actions.separate-nodes" -msgstr "Düğümleri ayır (%s)" - -msgid "workspace.path.actions.snap-nodes" -msgstr "Düğümleri tuttur (%s)" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.back" -msgstr "Arkaya gönder" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.backward" -msgstr "En arkaya gönder" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.copy" -msgstr "Kopyala" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.create-component" -msgstr "Bileşen oluştur" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.cut" -msgstr "Kes" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.delete" -msgstr "Sil" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.duplicate" -msgstr "Çoğalt" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.edit" -msgstr "Düzenle" - -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.flip-horizontal" -msgstr "Yatay ters çevir" - #: src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.flip-vertical" msgstr "Dikey ters çevir" #: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.forward" -msgstr "Öne getir" +msgid "workspace.shape.menu.flip-horizontal" +msgstr "Yatay ters çevir" -#: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.front" -msgstr "En öne getir" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.go-to-edit" +msgstr "Düzenlemek için biçim kütüphane dosyasına gidin" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hcenter" +msgstr "Yatay olarak ortaya hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.stretch" +msgstr "Ger" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.right" +msgstr "Sağ" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.left" +msgstr "Sol" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.center" +msgstr "Orta" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.bottom" +msgstr "Alt" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type" +msgstr "Tür" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.size" +msgstr "Boyut" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.set-default" +msgstr "Varsayılan olarak belirle" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.rows" +msgstr "Satırlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.margin" +msgstr "Kenar Boşluğu" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.height" +msgstr "Yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.columns" +msgstr "Sütunlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.column" +msgstr "Sütunlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.auto" +msgstr "Otomatik" + +#: src/app/main/ui/workspace/sidebar/options/menus/fill.cljs +msgid "workspace.options.fill" +msgstr "Doldur" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.exporting-object" +msgstr "Dışarı aktarılıyor…" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs +msgid "workspace.options.export.suffix" +msgstr "Son ek" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export-object" +msgstr "Şekli dışarı aktar" + +#: src/app/main/ui/workspace/sidebar/options/menus/exports.cljs, src/app/main/ui/handoff/exports.cljs +msgid "workspace.options.export" +msgstr "Dışarı Aktar" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.design" +msgstr "Tasarım" + +#: src/app/main/ui/workspace/sidebar/options/menus/component.cljs +msgid "workspace.options.component" +msgstr "Bileşen" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-snap-grid" +msgstr "Izgaraya tutturmayı kapat" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-scale-text" +msgstr "Metin ölçeklendirmeyi kapat" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.disable-dynamic-alignment" +msgstr "Dinamik hizalamayı kapat" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.radial" +msgstr "Dairesel degrade" + +#: src/app/main/data/workspace/libraries.cljs, src/app/main/ui/components/color_bullet.cljs +msgid "workspace.gradients.linear" +msgstr "Doğrusal degrade" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.create-group" +msgstr "Grup oluştur" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.components" +msgstr "Bileşenler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.colors" +msgstr "Renkler" + +msgid "workspace.assets.box-filter-graphics" +msgstr "Grafikler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.box-filter-all" +msgstr "Tüm varlıklar" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.assets" +msgstr "Varlıklar" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vtop" +msgstr "Üste hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vdistribute" +msgstr "Dikeyde dağıt" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hdistribute" +msgstr "Yatayda dağıt" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.edit-page" +msgstr "Sayfayı düzenle" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.dont-show-interactions" +msgstr "Etkileşimleri gösterme" + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.frame-not-found" +msgstr "Çerçeve bulunmadı." + +#: src/app/main/ui/handoff.cljs, src/app/main/ui/viewer.cljs +msgid "viewer.empty-state" +msgstr "Sayfada çerçeve bulunmuyor." + +#: src/app/main/ui/workspace.cljs +msgid "title.workspace" +msgstr "%s - Penpot" + +#: src/app/main/ui/dashboard/fonts.cljs +msgid "title.dashboard.font-providers" +msgstr "Yazıtipi Sağlayıcıları - %s - Penpot" + +#: 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 "İptal" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.remove-shared-confirm.accept" +msgstr "Paylaşılmış Kütüphane olarak kaldır" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.title" +msgstr "Sahip olarak terfi et" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.message" +msgstr "Bu kullanıcıyı sahip olarak terfi etmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.title" +msgstr "Takımdan ayrılmak" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint2" +msgstr "Ayrılmadan önce terfi etmek için başka bir üye seçin" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.mixed" +msgstr "Karışık" + +#: 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.saturation" +msgstr "Doygunluk" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.overlay" +msgstr "Üst katman" + +#: 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.color-burn" +msgstr "Renk yanması" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.curve" +msgstr "Eğri (%s)" + +msgid "workspace.path.actions.snap-nodes" +msgstr "Düğümleri tuttur (%s)" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.strikethrough" +msgstr "Üstü çizili" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.line-height" +msgstr "Satır yüksekliği" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.letter-spacing" +msgstr "Harf Aralıkları" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-fixed" +msgstr "Sabit" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-width" +msgstr "Otomatik genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.grow-auto-height" +msgstr "Otomatik yükseklik" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.google" +msgstr "Google" + +msgid "workspace.options.text-options.decoration" +msgstr "Süsleme" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-top" +msgstr "Üste hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-right" +msgstr "Sağa hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-middle" +msgstr "Merkeze hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-left" +msgstr "Sola hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-justify" +msgstr "İki yana yasla" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-center" +msgstr "Ortaya hizala" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.align-bottom" +msgstr "Alta hizala" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.frame" +msgstr "Çalışma Yüzeyi (%s)" + +msgid "workspace.undo.entry.single.frame" +msgstr "çalışma yüzeyi" + +msgid "workspace.undo.entry.multiple.frame" +msgstr "çalışma yüzeyi" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.select-artboard" +msgstr "Çalışma yüzeyi seç" + +msgid "modals.leave-and-reassign.forbiden" +msgstr "" +"Birisini takımın sahibi yapmadan takımı bırakamazsın. Takımı silmek " +"isteyebilirsin." + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.title" +msgstr "Takım üyesini sil" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.message" +msgstr "Bu üyeyi takımdan silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.delete-team-member-confirm.accept" +msgstr "Üyeyi sil" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.title" +msgstr "Takımın silinmesi" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.message" +msgstr "" +"Bu takımı silmek istediğinden emin misin? Takımla ilişkili dosyalar ve " +"projeler kalıcı olarak silinecektir." + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.delete-team-confirm.accept" +msgstr "Takımı sil" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.title" +msgstr "Projeyi sil" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.message" +msgstr "Bu projeyi silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/project_menu.cljs +msgid "modals.delete-project-confirm.accept" +msgstr "Projeyi sil" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.title" +msgstr "Sayfayı sil" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "modals.delete-page.body" +msgstr "Bu sayfayı silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.title" +msgstr "%s dosyanın silinmesi" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.message" +msgstr "%s dosyayı silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-multi-confirm.accept" +msgstr "Dosyalar sil" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.title" +msgstr "Dosya siliniyor" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.message" +msgstr "Bu dosyayı silmek istediğinden emin misin?" + +#: src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.delete-file-confirm.accept" +msgstr "Dosyayı sil" + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.title" +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." + +#: src/app/main/ui/comments.cljs +msgid "modals.delete-comment-thread.accept" +msgstr "Konuşmayı sil" + +msgid "handoff.tabs.code.selected.frame" +msgstr "Çalışma yüzeyi" + +msgid "handoff.attributes.typography.text-transform.titlecase" +msgstr "İlk Harfleri Büyük" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-transform" +msgstr "Metin Dönüşümü" + +#: src/app/main/ui/handoff/attributes/text.cljs +msgid "handoff.attributes.typography.text-decoration" +msgstr "Metin Süsleme" + +#: src/app/main/ui/handoff/attributes/shadow.cljs +msgid "handoff.attributes.shadow.shorthand.spread" +msgstr "Y" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vcenter" +msgstr "Dikey olarak ortaya hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.vbottom" +msgstr "Alta hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hright" +msgstr "Sağa hizala" + +#: src/app/main/ui/workspace/sidebar/align.cljs +msgid "workspace.align.hleft" +msgstr "Sola hizala" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.sitemap" +msgstr "Site haritası" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions-on-click" +msgstr "Tıklamada etkileşimleri göster" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.show-interactions" +msgstr "Etkileşimleri göster" + +#: 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 "Bağlantıyı paylaş" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.subtitle" +msgstr "Bağlantıya sahip herkes erişebilecek" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.remove-link" +msgstr "Bağlantıyı kaldır" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.placeholder" +msgstr "Paylaşım adresi burada görünecek" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.create-link" +msgstr "Bağlantı oluştur" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.share.copy-link" +msgstr "Bağlantıyı kopyala" + +#: src/app/main/ui/viewer/header.cljs +msgid "viewer.header.fullscreen" +msgstr "Tam Ekran" + +#: src/app/main/ui/dashboard/files.cljs +msgid "title.dashboard.files" +msgstr "%s - 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 "Karışık" + +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Giriş yap" + +#: src/app/main/ui/settings/change_email.cljs +msgid "notifications.validation-email-sent" +msgstr "" +"%s adresine doğrulama e-postası gönderildi. E-postalarınızı kontrol edin!" + +#: src/app/main/ui/settings/profile.cljs, src/app/main/ui/settings/options.cljs +msgid "notifications.profile-saved" +msgstr "Profil başarıyla kaydedildi!" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "notifications.profile-deletion-not-allowed" +msgstr "Profilini silemezsin. Önce takımlarını birine atamalsın." + +#: src/app/main/ui/dashboard/team.cljs +msgid "notifications.invitation-email-sent" +msgstr "Davet başarıyla iletildi" + +#: 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 "Paylaşılmış bir kütüphanede bir bileşen güncelle" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-settings" +msgstr "Ayarlar * %s - Penpot" + +#: src/app/main/ui/dashboard/team.cljs +msgid "title.team-members" +msgstr "Üyeler - %s - Penpot" + +#: src/app/main/ui/settings/profile.cljs +msgid "title.settings.profile" +msgstr "Profil - Penpot" + +#: src/app/main/ui/settings/password.cljs +msgid "title.settings.password" +msgstr "Parola - Penpot" + +#: src/app/main/ui/settings/options.cljs +msgid "title.settings.options" +msgstr "Ayarlar - Penpot" + +#: src/app/main/ui/settings/feedback.cljs +msgid "title.settings.feedback" +msgstr "Geri bildirimde bulun - Penpot" + +#: src/app/main/ui/dashboard/search.cljs +msgid "title.dashboard.search" +msgstr "Ara - %s - Penpot" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-libraries-need-sync" +msgstr "Güncelleme gerektiren Paylaşılmış Kütüphane bulunmuyor" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.group" +msgstr "Gölge grubu" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title" +msgstr "Gölge" + +#: 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/component.cljs, src/app/main/ui/workspace/context_menu.cljs msgid "workspace.shape.menu.go-main" msgstr "Ana bileşen dosyasına git" #: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.group" -msgstr "Grup" +msgid "workspace.shape.menu.front" +msgstr "En öne getir" #: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.hide" -msgstr "Gizle" +msgid "workspace.shape.menu.forward" +msgstr "Öne getir" #: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.lock" -msgstr "Kilitle" - -#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.mask" -msgstr "Maskele" - -#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.paste" -msgstr "Yapıştır" +msgid "workspace.shape.menu.edit" +msgstr "Düzenle" #: src/app/main/ui/workspace/context_menu.cljs -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-main" -msgstr "Ana bileşeni göster" +msgid "workspace.shape.menu.duplicate" +msgstr "Çoğalt" #: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.ungroup" -msgstr "Grubu dağıt" +msgid "workspace.shape.menu.delete" +msgstr "Sil" #: src/app/main/ui/workspace/context_menu.cljs -msgid "workspace.shape.menu.unlock" -msgstr "Çöz" +msgid "workspace.shape.menu.cut" +msgstr "Kes" -#: 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 "Ana bileşeni güncelle" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.create-component" +msgstr "Bileşen oluştur" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.sidebar.history" -msgstr "Geçmiş (%s)" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.copy" +msgstr "Kopyala" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.sidebar.layers" -msgstr "Katmanlar (%s)" +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.backward" +msgstr "En arkaya gönder" -#: 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 Öznitelikleri İçeri Aktarıldı" +msgid "workspace.path.actions.move-nodes" +msgstr "Düğümleri taşı (%s)" -#: src/app/main/ui/workspace/sidebar/sitemap.cljs -msgid "workspace.sidebar.sitemap" -msgstr "Sayfalar" +msgid "workspace.path.actions.merge-nodes" +msgstr "Düğümleri birleştir (%s)" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.assets" -msgstr "Varlıklar(%s)" +msgid "workspace.path.actions.make-curve" +msgstr "Eğriye (%s)" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.color-palette" -msgstr "Renk Paketi (%s)" +msgid "workspace.path.actions.make-corner" +msgstr "Köşeye (%s)" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.comments" -msgstr "Yorumlar (%s)" +msgid "workspace.path.actions.join-nodes" +msgstr "Düğümleri birleştir (%s)" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.curve" -msgstr "Eğri (%s)" +msgid "workspace.path.actions.draw-nodes" +msgstr "Düğüm çiz (%s)" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.ellipse" -msgstr "Elips (%s)" +msgid "workspace.path.actions.delete-node" +msgstr "Düğüm sil (%s)" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.frame" -msgstr "Çalışma Yüzeyi (%s)" +msgid "workspace.options.text-options.vertical-align" +msgstr "Düşey hizalama" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.image" -msgstr "Resim (%s)" +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.uppercase" +msgstr "Büyük Harf" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.move" -msgstr "Taşı" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.underline" +msgstr "Altı Çizili" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.rect" -msgstr "Dikdörtgen (%s)" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +#, fuzzy +msgid "workspace.options.text-options.title-selection" +msgstr "Metin seçimi" -#: src/app/main/ui/workspace/left_toolbar.cljs -msgid "workspace.toolbar.text" -msgstr "Metin (%s)" +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title-group" +msgstr "Grup metni" + +#: src/app/main/ui/workspace/sidebar/options/menus/text.cljs +msgid "workspace.options.text-options.title" +msgstr "Metin" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.options.text-options.lowercase" +msgstr "Küçük harf" + +msgid "workspace.undo.entry.multiple.group" +msgstr "gruplar" + +msgid "workspace.undo.entry.multiple.curve" +msgstr "eğriler" + +msgid "workspace.undo.entry.multiple.component" +msgstr "bileşenler" + +msgid "workspace.undo.entry.multiple.color" +msgstr "renk varlıkları" + +msgid "workspace.undo.entry.multiple.circle" +msgstr "daireler" #: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.empty" -msgstr "Şu ana kadar değişim geçmişi yok" - -#: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.delete" -msgstr "%s silindi" +msgid "workspace.undo.entry.move" +msgstr "Nesneler taşındı" #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.modify" msgstr "%s düzenlendi" #: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.move" -msgstr "Nesneler taşındı" - -msgid "workspace.undo.entry.multiple.circle" -msgstr "daireler" - -msgid "workspace.undo.entry.multiple.color" -msgstr "renk varlıkları" - -msgid "workspace.undo.entry.multiple.component" -msgstr "bileşenler" - -msgid "workspace.undo.entry.multiple.curve" -msgstr "eğriler" - -msgid "workspace.undo.entry.multiple.frame" -msgstr "çalışma yüzeyi" - -msgid "workspace.undo.entry.multiple.group" -msgstr "gruplar" - -msgid "workspace.undo.entry.multiple.media" -msgstr "grafik varlığı" - -msgid "workspace.undo.entry.multiple.multiple" -msgstr "nesneler" - -msgid "workspace.undo.entry.multiple.page" -msgstr "sayfalar" - -msgid "workspace.undo.entry.multiple.rect" -msgstr "dikdörtgenler" - -msgid "workspace.undo.entry.multiple.shape" -msgstr "şekiller" - -msgid "workspace.undo.entry.multiple.text" -msgstr "metinler" +msgid "workspace.undo.entry.delete" +msgstr "%s silindi" #: src/app/main/ui/workspace/sidebar/history.cljs -msgid "workspace.undo.entry.new" -msgstr "Yeni %s" +msgid "workspace.undo.empty" +msgstr "Şu ana kadar değişim geçmişi yok" -msgid "workspace.undo.entry.single.circle" -msgstr "daire" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.text" +msgstr "Metin (%s)" -msgid "workspace.undo.entry.single.color" -msgstr "renk varlığı" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.move" +msgstr "Taşı" -msgid "workspace.undo.entry.single.component" -msgstr "bileşen" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.image" +msgstr "Resim (%s)" -msgid "workspace.undo.entry.single.curve" -msgstr "eğri" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.ellipse" +msgstr "Elips (%s)" -msgid "workspace.undo.entry.single.frame" -msgstr "çalışma yüzeyi" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.comments" +msgstr "Yorumlar (%s)" -msgid "workspace.undo.entry.single.group" -msgstr "grup" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.assets" +msgstr "Varlıklar(%s)" -msgid "workspace.undo.entry.single.image" -msgstr "resim" +#: src/app/main/ui/workspace/sidebar/sitemap.cljs +msgid "workspace.sidebar.sitemap" +msgstr "Sayfalar" -msgid "workspace.undo.entry.single.media" -msgstr "grafik varlığı" +#: 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 Öznitelikleri İçeri Aktarıldı" -msgid "workspace.undo.entry.single.multiple" -msgstr "nesne" +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.layers" +msgstr "Katmanlar (%s)" -msgid "workspace.undo.entry.single.page" -msgstr "sayfa" +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.there-are-updates" +msgstr "Paylaşılmış kütüphanelerde güncellemeler mevcut" -msgid "workspace.undo.entry.single.rect" -msgstr "dikdörtgen" - -msgid "workspace.undo.entry.single.shape" -msgstr "şekil" - -msgid "workspace.undo.entry.single.text" -msgstr "metin" +#: src/app/main/data/workspace/libraries.cljs +msgid "workspace.updates.dismiss" +msgstr "Gözardı et" #: src/app/main/ui/workspace/sidebar/history.cljs msgid "workspace.undo.entry.unknown" @@ -2419,14 +1836,596 @@ msgstr "%s üstündeki işlem" msgid "workspace.undo.title" msgstr "Geçmiş" -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.dismiss" -msgstr "Gözardı et" +msgid "workspace.undo.entry.single.text" +msgstr "metin" -#: src/app/main/data/workspace/libraries.cljs -msgid "workspace.updates.there-are-updates" -msgstr "Paylaşılmış kütüphanelerde güncellemeler mevcut" +msgid "workspace.undo.entry.single.shape" +msgstr "şekil" + +msgid "workspace.undo.entry.single.rect" +msgstr "dikdörtgen" + +msgid "workspace.undo.entry.single.page" +msgstr "sayfa" + +msgid "workspace.undo.entry.single.multiple" +msgstr "nesne" + +msgid "workspace.undo.entry.single.media" +msgstr "grafik varlığı" + +msgid "workspace.undo.entry.single.image" +msgstr "resim" + +msgid "workspace.undo.entry.single.group" +msgstr "grup" + +msgid "workspace.undo.entry.single.curve" +msgstr "eğri" + +msgid "workspace.undo.entry.single.component" +msgstr "bileşen" + +msgid "workspace.undo.entry.single.color" +msgstr "renk varlığı" + +msgid "workspace.undo.entry.single.circle" +msgstr "daire" + +#: src/app/main/ui/workspace/sidebar/history.cljs +msgid "workspace.undo.entry.new" +msgstr "Yeni %s" + +msgid "workspace.undo.entry.multiple.text" +msgstr "metinler" + +msgid "workspace.undo.entry.multiple.shape" +msgstr "şekiller" + +msgid "workspace.undo.entry.multiple.rect" +msgstr "dikdörtgenler" + +msgid "workspace.undo.entry.multiple.page" +msgstr "sayfalar" + +msgid "workspace.undo.entry.multiple.multiple" +msgstr "nesneler" + +msgid "workspace.undo.entry.multiple.media" +msgstr "grafik varlığı" #: src/app/main/data/workspace/libraries.cljs msgid "workspace.updates.update" -msgstr "Güncelle" \ No newline at end of file +msgstr "Güncelle" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dotted" +msgstr "Noktalı" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.dashed" +msgstr "Çizgili" + +#: src/app/main/ui/workspace/sidebar/options/menus/stroke.cljs +msgid "workspace.options.stroke.center" +msgstr "Merkez" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.blend-mode.color" +msgstr "Renk" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.square" +msgstr "Kare" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.row" +msgstr "Satırlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.width" +msgstr "Genişlik" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.use-default" +msgstr "Varsayılanı kullan" + +#: src/app/main/ui/workspace/sidebar/options/menus/frame_grid.cljs +msgid "workspace.options.grid.params.type.top" +msgstr "Üst" + +#: src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +msgid "workspace.options.blur-options.title" +msgstr "Bulanıklık" + +msgid "workspace.options.blur-options.layer-blur" +msgstr "Katman" + +msgid "workspace.options.blur-options.background-blur" +msgstr "Arkaplan" + +msgid "workspace.library.own" +msgstr "Kütüphanelerim" + +msgid "workspace.library.libraries" +msgstr "Kütüphaneler" + +msgid "workspace.library.all" +msgstr "Tüm kütüphaneler" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.updates" +msgstr "GÜNCELLEMELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.update" +msgstr "Güncelle" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.search-shared-libraries" +msgstr "Paylaşılmış kütüphane ara" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.shared-libraries" +msgstr "PAYLAŞILMIŞ KÜTÜPHANELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-shared-libraries-available" +msgstr "Paylaşılmış Kütüphane bulunmuyor" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.no-matches-for" +msgstr "“%s“ için eşleşme bulunmadı" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.components" +msgstr "%s bileşen" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.library" +msgstr "KÜTÜPHANE" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.libraries" +msgstr "KÜTÜPHANELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.in-this-file" +msgstr "BU DOSYADAKİ KÜTÜPHANELER" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.graphics" +msgstr "%s grafik" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.file-library" +msgstr "Dosya kütüphanesi" + +#: src/app/main/ui/workspace/colorpicker.cljs +msgid "workspace.libraries.colors.save-color" +msgstr "Renk biçimini kaydet" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.recent-colors" +msgstr "Son renkler" + +#: src/app/main/ui/workspace/colorpicker/libraries.cljs, src/app/main/ui/workspace/colorpalette.cljs +msgid "workspace.libraries.colors.file-library" +msgstr "Dosya kütüphanesi" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.colors" +msgstr "%s renk" + +#: src/app/main/ui/workspace/libraries.cljs +msgid "workspace.libraries.add" +msgstr "Ekle" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.viewer" +msgstr "Görünüm modu (%s)" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.unsaved" +msgstr "Kaydedilmemiş değişiklikler" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saving" +msgstr "Kaydediliyor" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.saved" +msgstr "Kaydedildi" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.save-error" +msgstr "Kaydetmede hata" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-rules" +msgstr "Cetveli göster" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.show-palette" +msgstr "Renk paletini 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-grid" +msgstr "Izgarayı 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.select-all" +msgstr "Tümünü seç" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-rules" +msgstr "Cetveli gizle" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-palette" +msgstr "Renk paletini 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-grid" +msgstr "Izgaraları gizle" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.hide-assets" +msgstr "Varlıkları gizle" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.text-transform" +msgstr "Metin Dönüşümü" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.line-height" +msgstr "Satır Yüksekliği" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.letter-spacing" +msgstr "Harf Boşluğu" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-size" +msgstr "Boyut" + +#: src/app/main/ui/workspace/sidebar/options/menus/typography.cljs +msgid "workspace.assets.typography.font-id" +msgstr "Yazı tipi" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.shared" +msgstr "PAYLAŞILDI" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.selected-count" +msgid_plural "workspace.assets.selected-count" +msgstr[0] "Tek öge seçildi" +msgstr[1] "%s öge seçildi" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.search" +msgstr "Varlık ara" + +#: 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 "Yeniden adlandır" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.not-found" +msgstr "Varlık bulunmadı" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.libraries" +msgstr "Kütüphaneler" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group-name" +msgstr "Grup adı" + +#: src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.group" +msgstr "Grup" + +#: src/app/main/ui/workspace/sidebar/assets.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.graphics" +msgstr "Grafikler" + +#: 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.edit" +msgstr "Düzenle" + +#: src/app/main/ui/workspace/sidebar/sitemap.cljs, src/app/main/ui/workspace/sidebar/assets.cljs +msgid "workspace.assets.duplicate" +msgstr "Çoğalt" + +#: 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 "Sil" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.promote-owner-confirm.accept" +msgstr "Terfi et" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.message" +msgstr "Bu takımdan ayrılmak istediğinden emin misin?" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-confirm.accept" +msgstr "Takımdan ayrıl" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.title" +msgstr "Terfi etmek için bir üye seçin" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.select-memeber-to-promote" +msgstr "Terfi etmek için bir üye seçin" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.promote-and-leave" +msgstr "Terfi et ve ayrıl" + +#: src/app/main/ui/dashboard/sidebar.cljs +msgid "modals.leave-and-reassign.hint1" +msgstr "%s sahibisiniz." + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member.title" +msgstr "Takıma katılma daveti gönder" + +#: src/app/main/ui/dashboard/team.cljs +msgid "modals.invite-member-confirm.accept" +msgstr "Davet gönder" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.info" +msgstr "Hesabını silerek tüm projelerini ve arşivlerini kaybedeceksin." + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.title" +msgstr "Hesabını silmek istediğinden emin misin?" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.confirm" +msgstr "Evet, hesabımı sil" + +#: src/app/main/ui/settings/delete_account.cljs +msgid "modals.delete-account.cancel" +msgstr "İptal et ve hesabımı koru" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.title" +msgstr "E-postanızı değiştirin" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.submit" +msgstr "E-postayı değiştir" + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.new-email" +msgstr "Yeni e-posta" + +#: 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." + +#: src/app/main/ui/settings/change_email.cljs +msgid "modals.change-email.confirm-email" +msgstr "Yeni e-postayı doğrulayın" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.message" +msgstr "Paylaşılmış Kütüphane olarak “%s” Ekle" + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.hint" +msgstr "" +"Paylaşılmış Kütüphane olarak eklenince, bu dosya kütüphanesindeki varlıklar " +"diğer dosyalarınızdan da ulaşılabilecek." + +#: src/app/main/ui/workspace/header.cljs, src/app/main/ui/dashboard/file_menu.cljs +msgid "modals.add-shared-confirm.accept" +msgstr "Paylaşılmış Kütüphane olarak Ekle" + +#: src/app/main/data/workspace/persistence.cljs, src/app/main/data/workspace/persistence.cljs, src/app/main/data/media.cljs +msgid "media.loading" +msgstr "Resim yükleniyor…" + +#: src/app/main/ui/comments.cljs +msgid "labels.write-new-comment" +msgstr "Yeni yorum yaz" + +#: src/app/main/ui/dashboard/team.cljs +msgid "labels.viewer" +msgstr "Görüntüler" + +msgid "labels.upload-custom-fonts" +msgstr "Özel yazı tipi yükle" + +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.update-team" +msgstr "Takımı güncelle" + +msgid "labels.icons" +msgstr "Simgeler" + +#: src/app/main/ui/handoff/attributes/layout.cljs, src/app/main/ui/handoff/attributes/layout.cljs +msgid "handoff.attributes.layout.radius" +msgstr "Yarı Çap" + +#: src/app/main/ui/auth/verify_token.cljs, src/app/main/ui/auth.cljs +msgid "title.default" +msgstr "Penpot * Takımlar için Özgür Tasarım" + +#: src/app/main/ui/dashboard/libraries.cljs +msgid "title.dashboard.shared-libraries" +msgstr "Paylaşılmış Kütüphaneler - %s - Penpot" + +#: src/app/main/ui/dashboard/projects.cljs +msgid "title.dashboard.projects" +msgstr "Projeler - %s - Penpot" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-snap-grid" +msgstr "Izgaraya tuttur" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-scale-text" +msgstr "Metin ölçeklendirmeyi etkinleştir" + +#: src/app/main/ui/workspace/header.cljs +msgid "workspace.header.menu.enable-dynamic-alignment" +msgstr "Dinamik hizalamayı etkinleştir" + +#: 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 "Boyut" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.title.multiple" +msgstr "Gölge seçimi" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.spread" +msgstr "Yayılma" + +#: 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.inner-shadow" +msgstr "İç gölge" + +#: src/app/main/ui/workspace/sidebar/options/menus/shadow.cljs +msgid "workspace.options.shadow-options.drop-shadow" +msgstr "Kabartı gölgesi" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.rotation" +msgstr "Döndür" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.single-corners" +msgstr "Tek köşe" + +#: src/app/main/ui/workspace/sidebar/options/menus/measures.cljs +msgid "workspace.options.radius.all-corners" +msgstr "Tüm köşeler" + +msgid "workspace.options.radius" +msgstr "Yarı çap" + +#: src/app/main/ui/workspace/sidebar/options.cljs +msgid "workspace.options.prototype" +msgstr "Prototip" + +#: 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 "Konum" + +#: src/app/main/ui/workspace/sidebar/options/menus/interactions.cljs +msgid "workspace.options.none" +msgstr "Hiç biri" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title.multiple" +msgstr "Seçili katmanlar" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title.group" +msgstr "Katman grubu" + +#: src/app/main/ui/workspace/sidebar/options/menus/layer.cljs +msgid "workspace.options.layer-options.title" +msgstr "Katman" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.sidebar.history" +msgstr "Geçmiş (%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.update-main" +msgstr "Ana bileşeni güncelle" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.unlock" +msgstr "Çöz" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.ungroup" +msgstr "Grubu dağıt" + +#: 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 "Ana bileşeni göster" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.show" +msgstr "Göster" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.paste" +msgstr "Yapıştır" + +#: src/app/main/ui/workspace/context_menu.cljs, src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.mask" +msgstr "Maskele" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.lock" +msgstr "Kilitle" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.hide" +msgstr "Gizle" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.group" +msgstr "Grup" + +#: src/app/main/ui/workspace/context_menu.cljs +msgid "workspace.shape.menu.back" +msgstr "Arkaya gönder" + +msgid "workspace.path.actions.separate-nodes" +msgstr "Düğümleri ayır (%s)" + +msgid "workspace.path.actions.add-node" +msgstr "Düğüm ekle (%s)" + +#: 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." + +#: 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 "Hiçbiri" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.rect" +msgstr "Dikdörtgen (%s)" + +#: src/app/main/ui/workspace/left_toolbar.cljs +msgid "workspace.toolbar.color-palette" +msgstr "Renk Paketi (%s)" From ad4115acc8d1b80cde95e58740c13b32195d2052 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Fri, 18 Jun 2021 15:06:05 +0200 Subject: [PATCH 128/204] :arrow_up: Update shadow-cljs dependency. --- frontend/deps.edn | 2 +- frontend/package.json | 2 +- frontend/yarn.lock | 32 +++++++++----------------------- 3 files changed, 11 insertions(+), 25 deletions(-) diff --git a/frontend/deps.edn b/frontend/deps.edn index 9bcb8861eb..7d8848998d 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -23,7 +23,7 @@ :dev {:extra-deps - {thheller/shadow-cljs {:mvn/version "2.12.6"}}} + {thheller/shadow-cljs {:mvn/version "2.14.5"}}} :shadow-cljs {:main-opts ["-m" "shadow.cljs.devtools.cli"]} diff --git a/frontend/package.json b/frontend/package.json index 9474d74375..5ab1d3d14c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -33,7 +33,7 @@ "postcss-clean": "^1.2.2", "rimraf": "^3.0.0", "sass": "^1.35.1", - "shadow-cljs": "^2.14.2" + "shadow-cljs": "2.14.5" }, "dependencies": { "date-fns": "^2.22.1", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index ba27ea7440..439290c174 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -307,11 +307,6 @@ async-foreach@^0.1.3: resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= -async-limiter@~1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" - integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== - async-settle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" @@ -4574,17 +4569,17 @@ shadow-cljs-jar@1.3.2: resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== -shadow-cljs@^2.14.2: - version "2.14.2" - resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.14.2.tgz#dba651ea124028064aea6fa9a390f257cb6eede4" - integrity sha512-ficaYfBAATzJ6OGt/GbIl393+cqLchzNkdTrM2PY4ttbsAOyBfWd39t+PZcYpCqemXjkgfBdZt9DJda7WaHJGA== +shadow-cljs@2.14.5: + version "2.14.5" + resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.14.5.tgz#f71d9bf1e292d452ecc76ec0a061fdc895af36b5" + integrity sha512-+tn4f8bSD2P006bsuVAmQeBRSzNM55o2oPT2SfDYDcUYVuqevxnxuDc2xza+c2UsA55bnYezP71CSkFDC56Osw== dependencies: node-libs-browser "^2.2.1" readline-sync "^1.4.7" shadow-cljs-jar "1.3.2" source-map-support "^0.4.15" which "^1.3.1" - ws "^3.0.0" + ws "^7.4.6" shebang-command@^1.2.0: version "1.2.0" @@ -5249,11 +5244,6 @@ ua-parser-js@^0.7.18, ua-parser-js@^0.7.28: resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.28.tgz#8ba04e653f35ce210239c64661685bf9121dec31" integrity sha512-6Gurc1n//gjp9eQNXjD9O3M/sMwVtN5S8Lv9bvOYBfKfDNiIIhqiyi01vMBO45u4zkDE420w/e0se7Vs+sIg+g== -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - integrity sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og== - unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -5554,14 +5544,10 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^3.0.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - integrity sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA== - dependencies: - async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" +ws@^7.4.6: + version "7.5.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.0.tgz#0033bafea031fb9df041b2026fc72a571ca44691" + integrity sha512-6ezXvzOZupqKj4jUqbQ9tXuJNo+BR2gU8fFRk3XCP3e0G6WT414u5ELe6Y0vtp7kmSJ3F7YWObSNr1ESsgi4vw== xmldom@0.1.27: version "0.1.27" From 71759386c571905ffd16270cd96f20e91ad24db0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 10 Jun 2021 11:04:11 +0200 Subject: [PATCH 129/204] :sparkles: Detect movements inside a component and not override them --- common/src/app/common/data.cljc | 7 +- common/src/app/common/pages/changes.cljc | 14 ++- .../src/app/main/data/workspace/changes.cljs | 16 +-- .../app/main/data/workspace/transforms.cljs | 118 +++++++++++++----- 4 files changed, 107 insertions(+), 48 deletions(-) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index df18739ea4..bdf35dc00c 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -457,13 +457,18 @@ kw (if (keyword? kw) (name kw) kw)] (keyword (str prefix kw)))) - (defn tap "Simpilar to the tap in rxjs but for plain collections" [f coll] (f coll) coll) +(defn tap-r + "Same but with args reversed, for -> threads" + [coll f] + (f coll) + coll) + (defn map-diff "Given two maps returns the diff of its attributes in a map where the keys will be the attributes that change and the values the previous diff --git a/common/src/app/common/pages/changes.cljc b/common/src/app/common/pages/changes.cljc index 47eec12ead..e3a5274f29 100644 --- a/common/src/app/common/pages/changes.cljc +++ b/common/src/app/common/pages/changes.cljc @@ -408,20 +408,22 @@ (let [attr (:attr op) val (:val op) ignore (:ignore-touched op) + ignore-geometry (:ignore-geometry op) shape-ref (:shape-ref shape) group (get component-sync-attrs attr) root-name? (and (= group :name-group) (:component-root? shape))] (cond-> shape + ;; Depending on the origin of the attribute change, we need or not to + ;; set the "touched" flag for the group the attribute belongs to. + ;; In some cases we need to ignore touched only if the attribute is + ;; geometric (position, width or transformation). (and shape-ref group (not ignore) (not= val (get shape attr)) (not root-name?) - ;; FIXME: it's difficult to tell if the geometry changes affect - ;; an individual shape inside the component, or are for - ;; the whole component (in which case we shouldn't set - ;; touched). For the moment we disable geometry touched - ;; except width and height that seems to work well. - (or (not= group :geometry-group) (#{:width :height} attr))) + (not (and ignore-geometry + (and (= group :geometry-group) + (not (#{:width :height} attr)))))) (-> (update :touched cph/set-touched-group group) (dissoc :remote-synced?)) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index f18a871049..5b5c3fbbba 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -34,26 +34,26 @@ (defn- generate-operation "Given an object old and new versions and an attribute will append into changes the set and undo operations" - [changes attr old new] + [changes attr old new ignore-geometry?] (let [old-val (get old attr) new-val (get new attr)] (if (= old-val new-val) changes (-> changes - (update :rops conj {:type :set :attr attr :val new-val}) + (update :rops conj {:type :set :attr attr :val new-val :ignore-geometry ignore-geometry?}) (update :uops conj {:type :set :attr attr :val old-val :ignore-touched true}))))) (defn- update-shape-changes "Calculate the changes and undos to be done when a function is applied to a single object" - [changes page-id objects update-fn attrs id] + [changes page-id objects update-fn attrs id ignore-geometry?] (let [old-obj (get objects id) new-obj (update-fn old-obj) attrs (or attrs (d/concat #{} (keys old-obj) (keys new-obj))) {rops :rops uops :uops} - (reduce #(generate-operation %1 %2 old-obj new-obj) + (reduce #(generate-operation %1 %2 old-obj new-obj ignore-geometry?) {:rops [] :uops []} attrs) @@ -72,8 +72,8 @@ (defn update-shapes ([ids f] (update-shapes ids f nil)) - ([ids f {:keys [reg-objects? save-undo? keys] - :or {reg-objects? false save-undo? true}}] + ([ids f {:keys [reg-objects? save-undo? keys ignore-tree] + :or {reg-objects? false save-undo? true attrs nil}}] (us/assert ::coll-of-uuid ids) (us/assert fn? f) @@ -90,7 +90,9 @@ ids (into [] (filter some?) ids) - changes (reduce #(update-shape-changes %1 page-id objects f keys %2) changes ids)] + changes (reduce + #(update-shape-changes %1 page-id objects f keys %2 (get ignore-tree %2)) + changes ids)] (when-not (empty? (:redo-changes changes)) (let [reg-objs {:type :reg-objects diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 6438317716..feedb357fb 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -421,27 +421,72 @@ ;; -- Apply modifiers +(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] + (let [root (cond + (:component-root? shape) + shape + + (nil? root) + (cp/get-root-shape shape objects) + + :else root) + + transformed-root (cond + (:component-root? transformed-shape) + transformed-shape + + (nil? transformed-root) + (cp/get-root-shape transformed-shape objects) + + :else transformed-root) + + shape-delta (when root + (gpt/point (- (:x shape) (:x root)) + (- (:y shape) (:y root)))) + + transformed-shape-delta (when transformed-root + (gpt/point (- (:x transformed-shape) (:x transformed-root)) + (- (:y transformed-shape) (:y transformed-root)))) + + ignore-geometry? (= shape-delta transformed-shape-delta)] + + [root transformed-root ignore-geometry?])) + (defn- set-modifiers-recursive - [modif-tree objects shape modifiers] + "Apply the modifiers to one shape, and the corresponding ones to all children, + depending on the child constraints. The modifiers are not directly applied to + the objects tree, but to a separated structure (modif-tree), that may be + merged later with the real objects." + [modif-tree objects shape modifiers root transformed-root] (let [children (->> (get shape :shapes []) (map #(get objects %))) - transformed-shape (when (seq children) ; <- don't calculate it if not needed - (gsh/transform-shape - (assoc shape :modifiers (select-keys modifiers - [:resize-origin - :resize-vector])))) + transformed-shape (gsh/transform-shape (assoc shape :modifiers modifiers)) + [root transformed-root ignore-geometry?] + (check-delta shape root transformed-shape transformed-root objects) + + modifiers (assoc modifiers :ignore-geometry? ignore-geometry?) + + resized-shape (when (seq children) ; <- don't calculate it if not needed + (gsh/transform-shape + (assoc shape :modifiers (select-keys modifiers + [:resize-origin + :resize-vector])))) set-child (fn [modif-tree child] (let [child-modifiers (gsh/calc-child-modifiers shape - transformed-shape + resized-shape child modifiers)] (set-modifiers-recursive modif-tree objects child - child-modifiers)))] - + child-modifiers + root + transformed-root)))] (reduce set-child (update-in modif-tree [(:id shape) :modifiers] #(merge % modifiers)) children))) @@ -460,11 +505,13 @@ ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false))))] (reduce (fn [state id] - (update state :workspace-modifiers - #(set-modifiers-recursive % - objects - (get objects id) - modifiers))) + (update state :workspace-modifiers + #(set-modifiers-recursive % + objects + (get objects id) + modifiers + nil + nil))) state ids)))))) @@ -526,28 +573,31 @@ state (if set-modifiers? (ptk/update (set-modifiers ids) state) state) - object-modifiers (get state :workspace-modifiers)] + object-modifiers (get state :workspace-modifiers) + + ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)] (rx/of (dwu/start-undo-transaction) (dch/update-shapes - ids-with-children - (fn [shape] - (-> shape - (merge (get object-modifiers (:id shape))) - (gsh/transform-shape))) - {:reg-objects? true - ;; 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 - :flip-x - :flip-y] - }) + ids-with-children + (fn [shape] + (-> shape + (merge (get object-modifiers (:id shape))) + (gsh/transform-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 + :flip-x + :flip-y] + }) (clear-local-transform) (dwu/commit-undo-transaction))))))) @@ -577,7 +627,7 @@ (fn [objects shape-id] (let [shape (get objects shape-id) modifier (gsh/resize-modifiers shape attr value)] - (set-modifiers-recursive objects objects shape modifier)))] + (set-modifiers-recursive objects objects shape modifier nil nil)))] (d/update-in-when state From d89a4a12180debb28edb7d8080a7d9c6d2db4f3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 16 Jun 2021 16:28:11 +0200 Subject: [PATCH 130/204] :bug: Fix constraints detection on rotated structures --- .../app/common/geom/shapes/transforms.cljc | 56 +++++++++++++++---- .../app/main/data/workspace/transforms.cljs | 6 -- 2 files changed, 45 insertions(+), 17 deletions(-) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index dc969739f6..a25238cbf3 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -368,14 +368,27 @@ (dissoc :modifiers))) shape)))) - (defn calc-child-modifiers - [parent transformed-parent child parent-modifiers] + "Given the modifiers to apply to the parent, calculate the corresponding + modifiers for the child, depending on the child constraints." + [parent child parent-modifiers] (let [parent-rect (:selrect parent) - transformed-parent-rect (:selrect transformed-parent) child-rect (:selrect child) - origin (:resize-origin parent-modifiers) + ; If the modifiers include a resize vector, apply it to the parent, + ; and then calculate the displacement of the side opposite to the + ; origin of the vector. + origin (-> (:resize-origin parent-modifiers) + ((d/nilf transform-point-center) + (gco/center-shape parent) + (:resize-transform-inverse parent-modifiers (gmt/matrix)))) + + transformed-parent-rect (-> parent-rect + (gpr/rect->points) + (transform-points + origin + (gmt/scale-matrix (get parent-modifiers :resize-vector (gpt/point 1 1)))) + (gpr/points->selrect)) orig-h (when origin (cond @@ -407,6 +420,8 @@ :else 0)) + ;; Calculate the modifiers in the horizontal and vertical directions + ;; depending on the constraints, and the origin of the resize vector. constraints-h (get child :constraints-h (spec/default-constraints-h child)) constraints-v (get child :constraints-v (spec/default-constraints-v child)) @@ -423,13 +438,19 @@ :leftright (cond (= orig-h :left) - {:resize-origin (gpt/point (:x1 child-rect) (:y1 child-rect)) + {:resize-origin (-> (gpt/point (:x1 child-rect) (:y1 child-rect)) + (transform-point-center + (gco/center-shape child) + (:transform child (gmt/matrix)))) :resize-vector (gpt/point (/ (+ (:width child-rect) delta-h) (:width child-rect)) 1)} (= orig-h :right) - {:resize-origin (gpt/point (:x2 child-rect) (:y1 child-rect)) + {:resize-origin (-> (gpt/point (:x2 child-rect) (:y1 child-rect)) + (transform-point-center + (gco/center-shape child) + (:transform child (gmt/matrix)))) :resize-vector (gpt/point (/ (- (:width child-rect) delta-h) (:width child-rect)) 1)} @@ -460,13 +481,19 @@ :topbottom (cond (= orig-v :top) - {:resize-origin (gpt/point (:x1 child-rect) (:y1 child-rect)) + {:resize-origin (-> (gpt/point (:x1 child-rect) (:y1 child-rect)) + (transform-point-center + (gco/center-shape child) + (:transform child (gmt/matrix)))) :resize-vector (gpt/point 1 (/ (+ (:height child-rect) delta-v) (:height child-rect)))} (= orig-v :bottom) - {:resize-origin (gpt/point (:x1 child-rect) (:y2 child-rect)) + {:resize-origin (-> (gpt/point (:x1 child-rect) (:y2 child-rect)) + (transform-point-center + (gco/center-shape child) + (:transform child (gmt/matrix)))) :resize-vector (gpt/point 1 (/ (- (:height child-rect) delta-v) (:height child-rect)))} @@ -487,18 +514,25 @@ (cond-> {} (or (:displacement modifiers-h) (:displacement modifiers-v)) (assoc :displacement (gmt/translate-matrix - (gpt/point (get (:displacement modifiers-h) :x 0) - (get (:displacement modifiers-v) :y 0)))) + (-> (gpt/point (get (:displacement modifiers-h) :x 0) + (get (:displacement modifiers-v) :y 0)) + (gpt/transform + (:resize-transform parent-modifiers (gmt/matrix)))))) (or (:resize-vector modifiers-h) (:resize-vector modifiers-v)) (assoc :resize-origin (or (:resize-origin modifiers-h) ;; we assume that the origin is the same (:resize-origin modifiers-v)) ;; in any direction :resize-vector (gpt/point (get (:resize-vector modifiers-h) :x 1) (get (:resize-vector modifiers-v) :y 1))) + (:displacement parent-modifiers) (update :displacement #(if (nil? %) (:displacement parent-modifiers) - (gmt/add-translate % (:displacement parent-modifiers))))))) + (gmt/add-translate % (:displacement parent-modifiers)))) + + (:resize-transform parent-modifiers) + (assoc :resize-transform (:resize-transform parent-modifiers) + :resize-transform-inverse (:resize-transform-inverse parent-modifiers))))) (defn update-group-viewbox "Updates the viewbox for groups imported from SVG's" diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index feedb357fb..b1b3918994 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -471,14 +471,8 @@ modifiers (assoc modifiers :ignore-geometry? ignore-geometry?) - resized-shape (when (seq children) ; <- don't calculate it if not needed - (gsh/transform-shape - (assoc shape :modifiers (select-keys modifiers - [:resize-origin - :resize-vector])))) set-child (fn [modif-tree child] (let [child-modifiers (gsh/calc-child-modifiers shape - resized-shape child modifiers)] (set-modifiers-recursive modif-tree From ac1d0a5502b8eadd3913af2c45d7be7b020fed7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 18 Jun 2021 11:54:51 +0200 Subject: [PATCH 131/204] :bug: Fix taking into account attrs filter in update-shapes --- frontend/src/app/main/data/workspace/changes.cljs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/data/workspace/changes.cljs b/frontend/src/app/main/data/workspace/changes.cljs index 5b5c3fbbba..4ca6c4f0b0 100644 --- a/frontend/src/app/main/data/workspace/changes.cljs +++ b/frontend/src/app/main/data/workspace/changes.cljs @@ -72,7 +72,7 @@ (defn update-shapes ([ids f] (update-shapes ids f nil)) - ([ids f {:keys [reg-objects? save-undo? keys ignore-tree] + ([ids f {:keys [reg-objects? save-undo? attrs ignore-tree] :or {reg-objects? false save-undo? true attrs nil}}] (us/assert ::coll-of-uuid ids) @@ -91,7 +91,7 @@ ids (into [] (filter some?) ids) changes (reduce - #(update-shape-changes %1 page-id objects f keys %2 (get ignore-tree %2)) + #(update-shape-changes %1 page-id objects f attrs %2 (get ignore-tree %2)) changes ids)] (when-not (empty? (:redo-changes changes)) From 4e909dc369b16b65fbbec6a6c1adcb962d05315e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 21 Jun 2021 14:38:28 +0200 Subject: [PATCH 132/204] :sparkles: Emit numeric input changes only if value actually changed --- CHANGES.md | 1 + frontend/src/app/main/ui/components/numeric_input.cljs | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 819c43acfb..5a8a851d60 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ ### :bug: Bugs fixed +- Process numeric input changes only if the value actually changed. - Remove unnecesary redirect from history when user goes to workspace from dashboard [Taiga #1820](https://tree.taiga.io/project/penpot/issue/1820). - Fix tooltip position on view application [Taiga #1819](https://tree.taiga.io/project/penpot/issue/1819). - Fix dashboard navigation on moving file to other team [Taiga #1817](https://tree.taiga.io/project/penpot/issue/1817). diff --git a/frontend/src/app/main/ui/components/numeric_input.cljs b/frontend/src/app/main/ui/components/numeric_input.cljs index b1cf44c10e..a6bbb02e3c 100644 --- a/frontend/src/app/main/ui/components/numeric_input.cljs +++ b/frontend/src/app/main/ui/components/numeric_input.cljs @@ -70,11 +70,11 @@ apply-value (mf/use-callback - (mf/deps on-change update-input) + (mf/deps on-change update-input value) (fn [new-value] - (when new-value - (when on-change - (on-change new-value)) + (when (and (some? new-value) (not= new-value value) (some? on-change)) + (on-change new-value)) + (when (some? new-value) (update-input new-value)))) set-delta From 4d0dcc587609ca08218f7daf0f58b3af0f22d8cb Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 17 Jun 2021 16:24:39 +0200 Subject: [PATCH 133/204] :sparkles: Process interactions on import --- common/src/app/common/file_builder.cljc | 25 +++ frontend/src/app/main/ui/shapes/export.cljs | 159 +++++++++++--------- frontend/src/app/util/import/parser.cljc | 27 +++- frontend/src/app/worker/import.cljs | 76 +++++++--- 4 files changed, 192 insertions(+), 95 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 17d2fef26b..3b0c077baf 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -291,6 +291,31 @@ (-> file (update :parent-stack pop))) +(defn add-interaction + [file action-type event-type from-id destination-id] + + (assert (some? (lookup-shape file from-id)) (str "Cannot locate shape with id " from-id)) + (assert (some? (lookup-shape file destination-id)) (str "Cannot locate shape with id " destination-id)) + + (let [interactions (->> (lookup-shape file from-id) + :interactions + (filterv #(or (not= (:action-type %) action-type) + (not= (:event-type %) event-type)))) + conj (fnil conj []) + interactions (-> interactions + (conj + {:action-type action-type + :event-type event-type + :destination destination-id}))] + (commit-change + file + {:type :mod-obj + :page-id (:current-page-id file) + :id from-id + + :operations + [{:type :set :attr :interactions :val interactions}]}))) + (defn generate-changes [file] (:changes file)) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index c7d60b3c2d..a27009fd19 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -91,20 +91,22 @@ [(str "penpot:" (d/name k)) v])] (into {} (map prefix-entry) m))) + (mf/defc export-grid-data [{:keys [grids]}] - [:> "penpot:grids" #js {} - (for [{:keys [type display params]} grids] - (let [props (->> (d/without-keys params [:color]) - (prefix-keys) - (clj->js))] - [:> "penpot:grid" - (-> props - (obj/set! "penpot:color" (get-in params [:color :color])) - (obj/set! "penpot:opacity" (get-in params [:color :opacity])) - (obj/set! "penpot:type" (d/name type)) - (cond-> (some? display) - (obj/set! "penpot:display" (str display))))]))]) + (when-not (empty? grids) + [:> "penpot:grids" #js {} + (for [{:keys [type display params]} grids] + (let [props (->> (d/without-keys params [:color]) + (prefix-keys) + (clj->js))] + [:> "penpot:grid" + (-> props + (obj/set! "penpot:color" (get-in params [:color :color])) + (obj/set! "penpot:opacity" (get-in params [:color :opacity])) + (obj/set! "penpot:type" (d/name type)) + (cond-> (some? display) + (obj/set! "penpot:display" (str display))))]))])) (mf/defc export-page [{:keys [options]}] @@ -117,62 +119,85 @@ [:> "penpot:page" #js {} [:& export-grid-data {:grids grids}]])))) +(mf/defc export-shadow-data + [{:keys [shadow]}] + (for [{:keys [style hidden color offset-x offset-y blur spread]} shadow] + [:> "penpot:shadow" + #js {:penpot:shadow-type (d/name style) + :penpot:hidden (str hidden) + :penpot:color (str (:color color)) + :penpot:opacity (str (:opacity color)) + :penpot:offset-x (str offset-x) + :penpot:offset-y (str offset-y) + :penpot:blur (str blur) + :penpot:spread (str spread)}])) + +(mf/defc export-blur-data [{:keys [blur]}] + (when (some? blur) + (let [{:keys [type hidden value]} blur] + [:> "penpot:blur" + #js {:penpot:blur-type (d/name type) + :penpot:hidden (str hidden) + :penpot:value (str value)}]))) + +(mf/defc export-exports-data [{:keys [exports]}] + (for [{:keys [scale suffix type]} exports] + [:> "penpot:export" + #js {:penpot:type (d/name type) + :penpot:suffix suffix + :penpot:scale (str scale)}])) + +(mf/defc export-svg-data [shape] + [:* + (when (contains? shape :svg-attrs) + (let [svg-transform (get shape :svg-transform) + svg-attrs (->> shape :svg-attrs keys (mapv d/name) (str/join ",") ) + svg-defs (->> shape :svg-defs keys (mapv d/name) (str/join ","))] + [:> "penpot:svg-import" + #js {:penpot:svg-attrs (when-not (empty? svg-attrs) svg-attrs) + :penpot:svg-defs (when-not (empty? svg-defs) svg-defs) + :penpot:svg-transform (when svg-transform (str svg-transform)) + :penpot:svg-viewbox-x (get-in shape [:svg-viewbox :x]) + :penpot:svg-viewbox-y (get-in shape [:svg-viewbox :y]) + :penpot:svg-viewbox-width (get-in shape [:svg-viewbox :width]) + :penpot:svg-viewbox-height (get-in shape [:svg-viewbox :height])} + (for [[def-id def-xml] (:svg-defs shape)] + [:> "penpot:svg-def" #js {:def-id def-id} + [:& render-xml {:xml def-xml}]])])) + + (when (= (:type shape) :svg-raw) + (let [props + (-> (obj/new) + (obj/set! "penpot:x" (:x shape)) + (obj/set! "penpot:y" (:y shape)) + (obj/set! "penpot:width" (:width shape)) + (obj/set! "penpot:height" (:height shape)) + (obj/set! "penpot:tag" (-> (get-in shape [:content :tag]) d/name)) + (obj/merge! (-> (get-in shape [:content :attrs]) + (clj->js))))] + [:> "penpot:svg-content" props + (for [leaf (->> shape :content :content (filter string?))] + [:> "penpot:svg-child" {} leaf])]))]) + +(mf/defc export-interactions-data + [{:keys [interactions]}] + (when-not (empty? interactions) + [:> "penpot:interactions" #js {} + (for [{:keys [action-type destination event-type]} interactions] + [:> "penpot:interaction" + #js {:penpot:action-type (d/name action-type) + :penpot:destination (str destination) + :penpot:event-type (d/name event-type)}])])) + (mf/defc export-data [{:keys [shape]}] - (let [props (-> (obj/new) - (add-data shape))] + (let [props (-> (obj/new) (add-data shape)) + frame? (= (:type shape) :frame)] [:> "penpot:shape" props - (for [{:keys [style hidden color offset-x offset-y blur spread]} (:shadow shape)] - [:> "penpot:shadow" #js {:penpot:shadow-type (d/name style) - :penpot:hidden (str hidden) - :penpot:color (str (:color color)) - :penpot:opacity (str (:opacity color)) - :penpot:offset-x (str offset-x) - :penpot:offset-y (str offset-y) - :penpot:blur (str blur) - :penpot:spread (str spread)}]) - - (when (some? (:blur shape)) - (let [{:keys [type hidden value]} (:blur shape)] - [:> "penpot:blur" #js {:penpot:blur-type (d/name type) - :penpot:hidden (str hidden) - :penpot:value (str value)}])) - - (for [{:keys [scale suffix type]} (:exports shape)] - [:> "penpot:export" #js {:penpot:type (d/name type) - :penpot:suffix suffix - :penpot:scale (str scale)}]) - - (when (contains? shape :svg-attrs) - (let [svg-transform (get shape :svg-transform) - svg-attrs (->> shape :svg-attrs keys (mapv d/name) (str/join ",") ) - svg-defs (->> shape :svg-defs keys (mapv d/name) (str/join ","))] - [:> "penpot:svg-import" #js {:penpot:svg-attrs (when-not (empty? svg-attrs) svg-attrs) - :penpot:svg-defs (when-not (empty? svg-defs) svg-defs) - :penpot:svg-transform (when svg-transform (str svg-transform)) - :penpot:svg-viewbox-x (get-in shape [:svg-viewbox :x]) - :penpot:svg-viewbox-y (get-in shape [:svg-viewbox :y]) - :penpot:svg-viewbox-width (get-in shape [:svg-viewbox :width]) - :penpot:svg-viewbox-height (get-in shape [:svg-viewbox :height])} - (for [[def-id def-xml] (:svg-defs shape)] - [:> "penpot:svg-def" #js {:def-id def-id} - [:& render-xml {:xml def-xml}]])])) - - (when (= (:type shape) :svg-raw) - (let [props (-> (obj/new) - (obj/set! "penpot:x" (:x shape)) - (obj/set! "penpot:y" (:y shape)) - (obj/set! "penpot:width" (:width shape)) - (obj/set! "penpot:height" (:height shape)) - (obj/set! "penpot:tag" (-> (get-in shape [:content :tag]) d/name)) - (obj/merge! (-> (get-in shape [:content :attrs]) - (clj->js))))] - [:> "penpot:svg-content" props - (for [leaf (->> shape :content :content (filter string?))] - [:> "penpot:svg-child" {} leaf])])) - - - (when (and (= (:type shape) :frame) - (seq (:grids shape))) - [:& export-grid-data {:grids (:grids shape)}])])) + [:& export-shadow-data shape] + [:& export-blur-data shape] + [:& export-exports-data shape] + [:& export-svg-data shape] + [:& export-interactions-data shape] + [:& export-grid-data shape]])) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljc index 1ffc490d46..84af3163bc 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljc @@ -15,6 +15,12 @@ [app.util.path.parser :as upp] [cuerdas.core :as str])) +(def url-regex + #"url\(#([^\)]*)\)") + +(def uuid-regex + #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}") + (defn valid? [root] (contains? (:attrs root) :xmlns:penpot)) @@ -41,7 +47,7 @@ (defn find-all-nodes [node tag] (when (some? node) - (->> node :content (filterv #(= (:tag %) :defs))))) + (->> node :content (filterv #(= (:tag %) tag))))) (defn get-data ([node] @@ -65,6 +71,11 @@ (or (close? node) (some? (get-data node)))) +(defn get-id + [node] + (when-let [id (re-find uuid-regex (get-in node [:attrs :id]))] + (uuid/uuid id))) + (defn str->bool [val] (when (some? val) (= val "true"))) @@ -193,9 +204,6 @@ (assoc :content content) (assoc :center center)))) -(def url-regex #"url\(#([^\)]*)\)") - - (defn parse-stops [gradient-node] (->> gradient-node @@ -483,7 +491,7 @@ (defn remove-prefix [s] (cond-> s (string? s) - (str/replace #"\w{8}-\w{4}-\w{4}-\w{4}-\w{12}-" ""))) + (str/replace (re-pattern (str uuid-regex "-")) ""))) (defn get-svg-attrs [svg-data svg-attrs] @@ -640,3 +648,12 @@ (not (empty? grids)) (assoc-in [:options :saved-grids] grids)))) + +(defn parse-interactions + [node] + (let [interactions-node (get-data node :penpot:interactions)] + (->> (find-all-nodes interactions-node :penpot:interaction) + (mapv (fn [node] + {:destination (get-meta node :destination uuid/uuid) + :action-type (get-meta node :action-type keyword) + :event-type (get-meta node :event-type keyword)}))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index f9ee680fa3..e54098b432 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -78,36 +78,65 @@ (defn add-shape-file [file node] - (let [type (cip/get-type node) - close? (cip/close? node) - data (cip/parse-data type node)] - + (let [type (cip/get-type node) + close? (cip/close? node)] (if close? (case type - :frame - (fb/close-artboard file) + :frame (fb/close-artboard file) + :group (fb/close-group file) + :svg-raw (fb/close-svg-raw file) + #_default file) - :group - (fb/close-group file) + (let [data (cip/parse-data type node) + old-id (cip/get-id node) + interactions (cip/parse-interactions node) - :svg-raw - (fb/close-svg-raw file) + file (case type + :frame (fb/add-artboard file data) + :group (fb/add-group file data) + :rect (fb/create-rect file data) + :circle (fb/create-circle file data) + :path (fb/create-path file data) + :text (fb/create-text file data) + :image (fb/create-image file data) + :svg-raw (fb/create-svg-raw file data) + #_default file)] - ;; default - file) + (assert (some? old-id) "ID not found") - (case type - :frame (fb/add-artboard file data) - :group (fb/add-group file data) - :rect (fb/create-rect file data) - :circle (fb/create-circle file data) - :path (fb/create-path file data) - :text (fb/create-text file data) - :image (fb/create-image file data) - :svg-raw (fb/create-svg-raw file data) + ;; We store this data for post-processing after every shape has been + ;; added + (cond-> file + (some? (:last-id file)) + (assoc-in [:id-mapping old-id] (:last-id file)) - ;; default - file)))) + (not (empty? interactions)) + (assoc-in [:interactions old-id] interactions)))))) + +(defn post-process-file + [file] + + (letfn [(add-interaction + [id file {:keys [action-type event-type destination] :as interaction}] + (fb/add-interaction file action-type event-type id destination)) + + (add-interactions + [file [old-id interactions]] + (let [id (get-in file [:id-mapping old-id])] + (->> interactions + (mapv (fn [interaction] + (let [id (get-in file [:id-mapping (:destination interaction)])] + (assoc interaction :destination id)))) + (reduce + (partial add-interaction id) file)))) + + (process-interactions + [file] + (reduce add-interactions file (:interactions file)))] + + (-> file + (process-interactions) + (dissoc :id-mapping :interactions)))) (defn merge-reduce [f seed ob] (->> (rx/concat @@ -145,6 +174,7 @@ (rx/filter cip/shape?) (rx/mapcat (partial resolve-images file-id)) (rx/reduce add-shape-file (fb/add-page file page-data)) + (rx/map post-process-file) (rx/map fb/close-page))) (rx/empty))) From fbf1c10077bf86e51d22ef5447323b990cf3c8c2 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 18 Jun 2021 14:49:14 +0200 Subject: [PATCH 134/204] :sparkles: Export library data (images, typographies, colors) --- common/src/app/common/data.cljc | 4 + frontend/src/app/worker/export.cljs | 126 +++++++++++++++++++++++----- frontend/src/app/worker/import.cljs | 7 +- 3 files changed, 113 insertions(+), 24 deletions(-) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index bdf35dc00c..39abab9d4e 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -556,3 +556,7 @@ :else m))) + +(defn not-empty? + [coll] + (boolean (seq coll))) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 5c2d485a00..abd5e27534 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -6,36 +6,57 @@ (ns app.worker.export (:require + [app.common.data :as d] + [app.config :as cfg] [app.main.render :as r] [app.main.repo :as rp] [app.util.dom :as dom] - [app.util.zip :as uz] + [app.util.http :as http] [app.util.json :as json] + [app.util.zip :as uz] [app.worker.impl :as impl] - [beicon.core :as rx])) + [beicon.core :as rx] + [cuerdas.core :as str])) (defn create-manifest "Creates a manifest entry for the given files" - [team-id files] + [team-id file-id files] (letfn [(format-page [manifest page] (-> manifest (assoc (str (:id page)) {:name (:name page)}))) (format-file [manifest file] - (let [name (:name file) - pages (->> (get-in file [:data :pages]) (mapv str)) - index (->> (get-in file [:data :pages-index]) (vals) - (reduce format-page {}))] + (let [name (:name file) + is-shared (:is-shared file) + pages (->> (get-in file [:data :pages]) + (mapv str)) + index (->> (get-in file [:data :pages-index]) + (vals) + (reduce format-page {}))] (-> manifest (assoc (str (:id file)) - {:name name - :pages pages - :pagesIndex index}))))] + {:name name + :shared is-shared + :pages pages + :pagesIndex index + :hasComponents (d/not-empty? (get-in file [:data :components])) + :hasImages (d/not-empty? (get-in file [:data :media])) + :hasColors (d/not-empty? (get-in file [:data :colors])) + :hasTypographies (d/not-empty? (get-in file [:data :typographies]))}))))] (let [manifest {:teamId (str team-id) + :fileId (str file-id) :files (->> (vals files) (reduce format-file {}))}] (json/encode manifest)))) +(defn process-pages [file] + (let [pages (get-in file [:data :pages]) + pages-index (get-in file [:data :pages-index])] + (->> pages + (map #(hash-map + :file-id (:id file) + :data (get pages-index %)))))) + (defn get-page-data [{file-id :file-id {:keys [id name] :as data} :data}] (->> (r/render-page data) @@ -45,30 +66,45 @@ :file-id file-id :markup markup})))) -(defn process-pages [file] - (let [pages (get-in file [:data :pages]) - pages-index (get-in file [:data :pages-index])] - (->> pages - (map #(hash-map - :file-id (:id file) - :data (get pages-index %)))))) - (defn collect-page [{:keys [id file-id markup] :as page}] [(str file-id "/" id ".svg") markup]) +(defn collect-color + [result color] + (-> result + (assoc (str (:id color)) + (->> (select-keys color [:name :color :opacity :gradient]) + (d/deep-mapm + (fn [[k v]] + [(-> k str/camel) v])))))) + +(def ^:const typography-keys [:name :font-family :font-id :font-size + :font-style :font-variant-id :font-weight + :letter-spacing :line-height :text-transform]) +(defn collect-typography + [result typography] + (-> result + (assoc (str (:id typography)) + (->> (select-keys typography typography-keys) + (d/deep-mapm + (fn [[k v]] + [(-> k str/camel) v])))))) (defn export-file [team-id file-id] (let [files-stream - (->> (rp/query :file {:id file-id}) + (->> (rx/merge (rp/query :file {:id file-id}) + (->> (rp/query :file-libraries {:file-id file-id}) + (rx/flat-map identity) + (rx/map #(assoc % :is-shared true)))) (rx/reduce #(assoc %1 (:id %2) %2) {}) (rx/share)) manifest-stream (->> files-stream - (rx/map #(create-manifest team-id %)) + (rx/map #(create-manifest team-id file-id %)) (rx/map #(vector "manifest.json" %))) render-stream @@ -79,6 +115,48 @@ (rx/flat-map 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 (fn [[file-id colors]] + (let [markup + (->> (vals colors) + (reduce collect-color {}) + (json/encode))] + [(str file-id "/colors.json") markup])))) + + typographies-stream + (->> files-stream + (rx/flat-map vals) + (rx/map #(vector (:id %) (get-in % [:data :typographies]))) + (rx/filter #(d/not-empty? (second %))) + (rx/map (fn [[file-id typographies]] + (let [markup + (->> (vals typographies) + (reduce collect-typography {}) + (json/encode))] + [(str file-id "/typographies.json") markup])))) + + 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 + (fn [[file-id media]] + (->> media vals (mapv #(assoc % :file-id file-id))))) + + (rx/flat-map + (fn [media] + (->> (http/send! + {:uri (cfg/resolve-file-media media) + :response-type :blob + :method :get}) + (rx/map :body) + (rx/map #(vector (str file-id "/images/" (:id media)) %)))))) + pages-stream (->> render-stream (rx/map collect-page))] @@ -90,7 +168,13 @@ :file file-id :data (str "Render " (:file-name %) " - " (:name %))))) - (->> (rx/merge manifest-stream pages-stream) + (->> (rx/merge + manifest-stream + pages-stream + #_components-stream + media-stream + colors-stream + typographies-stream) (rx/reduce conj []) (rx/with-latest-from files-stream) (rx/flat-map (fn [[data files]] diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index e54098b432..0be1118773 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -25,12 +25,13 @@ (defn create-file "Create a new file on the back-end" - [project-id name] + [project-id file-desc] (let [file-id (uuid/next)] (rp/mutation :create-temp-file {:id file-id - :name name + :name (:name file-desc) + :is-shared (:shared file-desc) :project-id project-id :data (-> cp/empty-file-data (assoc :id file-id))}))) @@ -203,7 +204,7 @@ (rx/flat-map (comp :files json/decode :content)) (rx/flat-map (fn [[file-id file-desc]] - (->> (create-file project-id (:name file-desc)) + (->> (create-file project-id file-desc) (rx/flat-map #(process-file % file-id file-desc zip-file))))))) (defmethod impl/handler :import-file From 234a698538e682f6c5170d1a8941bb243d88267b Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 18 Jun 2021 15:29:07 +0200 Subject: [PATCH 135/204] :sparkle: Fix linter warnings --- .clj-kondo/config.edn | 9 +--- frontend/src/app/libs/file_builder.cljs | 30 ++++++------ .../app/main/data/workspace/path/edition.cljs | 4 +- .../main/data/workspace/path/selection.cljs | 36 +------------- .../app/main/data/workspace/transforms.cljs | 49 ++++++------------- frontend/src/app/main/ui/shapes/export.cljs | 3 +- .../main/ui/workspace/viewport/selection.cljs | 8 +-- .../util/import/{parser.cljc => parser.cljs} | 26 +++++----- frontend/src/app/worker/export.cljs | 2 +- frontend/src/app/worker/import.cljs | 3 +- 10 files changed, 55 insertions(+), 115 deletions(-) rename frontend/src/app/util/import/{parser.cljc => parser.cljs} (97%) diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index ec3b66f776..cbe153b340 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -17,14 +17,7 @@ {:exclude-files ["data_readers.clj" "app/util/perf.cljs" - "app/common/exceptions.cljc" - "app/util/import/.*" - "app/worker/export.cljs" - "app/worker/import.cljs" - "app/libs/.*" - "app/main/data/workspace/path/selection.cljs" - "app/main/data/workspace/transforms.cljs" - ]} + "app/common/exceptions.cljc"]} :linters {:unsorted-required-namespaces diff --git a/frontend/src/app/libs/file_builder.cljs b/frontend/src/app/libs/file_builder.cljs index 0560b919a6..1dbee9c552 100644 --- a/frontend/src/app/libs/file_builder.cljs +++ b/frontend/src/app/libs/file_builder.cljs @@ -25,59 +25,59 @@ (deftype File [^:mutable file] Object - (addPage [self name] + (addPage [_ name] (set! file (fb/add-page file {:name name})) (str (:current-page-id file))) - (addPage [self name options] + (addPage [_ name options] (set! file (fb/add-page file {:name name :options options})) (str (:current-page-id file))) - (closePage [self] + (closePage [_] (set! file (fb/close-page file))) - (addArtboard [self data] + (addArtboard [_ data] (set! file (fb/add-artboard file (parse-data data))) (str (:last-id file))) - (closeArtboard [self data] + (closeArtboard [_] (set! file (fb/close-artboard file))) - (addGroup [self data] + (addGroup [_ data] (set! file (fb/add-group file (parse-data data))) (str (:last-id file))) - (closeGroup [self] + (closeGroup [_] (set! file (fb/close-group file))) - (createRect [self data] + (createRect [_ data] (set! file (fb/create-rect file (parse-data data))) (str (:last-id file))) - (createCircle [self data] + (createCircle [_ data] (set! file (fb/create-circle file (parse-data data))) (str (:last-id file))) - (createPath [self data] + (createPath [_ data] (set! file (fb/create-path file (parse-data data))) (str (:last-id file))) - (createText [self data] + (createText [_ data] (set! file (fb/create-text file (parse-data data))) (str (:last-id file))) - (createImage [self data] + (createImage [_ data] (set! file (fb/create-image file (parse-data data))) (str (:last-id file))) - (createSVG [self data] + (createSVG [_ data] (set! file (fb/create-svg-raw file (parse-data data))) (str (:last-id file))) - (closeSVG [self] + (closeSVG [_] (set! file (fb/close-svg-raw file))) - (asMap [self] + (asMap [_] (clj->js file))) (defn create-file-export [^string name] diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 2005ed10cd..94bd92f587 100644 --- a/frontend/src/app/main/data/workspace/path/edition.cljs +++ b/frontend/src/app/main/data/workspace/path/edition.cljs @@ -141,9 +141,9 @@ selected? (contains? selected-points position)] (streams/drag-stream (rx/of - (when-not selected? (selection/select-node position shift? "drag")) + (when-not selected? (selection/select-node position shift?)) (drag-selected-points @ms/mouse-position)) - (rx/of (selection/select-node position shift? "click"))))))) + (rx/of (selection/select-node position shift?))))))) (defn drag-selected-points [start-position] diff --git a/frontend/src/app/main/data/workspace/path/selection.cljs b/frontend/src/app/main/data/workspace/path/selection.cljs index dec4027ea3..ab23ae5362 100644 --- a/frontend/src/app/main/data/workspace/path/selection.cljs +++ b/frontend/src/app/main/data/workspace/path/selection.cljs @@ -60,7 +60,7 @@ (some? id) (assoc-in [:workspace-local :edit-path id :selected-points] positions)))))) -(defn select-node [position shift? kk] +(defn select-node [position shift?] (ptk/reify ::select-node ptk/UpdateEvent (update [_ state] @@ -79,38 +79,6 @@ (some? id) (assoc-in [:workspace-local :edit-path id :selected-points] selected-points)))))) -(defn deselect-node [position shift?] - (ptk/reify ::deselect-node - ptk/UpdateEvent - (update [_ state] - (let [id (get-in state [:workspace-local :edition])] - (-> state - (update-in [:workspace-local :edit-path id :selected-points] (fnil disj #{}) position)))))) - -(defn add-to-selection-handler [index type] - (ptk/reify ::add-to-selection-handler - ptk/UpdateEvent - (update [_ state] - state))) - -(defn add-to-selection-node [index] - (ptk/reify ::add-to-selection-node - ptk/UpdateEvent - (update [_ state] - state))) - -(defn remove-from-selection-handler [index] - (ptk/reify ::remove-from-selection-handler - ptk/UpdateEvent - (update [_ state] - state))) - -(defn remove-from-selection-node [index] - (ptk/reify ::remove-from-selection-handler - ptk/UpdateEvent - (update [_ state] - state))) - (defn deselect-all [] (ptk/reify ::deselect-all ptk/UpdateEvent @@ -140,7 +108,7 @@ (ptk/reify ::handle-selection ptk/WatchEvent - (watch [_ state stream] + (watch [_ _ stream] (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) stoper (->> stream (rx/filter stop?)) from-p @ms/mouse-position] diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index b1b3918994..95d7834801 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -98,8 +98,8 @@ ;; -- RESIZE (defn start-resize - [handler initial ids shape] - (letfn [(resize [shape initial resizing-shapes layout [point lock? point-snap]] + [handler ids shape] + (letfn [(resize [shape initial layout [point lock? point-snap]] (let [{:keys [width height]} (:selrect shape) {:keys [rotation]} shape rotation (or rotation 0) @@ -181,7 +181,7 @@ (rx/switch-map (fn [[point :as current]] (->> (snap/closest-snap-point page-id resizing-shapes layout zoom point) (rx/map #(conj current %))))) - (rx/mapcat (partial resize shape initial-position resizing-shapes layout)) + (rx/mapcat (partial resize shape initial-position layout)) (rx/take-until stoper)) (rx/of (apply-modifiers ids) (finish-transform)))))))) @@ -196,7 +196,7 @@ (assoc-in [:workspace-local :transform] :rotate))) ptk/WatchEvent - (watch [it state stream] + (watch [_ _ stream] (let [stoper (rx/filter ms/mouse-up? stream) group (gsh/selection-rect shapes) group-center (gsh/center-selrect group) @@ -235,7 +235,7 @@ [] (ptk/reify ::start-move-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [initial (deref ms/mouse-position) selected (wsh/lookup-selected state {:omit-blocked? true}) stopper (rx/filter ms/mouse-up? stream)] @@ -259,7 +259,7 @@ (defn start-move-duplicate [from-position] (ptk/reify ::start-move-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ _ stream] (->> stream (rx/filter (ptk/type? ::dws/duplicate-selected)) (rx/first) @@ -268,7 +268,7 @@ (defn calculate-frame-for-move [ids] (ptk/reify ::calculate-frame-for-move ptk/WatchEvent - (watch [it state stream] + (watch [it state _] (let [position @ms/mouse-position page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -313,7 +313,7 @@ (assoc-in [:workspace-local :transform] :move))) ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) selected (wsh/lookup-selected state {:omit-blocked? true}) @@ -349,20 +349,6 @@ (calculate-frame-for-move ids) (finish-transform))))))))) -(defn- get-displacement-with-grid - "Retrieve the correct displacement delta point for the - provided direction speed and distances thresholds." - [shape direction options] - (let [grid-x (:grid-x options 10) - grid-y (:grid-y options 10) - x-mod (mod (:x shape) grid-x) - y-mod (mod (:y shape) grid-y)] - (case direction - :up (gpt/point 0 (- (if (zero? y-mod) grid-y y-mod))) - :down (gpt/point 0 (- grid-y y-mod)) - :left (gpt/point (- (if (zero? x-mod) grid-x x-mod)) 0) - :right (gpt/point (- grid-x x-mod) 0)))) - (defn- get-displacement "Retrieve the correct displacement delta point for the provided direction speed and distances thresholds." @@ -394,7 +380,7 @@ state)) ptk/WatchEvent - (watch [it state stream] + (watch [_ state stream] (if (= same-event (get-in state [:workspace-local :current-move-selected])) (let [selected (wsh/lookup-selected state {:omit-blocked? true}) move-events (->> stream @@ -539,7 +525,7 @@ (defn increase-rotation [ids rotation] (ptk/reify ::increase-rotation ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) @@ -559,7 +545,7 @@ (us/verify (s/coll-of uuid?) ids) (ptk/reify ::apply-modifiers ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [objects (wsh/lookup-page-objects state) children-ids (->> ids (mapcat #(cp/get-children % objects))) ids-with-children (d/concat [] children-ids ids) @@ -610,11 +596,6 @@ (update [_ state] (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - - update-children - (fn [objects ids modifiers] - (reduce #(assoc-in %1 [%2 :modifiers] modifiers) objects ids)) ;; For each shape updates the modifiers given as arguments update-shape @@ -629,7 +610,7 @@ #(reduce update-shape % ids)))) ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) ids (d/concat [] ids (mapcat #(cp/get-children % objects) ids))] @@ -638,7 +619,7 @@ (defn flip-horizontal-selected [] (ptk/reify ::flip-horizontal-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [objects (wsh/lookup-page-objects state) selected (wsh/lookup-selected state {:omit-blocked? true}) shapes (map #(get objects %) selected) @@ -654,7 +635,7 @@ (defn flip-vertical-selected [] (ptk/reify ::flip-vertical-selected ptk/WatchEvent - (watch [it state stream] + (watch [_ state _] (let [objects (wsh/lookup-page-objects state) selected (wsh/lookup-selected state {:omit-blocked? true}) shapes (map #(get objects %) selected) @@ -687,6 +668,6 @@ [] (ptk/reify ::selected-to-path ptk/WatchEvent - (watch [_ state stream] + (watch [_ state _] (let [ids (wsh/lookup-selected state {:omit-blocked? true})] (rx/of (dch/update-shapes ids ups/convert-to-path)))))) diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index a27009fd19..45068e47ca 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -191,8 +191,7 @@ (mf/defc export-data [{:keys [shape]}] - (let [props (-> (obj/new) (add-data shape)) - frame? (= (:type shape) :frame)] + (let [props (-> (obj/new) (add-data shape))] [:> "penpot:shape" props [:& export-shadow-data shape] [:& export-blur-data shape] diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index ad547c9e51..4f853229cc 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -286,9 +286,9 @@ shape-center (geom/center-shape shape) - on-resize (fn [current-position initial-position event] + on-resize (fn [current-position _initial-position event] (dom/stop-propagation event) - (st/emit! (dw/start-resize current-position initial-position selected shape))) + (st/emit! (dw/start-resize current-position selected shape))) on-rotate #(do (dom/stop-propagation %) (st/emit! (dw/start-rotate shapes)))] @@ -311,9 +311,9 @@ shape (geom/transform-shape shape {:round-coords? false}) shape' (if (debug? :simple-selection) (geom/setup {:type :rect} (geom/selection-rect [shape])) shape) - on-resize (fn [current-position initial-position event] + on-resize (fn [current-position _initial-position event] (dom/stop-propagation event) - (st/emit! (dw/start-resize current-position initial-position #{shape-id} shape'))) + (st/emit! (dw/start-resize current-position #{shape-id} shape'))) on-rotate #(do (dom/stop-propagation %) diff --git a/frontend/src/app/util/import/parser.cljc b/frontend/src/app/util/import/parser.cljs similarity index 97% rename from frontend/src/app/util/import/parser.cljc rename to frontend/src/app/util/import/parser.cljs index 84af3163bc..34de143e81 100644 --- a/frontend/src/app/util/import/parser.cljc +++ b/frontend/src/app/util/import/parser.cljs @@ -126,7 +126,7 @@ (defn without-penpot-prefix [m] (let [no-penpot-prefix? - (fn [[k v]] + (fn [[k _]] (not (str/starts-with? (d/name k) "penpot:")))] (into {} (filter no-penpot-prefix?) m))) @@ -191,11 +191,11 @@ [props svg-data] (let [values (->> (select-keys svg-data [:cx :cy :rx :ry]) (d/mapm (fn [_ val] (d/parse-double val))))] - - {:x (- (:cx values) (:rx values)) - :y (- (:cy values) (:ry values)) - :width (* (:rx values) 2) - :height (* (:ry values) 2)})) + (-> props + (assoc :x (- (:cx values) (:rx values)) + :y (- (:cy values) (:ry values)) + :width (* (:rx values) 2) + :height (* (:ry values) 2))))) (defn parse-path [props center svg-data] @@ -459,7 +459,7 @@ [props node] (let [shadows (extract-from-data node :penpot:shadow parse-shadow)] (cond-> props - (not (empty? shadows)) + (d/not-empty? shadows) (assoc :shadow shadows)))) (defn add-blur @@ -473,7 +473,7 @@ [props node] (let [exports (extract-from-data node :penpot:export parse-export)] (cond-> props - (not (empty? exports)) + (d/not-empty? exports) (assoc :exports exports)))) (defn add-layer-options @@ -507,7 +507,7 @@ (reduce assoc-key {})))) (defn get-svg-defs - [node svg-defs] + [node] (let [svg-import (get-data node :penpot:svg-import)] (->> svg-import @@ -545,7 +545,7 @@ (some? svg-defs) - (assoc :svg-defs (get-svg-defs node svg-defs)))) + (assoc :svg-defs (get-svg-defs node)))) props))) @@ -571,7 +571,7 @@ (defn add-frame-data [props node] (let [grids (parse-grids node)] (cond-> props - (not (empty? grids)) + (d/not-empty? grids) (assoc :grids grids)))) (defn has-image? @@ -641,12 +641,12 @@ background (:background style) grids (->> (parse-grids node) (group-by :type) - (d/mapm (fn [k v] (-> v first :params))))] + (d/mapm (fn [_ v] (-> v first :params))))] (cond-> {} (some? background) (assoc-in [:options :background] background) - (not (empty? grids)) + (d/not-empty? grids) (assoc-in [:options :saved-grids] grids)))) (defn parse-interactions diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index abd5e27534..894d69c469 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -182,7 +182,7 @@ (rx/map #(vector (get files file-id) %))))))))) (defmethod impl/handler :export-file - [{:keys [team-id project-id files] :as message}] + [{:keys [team-id files] :as message}] (->> (rx/from files) (rx/mapcat #(export-file team-id %)) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 0be1118773..9bbf1a4c69 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -17,7 +17,6 @@ [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx] - [cuerdas.core :as str] [tubax.core :as tubax])) ;; Upload changes batches size @@ -111,7 +110,7 @@ (some? (:last-id file)) (assoc-in [:id-mapping old-id] (:last-id file)) - (not (empty? interactions)) + (d/not-empty? interactions) (assoc-in [:interactions old-id] interactions)))))) (defn post-process-file From 9b9959da9a7651ade1e65e556c9baf05568cec99 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 22 Jun 2021 09:58:18 +0200 Subject: [PATCH 136/204] :sparkles: Export library components --- frontend/src/app/main/exports.cljs | 68 +++++++++++++++-- frontend/src/app/main/render.cljs | 13 +++- frontend/src/app/main/ui.cljs | 9 +++ frontend/src/app/main/ui/render.cljs | 27 +++++++ frontend/src/app/worker/export.cljs | 110 ++++++++++++++++++--------- 5 files changed, 183 insertions(+), 44 deletions(-) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index f3ae1e8183..ed0907b091 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -25,6 +25,7 @@ [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.text :as text] + [app.util.object :as obj] [app.util.timers :as ts] [cuerdas.core :as str] [rumext.alpha :as mf])) @@ -73,10 +74,11 @@ (mf/fnc group-wrapper [{:keys [shape frame] :as props}] (let [childs (mapv #(get objects %) (:shapes shape))] - [:& group-shape {:frame frame - :shape shape - :is-child-selected? true - :childs childs}])))) + [:& shape-container {:shape shape} + [:& group-shape {:frame frame + :shape shape + :is-child-selected? true + :childs childs}]])))) (defn svg-raw-wrapper-factory [objects] @@ -207,7 +209,8 @@ :height height :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns "http://www.w3.org/2000/svg"} + :xmlns "http://www.w3.org/2000/svg" + :xmlns:penpot "https://penpot.app/xmlns"} [:& wrapper {:shape frame :view-box vbox}]])) (mf/defc component-svg @@ -238,5 +241,58 @@ :height height :version "1.1" :xmlnsXlink "http://www.w3.org/1999/xlink" - :xmlns "http://www.w3.org/2000/svg"} + :xmlns "http://www.w3.org/2000/svg" + :xmlns:penpot "https://penpot.app/xmlns"} [:& wrapper {:shape group :view-box vbox}]])) + +(mf/defc component-symbol + [{:keys [id data] :as props}] + + (let [{:keys [name objects]} data + root (get objects id) + + {:keys [width height]} (:selrect root) + vbox (str "0 0 " width " " height) + + modifier (-> (gpt/point (:x root) (:y root)) + (gpt/negate) + (gmt/translate-matrix)) + + modifier-ids (concat [id] (cp/get-children id objects)) + update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier) + objects (reduce update-fn objects modifier-ids) + root (assoc-in root [:modifiers :displacement] modifier) + + group-wrapper + (mf/use-memo + (mf/deps objects) + #(group-wrapper-factory objects))] + + [:symbol {:id (str id) + :viewBox vbox} + [:title name] + [:& group-wrapper {:shape root :view-box vbox}]])) + +(mf/defc components-sprite-svg + {::mf/wrap-props false} + [props] + + (let [data (obj/get props "data") + children (obj/get props "children") + embed? (obj/get props "embed?")] + [:& (mf/provider embed/context) {:value embed?} + [:svg {:version "1.1" + :xmlns "http://www.w3.org/2000/svg" + :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns:penpot "https://penpot.app/xmlns" + :style {:width "100vw" + :height "100vh" + :display (when-not (some? children) "none")}} + + [:defs + (for [[component-id component-data] (:components data)] + [:& component-symbol {:id component-id + :key (str component-id) + :data component-data}])] + + children]])) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 26e62bacc6..64b760cfa4 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -66,5 +66,16 @@ (let [elem (mf/element exports/page-svg #js {:data data :embed? true})] (rds/renderToStaticMarkup elem))))))) +(defn render-components + [data] + (rx/concat + (->> (rx/merge + (populate-images-cache data) + (populate-fonts-cache data)) + (rx/ignore)) - + (->> (rx/of data) + (rx/map + (fn [data] + (let [elem (mf/element exports/components-sprite-svg #js {:data data :embed? true})] + (rds/renderToStaticMarkup elem))))))) diff --git a/frontend/src/app/main/ui.cljs b/frontend/src/app/main/ui.cljs index 8859dacd35..3bf450847e 100644 --- a/frontend/src/app/main/ui.cljs +++ b/frontend/src/app/main/ui.cljs @@ -82,6 +82,7 @@ ;; Used for export ["/render-object/:file-id/:page-id/:object-id" :render-object] + ["/render-sprite/:file-id" :render-sprite] ["/dashboard/team/:team-id" ["/members" :dashboard-team-members] @@ -172,6 +173,14 @@ :page-id page-id :object-id object-id}])) + :render-sprite + (do + (let [file-id (uuid (get-in route [:path-params :file-id])) + component-id (get-in route [:query-params :component-id]) + component-id (when (some? component-id) (uuid component-id))] + [:& render/render-sprite {:file-id file-id + :component-id component-id}])) + :workspace (let [project-id (some-> params :path :project-id uuid) file-id (some-> params :path :file-id uuid) diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index cb09796fc2..64fd56ec28 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -123,3 +123,30 @@ [:& object-svg {:objects @objects :object-id object-id :zoom 1}]))) + +(mf/defc render-sprite + [{:keys [file-id component-id] :as props}] + (let [file (mf/use-state nil)] + (mf/use-effect + (mf/deps file-id) + (fn [] + (->> (repo/query! :file {:id file-id}) + (rx/subs + (fn [result] + (reset! file result)))) + (constantly nil))) + + (when @file + [:* + [:& exports/components-sprite-svg {:data (:data @file) :embed true} + + (when (some? component-id) + [:use {:x 0 :y 0 + :xlinkHref (str "#" component-id)}])] + + (when-not (some? component-id) + [:ul + (for [[id data] (get-in @file [:data :components])] + (let [url (str "#/render-sprite/" (:id @file) "?component-id=" id)] + [:li [:a {:href url} (:name data)]]))])]))) + diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 894d69c469..3e9ee110d4 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -70,26 +70,77 @@ [{:keys [id file-id markup] :as page}] [(str file-id "/" id ".svg") markup]) -(defn collect-color - [result color] +(defn collect-entries [result data keys] (-> result - (assoc (str (:id color)) - (->> (select-keys color [:name :color :opacity :gradient]) + (assoc (str (:id data)) + (->> (select-keys data keys) (d/deep-mapm (fn [[k v]] [(-> k str/camel) v])))))) -(def ^:const typography-keys [:name :font-family :font-id :font-size - :font-style :font-variant-id :font-weight - :letter-spacing :line-height :text-transform]) +(def ^:const color-keys + [:name :color :opacity :gradient]) + +(def ^:const typography-keys + [:name :font-family :font-id :font-size :font-style :font-variant-id :font-weight + :letter-spacing :line-height :text-transform]) + +(def ^:const media-keys + [:name :mtype :width :height]) + +(defn collect-color + [result color] + (collect-entries result color color-keys)) + (defn collect-typography [result typography] - (-> result - (assoc (str (:id typography)) - (->> (select-keys typography typography-keys) - (d/deep-mapm - (fn [[k v]] - [(-> k str/camel) v])))))) + (collect-entries result typography typography-keys)) + +(defn collect-media + [result media] + (collect-entries result media media-keys)) + +(defn parse-library-color + [[file-id colors]] + (let [markup + (->> (vals colors) + (reduce collect-color {}) + (json/encode))] + [(str file-id "/colors.json") markup])) + +(defn parse-library-typographies + [[file-id typographies]] + (let [markup + (->> (vals typographies) + (reduce collect-typography {}) + (json/encode))] + [(str file-id "/typographies.json") markup])) + +(defn parse-library-media + [[file-id media]] + (rx/merge + (let [markup + (->> (vals media) + (reduce collect-media {}) + (json/encode))] + (rx/of (vector (str file-id "/images.json") markup))) + + (->> (rx/from (vals media)) + (rx/map #(assoc % :file-id file-id)) + (rx/flat-map + (fn [media] + (let [file-path (str file-id "/images/" (:id media) "." (dom/mtype->extension (:mtype media)))] + (->> (http/send! + {:uri (cfg/resolve-file-media media) + :response-type :blob + :method :get}) + (rx/map :body) + (rx/map #(vector file-path %))))))))) + +(defn parse-library-components + [file] + (->> (r/render-components (:data file)) + (rx/map #(vector (str (:id file) "/components.svg") %)))) (defn export-file [team-id file-id] @@ -120,42 +171,27 @@ (rx/flat-map vals) (rx/map #(vector (:id %) (get-in % [:data :colors]))) (rx/filter #(d/not-empty? (second %))) - (rx/map (fn [[file-id colors]] - (let [markup - (->> (vals colors) - (reduce collect-color {}) - (json/encode))] - [(str file-id "/colors.json") markup])))) + (rx/map 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 (fn [[file-id typographies]] - (let [markup - (->> (vals typographies) - (reduce collect-typography {}) - (json/encode))] - [(str file-id "/typographies.json") markup])))) + (rx/map parse-library-typographies)) 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 - (fn [[file-id media]] - (->> media vals (mapv #(assoc % :file-id file-id))))) + (rx/flat-map parse-library-media)) - (rx/flat-map - (fn [media] - (->> (http/send! - {:uri (cfg/resolve-file-media media) - :response-type :blob - :method :get}) - (rx/map :body) - (rx/map #(vector (str file-id "/images/" (:id media)) %)))))) + components-stream + (->> files-stream + (rx/flat-map vals) + (rx/filter #(d/not-empty? (get-in % [:data :components]))) + (rx/flat-map parse-library-components)) pages-stream (->> render-stream @@ -171,7 +207,7 @@ (->> (rx/merge manifest-stream pages-stream - #_components-stream + components-stream media-stream colors-stream typographies-stream) From ca9edf2bc97fb64f7c67b14a8282bff2cf1bfeb2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 22 Jun 2021 15:19:06 +0200 Subject: [PATCH 137/204] :recycle: Refactor resize shapes from the sidebar measures form --- .../app/main/data/workspace/transforms.cljs | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 95d7834801..48b00825f1 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -445,7 +445,8 @@ "Apply the modifiers to one shape, and the corresponding ones to all children, depending on the child constraints. The modifiers are not directly applied to the objects tree, but to a separated structure (modif-tree), that may be - merged later with the real objects." + merged later with the real objects. This way, the objects are changed only + once, avoiding unnecesary redrawings." [modif-tree objects shape modifiers root transformed-root] (let [children (->> (get shape :shapes []) (map #(get objects %))) @@ -594,20 +595,21 @@ (ptk/reify ::update-dimensions ptk/UpdateEvent (update [_ state] - (let [page-id (:current-page-id state) + objects (get-in state [:workspace-data :pages-index page-id :objects])] - ;; For each shape updates the modifiers given as arguments - update-shape - (fn [objects shape-id] - (let [shape (get objects shape-id) - modifier (gsh/resize-modifiers shape attr value)] - (set-modifiers-recursive objects objects shape modifier nil nil)))] - - (d/update-in-when - state - [:workspace-data :pages-index page-id :objects] - #(reduce update-shape % ids)))) + (reduce (fn [state id] + (let [shape (get objects id) + modifiers (gsh/resize-modifiers shape attr value)] + (update state :workspace-modifiers + #(set-modifiers-recursive % + objects + shape + modifiers + nil + nil)))) + state + ids))) ptk/WatchEvent (watch [_ state _] From ca4ce569e7bd8c75ad18ed2c0bb367a7ad4d9780 Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Wed, 23 Jun 2021 12:52:10 +0200 Subject: [PATCH 138/204] :books: Improve general README file --- README.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 61 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 29d90c1dd6..ebe1760c42 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,60 @@ [uri_license]: https://www.mozilla.org/en-US/MPL/2.0 [uri_license_image]: https://img.shields.io/badge/MPL-2.0-blue.svg -[![License: MPL-2.0][uri_license_image]][uri_license] -[![Gitter](https://badges.gitter.im/sereno-xyz/community.svg)](https://gitter.im/penpot/community) -[![Managed with Taiga.io](https://img.shields.io/badge/managed%20with-TAIGA.io-709f14.svg)](https://tree.taiga.io/project/penpot/ "Managed with Taiga.io") -[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/penpot/penpot) +

+
+ PENPOT +

+ +

License: MPL-2.0 +Gitter +Managed with Taiga.io +Gitpod ready-to-code

+ +![PENPOT](https://penpot.app/images/readme/home-ui.jpg) -# PENPOT # +## What is Penpot? ## -Penpot is the first Open Source design and prototyping platform meant -for cross-domain teams. Non dependent on operating systems, Penpot is -web based and works with open web standards (SVG). For all and -empowered by the community. +Penpot is the first **Open Source design** and prototyping platform meant for cross-domain teams. Non dependent on operating systems, Penpot is web based and works with open web standards (SVG). For all and empowered by the community. -![PENPOT](https://penpot.app/images/workspace-ui.jpg) +- [How to use](#how-to-use) +- [Help center](#help-center) +- [Contributing](#contributing) +- [Give feedback](#give-feedback) +- [Tutorials](#tutorials) +- [License](#license) +## How to use ## + +Login or Register on our Penpot cloud app. Create a team to work together on projects and share design assets or jump right away into Penpot and **start designing** by your own. + +✏️ [Start using Penpot](https://design.penpot.app) + +You can also install Penpot in a local environment. This section details everything you need to know to get Penpot up and running in production environments. Although it can be installed in many ways, the recommended approach is using **docker** and **docker-compose**. + +🐳 [Install docker](https://help.penpot.app/technical-guide/getting-started/) + +## Help center ## + +In this documentation you will find (almost) everything you need to know about how to work with Penpot. From the interface basics to advanced functionality. + +📖 [User guide](https://help.penpot.app/user-guide/) + +❓ [FAQs](https://help.penpot.app/faqs/) + +🖥️ [Technical guide](https://help.penpot.app/technical-guide/) + +❤️ [Contributing guide](https://help.penpot.app/contributing-guide/) + +![User guide](https://penpot.app/images/readme/help-center.jpg) ## Contributing ## +

+ Open Source +

+ **Open to you!** We love the open source software community. Contributing is our @@ -28,11 +64,24 @@ and improve Penpot. All your awesome ideas and code are welcome! Please refer to the [Contributing Guide](./CONTRIBUTING.md) +## Give feedback ## -## Documentation ## +You can ask and answer questions, have open-ended conversations, and follow along on decisions affecting the project. -Please refer to the [help center](https://help.penpot.app). +✉️ [Mail us](mailto:info@penpot.app) +💬 [Github discussions](https://github.com/penpot/penpot/discussions) + +🐞 [Github issues](mailto:info@penpot.apphttps://github.com/penpot/penpot/issues) + +✍️️ [Gitter](https://gitter.im/penpot/community) + +## Tutorials ## + +You can ask and answer questions, have open-ended conversations, and follow along on decisions affecting the project. +Would you like to know more about Penpot? We recommend you to visit our youtube channel and learn more about the functionalities and possibilities of Penpot with our video tutorials. + +🎞️ [Youtube channel](https://www.youtube.com/channel/UCAqS8G72uv9P5HG1IfgnQ9g) ## License ## From 32956859382bd15ff5df74fc247d5c9fa068469b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 23 Jun 2021 15:47:44 +0200 Subject: [PATCH 139/204] :sparkles: Improve algorithm for constraints calculation --- .../app/common/geom/shapes/transforms.cljc | 145 +++++++----------- 1 file changed, 59 insertions(+), 86 deletions(-) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index a25238cbf3..aba530151b 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -376,8 +376,7 @@ child-rect (:selrect child) ; If the modifiers include a resize vector, apply it to the parent, - ; and then calculate the displacement of the side opposite to the - ; origin of the vector. + ; to check the difference and calculate child transformations. origin (-> (:resize-origin parent-modifiers) ((d/nilf transform-point-center) (gco/center-shape parent) @@ -390,78 +389,50 @@ (gmt/scale-matrix (get parent-modifiers :resize-vector (gpt/point 1 1)))) (gpr/points->selrect)) - orig-h (when origin - (cond - (mth/close? (:x origin) (:x1 parent-rect)) :left - (mth/close? (:x origin) (:x2 parent-rect)) :right - :else :middle)) - - orig-v (when origin - (cond - (mth/close? (:y origin) (:y1 parent-rect)) :top - (mth/close? (:y origin) (:y2 parent-rect)) :bottom - :else :middle)) - - delta-h (when orig-h - (cond (= orig-h :left) - (- (:x2 transformed-parent-rect) (:x2 parent-rect)) - - (= orig-h :right) - (- (:x1 transformed-parent-rect) (:x1 parent-rect)) - - :else 0)) - - delta-v (when orig-v - (cond (= orig-v :top) - (- (:y2 transformed-parent-rect) (:y2 parent-rect)) - - (= orig-v :bottom) - (- (:y1 transformed-parent-rect) (:y1 parent-rect)) - - :else 0)) - ;; Calculate the modifiers in the horizontal and vertical directions - ;; depending on the constraints, and the origin of the resize vector. + ;; depending on the child constraints. constraints-h (get child :constraints-h (spec/default-constraints-h child)) constraints-v (get child :constraints-v (spec/default-constraints-v child)) modifiers-h (case constraints-h :left - (if (= orig-h :right) - {:displacement (gpt/point delta-h 0)} ;; we convert to matrix below - {}) + (let [delta-left (- (:x1 transformed-parent-rect) (:x1 parent-rect))] + (if-not (mth/almost-zero? delta-left) + {:displacement (gpt/point delta-left 0)} ;; we convert to matrix below + {})) :right - (if (= orig-h :left) - {:displacement (gpt/point delta-h 0)} - {}) + (let [delta-right (- (:x2 transformed-parent-rect) (:x2 parent-rect))] + (if-not (mth/almost-zero? delta-right) + {:displacement (gpt/point delta-right 0)} + {})) :leftright - (cond (= orig-h :left) - {:resize-origin (-> (gpt/point (:x1 child-rect) (:y1 child-rect)) - (transform-point-center - (gco/center-shape child) - (:transform child (gmt/matrix)))) - :resize-vector (gpt/point (/ (+ (:width child-rect) delta-h) - (:width child-rect)) - 1)} - - (= orig-h :right) - {:resize-origin (-> (gpt/point (:x2 child-rect) (:y1 child-rect)) - (transform-point-center - (gco/center-shape child) - (:transform child (gmt/matrix)))) - :resize-vector (gpt/point (/ (- (:width child-rect) delta-h) - (:width child-rect)) - 1)} - - :else {}) + (let [delta-left (- (:x1 transformed-parent-rect) (:x1 parent-rect)) + delta-width (- (:width transformed-parent-rect) (:width parent-rect))] + (if (or (not (mth/almost-zero? delta-left)) + (not (mth/almost-zero? delta-width))) + {:displacement (gpt/point delta-left 0) + :resize-origin (-> (gpt/point (+ (:x1 child-rect) delta-left) + (:y1 child-rect)) + (transform-point-center + (gco/center-rect child-rect) + (:transform child (gmt/matrix)))) + :resize-vector (gpt/point (/ (+ (:width child-rect) delta-width) + (:width child-rect)) 1)} + {})) :center - {:displacement (gpt/point (/ delta-h 2) 0)} + (let [parent-center (gco/center-rect parent-rect) + transformed-parent-center (gco/center-rect transformed-parent-rect) + delta-center (- (:x transformed-parent-center) (:x parent-center))] + (if-not (mth/almost-zero? delta-center) + {:displacement (gpt/point delta-center 0)} + {})) :scale - (if (:resize-origin parent-modifiers) + (if (and (:resize-vector parent-modifiers) + (not (mth/close? (:x (:resize-vector parent-modifiers)) 1))) {:resize-origin (:resize-origin parent-modifiers) :resize-vector (gpt/point (:x (:resize-vector parent-modifiers)) 1)} {}) @@ -470,41 +441,43 @@ modifiers-v (case constraints-v :top - (if (= orig-v :bottom) - {:displacement (gpt/point 0 delta-v)} - {}) + (let [delta-top (- (:y1 transformed-parent-rect) (:y1 parent-rect))] + (if-not (mth/almost-zero? delta-top) + {:displacement (gpt/point 0 delta-top)} ;; we convert to matrix below + {})) :bottom - (if (= orig-v :top) - {:displacement (gpt/point 0 delta-v)} - {}) + (let [delta-bottom (- (:y2 transformed-parent-rect) (:y2 parent-rect))] + (if-not (mth/almost-zero? delta-bottom) + {:displacement (gpt/point 0 delta-bottom)} + {})) :topbottom - (cond (= orig-v :top) - {:resize-origin (-> (gpt/point (:x1 child-rect) (:y1 child-rect)) - (transform-point-center - (gco/center-shape child) - (:transform child (gmt/matrix)))) - :resize-vector (gpt/point 1 - (/ (+ (:height child-rect) delta-v) + (let [delta-top (- (:y1 transformed-parent-rect) (:y1 parent-rect)) + delta-height (- (:height transformed-parent-rect) (:height parent-rect))] + (if (or (not (mth/almost-zero? delta-top)) + (not (mth/almost-zero? delta-height))) + {:displacement (gpt/point 0 delta-top) + :resize-origin (-> (gpt/point (:x1 child-rect) + (+ (:y1 child-rect) delta-top)) + (transform-point-center + (gco/center-rect child-rect) + (:transform child (gmt/matrix)))) + :resize-vector (gpt/point 1 (/ (+ (:height child-rect) delta-height) (:height child-rect)))} - - (= orig-v :bottom) - {:resize-origin (-> (gpt/point (:x1 child-rect) (:y2 child-rect)) - (transform-point-center - (gco/center-shape child) - (:transform child (gmt/matrix)))) - :resize-vector (gpt/point 1 - (/ (- (:height child-rect) delta-v) - (:height child-rect)))} - - :else {}) + {})) :center - {:displacement (gpt/point 0 (/ delta-v 2))} + (let [parent-center (gco/center-rect parent-rect) + transformed-parent-center (gco/center-rect transformed-parent-rect) + delta-center (- (:y transformed-parent-center) (:y parent-center))] + (if-not (mth/almost-zero? delta-center) + {:displacement (gpt/point 0 delta-center)} + {})) :scale - (if (:resize-origin parent-modifiers) + (if (and (:resize-vector parent-modifiers) + (not (mth/close? (:y (:resize-vector parent-modifiers)) 1))) {:resize-origin (:resize-origin parent-modifiers) :resize-vector (gpt/point 1 (:y (:resize-vector parent-modifiers)))} {}) From 150427cd395769e1f1f3e7df6c054beb4e8b45a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 24 Jun 2021 15:39:12 +0200 Subject: [PATCH 140/204] :bug: Fix contextual menu in dashboard shared libraries section --- CHANGES.md | 1 + frontend/src/app/main/data/dashboard.cljs | 13 +++++++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5a8a851d60..6162735171 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -20,6 +20,7 @@ - Fix dashboard navigation on moving file to other team [Taiga #1817](https://tree.taiga.io/project/penpot/issue/1817). - Fix workspace header presence styles and invalid link [Taiga #1813](https://tree.taiga.io/project/penpot/issue/1813). - Fix color-input wrong behavior (on workspace page color) [Taiga #1795](https://tree.taiga.io/project/penpot/issue/1795). +- Fix file contextual menu in shared libraries at dashboard [Taiga #1865](https://tree.taiga.io/project/penpot/issue/1865). ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index b55b5cab0d..96bf7c077b 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -73,6 +73,7 @@ (-> (assoc :current-team-id id) (dissoc :dashboard-files) (dissoc :dashboard-projects) + (dissoc :dashboard-shared-files) (dissoc :dashboard-recent-files) (dissoc :dashboard-team-members) (dissoc :dashboard-team-stats))))) @@ -206,7 +207,10 @@ (ptk/reify ::shared-files-fetched ptk/UpdateEvent (update [_ state] - (assoc state :dashboard-shared-files (d/index-by :id files))))) + (let [files (d/index-by :id files)] + (-> state + (assoc :dashboard-shared-files files) + (update :dashboard-files d/merge files)))))) (defn fetch-shared-files [] @@ -548,6 +552,7 @@ (update [_ state] (-> state (d/update-when :dashboard-files dissoc id) + (d/update-when :dashboard-shared-files dissoc id) (d/update-when :dashboard-recent-files dissoc id))) ptk/WatchEvent @@ -566,6 +571,7 @@ (update [_ state] (-> state (d/update-in-when [:dashboard-files id :name] (constantly name)) + (d/update-in-when [:dashboard-shared-files id :name] (constantly name)) (d/update-in-when [:dashboard-recent-files id :name] (constantly name)))) ptk/WatchEvent @@ -584,7 +590,10 @@ (update [_ state] (-> state (d/update-in-when [:dashboard-files id :is-shared] (constantly is-shared)) - (d/update-in-when [:dashboard-recent-files id :is-shared] (constantly is-shared)))) + (d/update-in-when [:dashboard-recent-files id :is-shared] (constantly is-shared)) + (cond-> + (not is-shared) + (d/update-when :dashboard-shared-files dissoc id)))) ptk/WatchEvent (watch [_ _ _] From 98f072619f717242ab63fd65e9a59f603c04cd1a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 22 Jun 2021 12:16:59 +0200 Subject: [PATCH 141/204] :sparkles: Allow removing background from frames --- frontend/src/app/main/ui/shapes/frame.cljs | 12 +++++++----- .../src/app/main/ui/workspace/viewport.cljs | 6 ++++-- .../viewport/thumbnail_renderer.cljs | 19 +++++++++++++++---- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/frame.cljs b/frontend/src/app/main/ui/shapes/frame.cljs index aa1930e779..d9e95c1351 100644 --- a/frontend/src/app/main/ui/shapes/frame.cljs +++ b/frontend/src/app/main/ui/shapes/frame.cljs @@ -10,8 +10,6 @@ [app.util.object :as obj] [rumext.alpha :as mf])) -(def frame-default-props {:fill-color "#ffffff"}) - (defn frame-shape [shape-wrapper] (mf/fnc frame-shape @@ -21,8 +19,11 @@ shape (unchecked-get props "shape") {:keys [width height]} shape - props (-> (merge frame-default-props shape) - (attrs/extract-style-attrs) + has-background? (or (some? (:fill-color shape)) + (some? (:fill-color-gradient shape))) + has-stroke? (not= :none (:stroke-style shape)) + + props (-> (attrs/extract-style-attrs shape) (obj/merge! #js {:x 0 :y 0 @@ -30,7 +31,8 @@ :height height :className "frame-background"}))] [:* - [:> :rect props] + (when (or has-background? has-stroke?) + [:> :rect props]) (for [item childs] [:& shape-wrapper {:frame shape :shape item diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a56430b5f0..a12d153db5 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -65,6 +65,7 @@ objects (mf/use-memo (mf/deps objects object-modifiers) #(cp/merge-modifiers objects object-modifiers)) + background (get options :background "#E8E9EA") ;; STATE alt? (mf/use-state false) @@ -157,7 +158,8 @@ [:div.viewport [:div.viewport-overlays - [:& wtr/frame-renderer {:objects objects}] + [:& wtr/frame-renderer {:objects objects + :background background}] (when show-comments? [:& comments/comments-layer {:vbox vbox @@ -185,7 +187,7 @@ :width (:width vport 0) :height (:height vport 0) :view-box (utils/format-viewbox vbox) - :style {:background-color (get options :background "#E8E9EA") + :style {:background-color background :pointer-events "none"}} [:& use/export-page {:options options}] diff --git a/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs b/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs index dc6d5eb694..95f27e5a93 100644 --- a/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/thumbnail_renderer.cljs @@ -17,7 +17,7 @@ (mf/defc frame-thumbnail "Renders the canvas and image for a frame thumbnail and stores its value into the shape" - [{:keys [shape on-thumbnail-data on-frame-not-found]}] + [{:keys [shape background on-thumbnail-data on-frame-not-found]}] (let [thumbnail-img (mf/use-ref nil) thumbnail-canvas (mf/use-ref nil) @@ -45,12 +45,21 @@ on-image-load (mf/use-callback - (mf/deps on-thumbnail-data) + (mf/deps on-thumbnail-data background) (fn [] - (let [canvas-node (mf/ref-val thumbnail-canvas) - img-node (mf/ref-val thumbnail-img) + (let [canvas-node (mf/ref-val thumbnail-canvas) + img-node (mf/ref-val thumbnail-img) + 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) + _ (.rect canvas-context 0 0 canvas-width canvas-height) + _ (set! (.-fillStyle canvas-context) background) + _ (.fill canvas-context) _ (.drawImage canvas-context img-node 0 0) + data (.toDataURL canvas-node "image/jpeg" 0.8)] (on-thumbnail-data data))))] @@ -72,6 +81,7 @@ {::mf/wrap-props false} [props] (let [objects (obj/get props "objects") + background (obj/get props "background") ;; Id of the current frame being rendered shape-id (mf/use-state nil) @@ -131,5 +141,6 @@ (when (and (some? @shape-id) (contains? objects @shape-id)) [:& frame-thumbnail {:key (str "thumbnail-" @shape-id) :shape (get objects @shape-id) + :background background :on-thumbnail-data on-thumbnail-data :on-frame-not-found on-frame-not-found}]))) From aa14d9626fc8c436e608da20c23d3fc7a87a05a7 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 22 Jun 2021 16:03:05 +0200 Subject: [PATCH 142/204] :sparkles: Add library elements to file builder --- common/src/app/common/file_builder.cljc | 216 +++++++++++++++++++----- 1 file changed, 176 insertions(+), 40 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 3b0c077baf..056e67804e 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -18,40 +18,53 @@ [cuerdas.core :as str])) (def root-frame uuid/zero) +(def conjv (fnil conj [])) +(def conjs (fnil conj #{})) ;; This flag controls if we should execute spec validation after every commit (def verify-on-commit? true) -(defn- commit-change [file change] - (when verify-on-commit? - (us/assert ::spec/change change)) - (-> file - (update :changes (fnil conj []) change) - (update :data ch/process-changes [change] verify-on-commit?))) +(defn- commit-change + ([file change] + (commit-change file change nil)) + + ([file change {:keys [add-container?] + :or {add-container? false}}] + (let [component-id (:current-component-id file) + change (cond-> change + (and add-container? (some? component-id)) + (assoc :component-id component-id) + + (and add-container? (nil? component-id)) + (assoc :page-id (:current-page-id file) + :frame-id (:current-frame-id file)))] + + (when verify-on-commit? + (us/assert ::spec/change change)) + (-> file + (update :changes conjv change) + (update :data ch/process-changes [change] verify-on-commit?))))) (defn- lookup-objects ([file] - (lookup-objects file (:current-page-id file))) - - ([file page-id] - (get-in file [:data :pages-index page-id :objects]))) + (if (some? (:current-component-id file)) + (get-in file [:data :components (:current-component-id file) :objects]) + (get-in file [:data :pages-index (:current-page-id file) :objects])))) (defn- lookup-shape [file shape-id] (-> (lookup-objects file) (get shape-id))) (defn- commit-shape [file obj] - (let [page-id (:current-page-id file) - frame-id (:current-frame-id file) - parent-id (-> file :parent-stack peek)] + (let [parent-id (-> file :parent-stack peek)] (-> file (commit-change {:type :add-obj :id (:id obj) - :page-id page-id - :frame-id frame-id - :parent-id parent-id - :obj obj})))) + :obj obj + :parent-id parent-id} + + {:add-container? true})))) (defn setup-rect-selrect [obj] (let [rect (select-keys obj [:x :y :width :height]) @@ -62,9 +75,9 @@ points (-> (gsh/rect->points rect) (gsh/transform-points center transform))] - (assoc obj - :selrect selrect - :points points))) + (-> obj + (assoc :selrect selrect) + (assoc :points points)))) (defn- setup-path-selrect [obj] @@ -105,6 +118,23 @@ :else (str tag)))) (str/capital (d/name type)))) +(defn- add-name + [file name] + (let [container-id (or (:current-component-id file) + (:current-page-id file))] + (-> file + (update-in [:unames container-id] conjs name)))) + +(defn- unique-name + [name file] + (let [container-id (or (:current-component-id file) + (:current-page-id file)) + unames (get-in file [:unames container-id])] + (d/unique-name (or unames #{}) name))) + +(defn clear-names [file] + (dissoc file :unames)) + (defn- check-name "Given a tag returns its layer name" [data file type] @@ -114,7 +144,7 @@ (assoc :name (generate-name type data)) :always - (update :name d/unique-name (:unames file)))) + (update :name unique-name file))) ;; PUBLIC API @@ -131,6 +161,8 @@ (defn add-page [file data] + + (assert (nil? (:current-component-id file))) (let [page-id (uuid/next) page (-> init/empty-page-data (assoc :id page-id) @@ -150,19 +182,18 @@ (assoc :parent-stack [root-frame]) ;; Last object id added - (assoc :last-id nil) - - ;; Current used names - (assoc :unames #{})))) + (assoc :last-id nil)))) (defn close-page [file] + (assert (nil? (:current-component-id file))) (-> file (dissoc :current-page-id) (dissoc :parent-stack) (dissoc :last-id) - (dissoc :unames))) + (clear-names))) (defn add-artboard [file data] + (assert (nil? (:current-component-id file))) (let [obj (-> (init/make-minimal-shape :frame) (merge data) (check-name file :frame) @@ -172,10 +203,11 @@ (commit-shape obj) (assoc :current-frame-id (:id obj)) (assoc :last-id (:id obj)) - (update :unames conj (:name obj)) - (update :parent-stack conj (:id obj))))) + (add-name (:name obj)) + (update :parent-stack conjv (:id obj))))) (defn close-artboard [file] + (assert (nil? (:current-component-id file))) (-> file (assoc :current-frame-id root-frame) (update :parent-stack pop))) @@ -191,8 +223,8 @@ (-> file (commit-shape obj) (assoc :last-id (:id obj)) - (update :unames conj (:name obj)) - (update :parent-stack conj (:id obj))))) + (add-name (:name obj)) + (update :parent-stack conjv (:id obj))))) (defn close-group [file] (let [group-id (-> file :parent-stack peek) @@ -205,15 +237,14 @@ (commit-change file {:type :del-obj - :page-id (:current-page-id file) - :id group-id}) + :id group-id} + {:add-container? true}) (:masked-group? group) (let [mask (first children)] (commit-change file {:type :mod-obj - :page-id (:current-page-id file) :id group-id :operations [{:type :set :attr :x :val (-> mask :selrect :x)} @@ -223,18 +254,20 @@ {: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 :points :val (-> mask :points)}]} + {:add-container? true})) :else (let [group' (gsh/update-group-selrect group children)] (commit-change file {:type :mod-obj - :page-id (:current-page-id file) :id group-id :operations [{:type :set :attr :selrect :val (:selrect group')} - {:type :set :attr :points :val (:points group')}]})))] + {:type :set :attr :points :val (:points group')}]} + + {:add-container? true})))] (-> file (update :parent-stack pop)))) @@ -253,7 +286,7 @@ (-> file (commit-shape obj) (assoc :last-id (:id obj)) - (update :unames conj (:name obj))))) + (add-name (:name obj))))) (defn create-rect [file data] (create-shape file :rect data)) @@ -275,7 +308,7 @@ (defn create-svg-raw [file data] (let [file (as-> file $ (create-shape $ :svg-raw data) - (update $ :parent-stack conj (:last-id $))) + (update $ :parent-stack conjv (:last-id $))) create-child (fn [file child] @@ -301,9 +334,8 @@ :interactions (filterv #(or (not= (:action-type %) action-type) (not= (:event-type %) event-type)))) - conj (fnil conj []) interactions (-> interactions - (conj + (conjv {:action-type action-type :event-type event-type :destination destination-id}))] @@ -319,3 +351,107 @@ (defn generate-changes [file] (:changes file)) + +(defn add-library-color + [file color] + + (let [id (uuid/next)] + (commit-change + file + {:type :add-color + :id id + :color color}) + + id)) + +(defn add-library-typography + [file typography] + (let [id (uuid/next)] + (commit-change + file + {:type :add-typography + :id id + :typography typography}) + + id)) + +(defn add-library-media + [file media] + (let [id (uuid/next)] + (commit-change + file + {:type :add-media + :id id + :media media}) + + id)) + +(defn start-component + [file data] + + (let [selrect init/empty-selrect + name (:name data) + obj (-> (init/make-minimal-group nil selrect name) + (merge data) + (check-name file :group) + (d/without-nils))] + (-> file + (commit-change + {:type :add-component + :id (:id obj) + :name name + :shapes [obj]}) + + (assoc :last-id (:id obj)) + (update :parent-stack conjv (:id obj)) + (assoc :current-component-id (:id obj))))) + +(defn finish-component + [file] + (let [component-id (:current-component-id file) + component (lookup-shape file component-id) + children (->> component :shapes (mapv #(lookup-shape file %))) + + file + (cond + (empty? children) + (commit-change + file + {:type :del-component + :id component-id}) + + (:masked-group? component) + (let [mask (first children)] + (commit-change + file + {: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)}]} + + {:add-container? true})) + + :else + (let [component' (gsh/update-group-selrect component children)] + (commit-change + file + {:type :mod-obj + :id component-id + :operations + [{:type :set :attr :selrect :val (:selrect component')} + {:type :set :attr :points :val (:points component')}]} + + {:add-container? true})))] + + (-> file + (dissoc :current-component-id) + (update :parent-stack pop)))) + + From 7d141227468a97d2d005c76bb5213753d93c5450 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 23 Jun 2021 15:51:06 +0200 Subject: [PATCH 143/204] :sparkles: Import library media,color,typographies --- common/src/app/common/data.cljc | 20 +++-- common/src/app/common/file_builder.cljc | 46 +++++------ frontend/src/app/util/zip.cljs | 20 +++-- frontend/src/app/worker/export.cljs | 6 +- frontend/src/app/worker/import.cljs | 105 +++++++++++++++++++++--- 5 files changed, 143 insertions(+), 54 deletions(-) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 39abab9d4e..8ad5ec59a0 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -13,6 +13,7 @@ [app.common.math :as mth] [cljs.analyzer.api :as aapi] [clojure.set :as set] + [cuerdas.core :as str] #?(:cljs [cljs.reader :as r] :clj [clojure.edn :as r]) #?(:cljs [cljs.core :as core] @@ -541,12 +542,13 @@ when it's a vector or a map" [mfn m] (let [do-map - (fn [[k v]] - (cond - (or (vector? v) (map? v)) - [k (deep-mapm mfn v)] - :else - (mfn [k v])))] + (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) @@ -560,3 +562,9 @@ (defn not-empty? [coll] (boolean (seq coll))) + +(defn kebab-keys [m] + (->> m + (deep-mapm + (fn [[k v]] + [(keyword (str/kebab (name k))) v])))) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 056e67804e..7f79275784 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -130,7 +130,7 @@ (let [container-id (or (:current-component-id file) (:current-page-id file)) unames (get-in file [:unames container-id])] - (d/unique-name (or unames #{}) name))) + (d/unique-name name (or unames #{})))) (defn clear-names [file] (dissoc file :unames)) @@ -355,36 +355,32 @@ (defn add-library-color [file color] - (let [id (uuid/next)] - (commit-change - file - {:type :add-color - :id id - :color color}) - - id)) + (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 add-library-typography [file typography] - (let [id (uuid/next)] - (commit-change - file - {:type :add-typography - :id id - :typography typography}) - - id)) + (let [id (or (:id typography) (uuid/next))] + (-> file + (commit-change + {:type :add-typography + :id id + :typography (assoc typography :id id)}) + (assoc :last-id id)))) (defn add-library-media [file media] - (let [id (uuid/next)] - (commit-change - file - {:type :add-media - :id id - :media media}) - - id)) + (let [id (or (:id media) (uuid/next))] + (-> file + (commit-change + {:type :add-media + :object (assoc media :id id)}) + (assoc :last-id id)))) (defn start-component [file data] diff --git a/frontend/src/app/util/zip.cljs b/frontend/src/app/util/zip.cljs index 47f3544f65..a3b6dc3f38 100644 --- a/frontend/src/app/util/zip.cljs +++ b/frontend/src/app/util/zip.cljs @@ -31,24 +31,28 @@ (rx/map :body) (rx/flat-map zip/loadAsync))) -(defn- process-file [entry path] +(defn- process-file + [entry path type] (cond (nil? entry) - (p/rejected "No file found") + (p/rejected (str "File not found: " path)) (.-dir entry) (p/resolved {:dir path}) :else - (-> (.async entry "text") + (-> (.async entry type) (p/then #(hash-map :path path :content %))))) (defn get-file "Gets a single file from the zip archive" - [zip path] - (-> (.file zip path) - (process-file path) - (rx/from))) + ([zip path] + (get-file zip path "text")) + + ([zip path type] + (-> (.file zip path) + (process-file path type) + (rx/from)))) (defn extract-files "Creates a stream that will emit values for every file in the zip" @@ -56,7 +60,7 @@ (let [promises (atom []) get-file (fn [path entry] - (let [current (process-file entry path)] + (let [current (process-file entry path "text")] (swap! promises conj current)))] (.forEach zip get-file) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 3e9ee110d4..6a9c71ba5a 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -41,7 +41,7 @@ :pages pages :pagesIndex index :hasComponents (d/not-empty? (get-in file [:data :components])) - :hasImages (d/not-empty? (get-in file [:data :media])) + :hasMedia (d/not-empty? (get-in file [:data :media])) :hasColors (d/not-empty? (get-in file [:data :colors])) :hasTypographies (d/not-empty? (get-in file [:data :typographies]))}))))] (let [manifest {:teamId (str team-id) @@ -123,13 +123,13 @@ (->> (vals media) (reduce collect-media {}) (json/encode))] - (rx/of (vector (str file-id "/images.json") markup))) + (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 file-id "/images/" (:id media) "." (dom/mtype->extension (:mtype media)))] + (let [file-path (str file-id "/media/" (:id media) "." (dom/mtype->extension (:mtype media)))] (->> (http/send! {:uri (cfg/resolve-file-media media) :response-type :blob diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 9bbf1a4c69..b05a05a506 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -11,12 +11,14 @@ [app.common.pages :as cp] [app.common.uuid :as uuid] [app.main.repo :as rp] + [app.util.dom :as dom] [app.util.http :as http] [app.util.import.parser :as cip] [app.util.json :as json] [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx] + [cuerdas.core :as str] [tubax.core :as tubax])) ;; Upload changes batches size @@ -144,7 +146,7 @@ (rx/merge-scan f seed ob)) (rx/last))) -(defn resolve-images +(defn resolve-media [file-id node] (if (and (not (cip/close? node)) (cip/has-image? node)) @@ -172,7 +174,7 @@ (assoc :name page-name))] (->> (rx/from nodes) (rx/filter cip/shape?) - (rx/mapcat (partial resolve-images file-id)) + (rx/mapcat (partial resolve-media file-id)) (rx/reduce add-shape-file (fb/add-page file page-data)) (rx/map post-process-file) (rx/map fb/close-page))) @@ -182,20 +184,98 @@ (str dir-id "/" id ".svg")) (defn process-page [file-id zip [page-id page-name]] - (->> (uz/get-file zip (get-page-path (d/name file-id) page-id)) - (rx/map (comp tubax/xml->clj :content)) - (rx/map #(vector page-name %)))) + (let [path (get-page-path (d/name file-id) page-id)] + (->> (uz/get-file zip path) + (rx/map (comp tubax/xml->clj :content)) + (rx/map #(vector page-name %))))) -(defn process-file +(defn process-file-pages [file file-id file-desc zip] - (let [index (:pagesIndex file-desc) + (let [index (:pages-index file-desc) pages (->> (:pages file-desc) (mapv #(vector % (get-in index [(keyword %) :name]))))] (->> (rx/from pages) (rx/mapcat #(process-page file-id zip %)) - (merge-reduce import-page file) - (rx/flat-map send-changes) - (rx/ignore)))) + (merge-reduce import-page file)))) + +(defn process-library-colors + [file file-id file-desc zip] + (if (:has-colors file-desc) + (let [add-color + (fn [file [id color]] + (let [color (-> (d/kebab-keys color) + (d/update-in-when [:gradient :type] keyword)) + file (fb/add-library-color file color)] + (assoc file [:library-mapping id] (:last-id file)))) + + path (str (d/name file-id) "/colors.json")] + (->> (uz/get-file zip path) + (rx/mapcat (comp json/decode :content)) + (rx/reduce add-color file))) + + (rx/of file))) + +(defn process-library-typographies + [file file-id file-desc zip] + (if (:has-typographies file-desc) + (let [add-typography + (fn [file [id typography]] + (let [typography (d/kebab-keys typography) + file (fb/add-library-typography file typography)] + (assoc file [:library-mapping id] (:last-id file)))) + + path (str (d/name file-id) "/typographies.json")] + (->> (uz/get-file zip path) + (rx/mapcat (comp json/decode :content)) + (rx/reduce add-typography file))) + + (rx/of file))) + +(defn process-library-media + [file file-id file-desc zip] + (rx/of file) + (if (:has-media file-desc) + (let [add-media + (fn [file media] + (let [file (fb/add-library-media file (dissoc media :old-id))] + (assoc file [:library-mapping (:old-id media)] (:last-id file)))) + + path (str (d/name file-id) "/media.json")] + + (->> (uz/get-file zip path) + (rx/mapcat (comp json/decode :content)) + (rx/flat-map + (fn [[id media]] + (let [file-path (str (d/name file-id) "/media/" (d/name id) "." (dom/mtype->extension (:mtype media)))] + (->> (uz/get-file zip file-path "blob") + (rx/map (fn [{blob :content}] + (let [content (.slice blob 0 (.-size blob) (:mtype media))] + {:name (:name media) + :file-id (:id file) + :content content + :is-local false}))) + (rx/flat-map #(rp/mutation! :upload-file-media-object %)) + (rx/map (fn [response] + (-> media + (assoc :old-id id) + (assoc :id (:id response))))))))) + (rx/reduce add-media file))) + + (rx/of file))) + +(defn process-library-components + [file file-id file-desc zip] + (rx/of file)) + +(defn process-file + [file file-id file-desc zip] + (->> (process-file-pages file file-id file-desc zip) + (rx/flat-map #(process-library-colors % file-id file-desc zip)) + (rx/flat-map #(process-library-typographies % file-id file-desc zip)) + (rx/flat-map #(process-library-media % file-id file-desc zip)) + (rx/flat-map #(process-library-components % file-id file-desc zip)) + (rx/flat-map send-changes) + (rx/ignore))) (defn process-package [project-id zip-file] @@ -203,8 +283,9 @@ (rx/flat-map (comp :files json/decode :content)) (rx/flat-map (fn [[file-id file-desc]] - (->> (create-file project-id file-desc) - (rx/flat-map #(process-file % file-id file-desc zip-file))))))) + (let [file-desc (d/kebab-keys file-desc)] + (->> (create-file project-id file-desc) + (rx/flat-map #(process-file % file-id file-desc zip-file)))))))) (defmethod impl/handler :import-file [{:keys [project-id files]}] From 85a6edb1fdc3b0eedd34f1a2499656a96607aea4 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 24 Jun 2021 16:18:06 +0200 Subject: [PATCH 144/204] :sparkles: Import components --- common/src/app/common/file_builder.cljc | 12 +++++-- frontend/src/app/main/data/dashboard.cljs | 1 - frontend/src/app/main/exports.cljs | 22 ++++++------ frontend/src/app/util/import/parser.cljs | 4 +++ frontend/src/app/worker/import.cljs | 42 +++++++++++++++++++---- 5 files changed, 60 insertions(+), 21 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 7f79275784..3df7fbe1e0 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -265,7 +265,11 @@ :id group-id :operations [{:type :set :attr :selrect :val (:selrect group')} - {:type :set :attr :points :val (:points 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)}]} {:add-container? true})))] @@ -442,7 +446,11 @@ :id component-id :operations [{:type :set :attr :selrect :val (:selrect component')} - {:type :set :attr :points :val (:points 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)}]} {:add-container? true})))] diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 96bf7c077b..3879896a48 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -40,7 +40,6 @@ (s/keys :req-un [::id ::name ::team-id - ::profile-id ::created-at ::modified-at ::is-pinned])) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index ed0907b091..1642f31182 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -74,11 +74,10 @@ (mf/fnc group-wrapper [{:keys [shape frame] :as props}] (let [childs (mapv #(get objects %) (:shapes shape))] - [:& shape-container {:shape shape} - [:& group-shape {:frame frame - :shape shape - :is-child-selected? true - :childs childs}]])))) + [:& group-shape {:frame frame + :shape shape + :is-child-selected? true + :childs childs}])))) (defn svg-raw-wrapper-factory [objects] @@ -87,7 +86,9 @@ (mf/fnc svg-raw-wrapper [{:keys [shape frame] :as props}] (let [childs (mapv #(get objects %) (:shapes shape))] - (if (and (contains? shape :svg-attrs) (map? (:content shape))) + (if (and (contains? shape :svg-attrs) + (map? (:content shape)) + (not= :svg (get-in shape [:content :tag]))) [:> shape-container {:shape shape} [:& svg-raw-shape {:frame frame :shape shape @@ -108,9 +109,8 @@ (let [shape (-> (gsh/transform-shape shape) (gsh/translate-to-frame frame)) opts #js {:shape shape} - svg-element? (and (= :svg-raw (:type shape)) - (not= :svg (get-in shape [:content :tag])))] - (if-not svg-element? + svg-raw? (= :svg-raw (:type shape))] + (if-not svg-raw? [:> shape-container {:shape shape} (case (:type shape) :text [:> text/text-shape opts] @@ -120,7 +120,6 @@ :circle [:> circle/circle-shape opts] :frame [:> frame-wrapper {:shape shape}] :group [:> group-wrapper {:shape shape :frame frame}] - :svg-raw [:> svg-raw-wrapper {:shape shape :frame frame}] nil)] ;; Don't wrap svg elements inside a otherwise some can break @@ -271,7 +270,8 @@ [:symbol {:id (str id) :viewBox vbox} [:title name] - [:& group-wrapper {:shape root :view-box vbox}]])) + [:> shape-container {:shape root} + [:& group-wrapper {:shape root :view-box vbox}]]])) (mf/defc components-sprite-svg {::mf/wrap-props false} diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index 34de143e81..acfa1adb48 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -173,6 +173,10 @@ (map #(:attrs %)) (reduce add-attrs node-attrs))) + (= type :frame) + (let [svg-node (->> node :content (d/seek #(= "frame-background" (get-in % [:attrs :class]))))] + (merge (add-attrs {} (:attrs svg-node)) node-attrs)) + (= type :svg-raw) (->> node :content last) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index b05a05a506..f80014394f 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -18,7 +18,6 @@ [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx] - [cuerdas.core :as str] [tubax.core :as tubax])) ;; Upload changes batches size @@ -141,10 +140,17 @@ (dissoc :id-mapping :interactions)))) (defn merge-reduce [f seed ob] - (->> (rx/concat - (rx/of seed) - (rx/merge-scan f seed ob)) - (rx/last))) + (let [current-acc (atom seed)] + (->> (rx/concat + (rx/of seed) + (->> ob + (rx/mapcat #(f @current-acc %)) + (rx/tap #(reset! current-acc %)))) + (rx/last)))) + +(defn skip-last + [n ob] + (.pipe ob (.skipLast js/rxjsOperators (int n)))) (defn resolve-media [file-id node] @@ -233,7 +239,6 @@ (defn process-library-media [file file-id file-desc zip] - (rx/of file) (if (:has-media file-desc) (let [add-media (fn [file media] @@ -263,9 +268,32 @@ (rx/of file))) +(defn add-component [file content] + (let [content (cip/find-node content :g) + data (cip/parse-data :group content) + file (-> file (fb/start-component data)) + id (-> (get-in content [:attrs :id]) (uuid/uuid)) + component-id (:last-id file) + file (assoc file [:library-mapping id] component-id) + nodes (cip/node-seq content)] + + (->> (rx/from nodes) + (rx/filter cip/shape?) + (rx/skip 1) + (skip-last 1) + (rx/mapcat (partial resolve-media (:id file))) + (rx/reduce add-shape-file file) + (rx/map #(fb/finish-component %))))) + (defn process-library-components [file file-id file-desc zip] - (rx/of file)) + (if (:has-components file-desc) + (let [path (str (d/name file-id) "/components.svg")] + (->> (uz/get-file zip path) + (rx/map (comp tubax/xml->clj :content)) + (rx/flat-map (fn [content] (->> (cip/node-seq content) (filter #(= :symbol (:tag %)))))) + (merge-reduce add-component file))) + (rx/of file))) (defn process-file [file file-id file-desc zip] From 91b6a0bf699ea3c5717691b1eec7eacc172a5071 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 25 Jun 2021 10:34:51 +0200 Subject: [PATCH 145/204] :bug: Fix problem with shadow menu --- .../app/main/ui/workspace/sidebar/options/menus/shadow.cljs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 5cd11f6f3a..ee75d4cfcc 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 @@ -70,7 +70,9 @@ (fn [value] (when (or (not valid?) (valid? value)) (do (st/emit! (dch/update-shapes ids #(assoc-in % [:shadow index attr] value))) - (when update-ref (dom/set-value! (mf/ref-val update-ref) value))))))) + (let [update-node (and update-ref (mf/ref-val update-ref))] + (when update-node + (dom/set-value! update-node value)))))))) update-color (fn [index] From e818170eec359cc5eb48fe622962ec241ddd6e28 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 25 Jun 2021 11:27:31 +0200 Subject: [PATCH 146/204] :bug: Fix problem when exporting components with images --- frontend/src/app/main/render.cljs | 37 ++++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/frontend/src/app/main/render.cljs b/frontend/src/app/main/render.cljs index 64b760cfa4..e3eb8858e1 100644 --- a/frontend/src/app/main/render.cljs +++ b/frontend/src/app/main/render.cljs @@ -30,16 +30,16 @@ [])) (defn populate-images-cache - [data] - (let [images (->> (:objects data) + [objects] + (let [images (->> objects (vals) (mapcat get-image-data))] (->> (rx/from images) (rx/map #(cfg/resolve-file-media %)) (rx/flat-map http/fetch-data-uri)))) -(defn populate-fonts-cache [data] - (let [texts (->> (:objects data) +(defn populate-fonts-cache [objects] + (let [texts (->> objects (vals) (filterv text?) (mapv :content)) ] @@ -56,8 +56,8 @@ [data] (rx/concat (->> (rx/merge - (populate-images-cache data) - (populate-fonts-cache data)) + (populate-images-cache (:objects data)) + (populate-fonts-cache (:objects data))) (rx/ignore)) (->> (rx/of data) @@ -68,14 +68,19 @@ (defn render-components [data] - (rx/concat - (->> (rx/merge - (populate-images-cache data) - (populate-fonts-cache data)) - (rx/ignore)) + (let [;; Join all components objects into a single map + objects (->> (:components data) + (vals) + (map :objects) + (reduce conj))] + (rx/concat + (->> (rx/merge + (populate-images-cache objects) + (populate-fonts-cache objects)) + (rx/ignore)) - (->> (rx/of data) - (rx/map - (fn [data] - (let [elem (mf/element exports/components-sprite-svg #js {:data data :embed? true})] - (rds/renderToStaticMarkup elem))))))) + (->> (rx/of data) + (rx/map + (fn [data] + (let [elem (mf/element exports/components-sprite-svg #js {:data data :embed? true})] + (rds/renderToStaticMarkup elem)))))))) From 08d2dbc9bb3657dc3f823db7efb6387d548ab4c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 28 Jun 2021 12:31:08 +0200 Subject: [PATCH 147/204] :sparkles: Preserve components on copy&paste when possible --- CHANGES.md | 14 +++++++------- frontend/src/app/main/data/workspace.cljs | 13 +++++++++++-- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 6162735171..a19de1b6dc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,13 +4,13 @@ ### :sparkles: New features -- Allow nested asset groups [Taiga #1716](https://tree.taiga.io/project/penpot/us/1716) -- Allow to ungroup assets [Taiga #1719](https://tree.taiga.io/project/penpot/us/1719) -- Allow to rename assets groups [Taiga #1721](https://tree.taiga.io/project/penpot/us/1721) -- Memorize collapse state of assets in panel [Taiga #1718](https://tree.taiga.io/project/penpot/us/1718) -- Headers button sets and menus review [Taiga #1663](https://tree.taiga.io/project/penpot/us/1663) -- Add the ability to offload file data to a cheaper storage when file - becomes inactive. +- Allow nested asset groups [Taiga #1716](https://tree.taiga.io/project/penpot/us/1716). +- Allow to ungroup assets [Taiga #1719](https://tree.taiga.io/project/penpot/us/1719). +- Allow to rename assets groups [Taiga #1721](https://tree.taiga.io/project/penpot/us/1721). +- Memorize collapse state of assets in panel [Taiga #1718](https://tree.taiga.io/project/penpot/us/1718). +- Headers button sets and menus review [Taiga #1663](https://tree.taiga.io/project/penpot/us/1663). +- Preserve components if possible, when pasted into a different file [Taiga #1063](https://tree.taiga.io/project/penpot/issue/1063). +- Add the ability to offload file data to a cheaper storage when file becomes inactive. ### :bug: Bugs fixed diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index aca9860a61..d131aa2a96 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1537,6 +1537,15 @@ (assoc change :index (get map-ids (:old-id change))) change))) + ;; Check if the shape is an instance whose master is defined in a + ;; library that is not linked to the current file + (foreign-instance? [shape objects state] + (let [root (cph/get-root-shape shape objects) + root-file-id (:component-file root)] + (and (some? root) + (not= root-file-id (:current-file-id state)) + (nil? (get-in state [:workspace-libraries root-file-id]))))) + ;; Procceed with the standard shape paste procediment. (do-paste [it state mouse-pos media] (let [media-idx (d/index-by :prev-id media) @@ -1551,8 +1560,8 @@ (assoc :parent-id parent-id) (cond-> - ;; Pasting from another file, we deattach components - (not= (:current-file-id state) (:file-id data)) + ;; if foreign instance, detach the shape + (foreign-instance? shape objects state) (dissoc :component-id :component-file :component-root? From 36cca0d871fe174f3992af7188671451c23ed022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 25 Jun 2021 16:16:48 +0200 Subject: [PATCH 148/204] :bug: Reset constraints when reparenting a shape --- frontend/src/app/main/data/workspace.cljs | 48 +++++++++++++++++++++-- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index d131aa2a96..7612639031 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -15,6 +15,7 @@ [app.common.math :as mth] [app.common.pages :as cp] [app.common.pages.helpers :as cph] + [app.common.pages.spec :as spec] [app.common.spec :as us] [app.common.transit :as t] [app.common.uuid :as uuid] @@ -702,7 +703,9 @@ ;; --- Change Shape Order (D&D Ordering) -(defn relocate-shapes-changes [objects parents parent-id page-id to-index ids groups-to-delete groups-to-unmask shapes-to-detach shapes-to-reroot shapes-to-deroot] +(defn relocate-shapes-changes [objects parents parent-id page-id to-index ids + groups-to-delete groups-to-unmask shapes-to-detach + shapes-to-reroot shapes-to-deroot shapes-to-unconstraint] (let [;; Changes to the shapes that are being move r-mov-change [{:type :mov-objects @@ -835,6 +838,43 @@ :val nil}]}) shapes-to-reroot) + + ;; Changes resetting constraints + + r-unconstraint-change + (map (fn [id] + (let [obj (get objects id) + parent (get objects parent-id) + frame-id (if (= (:type parent) :frame) + (:id parent) + (:frame-id parent))] + {:type :mod-obj + :page-id page-id + :id id + :operations [{:type :set + :attr :constraints-h + :val (spec/default-constraints-h + (assoc obj :parent-id parent-id :frame-id frame-id))} + {:type :set + :attr :constraints-v + :val (spec/default-constraints-v + (assoc obj :parent-id parent-id :frame-id frame-id))}]})) + shapes-to-unconstraint) + + u-unconstraint-change + (map (fn [id] + (let [obj (get objects id)] + {:type :mod-obj + :page-id page-id + :id id + :operations [{:type :set + :attr :constraints-h + :val (:constraints-h obj)} + {:type :set + :attr :constraints-v + :val (:constraints-v obj)}]})) + shapes-to-unconstraint) + r-reg-change [{:type :reg-objects :page-id page-id @@ -852,6 +892,7 @@ r-detach-change r-deroot-change r-reroot-change + r-unconstraint-change r-reg-change) uchanges (d/concat [] @@ -861,6 +902,7 @@ u-detach-change u-mask-change u-mov-change + u-unconstraint-change u-reg-change)] [rchanges uchanges])) @@ -971,9 +1013,9 @@ groups-to-unmask shapes-to-detach shapes-to-reroot - shapes-to-deroot) + shapes-to-deroot + ids)] - ] (rx/of (dch/commit-changes {:redo-changes rchanges :undo-changes uchanges :origin it}) From 484eb3a7c43fdbebdb90b06dd2a2d3b8628bf33d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 29 Jun 2021 21:19:34 +0200 Subject: [PATCH 149/204] :sparkles: Allow to set up id for media --- backend/src/app/rpc/mutations/media.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/src/app/rpc/mutations/media.clj b/backend/src/app/rpc/mutations/media.clj index 62241a48bd..8a5d0d518b 100644 --- a/backend/src/app/rpc/mutations/media.clj +++ b/backend/src/app/rpc/mutations/media.clj @@ -48,7 +48,7 @@ :opt-un [::id])) (sv/defmethod ::upload-file-media-object - [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id id] :as params}] (db/with-atomic [conn pool] (let [file (select-file conn file-id)] (teams/check-edition-permissions! conn profile-id (:team-id file)) @@ -92,7 +92,7 @@ (defn create-file-media-object - [{:keys [conn storage] :as cfg} {:keys [file-id is-local name content] :as params}] + [{:keys [conn storage] :as cfg} {:keys [id file-id is-local name content] :as params}] (media/validate-media-type (:content-type content)) (let [storage (assoc storage :conn conn) source-path (fs/path (:tempfile content)) @@ -118,7 +118,7 @@ (sto/put-object storage {:content (sto/content (:data thumb) (:size thumb)) :content-type (:mtype thumb)}))] (db/insert! conn :file-media-object - {:id (uuid/next) + {:id (or id (uuid/next)) :file-id file-id :is-local is-local :name name From 4c84b18bb6ce6240102981f2a9745b03dc68b276 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 29 Jun 2021 21:56:05 +0200 Subject: [PATCH 150/204] :sparkles: Add library linking to export/import --- backend/src/app/rpc/mutations/media.clj | 2 +- common/src/app/common/data.cljc | 5 +- common/src/app/common/file_builder.cljc | 24 +- frontend/src/app/main/ui/shapes/export.cljs | 118 +++-- frontend/src/app/util/import/parser.cljs | 56 ++- frontend/src/app/worker/export.cljs | 54 ++- frontend/src/app/worker/import.cljs | 457 +++++++++++++------- 7 files changed, 485 insertions(+), 231 deletions(-) diff --git a/backend/src/app/rpc/mutations/media.clj b/backend/src/app/rpc/mutations/media.clj index 8a5d0d518b..3467522f1d 100644 --- a/backend/src/app/rpc/mutations/media.clj +++ b/backend/src/app/rpc/mutations/media.clj @@ -48,7 +48,7 @@ :opt-un [::id])) (sv/defmethod ::upload-file-media-object - [{:keys [pool] :as cfg} {:keys [profile-id file-id id] :as params}] + [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] (db/with-atomic [conn pool] (let [file (select-file conn file-id)] (teams/check-edition-permissions! conn profile-id (:team-id file)) diff --git a/common/src/app/common/data.cljc b/common/src/app/common/data.cljc index 8ad5ec59a0..3d227cb380 100644 --- a/common/src/app/common/data.cljc +++ b/common/src/app/common/data.cljc @@ -547,6 +547,7 @@ (cond (or (vector? v) (map? v)) [k (deep-mapm mfn v)] + :else (mfn [k v]))))] (cond @@ -567,4 +568,6 @@ (->> m (deep-mapm (fn [[k v]] - [(keyword (str/kebab (name k))) v])))) + (if (or (keyword? k) (string? k)) + [(keyword (str/kebab (name k))) v] + [k v]))))) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 3df7fbe1e0..13b009128e 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -150,20 +150,22 @@ (defn create-file ([name] - (let [id (uuid/next)] - {:id id - :name name - :data (-> init/empty-file-data - (assoc :id id)) + (create-file (uuid/next) name)) - ;; We keep the changes so we can send them to the backend - :changes []}))) + ([id name] + {:id id + :name name + :data (-> init/empty-file-data + (assoc :id id)) + + ;; We keep the changes so we can send them to the backend + :changes []})) (defn add-page [file data] (assert (nil? (:current-component-id file))) - (let [page-id (uuid/next) + (let [page-id (or (:id data) (uuid/next)) page (-> init/empty-page-data (assoc :id page-id) (d/deep-merge data))] @@ -329,10 +331,10 @@ (update :parent-stack pop))) (defn add-interaction - [file action-type event-type from-id destination-id] + [file from-id {:keys [action-type event-type destination]}] (assert (some? (lookup-shape file from-id)) (str "Cannot locate shape with id " from-id)) - (assert (some? (lookup-shape file destination-id)) (str "Cannot locate shape with id " destination-id)) + (assert (some? (lookup-shape file destination)) (str "Cannot locate shape with id " destination)) (let [interactions (->> (lookup-shape file from-id) :interactions @@ -342,7 +344,7 @@ (conjv {:action-type action-type :event-type event-type - :destination destination-id}))] + :destination destination}))] (commit-change file {:type :mod-obj diff --git a/frontend/src/app/main/ui/shapes/export.cljs b/frontend/src/app/main/ui/shapes/export.cljs index 45068e47ca..b8fa606f32 100644 --- a/frontend/src/app/main/ui/shapes/export.cljs +++ b/frontend/src/app/main/ui/shapes/export.cljs @@ -29,62 +29,88 @@ :else nil)) +(defn uuid->string [m] + (->> m + (d/deep-mapm + (fn [[k v]] + (if (uuid? v) + [k (str v)] + [k v]))))) + (defn bool->str [val] (when (some? val) (str val))) +(defn add-factory [shape] + (fn add! + ([props attr] + (add! props attr str)) + + ([props attr trfn] + (let [val (get shape attr) + val (if (keyword? val) (d/name val) val) + ns-attr (str "penpot:" (-> attr d/name))] + (cond-> props + (some? val) + (obj/set! ns-attr (trfn val))))))) + (defn add-data "Adds as metadata properties that we cannot deduce from the exported SVG" [props shape] - (letfn [(add! - ([props attr] - (add! props attr str)) + (let [add! (add-factory shape) + group? (= :group (:type shape)) + rect? (= :rect (:type shape)) + text? (= :text (:type shape)) + mask? (and group? (:masked-group? shape)) + center (gsh/center-shape shape)] + (-> props + (add! :name) + (add! :blocked) + (add! :hidden) + (add! :type) + (add! :stroke-style) + (add! :stroke-alignment) + (add! :transform) + (add! :transform-inverse) + (add! :flip-x) + (add! :flip-y) + (add! :proportion) + (add! :proportion-lock) + (add! :rotation) + (obj/set! "penpot:center-x" (-> center :x str)) + (obj/set! "penpot:center-y" (-> center :y str)) - ([props attr trfn] - (let [val (get shape attr) - val (if (keyword? val) (d/name val) val) - ns-attr (str "penpot:" (-> attr d/name))] - (cond-> props - (some? val) - (obj/set! ns-attr (trfn val))))))] - (let [group? (= :group (:type shape)) - rect? (= :rect (:type shape)) - text? (= :text (:type shape)) - mask? (and group? (:masked-group? shape)) - center (gsh/center-shape shape)] - (-> props - (add! :name) - (add! :blocked) - (add! :hidden) - (add! :type) - (add! :stroke-style) - (add! :stroke-alignment) - (add! :transform) - (add! :transform-inverse) - (add! :flip-x) - (add! :flip-y) - (add! :proportion) - (add! :proportion-lock) - (add! :rotation) - (obj/set! "penpot:center-x" (-> center :x str)) - (obj/set! "penpot:center-y" (-> center :y str)) + ;; Constraints + (add! :constraints-h) + (add! :constraints-v) + (add! :fixed-scroll) - ;; Constraints - (add! :constraints-h) - (add! :constraints-v) - (add! :fixed-scroll) + (cond-> (and rect? (some? (:r1 shape))) + (-> (add! :r1) + (add! :r2) + (add! :r3) + (add! :r4))) - (cond-> (and rect? (some? (:r1 shape))) - (-> (add! :r1) - (add! :r2) - (add! :r3) - (add! :r4))) + (cond-> text? + (-> (add! :grow-type) + (add! :content (comp json/encode uuid->string)))) - (cond-> text? - (-> (add! :grow-type) - (add! :content json/encode))) + (cond-> mask? + (obj/set! "penpot:masked-group" "true"))))) - (cond-> mask? - (obj/set! "penpot:masked-group" "true")))))) + +(defn add-library-refs [props shape] + (let [add! (add-factory shape)] + (-> props + (add! :fill-color-ref-id) + (add! :fill-color-ref-file) + (add! :stroke-color-ref-id) + (add! :stroke-color-ref-file) + (add! :typography-ref-id) + (add! :typography-ref-file) + (add! :component-file) + (add! :component-id) + (add! :component-root) + (add! :shape-ref)))) (defn prefix-keys [m] (letfn [(prefix-entry [[k v]] @@ -191,7 +217,7 @@ (mf/defc export-data [{:keys [shape]}] - (let [props (-> (obj/new) (add-data shape))] + (let [props (-> (obj/new) (add-data shape) (add-library-refs shape))] [:> "penpot:shape" props [:& export-shadow-data shape] [:& export-blur-data shape] diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index acfa1adb48..b4d68d0b39 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -158,6 +158,27 @@ (d/deep-mapm (comp camelize fix-style) m))) +(defn string->uuid + "Looks in a map for keys or values that have uuid shape and converts them + into uuid objects" + [m] + (letfn [(convert [value] + (cond + (and (string? value) (re-matches uuid-regex value)) + (uuid/uuid value) + + (and (keyword? value) (re-matches uuid-regex (d/name value))) + (uuid/uuid (d/name value)) + + (vector? value) + (mapv convert value) + + :else + value))] + (->> m + (d/deep-mapm + (fn [pair] (->> pair (mapv convert))))))) + (def search-data-node? #{:rect :image :path :text :circle}) (defn get-svg-data @@ -397,7 +418,7 @@ [props node] (-> props (assoc :grow-type (get-meta node :grow-type keyword)) - (assoc :content (get-meta node :content json/decode)))) + (assoc :content (get-meta node :content (comp string->uuid json/decode))))) (defn add-group-data [props node] @@ -605,6 +626,37 @@ svg-data (or image-data pattern-data)] (:xlink:href svg-data))) +(defn add-library-refs + [props node] + + (let [fill-color-ref-id (get-meta node :fill-color-ref-id uuid/uuid) + fill-color-ref-file (get-meta node :fill-color-ref-file uuid/uuid) + stroke-color-ref-id (get-meta node :stroke-color-ref-id uuid/uuid) + stroke-color-ref-file (get-meta node :stroke-color-ref-file uuid/uuid) + component-id (get-meta node :component-id uuid/uuid) + component-file (get-meta node :component-file uuid/uuid) + shape-ref (get-meta node :shape-ref uuid/uuid) + component-root? (get-meta node :component-root str->bool)] + + (cond-> props + (some? fill-color-ref-id) + (assoc :fill-color-ref-id fill-color-ref-id + :fill-color-ref-file fill-color-ref-file) + + (some? stroke-color-ref-id) + (assoc :stroke-color-ref-id stroke-color-ref-id + :stroke-color-ref-file stroke-color-ref-file) + + (some? component-id) + (assoc :component-id component-id + :component-file component-file) + + component-root? + (assoc :component-root? component-root?) + + (some? shape-ref) + (assoc :shape-ref shape-ref)))) + (defn parse-data [type node] @@ -620,6 +672,7 @@ (add-blur node) (add-exports node) (add-svg-attrs node svg-data) + (add-library-refs node) (cond-> (= :svg-raw type) (add-svg-content node)) @@ -661,3 +714,4 @@ {:destination (get-meta node :destination uuid/uuid) :action-type (get-meta node :action-type keyword) :event-type (get-meta node :event-type keyword)}))))) + diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 6a9c71ba5a..34377667b6 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -18,6 +18,12 @@ [beicon.core :as rx] [cuerdas.core :as str])) +(defn rx-expand + "Recursively projects each source value to an Observable + which is merged in the output Observable." + [f ob] + (.pipe ob (.expand ^js js/rxjsOperators f))) + (defn create-manifest "Creates a manifest entry for the given files" [team-id file-id files] @@ -40,6 +46,7 @@ :shared is-shared :pages pages :pagesIndex index + :libraries (->> (:libraries file) (into #{}) (mapv str)) :hasComponents (d/not-empty? (get-in file [:data :components])) :hasMedia (d/not-empty? (get-in file [:data :media])) :hasColors (d/not-empty? (get-in file [:data :colors])) @@ -142,16 +149,49 @@ (->> (r/render-components (:data file)) (rx/map #(vector (str (:id file) "/components.svg") %)))) +(defn fetch-file-with-libraries [file-id] + (->> (rx/zip (rp/query :file {:id file-id}) + (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 collect-files + [file-id] + + (letfn [(fetch-dependencies [[files pending]] + (if (empty? pending) + ;; When not pending, we finish the generation + (rx/empty) + + ;; Still pending files, fetch the next one + (let [next (peek pending) + pending (pop pending)] + (if (contains? files next) + ;; The file is already in the result + (rx/of [files pending]) + + (->> (fetch-file-with-libraries next) + (rx/map + (fn [file] + [(-> files + (assoc (:id file) file)) + (as-> pending $ + (reduce conj $ (:libraries file)))])))))))] + (let [files {} + pending [file-id]] + (->> (rx/of [files pending]) + (rx-expand fetch-dependencies) + (rx/last) + (rx/map first))))) + (defn export-file [team-id file-id] - (let [files-stream - (->> (rx/merge (rp/query :file {:id file-id}) - (->> (rp/query :file-libraries {:file-id file-id}) - (rx/flat-map identity) - (rx/map #(assoc % :is-shared true)))) - (rx/reduce #(assoc %1 (:id %2) %2) {}) - (rx/share)) + (let [files-stream (->> (collect-files file-id) + (rx/share)) manifest-stream (->> files-stream diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index f80014394f..21a3dc9111 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -5,10 +5,12 @@ ;; Copyright (c) UXBOX Labs SL (ns app.worker.import + (:refer-clojure :exclude [resolve]) (:require [app.common.data :as d] [app.common.file-builder :as fb] [app.common.pages :as cp] + [app.common.text :as ct] [app.common.uuid :as uuid] [app.main.repo :as rp] [app.util.dom :as dom] @@ -18,23 +20,110 @@ [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx] + [cuerdas.core :as str] [tubax.core :as tubax])) +;;; TODO: Move to funcool/beicon + +(defn rx-merge-reduce [f seed ob] + (let [current-acc (atom seed)] + (->> (rx/concat + (rx/of seed) + (->> ob + (rx/mapcat #(f @current-acc %)) + (rx/tap #(reset! current-acc %)))) + (rx/last)))) + +(defn rx-skip-last + [n ob] + (.pipe ob (.skipLast js/rxjsOperators (int n)))) + ;; Upload changes batches size (def change-batch-size 100) +(defn get-file + "Resolves the file inside the context given its id and the data" + ([context type] + (get-file context type nil nil)) + + ([context type id] + (get-file context type id nil)) + + ([context type id media] + (let [file-id (:file-id context) + path (case type + :manifest (str "manifest.json") + :page (str file-id "/" id ".svg") + :colors (str file-id "/colors.json") + :typographies (str file-id "/typographies.json") + :media-list (str file-id "/media.json") + :media (let [ext (dom/mtype->extension (:mtype media))] + (str file-id "/media/" id "." ext)) + :components (str file-id "/components.svg")) + + svg? (str/ends-with? path "svg") + json? (str/ends-with? path "json") + other? (not (or svg? json?)) + + file-type (if other? "blob" "text")] + + (cond->> (uz/get-file (:zip context) path file-type) + svg? + (rx/map (comp tubax/xml->clj :content)) + + json? + (rx/map (comp json/decode :content)) + + other? + (rx/map :content))))) + +(defn resolve-factory + "Creates a wrapper around the atom to remap ids to new ids and keep + their relationship so they ids are coherent." + [] + (let [id-mapping-atom (atom {}) + resolve + (fn [id-mapping id] + (assert (uuid? id)) + (get id-mapping id)) + + set-id + (fn [id-mapping id] + (assert (uuid? id)) + (cond-> id-mapping + (nil? (resolve id-mapping id)) + (assoc id (uuid/next))))] + + (fn [id] + (swap! id-mapping-atom set-id id) + (resolve @id-mapping-atom id)))) + (defn create-file "Create a new file on the back-end" - [project-id file-desc] - (let [file-id (uuid/next)] + [context file-id] + (let [resolve (:resolve context) + file-id (resolve file-id)] (rp/mutation :create-temp-file {:id file-id - :name (:name file-desc) - :is-shared (:shared file-desc) - :project-id project-id + :name (:name context) + :is-shared (:shared context) + :project-id (:project-id context) :data (-> cp/empty-file-data (assoc :id file-id))}))) +(defn persist-file [file] + (rp/mutation :persist-temp-file {:id (:id file)})) + +(defn link-file-libraries + "Create a new file on the back-end" + [context file-id] + (let [resolve (:resolve context) + file-id (resolve file-id) + 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))))) + (defn send-changes "Creates batches of changes to be sent to the backend" [file] @@ -58,7 +147,7 @@ (rx/map first) (rx/tap #(reset! revn (:revn %)))) - (rp/mutation :persist-temp-file {:id (:id file)})))) + (rp/mutation :persist-temp-file {:id file-id})))) (defn upload-media-files "Upload a image to the backend and returns its id" @@ -76,8 +165,34 @@ :is-local true})) (rx/flat-map #(rp/mutation! :upload-file-media-object %)))) -(defn add-shape-file - [file node] +(defn resolve-text-content [node context] + (let [resolve (:resolve context)] + (->> node + (ct/transform-nodes + (fn [item] + (-> item + (d/update-when :fill-color-ref-id resolve) + (d/update-when :fill-color-ref-file resolve) + (d/update-when :typography-ref-id resolve) + (d/update-when :typography-ref-file resolve))))))) + +(defn resolve-data-ids + [data type context] + (let [resolve (:resolve context)] + (-> data + (d/update-when :fill-color-ref-id resolve) + (d/update-when :fill-color-ref-file resolve) + (d/update-when :stroke-color-ref-id resolve) + (d/update-when :stroke-color-ref-file resolve) + (d/update-when :component-id resolve) + (d/update-when :component-file resolve) + (d/update-when :shape-ref resolve) + + (cond-> (= type :text) + (d/update-when :content resolve-text-content context))))) + +(defn process-import-node + [context file node] (let [type (cip/get-type node) close? (cip/close? node)] @@ -88,18 +203,23 @@ :svg-raw (fb/close-svg-raw file) #_default file) - (let [data (cip/parse-data type node) + (let [resolve (:resolve context) old-id (cip/get-id node) - interactions (cip/parse-interactions node) + interactions (->> (cip/parse-interactions node) + (mapv #(update % :destination resolve))) + + data (-> (cip/parse-data type node) + (resolve-data-ids type context) + (assoc :id (resolve old-id))) file (case type - :frame (fb/add-artboard file data) - :group (fb/add-group file data) - :rect (fb/create-rect file data) - :circle (fb/create-circle file data) - :path (fb/create-path file data) - :text (fb/create-text file data) - :image (fb/create-image file data) + :frame (fb/add-artboard file data) + :group (fb/add-group file data) + :rect (fb/create-rect file data) + :circle (fb/create-circle file data) + :path (fb/create-path file data) + :text (fb/create-text file data) + :image (fb/create-image file data) :svg-raw (fb/create-svg-raw file data) #_default file)] @@ -108,49 +228,24 @@ ;; We store this data for post-processing after every shape has been ;; added (cond-> file - (some? (:last-id file)) - (assoc-in [:id-mapping old-id] (:last-id file)) - (d/not-empty? interactions) - (assoc-in [:interactions old-id] interactions)))))) + (assoc-in [:interactions (:id data)] interactions)))))) -(defn post-process-file +(defn setup-interactions [file] - (letfn [(add-interaction - [id file {:keys [action-type event-type destination] :as interaction}] - (fb/add-interaction file action-type event-type id destination)) - - (add-interactions - [file [old-id interactions]] - (let [id (get-in file [:id-mapping old-id])] - (->> interactions - (mapv (fn [interaction] - (let [id (get-in file [:id-mapping (:destination interaction)])] - (assoc interaction :destination id)))) - (reduce - (partial add-interaction id) file)))) + (letfn [(add-interactions + [file [id interactions]] + (->> interactions + (reduce #(fb/add-interaction %1 id %2) file))) (process-interactions [file] - (reduce add-interactions file (:interactions file)))] + (let [interactions (:interactions file) + file (dissoc file :interactions)] + (->> interactions (reduce add-interactions file))))] - (-> file - (process-interactions) - (dissoc :id-mapping :interactions)))) - -(defn merge-reduce [f seed ob] - (let [current-acc (atom seed)] - (->> (rx/concat - (rx/of seed) - (->> ob - (rx/mapcat #(f @current-acc %)) - (rx/tap #(reset! current-acc %)))) - (rx/last)))) - -(defn skip-last - [n ob] - (.pipe ob (.skipLast js/rxjsOperators (int n)))) + (-> file process-interactions))) (defn resolve-media [file-id node] @@ -172,155 +267,189 @@ (rx/observe-on :async)))) (defn import-page - [file [page-name content]] - (if (cip/valid? content) - (let [nodes (->> content cip/node-seq) - file-id (:id file) - page-data (-> (cip/parse-page-data content) - (assoc :name page-name))] - (->> (rx/from nodes) - (rx/filter cip/shape?) - (rx/mapcat (partial resolve-media file-id)) - (rx/reduce add-shape-file (fb/add-page file page-data)) - (rx/map post-process-file) - (rx/map fb/close-page))) - (rx/empty))) + [context file [page-id page-name content]] + (let [nodes (->> content cip/node-seq) + file-id (:id file) + resolve (:resolve context) + page-data (-> (cip/parse-page-data content) + (assoc :name page-name) + (assoc :id (resolve page-id))) + file (-> file (fb/add-page page-data))] + (->> (rx/from nodes) + (rx/filter cip/shape?) + (rx/mapcat (partial resolve-media file-id)) + (rx/reduce (partial process-import-node context) file) + (rx/map (comp fb/close-page setup-interactions))))) -(defn get-page-path [dir-id id] - (str dir-id "/" id ".svg")) +(defn import-component [context file node] + (let [resolve (:resolve context) + content (cip/find-node node :g) + file-id (:id file) + old-id (cip/get-id node) + id (resolve old-id) + data (-> (cip/parse-data :group content) + (assoc :id id)) -(defn process-page [file-id zip [page-id page-name]] - (let [path (get-page-path (d/name file-id) page-id)] - (->> (uz/get-file zip path) - (rx/map (comp tubax/xml->clj :content)) - (rx/map #(vector page-name %))))) + file (-> file (fb/start-component data)) + children (cip/node-seq node)] + + (->> (rx/from children) + (rx/filter cip/shape?) + (rx/skip 1) + (rx-skip-last 1) + (rx/mapcat (partial resolve-media file-id)) + (rx/reduce (partial process-import-node context) file) + (rx/map fb/finish-component)))) + +(defn process-pages + [context file] + (let [index (:pages-index context) + get-page-data + (fn [page-id] + [page-id (get-in index [page-id :name])]) + + pages (->> (:pages context) (mapv get-page-data))] -(defn process-file-pages - [file file-id file-desc zip] - (let [index (:pages-index file-desc) - pages (->> (:pages file-desc) - (mapv #(vector % (get-in index [(keyword %) :name]))))] (->> (rx/from pages) - (rx/mapcat #(process-page file-id zip %)) - (merge-reduce import-page file)))) + (rx/mapcat + (fn [[page-id page-name]] + (->> (get-file context :page page-id) + (rx/map (fn [page-data] [page-id page-name page-data]))))) + (rx-merge-reduce (partial import-page context) file)))) (defn process-library-colors - [file file-id file-desc zip] - (if (:has-colors file-desc) - (let [add-color + [context file] + (if (:has-colors context) + (let [resolve (:resolve context) + add-color (fn [file [id color]] - (let [color (-> (d/kebab-keys color) - (d/update-in-when [:gradient :type] keyword)) - file (fb/add-library-color file color)] - (assoc file [:library-mapping id] (:last-id file)))) - - path (str (d/name file-id) "/colors.json")] - (->> (uz/get-file zip path) - (rx/mapcat (comp json/decode :content)) + (let [color (-> color + (d/update-in-when [:gradient :type] keyword) + (assoc :id (resolve id)))] + (fb/add-library-color file color)))] + (->> (get-file context :colors) + (rx/flat-map (comp d/kebab-keys cip/string->uuid)) (rx/reduce add-color file))) (rx/of file))) (defn process-library-typographies - [file file-id file-desc zip] - (if (:has-typographies file-desc) - (let [add-typography - (fn [file [id typography]] - (let [typography (d/kebab-keys typography) - file (fb/add-library-typography file typography)] - (assoc file [:library-mapping id] (:last-id file)))) - - path (str (d/name file-id) "/typographies.json")] - (->> (uz/get-file zip path) - (rx/mapcat (comp json/decode :content)) - (rx/reduce add-typography file))) + [context file] + (if (:has-typographies context) + (let [resolve (:resolve context)] + (->> (get-file context :typographies) + (rx/flat-map (comp d/kebab-keys cip/string->uuid)) + (rx/map (fn [[id typography]] + (-> typography + (d/kebab-keys) + (assoc :id (resolve id))))) + (rx/reduce fb/add-library-typography file))) (rx/of file))) (defn process-library-media - [file file-id file-desc zip] - (if (:has-media file-desc) - (let [add-media - (fn [file media] - (let [file (fb/add-library-media file (dissoc media :old-id))] - (assoc file [:library-mapping (:old-id media)] (:last-id file)))) - - path (str (d/name file-id) "/media.json")] - - (->> (uz/get-file zip path) - (rx/mapcat (comp json/decode :content)) + [context file] + (if (:has-media context) + (let [resolve (:resolve context)] + (->> (get-file context :media-list) + (rx/flat-map (comp d/kebab-keys cip/string->uuid)) (rx/flat-map (fn [[id media]] - (let [file-path (str (d/name file-id) "/media/" (d/name id) "." (dom/mtype->extension (:mtype media)))] - (->> (uz/get-file zip file-path "blob") - (rx/map (fn [{blob :content}] + (let [media (assoc media :id (resolve id))] + (->> (get-file context :media id media) + (rx/map (fn [blob] (let [content (.slice blob 0 (.-size blob) (:mtype media))] {:name (:name media) + :id (:id media) :file-id (:id file) :content content :is-local false}))) (rx/flat-map #(rp/mutation! :upload-file-media-object %)) - (rx/map (fn [response] - (-> media - (assoc :old-id id) - (assoc :id (:id response))))))))) - (rx/reduce add-media file))) + (rx/map (constantly media)))))) + (rx/reduce fb/add-library-media file))) (rx/of file))) -(defn add-component [file content] - (let [content (cip/find-node content :g) - data (cip/parse-data :group content) - file (-> file (fb/start-component data)) - id (-> (get-in content [:attrs :id]) (uuid/uuid)) - component-id (:last-id file) - file (assoc file [:library-mapping id] component-id) - nodes (cip/node-seq content)] - - (->> (rx/from nodes) - (rx/filter cip/shape?) - (rx/skip 1) - (skip-last 1) - (rx/mapcat (partial resolve-media (:id file))) - (rx/reduce add-shape-file file) - (rx/map #(fb/finish-component %))))) - (defn process-library-components - [file file-id file-desc zip] - (if (:has-components file-desc) - (let [path (str (d/name file-id) "/components.svg")] - (->> (uz/get-file zip path) - (rx/map (comp tubax/xml->clj :content)) - (rx/flat-map (fn [content] (->> (cip/node-seq content) (filter #(= :symbol (:tag %)))))) - (merge-reduce add-component file))) + [context file] + (if (:has-components context) + (let [split-components + (fn [content] (->> (cip/node-seq content) + (filter #(= :symbol (:tag %)))))] + + (->> (get-file context :components) + (rx/flat-map split-components) + (rx-merge-reduce (partial import-component context) file))) (rx/of file))) (defn process-file - [file file-id file-desc zip] - (->> (process-file-pages file file-id file-desc zip) - (rx/flat-map #(process-library-colors % file-id file-desc zip)) - (rx/flat-map #(process-library-typographies % file-id file-desc zip)) - (rx/flat-map #(process-library-media % file-id file-desc zip)) - (rx/flat-map #(process-library-components % file-id file-desc zip)) + [context file] + + (->> (rx/of file) + (rx/flat-map (partial process-pages context)) + (rx/flat-map (partial process-library-colors context)) + (rx/flat-map (partial process-library-typographies context)) + (rx/flat-map (partial process-library-media context)) + (rx/flat-map (partial process-library-components context)) (rx/flat-map send-changes) (rx/ignore))) -(defn process-package - [project-id zip-file] - (->> (uz/get-file zip-file "manifest.json") - (rx/flat-map (comp :files json/decode :content)) +(defn create-files [context manifest] + (->> manifest :files rx/from (rx/flat-map (fn [[file-id file-desc]] - (let [file-desc (d/kebab-keys file-desc)] - (->> (create-file project-id file-desc) - (rx/flat-map #(process-file % file-id file-desc zip-file)))))))) + (create-file (merge context file-desc) file-id))) + (rx/reduce #(assoc %1 (:id %2) %2) {}))) + +(defn link-libraries [context manifest] + (->> manifest :files rx/from + (rx/flat-map + (fn [[file-id file-desc]] + (link-file-libraries (merge context file-desc) file-id))))) + +(defn process-files [context manifest files] + (->> manifest :files rx/from + (rx/flat-map + (fn [[file-id file-desc]] + (let [resolve (:resolve context) + context (-> context + (merge file-desc) + (assoc :file-id file-id)) + file (get files (resolve file-id))] + (process-file context file)))))) + +(defn process-package + [context] + (->> (get-file context :manifest) + (rx/map (comp d/kebab-keys cip/string->uuid)) + + ;; Create the temporary files + (rx/mapcat (fn [manifest] + (->> (create-files context manifest) + (rx/map #(vector manifest %))))) + + ;; Set-up the files dependencies + (rx/mapcat (fn [[manifest files]] + (rx/concat + (link-libraries context manifest) + (rx/of [manifest files])))) + + ;; Creates files data + (rx/mapcat (fn [[manifest files]] + (process-files context manifest files))) + + ;; Mark temporary files as persisted + (rx/mapcat persist-file))) (defmethod impl/handler :import-file [{:keys [project-id files]}] - (->> (rx/from files) - (rx/flat-map uz/load-from-url) - (rx/flat-map (partial process-package project-id)) - (rx/catch - (fn [err] - (.error js/console "ERROR" err (clj->js (.-data err))))))) + (let [context {:project-id project-id + :resolve (resolve-factory)}] + (->> (rx/from files) + (rx/flat-map uz/load-from-url) + (rx/map #(assoc context :zip %)) + (rx/flat-map process-package) + (rx/catch + (fn [err] + (.error js/console "ERROR" err (clj->js (.-data err)))))))) From 61b7c279d6e8b1c9ab093008c77f01e35ca9aba4 Mon Sep 17 00:00:00 2001 From: elhombretecla Date: Wed, 30 Jun 2021 09:14:23 +0200 Subject: [PATCH 151/204] :lipstick: Change sidebar order --- .../workspace/sidebar/options/menus/measures.cljs | 13 +++++++------ .../ui/workspace/sidebar/options/shapes/group.cljs | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) 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 7ac385827a..ae9ba63ba4 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 @@ -419,9 +419,10 @@ [:option {:value "topbottom"} (tr "workspace.options.constraints.topbottom")] [:option {:value "center"} (tr "workspace.options.constraints.center")] [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] - (when first-level? - [:div.row-flex - [:div.fix-when {:class (dom/classnames :active (:fixed-scroll values)) - :on-click on-fixed-scroll-clicked} - i/pin - [:span (tr "workspace.options.constraints.fix-when-scrolling")]]])]]]])])) + ;; (when first-level? + ;; [:div.row-flex + ;; [:div.fix-when {:class (dom/classnames :active (:fixed-scroll values)) + ;; :on-click on-fixed-scroll-clicked} + ;; i/pin + ;; [:span (tr "workspace.options.constraints.fix-when-scrolling")]]]) + ]]]])])) 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 eae1f87461..377d136be9 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 @@ -40,8 +40,8 @@ [:div.options [:& measures-menu {:type type :ids measure-ids :values measure-values}] - [:& layer-menu {:type type :ids layer-ids :values layer-values}] [:& component-menu {:ids comp-ids :values comp-values}] + [:& layer-menu {:type type :ids layer-ids :values layer-values}] (when-not (empty? fill-ids) [:& fill-menu {:type type :ids fill-ids :values fill-values}]) From 5f1ed511eaf3e94db555e30b53ee14352fa58a85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 30 Jun 2021 14:14:24 +0200 Subject: [PATCH 152/204] :recycle: Refactor to separate constraints to its own module --- .../sidebar/options/menus/constraints.cljs | 179 ++++++++++++++++++ .../sidebar/options/menus/measures.cljs | 142 +------------- .../sidebar/options/shapes/circle.cljs | 6 +- .../sidebar/options/shapes/group.cljs | 21 +- .../sidebar/options/shapes/image.cljs | 9 +- .../sidebar/options/shapes/multiple.cljs | 154 ++++++++------- .../sidebar/options/shapes/path.cljs | 6 +- .../sidebar/options/shapes/rect.cljs | 5 + .../sidebar/options/shapes/svg_raw.cljs | 7 + .../sidebar/options/shapes/text.cljs | 5 + 10 files changed, 313 insertions(+), 221 deletions(-) create mode 100644 frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs 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 new file mode 100644 index 0000000000..a0ec0508fc --- /dev/null +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/constraints.cljs @@ -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) UXBOX Labs SL + +(ns app.main.ui.workspace.sidebar.options.menus.constraints + (:require + [app.common.data :as d] + [app.common.geom.shapes :as gsh] + [app.common.pages.spec :as spec] + [app.common.uuid :as uuid] + [app.main.data.workspace.changes :as dch] + [app.main.refs :as refs] + [app.main.store :as st] + [app.main.ui.icons :as i] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [cuerdas.core :as str] + [rumext.alpha :as mf])) + +(def constraint-attrs [:constraints-h + :constraints-v + :fixed-scroll + :parent-id + :frame-id]) + +(mf/defc constraints-menu + [{:keys [ids values] :as props}] + (let [old-shapes (deref (refs/objects-by-id ids)) + 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)] + (cond-> values + (not= (:x values) :multiple) (assoc :x x) + (not= (:y values) :multiple) (assoc :y y))) + + values (let [{:keys [width height]} (-> shapes first :selrect)] + (cond-> values + (not= (:width values) :multiple) (assoc :width width) + (not= (:height values) :multiple) (assoc :height height))) + + in-frame? (and (some? ids) + (not= (:parent-id values) uuid/zero)) + ;; TODO: uncomment when fixed-scroll is fully implemented + ;; first-level? (and in-frame? + ;; (= (:parent-id values) (:frame-id values))) + + constraints-h (get values :constraints-h (spec/default-constraints-h values)) + constraints-v (get values :constraints-v (spec/default-constraints-v values)) + + on-constraint-button-clicked + (mf/use-callback + (mf/deps [ids values]) + (fn [button] + (fn [_] + (let [constraints-h (get values :constraints-h :scale) + constraints-v (get values :constraints-v :scale) + + [constraint new-value] + (case button + :top (case constraints-v + :top [:constraints-v :scale] + :topbottom [:constraints-v :bottom] + :bottom [:constraints-v :topbottom] + [:constraints-v :top]) + :bottom (case constraints-v + :bottom [:constraints-v :scale] + :topbottom [:constraints-v :top] + :top [:constraints-v :topbottom] + [:constraints-v :bottom]) + :left (case constraints-h + :left [:constraints-h :scale] + :leftright [:constraints-h :right] + :right [:constraints-h :leftright] + [:constraints-h :left]) + :right (case constraints-h + :right [:constraints-h :scale] + :leftright [:constraints-h :left] + :left [:constraints-h :leftright] + [:constraints-h :right]) + :centerv (case constraints-v + :center [:constraints-v :scale] + [:constraints-v :center]) + :centerh (case constraints-h + :center [:constraints-h :scale] + [:constraints-h :center]))] + (st/emit! (dch/update-shapes + ids + #(assoc % constraint new-value))))))) + + on-constraint-select-changed + (mf/use-callback + (mf/deps [ids values]) + (fn [constraint] + (fn [event] + (let [value (-> (dom/get-target-val event) (keyword))] + (when-not (str/empty? value) + (st/emit! (dch/update-shapes + ids + #(assoc % constraint value)))))))) + + ;; TODO: uncomment when fixed-scroll is fully implemented + ;; on-fixed-scroll-clicked + ;; (mf/use-callback + ;; (mf/deps [ids values]) + ;; (fn [_] + ;; (st/emit! (dch/update-shapes ids #(update % :fixed-scroll not))))) + ] + + ;; CONSTRAINTS + (when in-frame? + [:div.element-set + [:div.element-set-title + [:span (tr "workspace.options.constraints")]] + + [:div.element-set-content + [:div.row-flex.align-top + + [:div.constraints-widget + [:div.constraints-box] + [:div.constraint-button.top + {:class (dom/classnames :active (or (= constraints-v :top) + (= constraints-v :topbottom))) + :on-click (on-constraint-button-clicked :top)}] + [:div.constraint-button.bottom + {:class (dom/classnames :active (or (= constraints-v :bottom) + (= constraints-v :topbottom))) + :on-click (on-constraint-button-clicked :bottom)}] + [:div.constraint-button.left + {:class (dom/classnames :active (or (= constraints-h :left) + (= constraints-h :leftright))) + :on-click (on-constraint-button-clicked :left)}] + [:div.constraint-button.right + {:class (dom/classnames :active (or (= constraints-h :right) + (= constraints-h :leftright))) + :on-click (on-constraint-button-clicked :right)}] + [:div.constraint-button.centerv + {:class (dom/classnames :active (= constraints-v :center)) + :on-click (on-constraint-button-clicked :centerv)}] + [:div.constraint-button.centerh + {:class (dom/classnames :active (= constraints-h :center)) + :on-click (on-constraint-button-clicked :centerh)}]] + + [:div.constraints-form + [:div.row-flex + [:span.left-right i/full-screen] + [:select.input-select {:on-change (on-constraint-select-changed :constraints-h) + :value (d/name constraints-h "scale")} + (when (= constraints-h :multiple) + [:option {:value ""} (tr "settings.multiple")]) + [:option {:value "left"} (tr "workspace.options.constraints.left")] + [:option {:value "right"} (tr "workspace.options.constraints.right")] + [:option {:value "leftright"} (tr "workspace.options.constraints.leftright")] + [:option {:value "center"} (tr "workspace.options.constraints.center")] + [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] + [:div.row-flex + [:span.top-bottom i/full-screen] + [:select.input-select {:on-change (on-constraint-select-changed :constraints-v) + :value (d/name constraints-v "scale")} + (when (= constraints-v :multiple) + [:option {:value ""} (tr "settings.multiple")]) + [:option {:value "top"} (tr "workspace.options.constraints.top")] + [:option {:value "bottom"} (tr "workspace.options.constraints.bottom")] + [:option {:value "topbottom"} (tr "workspace.options.constraints.topbottom")] + [:option {:value "center"} (tr "workspace.options.constraints.center")] + [:option {:value "scale"} (tr "workspace.options.constraints.scale")] + ;; TODO: uncomment when fixed-scroll is fully implemented + ;; (when first-level? + ;; [:div.row-flex + ;; [:div.fix-when {:class (dom/classnames :active (:fixed-scroll values)) + ;; :on-click on-fixed-scroll-clicked} + ;; i/pin + ;; [:span (tr "workspace.options.constraints.fix-when-scrolling")]]]) + ]]]]]]))) 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 ae9ba63ba4..9305404f56 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 @@ -9,8 +9,6 @@ [app.common.data :as d] [app.common.geom.shapes :as gsh] [app.common.math :as math] - [app.common.pages.spec :as spec] - [app.common.uuid :as uuid] [app.main.data.workspace :as udw] [app.main.data.workspace.changes :as dch] [app.main.refs :as refs] @@ -19,7 +17,6 @@ [app.main.ui.icons :as i] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] - [cuerdas.core :as str] [rumext.alpha :as mf])) (def measure-attrs [:proportion-lock @@ -28,12 +25,7 @@ :rotation :rx :ry :r1 :r2 :r3 :r4 - :selrect - :constraints-h - :constraints-v - :fixed-scroll - :parent-id - :frame-id]) + :selrect]) (defn- attr->string [attr values] (let [value (attr values)] @@ -69,13 +61,6 @@ proportion-lock (:proportion-lock values) - in-frame? (not= (:parent-id values) uuid/zero) - first-level? (and in-frame? - (= (:parent-id values) (:frame-id values))) - - constraints-h (get values :constraints-h (spec/default-constraints-h values)) - constraints-v (get values :constraints-v (spec/default-constraints-v values)) - on-size-change (mf/use-callback (mf/deps ids) @@ -173,64 +158,8 @@ on-radius-r3-change #(on-radius-4-change % :r3) on-radius-r4-change #(on-radius-4-change % :r4) - select-all #(-> % (dom/get-target) (.select)) + select-all #(-> % (dom/get-target) (.select))] - on-constraint-button-clicked - (mf/use-callback - (mf/deps [ids values]) - (fn [button] - (fn [_] - (let [constraints-h (get values :constraints-h :scale) - constraints-v (get values :constraints-v :scale) - - [constraint new-value] - (case button - :top (case constraints-v - :top [:constraints-v :scale] - :topbottom [:constraints-v :bottom] - :bottom [:constraints-v :topbottom] - [:constraints-v :top]) - :bottom (case constraints-v - :bottom [:constraints-v :scale] - :topbottom [:constraints-v :top] - :top [:constraints-v :topbottom] - [:constraints-v :bottom]) - :left (case constraints-h - :left [:constraints-h :scale] - :leftright [:constraints-h :right] - :right [:constraints-h :leftright] - [:constraints-h :left]) - :right (case constraints-h - :right [:constraints-h :scale] - :leftright [:constraints-h :left] - :left [:constraints-h :leftright] - [:constraints-h :right]) - :centerv (case constraints-v - :center [:constraints-v :scale] - [:constraints-v :center]) - :centerh (case constraints-h - :center [:constraints-h :scale] - [:constraints-h :center]))] - (st/emit! (dch/update-shapes - ids - #(assoc % constraint new-value))))))) - - on-constraint-select-changed - (mf/use-callback - (mf/deps [ids values]) - (fn [constraint] - (fn [event] - (let [value (-> (dom/get-target-val event) (keyword))] - (when-not (str/empty? value) - (st/emit! (dch/update-shapes - ids - #(assoc % constraint value)))))))) - - on-fixed-scroll-clicked - (mf/use-callback - (mf/deps [ids values]) - (fn [_] - (st/emit! (dch/update-shapes ids #(update % :fixed-scroll not)))))] [:* [:div.element-set [:div.element-set-content @@ -360,69 +289,4 @@ :min 0 :on-click select-all :on-change on-radius-r4-change - :value (attr->string :r4 values)}]]])]))]] - - ;; CONSTRAINTS - (when in-frame? - [:div.element-set - [:div.element-set-title - [:span (tr "workspace.options.constraints")]] - - [:div.element-set-content - [:div.row-flex.align-top - - [:div.constraints-widget - [:div.constraints-box] - [:div.constraint-button.top - {:class (dom/classnames :active (or (= constraints-v :top) - (= constraints-v :topbottom))) - :on-click (on-constraint-button-clicked :top)}] - [:div.constraint-button.bottom - {:class (dom/classnames :active (or (= constraints-v :bottom) - (= constraints-v :topbottom))) - :on-click (on-constraint-button-clicked :bottom)}] - [:div.constraint-button.left - {:class (dom/classnames :active (or (= constraints-h :left) - (= constraints-h :leftright))) - :on-click (on-constraint-button-clicked :left)}] - [:div.constraint-button.right - {:class (dom/classnames :active (or (= constraints-h :right) - (= constraints-h :leftright))) - :on-click (on-constraint-button-clicked :right)}] - [:div.constraint-button.centerv - {:class (dom/classnames :active (= constraints-v :center)) - :on-click (on-constraint-button-clicked :centerv)}] - [:div.constraint-button.centerh - {:class (dom/classnames :active (= constraints-h :center)) - :on-click (on-constraint-button-clicked :centerh)}]] - - [:div.constraints-form - [:div.row-flex - [:span.left-right i/full-screen] - [:select.input-select {:on-change (on-constraint-select-changed :constraints-h) - :value (d/name constraints-h "scale")} - (when (= constraints-h :multiple) - [:option {:value ""} (tr "settings.multiple")]) - [:option {:value "left"} (tr "workspace.options.constraints.left")] - [:option {:value "right"} (tr "workspace.options.constraints.right")] - [:option {:value "leftright"} (tr "workspace.options.constraints.leftright")] - [:option {:value "center"} (tr "workspace.options.constraints.center")] - [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] - [:div.row-flex - [:span.top-bottom i/full-screen] - [:select.input-select {:on-change (on-constraint-select-changed :constraints-v) - :value (d/name constraints-v "scale")} - (when (= constraints-v :multiple) - [:option {:value ""} (tr "settings.multiple")]) - [:option {:value "top"} (tr "workspace.options.constraints.top")] - [:option {:value "bottom"} (tr "workspace.options.constraints.bottom")] - [:option {:value "topbottom"} (tr "workspace.options.constraints.topbottom")] - [:option {:value "center"} (tr "workspace.options.constraints.center")] - [:option {:value "scale"} (tr "workspace.options.constraints.scale")]]] - ;; (when first-level? - ;; [:div.row-flex - ;; [:div.fix-when {:class (dom/classnames :active (:fixed-scroll values)) - ;; :on-click on-fixed-scroll-clicked} - ;; i/pin - ;; [:span (tr "workspace.options.constraints.fix-when-scrolling")]]]) - ]]]])])) + :value (attr->string :r4 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 df658997dd..713022c439 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 @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.shapes.circle (:require [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-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.measures :refer [measure-attrs measures-menu]] @@ -21,12 +22,15 @@ type (:type shape) measure-values (select-keys shape measure-attrs) stroke-values (select-keys shape stroke-attrs) - layer-values (select-keys shape layer-attrs)] + layer-values (select-keys shape layer-attrs) + constraint-values (select-keys shape constraint-attrs)] [:* [:& measures-menu {:ids ids :type type :values measure-values :options #{:size :position :rotation}}] + [:& 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/group.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/group.cljs index 377d136be9..c7778b0b60 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 @@ -9,6 +9,7 @@ [app.common.data :as d] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] [app.main.ui.workspace.sidebar.options.menus.component :refer [component-attrs component-menu]] + [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.measures :refer [measures-menu]] @@ -28,19 +29,21 @@ objects (->> shape-with-children (group-by :id) (d/mapm (fn [_ v] (first v)))) type :group - [measure-ids measure-values] (get-attrs [shape] objects :measure) - [layer-ids layer-values] (get-attrs [shape] objects :layer) - [fill-ids fill-values] (get-attrs [shape] objects :fill) - [shadow-ids shadow-values] (get-attrs [shape] objects :shadow) - [blur-ids blur-values] (get-attrs [shape] objects :blur) - [stroke-ids stroke-values] (get-attrs [shape] objects :stroke) - [text-ids text-values] (get-attrs [shape] objects :text) - [svg-ids svg-values] [[(:id shape)] (select-keys shape [:svg-attrs])] - [comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)]] + [measure-ids measure-values] (get-attrs [shape] objects :measure) + [layer-ids layer-values] (get-attrs [shape] objects :layer) + [constraint-ids constraint-values] (get-attrs [shape] objects :constraint) + [fill-ids fill-values] (get-attrs [shape] objects :fill) + [shadow-ids shadow-values] (get-attrs [shape] objects :shadow) + [blur-ids blur-values] (get-attrs [shape] objects :blur) + [stroke-ids stroke-values] (get-attrs [shape] objects :stroke) + [text-ids text-values] (get-attrs [shape] objects :text) + [svg-ids svg-values] [[(:id shape)] (select-keys shape [:svg-attrs])] + [comp-ids comp-values] [[(:id shape)] (select-keys shape component-attrs)]] [:div.options [:& measures-menu {:type type :ids measure-ids :values measure-values}] [:& component-menu {:ids comp-ids :values comp-values}] + [:& 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 a506b56b80..b71574c29c 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 @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.shapes.image (:require [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] @@ -17,14 +18,20 @@ (let [ids [(:id shape)] type (:type shape) measure-values (select-keys shape measure-attrs) - layer-values (select-keys shape layer-attrs)] + layer-values (select-keys shape layer-attrs) + constraint-values (select-keys shape constraint-attrs)] [:* [:& measures-menu {:ids ids :type type :values measure-values}] + + [:& constraints-menu {:ids ids + :values constraint-values}] + [:& layer-menu {:ids ids :type type :values layer-values}] + [:& shadow-menu {:ids ids :values (select-keys shape [:shadow])}] 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 42d822850f..5f69d1ea9a 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 @@ -10,6 +10,7 @@ [app.common.data :as d] [app.common.text :as txt] [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-attrs blur-menu]] + [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-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.measures :refer [measure-attrs measures-menu]] @@ -22,85 +23,94 @@ ;; attribute and how to handle them (def type->props {:frame - {:measure :shape - :layer :shape - :fill :shape - :shadow :children - :blur :children - :stroke :children - :text :children} + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :children + :blur :children + :stroke :children + :text :children} :group - {:measure :shape - :layer :shape - :fill :children - :shadow :shape - :blur :shape - :stroke :children - :text :children} + {:measure :shape + :layer :shape + :constraint :shape + :fill :children + :shadow :shape + :blur :shape + :stroke :children + :text :children} :path - {:measure :shape - :layer :shape - :fill :shape - :shadow :shape - :blur :shape - :stroke :shape - :text :ignore} + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :shape + :blur :shape + :stroke :shape + :text :ignore} :text - {:measure :shape - :layer :shape - :fill :text - :shadow :shape - :blur :shape - :stroke :ignore - :text :text} + {:measure :shape + :layer :shape + :constraint :shape + :fill :text + :shadow :shape + :blur :shape + :stroke :ignore + :text :text} :image - {:measure :shape - :layer :shape - :fill :ignore - :shadow :shape - :blur :shape - :stroke :ignore - :text :ignore} + {:measure :shape + :layer :shape + :constraint :shape + :fill :ignore + :shadow :shape + :blur :shape + :stroke :ignore + :text :ignore} :rect - {:measure :shape - :layer :shape - :fill :shape - :shadow :shape - :blur :shape - :stroke :shape - :text :ignore} + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :shape + :blur :shape + :stroke :shape + :text :ignore} :circle - {:measure :shape - :layer :shape - :fill :shape - :shadow :shape - :blur :shape - :stroke :shape - :text :ignore} + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :shape + :blur :shape + :stroke :shape + :text :ignore} :svg-raw - {:measure :shape - :layer :shape - :fill :shape - :shadow :shape - :blur :shape - :stroke :shape - :text :ignore}}) + {:measure :shape + :layer :shape + :constraint :shape + :fill :shape + :shadow :shape + :blur :shape + :stroke :shape + :text :ignore}}) (def props->attrs - {:measure measure-attrs - :layer layer-attrs - :fill fill-attrs - :shadow shadow-attrs - :blur blur-attrs - :stroke stroke-attrs - :text ot/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}) (def shadow-keys [:style :color :offset-x :offset-y :blur :spread]) @@ -176,18 +186,22 @@ objects (->> shapes-with-children (group-by :id) (d/mapm (fn [_ v] (first v)))) type :multiple - [measure-ids measure-values] (get-attrs shapes objects :measure) - [layer-ids layer-values] (get-attrs shapes objects :layer) - [fill-ids fill-values] (get-attrs shapes objects :fill) - [shadow-ids shadow-values] (get-attrs shapes objects :shadow) - [blur-ids blur-values] (get-attrs shapes objects :blur) - [stroke-ids stroke-values] (get-attrs shapes objects :stroke) - [text-ids text-values] (get-attrs shapes objects :text)] + [measure-ids measure-values] (get-attrs shapes objects :measure) + [layer-ids layer-values] (get-attrs shapes objects :layer) + [constraint-ids constraint-values] (get-attrs shapes objects :constraint) + [fill-ids fill-values] (get-attrs shapes objects :fill) + [shadow-ids shadow-values] (get-attrs shapes objects :shadow) + [blur-ids blur-values] (get-attrs shapes objects :blur) + [stroke-ids stroke-values] (get-attrs shapes objects :stroke) + [text-ids text-values] (get-attrs shapes objects :text)] [:div.options (when-not (empty? measure-ids) [:& measures-menu {:type type :ids measure-ids :values measure-values}]) + (when-not (empty? constraint-ids) + [:& constraints-menu {:ids constraint-ids :values constraint-values}]) + (when-not (empty? layer-ids) [:& layer-menu {:type type :ids layer-ids :values layer-values}]) 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 1139f19b92..9d8727a028 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 @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.shapes.path (:require [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-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.measures :refer [measure-attrs measures-menu]] @@ -21,11 +22,14 @@ type (:type shape) measure-values (select-keys shape measure-attrs) stroke-values (select-keys shape stroke-attrs) - layer-values (select-keys shape layer-attrs)] + layer-values (select-keys shape layer-attrs) + constraint-values (select-keys shape constraint-attrs)] [:* [:& measures-menu {:ids ids :type type :values measure-values}] + [:& 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 17d1c991a4..ad537732a0 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 @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.shapes.rect (:require [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-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.measures :refer [measure-attrs measures-menu]] @@ -22,6 +23,7 @@ type (:type shape) measure-values (select-keys shape measure-attrs) layer-values (select-keys shape layer-attrs) + constraint-values (select-keys shape constraint-attrs) fill-values (select-keys shape fill-attrs) stroke-values (select-keys shape stroke-attrs)] [:* @@ -29,6 +31,9 @@ :type type :values measure-values}] + [:& 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/svg_raw.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/shapes/svg_raw.cljs index 6d6ae07356..5283cd8114 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 @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.sidebar.options.shapes.svg-raw (:require [app.main.ui.workspace.sidebar.options.menus.blur :refer [blur-menu]] + [app.main.ui.workspace.sidebar.options.menus.constraints :refer [constraint-attrs constraints-menu]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-attrs fill-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] [app.main.ui.workspace.sidebar.options.menus.shadow :refer [shadow-menu]] @@ -93,6 +94,7 @@ type (:type shape) {:keys [tag] :as content} (:content shape) measure-values (select-keys shape measure-attrs) + constraint-values (select-keys shape constraint-attrs) fill-values (get-fill-values shape) stroke-values (get-stroke-values shape)] @@ -102,12 +104,17 @@ :type type :values measure-values}] + [:& constraints-menu {:ids ids + :values constraint-values}] + [:& fill-menu {:ids ids :type type :values fill-values}] + [:& stroke-menu {:ids ids :type type :values stroke-values}] + [:& shadow-menu {:ids ids :values (select-keys shape [:shadow])}] 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 0274d00e92..2989834322 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 @@ -10,6 +10,7 @@ [app.main.data.workspace.texts :as dwt] [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]] [app.main.ui.workspace.sidebar.options.menus.fill :refer [fill-menu]] [app.main.ui.workspace.sidebar.options.menus.layer :refer [layer-attrs layer-menu]] [app.main.ui.workspace.sidebar.options.menus.measures :refer [measure-attrs measures-menu]] @@ -60,6 +61,10 @@ :type type :values (select-keys shape measure-attrs)}] + [:& constraints-menu + {:ids ids + :values (select-keys shape constraint-attrs)}] + [:& layer-menu {:ids ids :type type :values layer-values}] From c98958053c5bd97292785d4b95f68929ddfc3814 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 1 Jul 2021 16:55:31 +0200 Subject: [PATCH 153/204] :bug: Fix geometry sync for subcomponents --- .../data/workspace/libraries_helpers.cljs | 45 +++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 7a88d948b6..780b7b5757 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -631,20 +631,14 @@ set-remote-synced?))) both (fn [child-inst child-main] - (let [sub-root? (and (:component-id shape-inst) - (not (:component-root? shape-inst)))] - (generate-sync-shape-direct-recursive container - child-inst - component - child-main - (if sub-root? - shape-inst - root-inst) - (if sub-root? - shape-main - root-main) - reset? - initial-root?))) + (generate-sync-shape-direct-recursive container + child-inst + component + child-main + root-inst + root-main + reset? + initial-root?)) moved (fn [child-inst child-main] (move-shape @@ -752,20 +746,13 @@ false)) both (fn [child-inst child-main] - (let [sub-root? (and (:component-id shape-inst) - (not (:component-root? shape-inst)))] - - (generate-sync-shape-inverse-recursive container - child-inst - component - child-main - (if sub-root? - shape-inst - root-inst) - (if sub-root? - shape-main - root-main) - initial-root?))) + (generate-sync-shape-inverse-recursive container + child-inst + component + child-main + root-inst + root-main + initial-root?)) moved (fn [child-inst child-main] (move-shape @@ -1160,6 +1147,8 @@ ; sync only the position relative to the origin of the component. ; We solve this by moving the origin shape so it is aligned with ; the dest root before syncing. + ; In case of subinstances, the comparison is always done with the + ; near component, because this is that we are syncing with. origin-shape (reposition-shape origin-shape origin-root dest-root) touched (get dest-shape :touched #{})] From ea8bc687c048ff2c194718f65a0d3517e199ba05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Mon, 28 Jun 2021 16:00:50 +0200 Subject: [PATCH 154/204] :sparkles: Detach instance when syncing if the master component is gone --- .../app/main/data/workspace/libraries.cljs | 58 +------- .../data/workspace/libraries_helpers.cljs | 133 ++++++++++++------ 2 files changed, 97 insertions(+), 94 deletions(-) diff --git a/frontend/src/app/main/data/workspace/libraries.cljs b/frontend/src/app/main/data/workspace/libraries.cljs index 7996e492ad..b267fb3885 100644 --- a/frontend/src/app/main/data/workspace/libraries.cljs +++ b/frontend/src/app/main/data/workspace/libraries.cljs @@ -27,7 +27,7 @@ [beicon.core :as rx] [potok.core :as ptk])) -;; Change this to :info :debug or :trace to debug this module +;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default (log/set-level! :warn) (defn- log-changes @@ -450,57 +450,13 @@ (ptk/reify ::detach-component ptk/WatchEvent (watch [it state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - shapes (cp/get-object-with-children id objects) + (let [local-library (dwlh/get-local-file state) + container (cp/get-container (get state :current-page-id) + :page + local-library) - rchanges (mapv (fn [obj] - {:type :mod-obj - :page-id page-id - :id (:id obj) - :operations [{:type :set - :attr :component-id - :val nil} - {:type :set - :attr :component-file - :val nil} - {:type :set - :attr :component-root? - :val nil} - {:type :set - :attr :remote-synced? - :val nil} - {:type :set - :attr :shape-ref - :val nil} - {:type :set - :attr :touched - :val nil}]}) - shapes) - - uchanges (mapv (fn [obj] - {:type :mod-obj - :page-id page-id - :id (:id obj) - :operations [{:type :set - :attr :component-id - :val (:component-id obj)} - {:type :set - :attr :component-file - :val (:component-file obj)} - {:type :set - :attr :component-root? - :val (:component-root? obj)} - {:type :set - :attr :remote-synced? - :val (:remote-synced? obj)} - {:type :set - :attr :shape-ref - :val (:shape-ref obj)} - {:type :set - :attr :touched - :val (:touched obj)}]}) - shapes)] + [rchanges uchanges] + (dwlh/generate-detach-instance id container)] (rx/of (dch/commit-changes {:redo-changes rchanges :undo-changes uchanges diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 780b7b5757..5c93525f42 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -17,7 +17,7 @@ [cljs.spec.alpha :as s] [clojure.set :as set])) -;; Change this to :info :debug or :trace to debug this module +;; Change this to :info :debug or :trace to debug this module, or :warn to reset to default (log/set-level! :warn) (defonce empty-changes [[] []]) @@ -200,6 +200,63 @@ (get component :objects) identity))) +(defn generate-detach-instance + "Generate changes to remove the links between a shape and all its children + with a component." + [shape-id container] + (let [shapes (cp/get-object-with-children shape-id (:objects container)) + rchanges (mapv (fn [obj] + (make-change + container + {:type :mod-obj + :id (:id obj) + :operations [{:type :set + :attr :component-id + :val nil} + {:type :set + :attr :component-file + :val nil} + {:type :set + :attr :component-root? + :val nil} + {:type :set + :attr :remote-synced? + :val nil} + {:type :set + :attr :shape-ref + :val nil} + {:type :set + :attr :touched + :val nil}]})) + shapes) + + uchanges (mapv (fn [obj] + (make-change + container + {:type :mod-obj + :id (:id obj) + :operations [{:type :set + :attr :component-id + :val (:component-id obj)} + {:type :set + :attr :component-file + :val (:component-file obj)} + {:type :set + :attr :component-root? + :val (:component-root? obj)} + {:type :set + :attr :remote-synced? + :val (:remote-synced? obj)} + {:type :set + :attr :shape-ref + :val (:shape-ref obj)} + {:type :set + :attr :touched + :val (:touched obj)}]})) + shapes)] + + [rchanges uchanges])) + ;; ---- General library synchronization functions ---- @@ -216,26 +273,20 @@ :file (pretty-file file-id state) :library (pretty-file library-id state)) - (let [file (get-file state file-id) - library (get-file state library-id) - library-items (get library asset-type)] - - (if (empty? library-items) - empty-changes - - (loop [pages (vals (get file :pages-index)) - rchanges [] - uchanges []] - (if-let [page (first pages)] - (let [[page-rchanges page-uchanges] - (generate-sync-container asset-type - library-id - state - (cp/make-container page :page))] - (recur (next pages) - (d/concat rchanges page-rchanges) - (d/concat uchanges page-uchanges))) - [rchanges uchanges]))))) + (let [file (get-file state file-id)] + (loop [pages (vals (get file :pages-index)) + rchanges [] + uchanges []] + (if-let [page (first pages)] + (let [[page-rchanges page-uchanges] + (generate-sync-container asset-type + library-id + state + (cp/make-container page :page))] + (recur (next pages) + (d/concat rchanges page-rchanges) + (d/concat uchanges page-uchanges))) + [rchanges uchanges])))) (defn generate-sync-library "Generate changes to synchronize all shapes in all components of the @@ -248,27 +299,21 @@ :file (pretty-file file-id state) :library (pretty-file library-id state)) - (let [file (get-file state file-id) - library (get-file state library-id) - library-items (get library asset-type)] - - (if (empty? library-items) - empty-changes - - (loop [local-components (vals (get file :components)) - rchanges [] - uchanges []] - (if-let [local-component (first local-components)] - (let [[comp-rchanges comp-uchanges] - (generate-sync-container asset-type - library-id - state - (cp/make-container local-component - :component))] - (recur (next local-components) - (d/concat rchanges comp-rchanges) - (d/concat uchanges comp-uchanges))) - [rchanges uchanges]))))) + (let [file (get-file state file-id)] + (loop [local-components (vals (get file :components)) + rchanges [] + uchanges []] + (if-let [local-component (first local-components)] + (let [[comp-rchanges comp-uchanges] + (generate-sync-container asset-type + library-id + state + (cp/make-container local-component + :component))] + (recur (next local-components) + (d/concat rchanges comp-rchanges) + (d/concat uchanges comp-uchanges))) + [rchanges uchanges])))) (defn- generate-sync-container "Generate changes to synchronize all shapes in a particular container (a page @@ -568,7 +613,9 @@ root-main reset? initial-root?) - empty-changes))) + ; If the component is not found, because the master component has been + ; deleted or the library unlinked, detach the instance. + (generate-detach-instance shape-id container)))) (defn- generate-sync-shape-direct-recursive [container shape-inst component shape-main root-inst root-main reset? initial-root?] From dac7a6497f5db32227756634b75ab0e69da5a7bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 29 Jun 2021 17:41:12 +0200 Subject: [PATCH 155/204] :sparkles: Detach colors when deleted in the file library --- .../data/workspace/libraries_helpers.cljs | 60 +++++++++++++------ .../app/main/ui/workspace/sidebar/assets.cljs | 38 +++++++----- 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 5c93525f42..4c33f41a5e 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -23,13 +23,13 @@ (defonce empty-changes [[] []]) (defonce color-sync-attrs - [[:fill-color-ref-id :color :fill-color] - [:fill-color-ref-id :gradient :fill-color-gradient] - [:fill-color-ref-id :opacity :fill-opacity] + [[:fill-color-ref-id :fill-color-ref-file :color :fill-color] + [:fill-color-ref-id :fill-color-ref-file :gradient :fill-color-gradient] + [:fill-color-ref-id :fill-color-ref-file :opacity :fill-opacity] - [:stroke-color-ref-id :color :stroke-color] - [:stroke-color-ref-id :gradient :stroke-color-gradient] - [:stroke-color-ref-id :opacity :stroke-opacity]]) + [:stroke-color-ref-id :stroke-color-ref-file :color :stroke-color] + [:stroke-color-ref-id :stroke-color-ref-file :gradient :stroke-color-gradient] + [:stroke-color-ref-id :stroke-color-ref-file :opacity :stroke-opacity]]) (declare generate-sync-container) (declare generate-sync-shape) @@ -368,7 +368,7 @@ attr-ref-file (keyword (str attr "-ref-file"))] (and (get shape attr-ref-id) (= library-id (get shape attr-ref-file)))) - (map #(nth % 2) color-sync-attrs)))) + (map #(nth % 3) color-sync-attrs)))) :typographies (fn [shape] @@ -430,12 +430,14 @@ :fill-color (:color color) :fill-opacity (:opacity color) :fill-color-gradient (:gradient color)) - node))] + (assoc node + :fill-color-ref-id nil + :fill-color-ref-file nil)))] (generate-sync-text-shape shape container update-node)) (loop [attrs (seq color-sync-attrs) roperations [] uoperations []] - (let [[attr-ref-id color-attr attr] (first attrs)] + (let [[attr-ref-id attr-ref-file color-attr attr] (first attrs)] (if (nil? attr) (if (empty? roperations) empty-changes @@ -455,17 +457,37 @@ roperations uoperations) (let [color (get colors (get shape attr-ref-id)) - roperation {:type :set - :attr attr - :val (color-attr color) - :ignore-touched true} - uoperation {:type :set - :attr attr - :val (get shape attr) - :ignore-touched true}] + roperations' (if color + [{:type :set + :attr attr + :val (color-attr color) + :ignore-touched true}] + ;; If the referenced color does no longer exist in the library, + ;; we must unlink the color in the shape + [{:type :set + :attr attr-ref-id + :val nil + :ignore-touched true} + {:type :set + :attr attr-ref-file + :val nil + :ignore-touched true}]) + uoperations' (if color + [{:type :set + :attr attr + :val (get shape attr) + :ignore-touched true}] + [{:type :set + :attr attr-ref-id + :val (get shape attr-ref-id) + :ignore-touched true} + {:type :set + :attr attr-ref-file + :val (get shape attr-ref-file) + :ignore-touched true}])] (recur (next attrs) - (conj roperations roperation) - (conj uoperations uoperation)))))))))) + (concat roperations roperations') + (concat uoperations uoperations')))))))))) (defmethod generate-sync-shape :typographies [_ library-id state container shape] diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 96e035cd4c..2acde247d5 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -369,8 +369,10 @@ (fn [] (if (or multi-components? multi-assets?) (on-assets-delete) - (st/emit! (dwl/delete-component {:id (:component-id @state)}))) - (st/emit! (dwl/sync-file file-id file-id)))) + (st/emit! (dwu/start-undo-transaction) + (dwl/delete-component {:id (:component-id @state)}) + (dwl/sync-file file-id file-id) + (dwu/commit-undo-transaction))))) on-rename (mf/use-callback @@ -801,11 +803,14 @@ delete-color (mf/use-callback - (mf/deps @state multi-colors? multi-assets?) + (mf/deps @state multi-colors? multi-assets? file-id) (fn [] (if (or multi-colors? multi-assets?) (on-assets-delete) - (st/emit! (dwl/delete-color color))))) + (st/emit! (dwu/start-undo-transaction) + (dwl/delete-color color) + (dwl/sync-file file-id file-id) + (dwu/commit-undo-transaction))))) rename-color-clicked (fn [event] @@ -1489,16 +1494,21 @@ (mf/use-callback (mf/deps @selected-assets) (fn [] - (st/emit! (dwu/start-undo-transaction)) - (apply st/emit! (map #(dwl/delete-component {:id %}) - (:components @selected-assets))) - (apply st/emit! (map #(dwl/delete-media {:id %}) - (:graphics @selected-assets))) - (apply st/emit! (map #(dwl/delete-color {:id %}) - (:colors @selected-assets))) - (apply st/emit! (map #(dwl/delete-typography %) - (:typographies @selected-assets))) - (st/emit! (dwu/commit-undo-transaction))))] + (let [selected-assets @selected-assets] + (st/emit! (dwu/start-undo-transaction)) + (apply st/emit! (map #(dwl/delete-component {:id %}) + (:components selected-assets))) + (apply st/emit! (map #(dwl/delete-media {:id %}) + (:graphics selected-assets))) + (apply st/emit! (map #(dwl/delete-color {:id %}) + (:colors selected-assets))) + (apply st/emit! (map #(dwl/delete-typography %) + (:typographies selected-assets))) + (when (or (d/not-empty? (:components selected-assets)) + (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)))))] [:div.tool-window {:on-context-menu #(dom/prevent-default %) :on-click unselect-all} From 723cb3b5464949a2d3115fd333bf21da1aa07fb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 30 Jun 2021 09:52:53 +0200 Subject: [PATCH 156/204] :sparkles: Detach typographies when deleted in the file library --- CHANGES.md | 1 + frontend/src/app/main/data/workspace/libraries_helpers.cljs | 3 ++- frontend/src/app/main/ui/workspace/sidebar/assets.cljs | 5 ++++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a19de1b6dc..4d477357e7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ - Process numeric input changes only if the value actually changed. - Remove unnecesary redirect from history when user goes to workspace from dashboard [Taiga #1820](https://tree.taiga.io/project/penpot/issue/1820). +- Detach shapes from deleted assets [Taiga #1850](https://tree.taiga.io/project/penpot/issue/1850). - Fix tooltip position on view application [Taiga #1819](https://tree.taiga.io/project/penpot/issue/1819). - Fix dashboard navigation on moving file to other team [Taiga #1817](https://tree.taiga.io/project/penpot/issue/1817). - Fix workspace header presence styles and invalid link [Taiga #1813](https://tree.taiga.io/project/penpot/issue/1813). diff --git a/frontend/src/app/main/data/workspace/libraries_helpers.cljs b/frontend/src/app/main/data/workspace/libraries_helpers.cljs index 4c33f41a5e..788b2453a0 100644 --- a/frontend/src/app/main/data/workspace/libraries_helpers.cljs +++ b/frontend/src/app/main/data/workspace/libraries_helpers.cljs @@ -499,7 +499,8 @@ update-node (fn [node] (if-let [typography (get typographies (:typography-ref-id node))] (merge node (d/without-keys typography [:name :id])) - node))] + (dissoc node :typography-ref-id + :typography-ref-file)))] (generate-sync-text-shape shape container update-node))) (defn- get-assets diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 2acde247d5..766c6cda30 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -1252,7 +1252,10 @@ (fn [] (if (or multi-typographies? multi-assets?) (on-assets-delete) - (st/emit! (dwl/delete-typography (:id @state)))))) + (st/emit! (dwu/start-undo-transaction) + (dwl/delete-typography (:id @state)) + (dwl/sync-file file-id file-id) + (dwu/commit-undo-transaction))))) editting-id (or (:rename-typography local) (:edit-typography local))] From e13cfad9da500a635cc9c0ff33038b5ad69e1d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 2 Jul 2021 09:56:21 +0200 Subject: [PATCH 157/204] :bug: Include constraints in the list of synced attrs --- common/src/app/common/pages/common.cljc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/src/app/common/pages/common.cljc b/common/src/app/common/pages/common.cljc index 9a39105bf5..29319abef4 100644 --- a/common/src/app/common/pages/common.cljc +++ b/common/src/app/common/pages/common.cljc @@ -56,5 +56,8 @@ :transform-inverse :geometry-group :shadow :shadow-group :blur :blur-group - :masked-group? :mask-group}) + :masked-group? :mask-group + :constraints-h :constraints-group + :constraints-v :constraints-group + :fixed-scroll :constraints-group}) From 1376c26defe9e5e1aa1cfa7aec690f2f9e5d9ffa Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 5 Jul 2021 11:46:40 +0200 Subject: [PATCH 158/204] :paperclip: Minor changes on register page. --- frontend/src/app/main/ui/auth/register.cljs | 3 +-- frontend/translations/en.po | 4 ++++ frontend/translations/es.po | 4 ++++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index f6022e2899..97ea2a948e 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -223,7 +223,7 @@ [:div.fields-row [:& fm/input {:name :accept-newsletter-subscription :class "check-primary" - :label (tr "auth.terms-privacy-agreement") + :label (tr "auth.newsletter-subscription") :type "checkbox"}]]) [:& fm/submit-button @@ -233,7 +233,6 @@ (mf/defc register-validate-page [{:keys [params] :as props}] - (prn "register-validate-page" params) [:div.form-container [:h1 (tr "auth.register-title")] [:div.subtitle (tr "auth.register-subtitle")] diff --git a/frontend/translations/en.po b/frontend/translations/en.po index b873e8b916..6d3a522825 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -159,6 +159,10 @@ msgstr "" "When creating a new account, you agree to our terms of service and privacy " "policy." +#: 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/register.cljs msgid "auth.verification-email-sent" msgstr "We've sent a verification email to" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index c8cabdb7ee..d511f003f3 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -163,6 +163,10 @@ msgstr "" "Al crear una nueva cuenta, aceptas nuestros términos de servicio y política " "de privacidad." +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "Estoy de acuerdo en suscribirme a la lista de correo de Penpot" + #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "Hemos enviado un email de verificación a" From 083696a8990696711d13b8c8b1859ab48776f6de Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 5 Jul 2021 12:18:36 +0200 Subject: [PATCH 159/204] :arrow_up: Update deps on devenv dockerfile. --- docker/devenv/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker/devenv/Dockerfile b/docker/devenv/Dockerfile index 0f7400cf74..5508f5a7da 100644 --- a/docker/devenv/Dockerfile +++ b/docker/devenv/Dockerfile @@ -3,10 +3,10 @@ LABEL maintainer="Andrey Antukh " ARG DEBIAN_FRONTEND=noninteractive -ENV NODE_VERSION=v14.16.1 \ - CLOJURE_VERSION=1.10.3.822 \ - CLJKONDO_VERSION=2021.06.01 \ - BABASHKA_VERSION=0.4.4 \ +ENV NODE_VERSION=v14.17.2 \ + CLOJURE_VERSION=1.10.3.882 \ + CLJKONDO_VERSION=2021.06.18 \ + BABASHKA_VERSION=0.4.6 \ LANG=en_US.UTF-8 \ LC_ALL=en_US.UTF-8 From 1b1c0ff9e45a28a34b75414f04e1873a845b597d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Mon, 5 Jul 2021 12:19:11 +0200 Subject: [PATCH 160/204] :bug: Fix incorrect terms check validation on register page. --- frontend/src/app/main/ui/auth/register.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index 97ea2a948e..fcc619c112 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -178,7 +178,7 @@ :else (st/emit! (rt/nav :auth-register-success {} {:email (:email data)})))) -(s/def ::accept-terms-and-privacy ::us/boolean) +(s/def ::accept-terms-and-privacy (s/and ::us/boolean true?)) (s/def ::accept-newsletter-subscription ::us/boolean) (s/def ::register-validate-form From d0ab8135203cf1cedd43c3325772cf2f37cf0ae8 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 1 Jul 2021 17:47:32 +0200 Subject: [PATCH 161/204] :sparkles: Import/export UI and final touches --- .../resources/styles/main/partials/modal.scss | 327 +++++++++++++++++- frontend/src/app/main/ui/dashboard.cljs | 3 +- .../src/app/main/ui/dashboard/export.cljs | 92 +++++ .../src/app/main/ui/dashboard/file_menu.cljs | 25 +- .../src/app/main/ui/dashboard/import.cljs | 294 +++++++++++++++- .../app/main/ui/dashboard/project_menu.cljs | 4 +- frontend/src/app/main/ui/icons.clj | 5 +- frontend/src/app/main/ui/loader.cljs | 2 +- frontend/src/app/util/dom.cljs | 4 + frontend/src/app/worker/export.cljs | 46 ++- frontend/src/app/worker/import.cljs | 127 ++++--- frontend/translations/en.po | 44 ++- 12 files changed, 856 insertions(+), 117 deletions(-) create mode 100644 frontend/src/app/main/ui/dashboard/export.cljs diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index bb0b12751e..e61ec1423a 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -202,12 +202,338 @@ background: $color-primary; border: 1px solid $color-primary; color: $color-black; + &:hover { background: $color-primary-dark; } } } +} +.import-dialog, +.export-dialog { + background-color: $color-white; + border: 1px solid $color-gray-20; + width: 30rem; + + p { + font-size: $fs14; + color: $color-black; + } + + .detail { + font-size: $fs12; + } + + .detail, .explain { + padding: 0 1rem; + } + + .cancel-button { + border: 1px solid $color-gray-20; + background: $color-white; + border-radius: 3px; + padding: 0.3rem 1.25rem; + cursor: pointer; + margin-right: 8px; + + &:hover { + background: $color-gray-20; + } + } + + .accept-button { + background: $color-primary; + border-radius: 3px; + border: 1px solid $color-primary; + color: $color-black; + cursor: pointer; + padding: 0.3rem 1.25rem; + + &[disabled] { + border: 1px solid #E3E3E3; + } + + &:hover { + background: $color-primary-dark; + } + } + + .modal-content { + flex: 1; + overflow-y: auto; + max-height: calc(65vh); + } + + .modal-header-title { + padding-left: 2rem; + + h2 { + font-size: $fs14; + } + } + + .modal-content { + padding: 1rem; + } +} + +.import-dialog { + min-height: 215px; + + svg { + max-width: 18px; + max-height: 18px; + } + + .file-entry { + margin: 0.75rem 1rem; + user-select: none; + + &.editable:hover { + .file-name-label { + background-color: $color-primary-lighter; + } + .edit-entry-buttons { + display: flex; + background-color: $color-primary-lighter; + } + } + } + + .file-icon { + width: 18px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 1rem; + + svg { + width: 18px; + height: 18px; + } + + #loader-pencil { + fill: $color-black; + } + + .icon-tick { + fill: $color-success; + } + + .icon-close { + transform: rotate(45deg); + fill: $color-danger; + } + } + + .file-name { + display: flex; + align-items: center; + color: $color-black; + + .file-name-label { + flex: 1; + white-space: nowrap; + display: flex; + align-items: center; + height: 2rem; + margin-left: -0.25rem; + padding-left: 0.25rem; + + .icon-library { + width: 14px; + fill: $color-gray-20; + margin-left: 0.5rem; + padding-top: 1px + } + } + + .file-name-edit { + width: 100%; + + input { + margin: 0; + border: none; + border-bottom: 1px solid $color-gray-20; + height: 2rem; + width: 100%; + } + } + } + + .feedback-banner { + color: $color-black; + background: $color-success-lighter; + height: 40px; + display: flex; + align-items: center; + margin: 0 1rem; + + .message { + padding: 0 1rem; + font-size: $fs12; + } + + .icon { + background: $color-success; + height: 40px; + width: 40px; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 20px; + height: 20px; + fill: $color-white; + } + } + } + + .error-message { + margin: 0 2rem; + color: $color-danger; + font-size: $fs12; + font-style: italic; + } + + .linked-libraries { + display: flex; + flex-wrap: wrap; + margin-left: 2rem; + + .icon-chain, .icon-unchain { + width: 10px; + height: 10px; + margin-right: 2px; + } + + .linked-library-tag { + font-size: $fs10; + color: $color-black; + background: #d8f7fe; + border-radius: 3px; + padding: 2px 4px; + display: flex; + align-items: center; + margin: 0.25rem; + + &.error { + background-color: $color-danger-lighter; + } + } + } + + .edit-entry-buttons { + display: flex; + flex-direction: row; + font-size: $fs14; + height: 2rem; + display: none; + + button { + border: none; + background: none; + display: block; + cursor: pointer; + + svg { + width: 14px; + height: 14px; + } + + &:hover svg { + fill: $color-primary; + } + } + } +} + +.export-dialog { + min-height: 24rem; + + .export-option { + border-radius: 4px; + border: 1px solid $color-gray-10; + margin-bottom: 0.5rem; + + h3 { + font-weight: 700; + } + + h3, p { + font-size: $fs12; + line-height: 1.5; + margin: 0; + color: $color-black; + padding: 0; + } + + &.selected { + border: 1px solid $color-primary; + } + } + + .option-container { + display: block; + position: relative; + padding-left: 40px; + padding-right: 1rem; + padding-top: 1rem; + padding-bottom: 1rem; + // margin-bottom: 12px; + cursor: pointer; + user-select: none; + + input { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; + } + + .option-radio-check { + position: absolute; + top: 1rem; + left: 12px; + height: 18px; + width: 18px; + background-color: $color-white; + border: 1px solid $color-gray-10; + border-radius: 50%; + } + + &:hover input ~ .option-radio-check { + border-color: $color-primary; + } + + input:checked ~ .option-radio-check { + border-color: $color-primary; + background-color: $color-white; + } + + .option-radio-check:after { + content: ""; + position: absolute; + display: none; + } + + input:checked ~ .option-radio-check:after { + display: block; + background-color: $color-primary; + } + + .option-radio-check:after { + top: 3px; + left: 3px; + width: 10px; + height: 10px; + border-radius: 50%; + background: white; + } + } } .libraries-dialog { @@ -564,4 +890,3 @@ top: 0; } } - diff --git a/frontend/src/app/main/ui/dashboard.cljs b/frontend/src/app/main/ui/dashboard.cljs index ef87216974..9d2f00178f 100644 --- a/frontend/src/app/main/ui/dashboard.cljs +++ b/frontend/src/app/main/ui/dashboard.cljs @@ -13,8 +13,10 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.context :as ctx] + [app.main.ui.dashboard.export] [app.main.ui.dashboard.files :refer [files-section]] [app.main.ui.dashboard.fonts :refer [fonts-page font-providers-page]] + [app.main.ui.dashboard.import] [app.main.ui.dashboard.libraries :refer [libraries-page]] [app.main.ui.dashboard.projects :refer [projects-section]] [app.main.ui.dashboard.search :refer [search-page]] @@ -131,4 +133,3 @@ :section section :search-term search-term :team team}])])]])) - diff --git a/frontend/src/app/main/ui/dashboard/export.cljs b/frontend/src/app/main/ui/dashboard/export.cljs new file mode 100644 index 0000000000..059c91f057 --- /dev/null +++ b/frontend/src/app/main/ui/dashboard/export.cljs @@ -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) UXBOX Labs SL + +(ns app.main.ui.dashboard.export + (:require + [app.common.data :as d] + [app.main.data.modal :as modal] + [app.main.store :as st] + [app.main.ui.icons :as i] + [app.main.worker :as uw] + [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [beicon.core :as rx] + [rumext.alpha :as mf])) + +(def ^:const options [:all :merge :detach]) + +(mf/defc export-dialog + {::mf/register modal/components + ::mf/register-as :export} + [{:keys [team-id files]}] + (let [selected-option (mf/use-state :all) + + cancel-fn + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (modal/hide)))) + + accept-fn + (mf/use-callback + (mf/deps @selected-option) + (fn [event] + (dom/prevent-default event) + + (->> (uw/ask-many! + {:cmd :export-file + :team-id team-id + :export-type @selected-option + :files files}) + (rx/subs + (fn [msg] + (when (= :finish (:type msg)) + (dom/trigger-download-uri (:filename msg) (:mtype msg) (:uri msg)))))) + + (st/emit! (modal/hide)))) + + on-change-handler + (mf/use-callback + (fn [_ type] + (reset! selected-option type)))] + + [:div.modal-overlay + [:div.modal-container.export-dialog + [:div.modal-header + [:div.modal-header-title + [:h2 (tr "dashboard.export.title")]] + + [:div.modal-close-button + {:on-click cancel-fn} i/close]] + + [:div.modal-content + [:p.explain (tr "dashboard.export.explain")] + [:p.detail (tr "dashboard.export.detail")] + + (for [type [:all :merge :detach]] + (let [selected? (= @selected-option type)] + [:div.export-option {:class (when selected? "selected")} + [:label.option-container + [:h3 (tr (str "dashboard.export.options." (d/name type) ".title"))] + [:p (tr (str "dashboard.export.options." (d/name type) ".message"))] + [:input {:type "radio" + :checked selected? + :on-change #(on-change-handler % type) + :name "export-option"}] + [:span {:class "option-radio-check"}]]]))] + + [:div.modal-footer + [:div.action-buttons + [:input.cancel-button + {:type "button" + :value (tr "labels.cancel") + :on-click cancel-fn}] + + [:input.accept-button + {:class "primary" + :type "button" + :value (tr "labels.export") + :on-click accept-fn}]]]]])) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 8da556dbc3..8f73075b5d 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -13,8 +13,6 @@ [app.main.store :as st] [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.context :as ctx] - [app.main.worker :as uw] - [app.util.debug :as d] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] @@ -158,18 +156,11 @@ on-export-files (fn [_] - (->> (uw/ask-many! - {:cmd :export-file - :team-id current-team-id - :files (->> files (mapv :id))}) - (rx/subs - (fn [msg] - (case (:type msg) - :progress - (prn "[Progress]" (:data msg)) - - :finish - (dom/trigger-download-uri (:filename msg) (:mtype msg) (:uri msg)))))))] + (st/emit! + (modal/show + {:type :export + :team-id current-team-id + :files (->> files (mapv :id))})))] (mf/use-effect (fn [] @@ -195,8 +186,7 @@ [[(tr "dashboard.duplicate-multi" file-count) on-duplicate] (when (or (seq current-projects) (seq other-teams)) [(tr "dashboard.move-to-multi" file-count) nil sub-options]) - (when (d/debug? :export) - [(tr "dashboard.export-multi" file-count) on-export-files]) + [(tr "dashboard.export-multi" file-count) on-export-files] [:separator] [(tr "labels.delete-multi-files" file-count) on-delete]] @@ -208,8 +198,7 @@ (if (:is-shared file) [(tr "dashboard.remove-shared") on-del-shared] [(tr "dashboard.add-shared") on-add-shared]) - (when (d/debug? :export) - [(tr "dashboard.export-single") on-export-files]) + [(tr "dashboard.export-single") on-export-files] [:separator] [(tr "labels.delete") on-delete]])] diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index e3f63b38d1..005ba87892 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -6,37 +6,41 @@ (ns app.main.ui.dashboard.import (:require + [app.common.data :as d] + [app.main.data.modal :as modal] + [app.main.store :as st] [app.main.ui.components.file-uploader :refer [file-uploader]] + [app.main.ui.icons :as i] [app.main.worker :as uw] + [app.util.data :refer [classnames]] [app.util.dom :as dom] + [app.util.i18n :as i18n :refer [tr]] + [app.util.keyboard :as kbd] [app.util.logging :as log] [beicon.core :as rx] [rumext.alpha :as mf])) (log/set-level! :debug) +(defn rx-delay-emit [ms ob] + (->> ob (rx/mapcat #(rx/delay ms (rx/of %))))) + (defn use-import-file [project-id on-finish-import] (mf/use-callback (mf/deps project-id on-finish-import) (fn [files] (when files - (let [files (->> files (mapv dom/create-uri))] - (->> (uw/ask-many! - {:cmd :import-file - :project-id project-id - :files files}) - - (rx/subs - (fn [result] - (log/debug :action "import-result" :result result)) - - (fn [err] - (log/debug :action "import-error" :result err)) - - (fn [] - (log/debug :action "import-end") - (when on-finish-import (on-finish-import)))))))))) + (let [files (->> files + (mapv + (fn [file] + {:name (.-name file) + :uri (dom/create-uri file)})))] + (st/emit! (modal/show + {:type :import + :project-id project-id + :files files + :on-finish-import on-finish-import}))))))) (mf/defc import-form {::mf/forward-ref true} @@ -49,6 +53,264 @@ :ref external-ref :on-selected on-file-selected}]])) +(defn update-file [files file-id new-name] + (->> files + (mapv + (fn [file] + (cond-> file + (= (:file-id file) file-id) + (assoc :name new-name)))))) +(defn remove-file [files file-id] + (->> files + (mapv + (fn [file] + (cond-> file + (= (:file-id file) file-id) + (assoc :deleted? true)))))) +(defn set-analyze-error + [files uri] + (->> files + (mapv (fn [file] + (cond-> file + (= uri (:uri file)) + (assoc :status :analyze-error)))))) +(defn set-analyze-result [files uri data] + (let [exiting-files? (into #{} (->> files (map :file-id) (filter some?))) + replace-file + (fn [file] + (if (and (= uri (:uri file) ) + (= (:status file) :analyzing)) + (->> (:files data) + (remove (comp exiting-files? first) ) + (mapv (fn [[file-id file-data]] + (-> file-data + (assoc :file-id file-id + :status :ready + :uri uri))))) + [file]))] + (into [] (mapcat replace-file) files))) + +(defn mark-files-importing [files] + (->> files + (filter #(= :ready (:status %))) + (mapv #(assoc % :status :importing)))) + +(defn update-status [files file-id status] + (->> files + (mapv (fn [file] + (cond-> file + (= file-id (:file-id file)) + (assoc :status status)))))) + +(mf/defc import-entry + [{:keys [state file editing?]}] + + (let [loading? (or (= :analyzing (:status file)) + (= :importing (:status file))) + load-success? (= :import-success (:status file)) + analyze-error? (= :analyze-error (:status file)) + import-error? (= :import-error (:status file)) + ready? (= :ready (:status file)) + is-shared? (:shared file) + + handle-edit-key-press + (mf/use-callback + (fn [e] + (when (or (kbd/enter? e) (kbd/esc? e)) + (dom/prevent-default e) + (dom/stop-propagation e) + (dom/blur! (dom/get-target e))))) + + handle-edit-blur + (mf/use-callback + (mf/deps file) + (fn [e] + (let [value (dom/get-target-val e)] + (swap! state #(-> (assoc % :editing nil) + (update :files update-file (:file-id file) value)))))) + + handle-edit-entry + (mf/use-callback + (mf/deps file) + (fn [] + (swap! state assoc :editing (:file-id file)))) + + handle-remove-entry + (mf/use-callback + (mf/deps file) + (fn [] + (swap! state update :files remove-file (:file-id file))))] + + [:div.file-entry + {:class (classnames :loading loading? + :success load-success? + :error (or import-error? analyze-error?) + :editable (and ready? (not editing?)))} + + [:div.file-name + [:div.file-icon + (cond loading? i/loader-pencil + ready? i/logo-icon + load-success? i/tick + import-error? i/close + analyze-error? i/close)] + + (if editing? + [:div.file-name-edit + [:input {:type "text" + :auto-focus true + :default-value (:name file) + :on-key-press handle-edit-key-press + :on-blur handle-edit-blur}]] + + [:div.file-name-label (:name file) (when is-shared? i/library)]) + + [:div.edit-entry-buttons + [:button {:on-click handle-edit-entry} i/pencil] + [:button {:on-click handle-remove-entry} i/trash]]] + + (when analyze-error? + [:div.error-message + (tr "dashboard.import.analyze-error")]) + + (when import-error? + [:div.error-message + (tr "dashboard.import.import-error")]) + + [:div.linked-libraries + (for [library-id (:libraries file)] + (let [library-data (->> @state :files (d/seek #(= library-id (:file-id %)))) + error? (or (:deleted? library-data) (:import-error library-data))] + (when (some? library-data) + [:div.linked-library-tag {:class (when error? "error")} + (if error? i/unchain i/chain) (:name library-data)])))]])) + +(mf/defc import-dialog + {::mf/register modal/components + ::mf/register-as :import} + [{:keys [project-id files on-finish-import]}] + (let [state (mf/use-state + {:status :analyzing + :editing nil + :files (->> files + (mapv #(assoc % :status :analyzing)))}) + + analyze-import + (mf/use-callback + (fn [files] + (->> (uw/ask-many! + {:cmd :analyze-import + :files (->> files (mapv :uri))}) + (rx-delay-emit 1000) + (rx/subs + (fn [{:keys [uri data error] :as msg}] + (log/debug :msg msg) + (if (some? error) + (swap! state update :files set-analyze-error uri) + (swap! state update :files set-analyze-result uri data))))))) + + import-files + (mf/use-callback + (fn [project-id files] + (->> (uw/ask-many! + {:cmd :import-files + :project-id project-id + :files files}) + (rx-delay-emit 1000) + (rx/subs + (fn [{:keys [file-id status] :as msg}] + (log/debug :msg msg) + (swap! state update :files update-status file-id status)))))) + + handle-cancel + (mf/use-callback + (mf/deps (:editing @state)) + (fn [event] + (when (nil? (:editing @state)) + (dom/prevent-default event) + (st/emit! (modal/hide))))) + + handle-continue + (mf/use-callback + (mf/deps project-id (:files @state)) + (fn [event] + (dom/prevent-default event) + (let [files (->> @state :files (filterv #(= :ready (:status %))))] + (import-files project-id files)) + + (swap! state + (fn [state] + (-> state + (assoc :status :importing) + (update :files mark-files-importing)))))) + + handle-accept + (mf/use-callback + (fn [event] + (dom/prevent-default event) + (st/emit! (modal/hide)) + (when on-finish-import (on-finish-import)))) + + success-files (->> @state :files (filter #(= (:status %) :import-success)) count) + pending-analysis? (> (->> @state :files (filter #(= (:status %) :analyzing)) count) 0) + pending-import? (> (->> @state :files (filter #(= (:status %) :importing)) count) 0)] + + (mf/use-effect + (fn [] + (let [sub (analyze-import files)] + #(rx/dispose! sub)))) + + (mf/use-effect + (fn [] + ;; dispose uris when the component is umount + #(doseq [file files] + (dom/revoke-uri (:uri file))))) + + [:div.modal-overlay + [:div.modal-container.import-dialog + [:div.modal-header + [:div.modal-header-title + [:h2 (tr "dashboard.import")]] + + [:div.modal-close-button + {:on-click handle-cancel} i/close]] + + [:div.modal-content + (when (and (= :importing (:status @state)) + (not pending-import?)) + [:div.feedback-banner + [:div.icon i/checkbox-checked] + [:div.message (tr "dashboard.import.import-message" success-files)]]) + + (for [file (->> (:files @state) (filterv (comp not :deleted?)))] + (let [editing? (and (some? (:file-id file)) + (= (:file-id file) (:editing @state)))] + [:& import-entry {:state state + :file file + :editing? editing?}]))] + + [:div.modal-footer + [:div.action-buttons + [:input.cancel-button + {:type "button" + :value (tr "labels.cancel") + :on-click handle-cancel}] + + (when (= :analyzing (:status @state)) + [:input.accept-button + {:class "primary" + :type "button" + :value (tr "labels.continue") + :disabled pending-analysis? + :on-click handle-continue}]) + + (when (= :importing (:status @state)) + [:input.accept-button + {:class "primary" + :type "button" + :value (tr "labels.accept") + :disabled pending-import? + :on-click handle-accept}])]]]])) diff --git a/frontend/src/app/main/ui/dashboard/project_menu.cljs b/frontend/src/app/main/ui/dashboard/project_menu.cljs index 48e0ab05e4..8967e8a0ca 100644 --- a/frontend/src/app/main/ui/dashboard/project_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/project_menu.cljs @@ -14,7 +14,6 @@ [app.main.ui.components.context-menu :refer [context-menu]] [app.main.ui.context :as ctx] [app.main.ui.dashboard.import :as udi] - [app.util.debug :as d] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.router :as rt] @@ -107,8 +106,7 @@ [(tr "dashboard.move-to") nil (for [team teams] [(:name team) (on-move (:id team))])]) - (when (d/debug? :import) - [(tr "dashboard.import") on-import-files]) + [(tr "dashboard.import") on-import-files] [:separator] [(tr "labels.delete") on-delete]]}]])) diff --git a/frontend/src/app/main/ui/icons.clj b/frontend/src/app/main/ui/icons.clj index c44da08e5f..a31a1fa92d 100644 --- a/frontend/src/app/main/ui/icons.clj +++ b/frontend/src/app/main/ui/icons.clj @@ -9,8 +9,9 @@ (defmacro icon-xref [id] - (let [href (str "#icon-" (name id))] + (let [href (str "#icon-" (name id)) + class (str "icon-" (name id))] `(rumext.alpha/html - [:svg {:width 500 :height 500} + [:svg {:width 500 :height 500 :class ~class} [:use {:xlinkHref ~href}]]))) diff --git a/frontend/src/app/main/ui/loader.cljs b/frontend/src/app/main/ui/loader.cljs index bd5500b18c..424e071dd2 100644 --- a/frontend/src/app/main/ui/loader.cljs +++ b/frontend/src/app/main/ui/loader.cljs @@ -15,4 +15,4 @@ (mf/defc loader [] (when (mf/deref st/loader) - [:div.loader-content i/loader])) + [:div.loader-content i/loader-pencil])) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index 66a5f53f20..ccdf6be61d 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -213,6 +213,10 @@ [node] (.focus node)) +(defn blur! + [node] + (.blur node)) + (defn fullscreen? [] (cond diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 34377667b6..045e122d42 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -26,7 +26,7 @@ (defn create-manifest "Creates a manifest entry for the given files" - [team-id file-id files] + [team-id file-id export-type files] (letfn [(format-page [manifest page] (-> manifest (assoc (str (:id page)) @@ -47,6 +47,7 @@ :pages pages :pagesIndex index :libraries (->> (:libraries file) (into #{}) (mapv str)) + :exportType (d/name export-type) :hasComponents (d/not-empty? (get-in file [:data :components])) :hasMedia (d/not-empty? (get-in file [:data :media])) :hasColors (d/not-empty? (get-in file [:data :colors])) @@ -158,8 +159,36 @@ (-> file (assoc :libraries libraries-ids))))))) +(defn merge-assets [target-file assets-files] + (let [merge-file-assets + (fn [target file] + (-> target + (update-in [:data :colors] merge (get-in file [:data :colors])) + (update-in [:data :typographies] merge (get-in file [:data :typographies])) + (update-in [:data :media] merge (get-in file [:data :media])) + (update-in [:data :components] merge (get-in file [:data :components]))))] + + (->> assets-files + (reduce merge-file-assets target-file)))) + +(defn detach-libraries + [files file-id] + files) + +(defn process-export + [file-id export-type files] + + (case export-type + :all files + :merge (let [file-list (-> files (d/without-keys [file-id]) vals)] + (-> (select-keys files [file-id]) + (update file-id merge-assets file-list) + (update file-id dissoc :libraries))) + :detach (-> (select-keys files [file-id]) + (update file-id detach-libraries file-id)))) + (defn collect-files - [file-id] + [file-id export-type] (letfn [(fetch-dependencies [[files pending]] (if (empty? pending) @@ -185,17 +214,18 @@ (->> (rx/of [files pending]) (rx-expand fetch-dependencies) (rx/last) - (rx/map first))))) + (rx/map first) + (rx/map #(process-export file-id export-type %)))))) (defn export-file - [team-id file-id] + [team-id file-id export-type] - (let [files-stream (->> (collect-files file-id) + (let [files-stream (->> (collect-files file-id export-type) (rx/share)) manifest-stream (->> files-stream - (rx/map #(create-manifest team-id file-id %)) + (rx/map #(create-manifest team-id file-id export-type %)) (rx/map #(vector "manifest.json" %))) render-stream @@ -258,10 +288,10 @@ (rx/map #(vector (get files file-id) %))))))))) (defmethod impl/handler :export-file - [{:keys [team-id files] :as message}] + [{:keys [team-id files export-type] :as message}] (->> (rx/from files) - (rx/mapcat #(export-file team-id %)) + (rx/mapcat #(export-file team-id % export-type)) (rx/map (fn [value] (if (contains? value :type) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 21a3dc9111..79a33129ae 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -84,25 +84,26 @@ (let [id-mapping-atom (atom {}) resolve (fn [id-mapping id] - (assert (uuid? id)) + (assert (uuid? id) (str id)) (get id-mapping id)) set-id (fn [id-mapping id] - (assert (uuid? id)) + (assert (uuid? id) (str id)) (cond-> id-mapping (nil? (resolve id-mapping id)) (assoc id (uuid/next))))] (fn [id] - (swap! id-mapping-atom set-id id) - (resolve @id-mapping-atom id)))) + (when (some? id) + (swap! id-mapping-atom set-id id) + (resolve @id-mapping-atom id))))) (defn create-file "Create a new file on the back-end" - [context file-id] + [context] (let [resolve (:resolve context) - file-id (resolve file-id)] + file-id (resolve (:file-id context))] (rp/mutation :create-temp-file {:id file-id @@ -111,19 +112,19 @@ :project-id (:project-id context) :data (-> cp/empty-file-data (assoc :id file-id))}))) -(defn persist-file [file] - (rp/mutation :persist-temp-file {:id (:id file)})) - (defn link-file-libraries "Create a new file on the back-end" - [context file-id] + [context] (let [resolve (:resolve context) - file-id (resolve file-id) + file-id (resolve (:file-id context)) 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))))) +(defn persist-file [file] + (rp/mutation :persist-temp-file {:id (:id file)})) + (defn send-changes "Creates batches of changes to be sent to the backend" [file] @@ -391,65 +392,59 @@ (rx/flat-map (partial process-library-typographies context)) (rx/flat-map (partial process-library-media context)) (rx/flat-map (partial process-library-components context)) - (rx/flat-map send-changes) - (rx/ignore))) + (rx/flat-map send-changes))) -(defn create-files [context manifest] - (->> manifest :files rx/from +(defn create-files + [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) + (rx/map #(vector % (first (get data (:file-id context))))))))) + + (->> (rx/from files) + (rx/map #(merge context %)) + (rx/flat-map link-file-libraries) + (rx/ignore))))) + +(defmethod impl/handler :analyze-import + [{:keys [files]}] + + (->> (rx/from files) (rx/flat-map - (fn [[file-id file-desc]] - (create-file (merge context file-desc) file-id))) - (rx/reduce #(assoc %1 (:id %2) %2) {}))) + (fn [uri] + (->> (rx/of uri) + (rx/flat-map uz/load-from-url) + (rx/flat-map #(get-file {:zip %} :manifest)) + (rx/map (comp d/kebab-keys cip/string->uuid)) + (rx/map #(hash-map :uri uri :data %)) + (rx/catch #(rx/of {:uri uri :error (.-message %)}))))))) -(defn link-libraries [context manifest] - (->> manifest :files rx/from - (rx/flat-map - (fn [[file-id file-desc]] - (link-file-libraries (merge context file-desc) file-id))))) - -(defn process-files [context manifest files] - (->> manifest :files rx/from - (rx/flat-map - (fn [[file-id file-desc]] - (let [resolve (:resolve context) - context (-> context - (merge file-desc) - (assoc :file-id file-id)) - file (get files (resolve file-id))] - (process-file context file)))))) - -(defn process-package - [context] - (->> (get-file context :manifest) - (rx/map (comp d/kebab-keys cip/string->uuid)) - - ;; Create the temporary files - (rx/mapcat (fn [manifest] - (->> (create-files context manifest) - (rx/map #(vector manifest %))))) - - ;; Set-up the files dependencies - (rx/mapcat (fn [[manifest files]] - (rx/concat - (link-libraries context manifest) - (rx/of [manifest files])))) - - ;; Creates files data - (rx/mapcat (fn [[manifest files]] - (process-files context manifest files))) - - ;; Mark temporary files as persisted - (rx/mapcat persist-file))) - -(defmethod impl/handler :import-file +(defmethod impl/handler :import-files [{:keys [project-id files]}] (let [context {:project-id project-id :resolve (resolve-factory)}] - (->> (rx/from files) - (rx/flat-map uz/load-from-url) - (rx/map #(assoc context :zip %)) - (rx/flat-map process-package) - (rx/catch - (fn [err] - (.error js/console "ERROR" err (clj->js (.-data err)))))))) + (->> (create-files context files) + (rx/catch #(.error js/console "IMPORT ERROR" %)) + (rx/flat-map + (fn [[file data]] + (->> (uz/load-from-url (:uri data)) + (rx/map #(-> context (assoc :zip %) (merge data))) + (rx/flat-map #(process-file % file)) + (rx/map + (fn [_] + {:status :import-success + :file-id (:file-id data)})) + + (rx/catch + (fn [err] + (.error js/console "ERROR" (:file-id data) err) + (rx/of {:status :import-error + :file-id (:file-id data) + :error (.-message err) + :error-data (clj->js (.-data err))}))))))))) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 6d3a522825..ec28a49227 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -822,6 +822,9 @@ msgstr "Admin" msgid "labels.all" msgstr "All" +msgid "labels.continue" +msgstr "Continue" + #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "" @@ -2696,4 +2699,43 @@ 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 "Click to close the path" + +msgid "dashboard.import.import-message" +msgstr "%s files have been imported succesfully." + +msgid "dashboard.import.import-error" +msgstr "There was a problem importing the file. The file wasn't imported." + +msgid "dashboard.import.analyze-error" +msgstr "Oops! We couldn't import this file" + +msgid "dashboard.export.title" +msgstr "Export files" + +msgid "dashboard.export.explain" +msgstr "One or more files that you want to export are using shared libraries. What do you want to do with their assets*?" + +msgid "dashboard.export.detail" +msgstr "* Might include components, graphics, colors and/or typographies." + +msgid "dashboard.export.options.all.title" +msgstr "Export shared libraries" + +msgid "dashboard.export.options.all.message" +msgstr "files with shared libraries will be included in the export, maintaining their linkage." + +msgid "dashboard.export.options.merge.title" +msgstr "Include shared library assets in file libraries" + +msgid "dashboard.export.options.merge.message" +msgstr "Your file will be exported with all external assets merged into the file library." + +msgid "dashboard.export.options.detach.title" +msgstr "Treat shared library assets as basic objects" + +msgid "dashboard.export.options.detach.message" +msgstr "Shared libraries will not be included in the export and no assets will be added to the library. " + +msgid "labels.export" +msgstr "Export" From 2771cab71a0cb681bc4b7c741882e89d03cebb54 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Fri, 2 Jul 2021 15:11:28 +0200 Subject: [PATCH 162/204] :sparkles: Export options --- .../src/app/main/ui/workspace/header.cljs | 13 +- frontend/src/app/worker/export.cljs | 187 ++++++++++++++++-- 2 files changed, 188 insertions(+), 12 deletions(-) diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 6eccf64825..2461732648 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -132,7 +132,15 @@ (handle-blur event))) start-editing-name (fn [event] (dom/prevent-default event) - (reset! editing? true))] + (reset! editing? true)) + + on-export-files + (fn [_] + (st/emit! + (modal/show + {:type :export + :team-id team-id + :files [(:id file)]})))] (mf/use-effect (mf/deps @editing?) #(when @editing? @@ -232,6 +240,9 @@ [:li.feedback {:on-click (st/emitf (rt/nav :settings-feedback))} [:span (tr "labels.give-feedback")] [:span.primary-badge "ALPHA"]]) + + [:li.export-file {:on-click on-export-files} + [:span (tr "dashboard.export-single")]] ]]])) ;; --- Header Component diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 045e122d42..075b658423 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -7,6 +7,7 @@ (ns app.worker.export (:require [app.common.data :as d] + [app.common.text :as ct] [app.config :as cfg] [app.main.render :as r] [app.main.repo :as rp] @@ -159,22 +160,184 @@ (-> file (assoc :libraries libraries-ids))))))) +(defn get-component-ref-file + [objects shape] + + (cond + (contains? shape :component-file) + (get shape :component-file) + + (contains? shape :shape-ref) + (recur objects (get objects (:parent-id shape))) + + :else + nil)) + +(defn detach-external-references + [file file-id] + (let [detach-text + (fn [content] + (->> content + (ct/transform-nodes + #(cond-> % + (not= file-id (:fill-color-ref-file %)) + (dissoc :fill-color-ref-id :fill-color-ref-file) + + (not= file-id (:typography-ref-file %)) + (dissoc :typography-ref-id :typography-ref-file))))) + + detach-shape + (fn [objects shape] + (cond-> shape + (not= file-id (:fill-color-ref-file shape)) + (dissoc :fill-color-ref-id :fill-color-ref-file) + + (not= file-id (:stroke-color-ref-file shape)) + (dissoc :stroke-color-ref-id :stroke-color-ref-file) + + (not= file-id (get-component-ref-file objects shape)) + (dissoc :component-file :component-file :shape-ref :component-root) + + (= :text (:type shape)) + (update :content detach-text))) + + detach-objects + (fn [objects] + (->> objects + (d/mapm #(detach-shape objects %2)))) + + detach-pages + (fn [pages-index] + (->> pages-index + (d/mapm + (fn [_ data] + (-> data + (update :objects detach-objects))))))] + + (-> file + (update-in [:data :pages-index] detach-pages)))) + +(defn make-local-external-references + [file file-id] + (let [detach-text + (fn [content] + (->> content + (ct/transform-nodes + #(cond-> % + (not= file-id (:fill-color-ref-file %)) + (assoc :fill-color-ref-file file-id) + + (not= file-id (:typography-ref-file %)) + (assoc :typography-ref-file file-id))))) + + detach-shape + (fn [shape] + (cond-> shape + (not= file-id (:fill-color-ref-file shape)) + (assoc :fill-color-ref-file file-id) + + (not= file-id (:stroke-color-ref-file shape)) + (assoc :stroke-color-ref-file file-id) + + (not= file-id (:component-file shape)) + (assoc :component-file file-id) + + (= :text (:type shape)) + (update :content detach-text))) + + detach-objects + (fn [objects] + (->> objects + (d/mapm #(detach-shape %2)))) + + detach-pages + (fn [pages-index] + (->> pages-index + (d/mapm + (fn [_ data] + (-> data + (update :objects detach-objects))))))] + (-> file + (update-in [:data :pages-index] detach-pages)))) + +(defn collect-external-references + [file] + + (let [get-text-refs + (fn [content] + (->> content + (ct/node-seq #(or (contains? % :fill-color-ref-id) + (contains? % :typography-ref-id))) + + (mapcat (fn [node] + (cond-> [] + (contains? node :fill-color-ref-id) + (conj {:id (:fill-color-ref-id node) + :file-id (:fill-color-ref-file node)}) + + (contains? node :typography-ref-id) + (conj {:id (:typography-ref-id node) + :file-id (:typography-ref-file node)}) + ))) + + (into []))) + + get-shape-refs + (fn [[_ shape]] + (cond-> [] + (contains? shape :fill-color-ref-id) + (conj {:id (:fill-color-ref-id shape) + :file-id (:fill-color-ref-file shape)}) + + (contains? shape :stroke-color-ref-id) + (conj {:id (:stroke-color-ref-id shape) + :file-id (:stroke-color-ref-file shape)}) + + (contains? shape :component-id) + (conj {:id (:component-id shape) + :file-id (:component-file shape)}) + + (= :text (:type shape)) + (d/concat (get-text-refs (:content shape)))))] + + (->> (get-in file [:data :pages-index]) + (vals) + (mapcat :objects) + (mapcat get-shape-refs) + (filter (comp some? :file-id)) + (filter (comp some? :id)) + (group-by :file-id) + (d/mapm #(mapv :id %2))))) + (defn merge-assets [target-file assets-files] - (let [merge-file-assets + (let [external-refs (collect-external-references target-file) + + merge-file-assets (fn [target file] - (-> target - (update-in [:data :colors] merge (get-in file [:data :colors])) - (update-in [:data :typographies] merge (get-in file [:data :typographies])) - (update-in [:data :media] merge (get-in file [:data :media])) - (update-in [:data :components] merge (get-in file [:data :components]))))] + (let [colors (-> (get-in file [:data :colors]) + (select-keys (get external-refs (:id file)))) + typographies (-> (get-in file [:data :typographies]) + (select-keys (get external-refs (:id file)))) + media (-> (get-in file [:data :media]) + (select-keys (get external-refs (:id file)))) + components (-> (get-in file [:data :components]) + (select-keys (get external-refs (:id file))))] + (cond-> target + (d/not-empty? colors) + (update-in [:data :colors] merge colors) + + (d/not-empty? typographies) + (update-in [:data :typographies] merge typographies) + + (d/not-empty? media) + (update-in [:data :media] merge media) + + (d/not-empty? components) + (update-in [:data :components] merge components))))] (->> assets-files (reduce merge-file-assets target-file)))) -(defn detach-libraries - [files file-id] - files) - (defn process-export [file-id export-type files] @@ -183,9 +346,11 @@ :merge (let [file-list (-> files (d/without-keys [file-id]) vals)] (-> (select-keys files [file-id]) (update file-id merge-assets file-list) + (update file-id make-local-external-references file-id) (update file-id dissoc :libraries))) :detach (-> (select-keys files [file-id]) - (update file-id detach-libraries file-id)))) + (update file-id detach-external-references file-id) + (update file-id dissoc :libraries)))) (defn collect-files [file-id export-type] From 96891a5e5c85481f2f875b21185874e86dfac8f6 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Jul 2021 11:35:56 +0200 Subject: [PATCH 163/204] :sparkles: Upgraded beicon version --- frontend/deps.edn | 2 +- .../resources/styles/main/partials/modal.scss | 1 - .../src/app/main/ui/dashboard/import.cljs | 11 ++++------ frontend/src/app/worker/export.cljs | 8 +------ frontend/src/app/worker/import.cljs | 21 +++---------------- 5 files changed, 9 insertions(+), 34 deletions(-) diff --git a/frontend/deps.edn b/frontend/deps.edn index 7d8848998d..2958789ddb 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -6,7 +6,7 @@ binaryage/devtools {:mvn/version "RELEASE"} metosin/reitit-core {:mvn/version "0.5.13"} - funcool/beicon {:mvn/version "2021.06.03-0"} + funcool/beicon {:mvn/version "2021.07.05-1"} funcool/okulary {:mvn/version "2020.04.14-0"} funcool/potok {:mvn/version "2021.06.07-0"} funcool/rumext {:mvn/version "2021.05.12-1"} diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index e61ec1423a..adb71207fd 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -482,7 +482,6 @@ padding-right: 1rem; padding-top: 1rem; padding-bottom: 1rem; - // margin-bottom: 12px; cursor: pointer; user-select: none; diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 005ba87892..342bbbaaae 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -22,9 +22,6 @@ (log/set-level! :debug) -(defn rx-delay-emit [ms ob] - (->> ob (rx/mapcat #(rx/delay ms (rx/of %))))) - (defn use-import-file [project-id on-finish-import] (mf/use-callback @@ -78,13 +75,13 @@ (assoc :status :analyze-error)))))) (defn set-analyze-result [files uri data] - (let [exiting-files? (into #{} (->> files (map :file-id) (filter some?))) + (let [existing-files? (into #{} (->> files (map :file-id) (filter some?))) replace-file (fn [file] (if (and (= uri (:uri file) ) (= (:status file) :analyzing)) (->> (:files data) - (remove (comp exiting-files? first) ) + (remove (comp existing-files? first) ) (mapv (fn [[file-id file-data]] (-> file-data (assoc :file-id file-id @@ -204,7 +201,7 @@ (->> (uw/ask-many! {:cmd :analyze-import :files (->> files (mapv :uri))}) - (rx-delay-emit 1000) + (rx/delay-emit 1000) (rx/subs (fn [{:keys [uri data error] :as msg}] (log/debug :msg msg) @@ -219,7 +216,7 @@ {:cmd :import-files :project-id project-id :files files}) - (rx-delay-emit 1000) + (rx/delay-emit 1000) (rx/subs (fn [{:keys [file-id status] :as msg}] (log/debug :msg msg) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 075b658423..f2fbe16524 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -19,12 +19,6 @@ [beicon.core :as rx] [cuerdas.core :as str])) -(defn rx-expand - "Recursively projects each source value to an Observable - which is merged in the output Observable." - [f ob] - (.pipe ob (.expand ^js js/rxjsOperators f))) - (defn create-manifest "Creates a manifest entry for the given files" [team-id file-id export-type files] @@ -377,7 +371,7 @@ (let [files {} pending [file-id]] (->> (rx/of [files pending]) - (rx-expand fetch-dependencies) + (rx/expand fetch-dependencies) (rx/last) (rx/map first) (rx/map #(process-export file-id export-type %)))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 79a33129ae..c7be805b4f 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -23,21 +23,6 @@ [cuerdas.core :as str] [tubax.core :as tubax])) -;;; TODO: Move to funcool/beicon - -(defn rx-merge-reduce [f seed ob] - (let [current-acc (atom seed)] - (->> (rx/concat - (rx/of seed) - (->> ob - (rx/mapcat #(f @current-acc %)) - (rx/tap #(reset! current-acc %)))) - (rx/last)))) - -(defn rx-skip-last - [n ob] - (.pipe ob (.skipLast js/rxjsOperators (int n)))) - ;; Upload changes batches size (def change-batch-size 100) @@ -297,7 +282,7 @@ (->> (rx/from children) (rx/filter cip/shape?) (rx/skip 1) - (rx-skip-last 1) + (rx/skip-last 1) (rx/mapcat (partial resolve-media file-id)) (rx/reduce (partial process-import-node context) file) (rx/map fb/finish-component)))) @@ -316,7 +301,7 @@ (fn [[page-id page-name]] (->> (get-file context :page page-id) (rx/map (fn [page-data] [page-id page-name page-data]))))) - (rx-merge-reduce (partial import-page context) file)))) + (rx/concat-reduce (partial import-page context) file)))) (defn process-library-colors [context file] @@ -380,7 +365,7 @@ (->> (get-file context :components) (rx/flat-map split-components) - (rx-merge-reduce (partial import-component context) file))) + (rx/concat-reduce (partial import-component context) file))) (rx/of file))) (defn process-file From 21495762891dc86c8cd6bd3b0df0c87fc98ece8e Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Jul 2021 13:11:40 +0200 Subject: [PATCH 164/204] :sparkles: Updated translations --- frontend/translations/en.po | 102 +++++++++++++++++++----------------- frontend/translations/es.po | 94 +++++++++++++++++++++++++-------- 2 files changed, 127 insertions(+), 69 deletions(-) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index ec28a49227..86b863577c 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -89,6 +89,10 @@ msgstr "Login with OpenID (SSO)" 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." @@ -159,10 +163,6 @@ msgstr "" "When creating a new account, you agree to our terms of service and privacy " "policy." -#: 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/register.cljs msgid "auth.verification-email-sent" msgstr "We've sent a verification email to" @@ -212,6 +212,41 @@ msgstr "Export %s files" msgid "dashboard.export-single" msgstr "Export file" +msgid "dashboard.export.detail" +msgstr "* Might include components, graphics, colors and/or typographies." + +msgid "dashboard.export.explain" +msgstr "" +"One or more files that you want to export are using shared libraries. What " +"do you want to do with their assets*?" + +msgid "dashboard.export.options.all.message" +msgstr "" +"files with shared libraries will be included in the export, maintaining " +"their linkage." + +msgid "dashboard.export.options.all.title" +msgstr "Export shared libraries" + +msgid "dashboard.export.options.detach.message" +msgstr "" +"Shared libraries will not be included in the export and no assets will be " +"added to the library. " + +msgid "dashboard.export.options.detach.title" +msgstr "Treat shared library assets as basic objects" + +msgid "dashboard.export.options.merge.message" +msgstr "" +"Your file will be exported with all external assets merged into the file " +"library." + +msgid "dashboard.export.options.merge.title" +msgstr "Include shared library assets in file libraries" + +msgid "dashboard.export.title" +msgstr "Export files" + msgid "dashboard.fonts.deleted-placeholder" msgstr "Font deleted" @@ -238,6 +273,15 @@ msgstr "" msgid "dashboard.import" msgstr "Import files" +msgid "dashboard.import.analyze-error" +msgstr "Oops! We couldn't import this file" + +msgid "dashboard.import.import-error" +msgstr "There was a problem importing the file. The file wasn't imported." + +msgid "dashboard.import.import-message" +msgstr "%s files have been imported succesfully." + #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" msgstr "Invite to team" @@ -822,9 +866,6 @@ msgstr "Admin" msgid "labels.all" msgstr "All" -msgid "labels.continue" -msgstr "Continue" - #: src/app/main/ui/static.cljs msgid "labels.bad-gateway.desc-message" msgstr "" @@ -853,6 +894,9 @@ msgstr "Confirm password" msgid "labels.content" msgstr "Content" +msgid "labels.continue" +msgstr "Continue" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "Create" @@ -904,6 +948,9 @@ msgstr "Editor" msgid "labels.email" msgstr "Email" +msgid "labels.export" +msgstr "Export" + #: src/app/main/ui/settings/feedback.cljs msgid "labels.feedback-disabled" msgstr "Feedback disabled" @@ -2699,43 +2746,4 @@ msgid "workspace.updates.update" msgstr "Update" msgid "workspace.viewport.click-to-close-path" -msgstr "Click to close the path" - -msgid "dashboard.import.import-message" -msgstr "%s files have been imported succesfully." - -msgid "dashboard.import.import-error" -msgstr "There was a problem importing the file. The file wasn't imported." - -msgid "dashboard.import.analyze-error" -msgstr "Oops! We couldn't import this file" - -msgid "dashboard.export.title" -msgstr "Export files" - -msgid "dashboard.export.explain" -msgstr "One or more files that you want to export are using shared libraries. What do you want to do with their assets*?" - -msgid "dashboard.export.detail" -msgstr "* Might include components, graphics, colors and/or typographies." - -msgid "dashboard.export.options.all.title" -msgstr "Export shared libraries" - -msgid "dashboard.export.options.all.message" -msgstr "files with shared libraries will be included in the export, maintaining their linkage." - -msgid "dashboard.export.options.merge.title" -msgstr "Include shared library assets in file libraries" - -msgid "dashboard.export.options.merge.message" -msgstr "Your file will be exported with all external assets merged into the file library." - -msgid "dashboard.export.options.detach.title" -msgstr "Treat shared library assets as basic objects" - -msgid "dashboard.export.options.detach.message" -msgstr "Shared libraries will not be included in the export and no assets will be added to the library. " - -msgid "labels.export" -msgstr "Export" +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 d511f003f3..06611400ef 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -2,8 +2,8 @@ msgid "" msgstr "" "PO-Revision-Date: 2021-06-18 09:19+0000\n" "Last-Translator: andy \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" @@ -91,6 +91,10 @@ msgstr "Entrar con OpenID (SSO)" msgid "auth.new-password" msgstr "Introduce la nueva contraseña" +#: src/app/main/ui/auth/register.cljs +msgid "auth.newsletter-subscription" +msgstr "Estoy de acuerdo en suscribirme a la lista de correo de Penpot" + #: src/app/main/ui/auth/recovery.cljs msgid "auth.notifications.invalid-token-error" msgstr "El código de recuperación no es válido." @@ -163,10 +167,6 @@ msgstr "" "Al crear una nueva cuenta, aceptas nuestros términos de servicio y política " "de privacidad." -#: src/app/main/ui/auth/register.cljs -msgid "auth.newsletter-subscription" -msgstr "Estoy de acuerdo en suscribirme a la lista de correo de Penpot" - #: src/app/main/ui/auth/register.cljs msgid "auth.verification-email-sent" msgstr "Hemos enviado un email de verificación a" @@ -210,6 +210,41 @@ msgstr "Duplicar %s archivos" msgid "dashboard.empty-files" msgstr "Todavía no hay ningún archivo aquí" +msgid "dashboard.export.detail" +msgstr "* Pueden incluir components, gráficos, colores y/o tipografias." + +msgid "dashboard.export.explain" +msgstr "" +"Uno o mas ficheros que quieres exportar usan librerias compartidas. ¿Qué " +"quieres hacer con los recursos*?" + +msgid "dashboard.export.options.all.message" +msgstr "" +"ficheros con librerias compartidas se inclurán en el paquete de exportación " +"y mantendrán los enlaces." + +msgid "dashboard.export.options.all.title" +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." + +msgid "dashboard.export.options.detach.title" +msgstr "Usar los recursos como objetos básicos." + +msgid "dashboard.export.options.merge.message" +msgstr "" +"Tu fichero será exportado con todos los recursos dentro de la libreria del " +"propio fichero." + +msgid "dashboard.export.options.merge.title" +msgstr "Incluir librerias compartidas dentro de las librerias del fichero." + +msgid "dashboard.export.title" +msgstr "Exportar ficheros" + msgid "dashboard.fonts.deleted-placeholder" msgstr "Fuente eliminada." @@ -230,8 +265,18 @@ 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](1). También te " -"puede interesar leer más sobre licencias tipográficas: [font licensing](2)." +"sección de Derechos de Contenido: [Penpot's Terms of Service](1). También " +"te puede interesar leer más sobre licencias tipográficas: [font " +"licensing](2)." + +msgid "dashboard.import.analyze-error" +msgstr "¡Vaya! No hemos podido importar el fichero." + +msgid "dashboard.import.import-error" +msgstr "Hubo un problema importando el fichero. No se ha creado el fichero." + +msgid "dashboard.import.import-message" +msgstr "%s ficheros han sido importados con éxito." #: src/app/main/ui/dashboard/team.cljs msgid "dashboard.invite-profile" @@ -584,9 +629,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" @@ -849,6 +894,9 @@ msgstr "Confirmar contraseña" msgid "labels.content" msgstr "Contenido" +msgid "labels.continue" +msgstr "Continuar" + #: src/app/main/ui/workspace/sidebar/assets.cljs msgid "labels.create" msgstr "Crear" @@ -857,6 +905,10 @@ msgstr "Crear" msgid "labels.create-team" msgstr "Crea un nuevo equipo" +#: src/app/main/ui/dashboard/team_form.cljs +msgid "labels.create-team.placeholder" +msgstr "Introduce un nuevo nombre de equipo" + msgid "labels.custom-fonts" msgstr "Fuentes personalizadas" @@ -896,6 +948,9 @@ msgstr "Editor" msgid "labels.email" msgstr "Correo electrónico" +msgid "labels.export" +msgstr "Exportar" + #: src/app/main/ui/settings/feedback.cljs msgid "labels.feedback-disabled" msgstr "El modulo de recepción de opiniones esta deshabilitado." @@ -1151,8 +1206,7 @@ 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" @@ -1387,6 +1441,10 @@ msgstr "Perfil guardado correctamente!" msgid "notifications.validation-email-sent" msgstr "Verificación de email enviada a %s. Comprueba tu correo." +#: src/app/main/ui/auth/recovery.cljs +msgid "profile.recovery.go-to-login" +msgstr "Ir al 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 "Varios" @@ -2684,12 +2742,4 @@ msgid "workspace.updates.update" msgstr "Actualizar" msgid "workspace.viewport.click-to-close-path" -msgstr "Pulsar para cerrar la ruta" - -#: src/app/main/ui/auth/recovery.cljs -msgid "profile.recovery.go-to-login" -msgstr "Ir al login" - -#: src/app/main/ui/dashboard/team_form.cljs -msgid "labels.create-team.placeholder" -msgstr "Introduce un nuevo nombre de equipo" +msgstr "Pulsar para cerrar la ruta" \ No newline at end of file From 6e40e4e994bb100a18a8e920f3ac94135f34d654 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Jul 2021 18:13:45 +0200 Subject: [PATCH 165/204] :books: Update changelog --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 4d477357e7..a1fc4eb470 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ - Headers button sets and menus review [Taiga #1663](https://tree.taiga.io/project/penpot/us/1663). - Preserve components if possible, when pasted into a different file [Taiga #1063](https://tree.taiga.io/project/penpot/issue/1063). - Add the ability to offload file data to a cheaper storage when file becomes inactive. +- Import/Export Penpot files from dashboard. ### :bug: Bugs fixed From f312c122ca5ebc45915f90bbe44bbb9435903faa Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Jul 2021 18:14:11 +0200 Subject: [PATCH 166/204] :bug: Migration to solve a problem with mime types --- .../src/app/migrations/sql/0062-fix-metadata-media.sql | 8 ++++++++ frontend/src/app/main/ui/workspace/header.cljs | 6 +++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 backend/src/app/migrations/sql/0062-fix-metadata-media.sql diff --git a/backend/src/app/migrations/sql/0062-fix-metadata-media.sql b/backend/src/app/migrations/sql/0062-fix-metadata-media.sql new file mode 100644 index 0000000000..5b98236961 --- /dev/null +++ b/backend/src/app/migrations/sql/0062-fix-metadata-media.sql @@ -0,0 +1,8 @@ +-- Fix problem with content-type inconherence + +UPDATE storage_object so +SET metadata = jsonb_set(metadata, '{~:content-type}', to_jsonb(fmo.mtype)) +FROM file_media_object fmo +WHERE so.id = fmo.media_id and + so.metadata->>'~:content-type' != fmo.mtype; + diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 2461732648..18c51d3ece 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -236,13 +236,13 @@ [:li {:on-click on-add-shared} [:span (tr "dashboard.add-shared")]]) + [:li.export-file {:on-click on-export-files} + [:span (tr "dashboard.export-single")]] + (when cfg/feedback-enabled [:li.feedback {:on-click (st/emitf (rt/nav :settings-feedback))} [:span (tr "labels.give-feedback")] [:span.primary-badge "ALPHA"]]) - - [:li.export-file {:on-click on-export-files} - [:span (tr "dashboard.export-single")]] ]]])) ;; --- Header Component From ddbdc2a27fb2a2f9bedf80cfe47d271be26a963d Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 5 Jul 2021 18:14:49 +0200 Subject: [PATCH 167/204] :sparkles: Import/export folders in library elements --- common/src/app/common/file_builder.cljc | 2 + frontend/src/app/main/exports.cljs | 7 +-- .../src/app/main/ui/dashboard/import.cljs | 6 ++- frontend/src/app/util/import/parser.cljs | 6 ++- frontend/src/app/worker/export.cljs | 6 +-- frontend/src/app/worker/import.cljs | 44 +++++++++++++------ 6 files changed, 48 insertions(+), 23 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 13b009128e..348c4fdd32 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -393,6 +393,7 @@ (let [selrect init/empty-selrect name (:name data) + path (:path data) obj (-> (init/make-minimal-group nil selrect name) (merge data) (check-name file :group) @@ -402,6 +403,7 @@ {:type :add-component :id (:id obj) :name name + :path path :shapes [obj]}) (assoc :last-id (:id obj)) diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index 1642f31182..b6e6e9894b 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -247,7 +247,7 @@ (mf/defc component-symbol [{:keys [id data] :as props}] - (let [{:keys [name objects]} data + (let [{:keys [name path objects]} data root (get objects id) {:keys [width height]} (:selrect root) @@ -267,8 +267,9 @@ (mf/deps objects) #(group-wrapper-factory objects))] - [:symbol {:id (str id) - :viewBox vbox} + [:> "symbol" #js {:id (str id) + :viewBox vbox + "penpot:path" path} [:title name] [:> shape-container {:shape root} [:& group-wrapper {:shape root :view-box vbox}]]])) diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index 342bbbaaae..cd9a976201 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -22,6 +22,8 @@ (log/set-level! :debug) +(def ^:const emit-delay 1000) + (defn use-import-file [project-id on-finish-import] (mf/use-callback @@ -201,7 +203,7 @@ (->> (uw/ask-many! {:cmd :analyze-import :files (->> files (mapv :uri))}) - (rx/delay-emit 1000) + (rx/delay-emit emit-delay) (rx/subs (fn [{:keys [uri data error] :as msg}] (log/debug :msg msg) @@ -216,7 +218,7 @@ {:cmd :import-files :project-id project-id :files files}) - (rx/delay-emit 1000) + (rx/delay-emit emit-delay) (rx/subs (fn [{:keys [file-id status] :as msg}] (log/debug :msg msg) diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index b4d68d0b39..3c46a9218e 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -73,8 +73,10 @@ (defn get-id [node] - (when-let [id (re-find uuid-regex (get-in node [:attrs :id]))] - (uuid/uuid id))) + (let [attr-id (get-in node [:attrs :id]) + id (when (string? attr-id) (re-find uuid-regex attr-id))] + (when (some? id) + (uuid/uuid id)))) (defn str->bool [val] diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index f2fbe16524..43c2828d52 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -82,14 +82,14 @@ [(-> k str/camel) v])))))) (def ^:const color-keys - [:name :color :opacity :gradient]) + [:name :color :opacity :gradient :path]) (def ^:const typography-keys [:name :font-family :font-id :font-size :font-style :font-variant-id :font-weight - :letter-spacing :line-height :text-transform]) + :letter-spacing :line-height :text-transform :path]) (def ^:const media-keys - [:name :mtype :width :height]) + [:name :mtype :width :height :path]) (defn collect-color [result color] diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index c7be805b4f..107b092313 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -21,7 +21,10 @@ [app.worker.impl :as impl] [beicon.core :as rx] [cuerdas.core :as str] - [tubax.core :as tubax])) + [tubax.core :as tubax] + [app.util.logging :as log])) + +(log/set-level! :trace) ;; Upload changes batches size (def change-batch-size 100) @@ -46,20 +49,23 @@ (str file-id "/media/" id "." ext)) :components (str file-id "/components.svg")) - svg? (str/ends-with? path "svg") - json? (str/ends-with? path "json") - other? (not (or svg? json?)) + parse-svg? (and (not= type :media) (str/ends-with? path "svg")) + parse-json? (and (not= type :media) (str/ends-with? path "json")) + no-parse? (or (= type :media) + (not (or parse-svg? parse-json?))) - file-type (if other? "blob" "text")] + file-type (if (or parse-svg? parse-json?) "text" "blob")] + + (log/debug :action "parsing" :path path) (cond->> (uz/get-file (:zip context) path file-type) - svg? + parse-svg? (rx/map (comp tubax/xml->clj :content)) - json? + parse-json? (rx/map (comp json/decode :content)) - other? + no-parse? (rx/map :content))))) (defn resolve-factory @@ -138,6 +144,9 @@ (defn upload-media-files "Upload a image to the backend and returns its id" [file-id name data-uri] + + (log/debug :action "uploading" :file-id file-id :name name) + (->> (http/send! {:uri data-uri :response-type :blob @@ -156,11 +165,18 @@ (->> node (ct/transform-nodes (fn [item] - (-> item - (d/update-when :fill-color-ref-id resolve) - (d/update-when :fill-color-ref-file resolve) - (d/update-when :typography-ref-id resolve) - (d/update-when :typography-ref-file resolve))))))) + (cond-> item + (uuid? (get item :fill-color-ref-id)) + (d/update-when :fill-color-ref-id resolve) + + (uuid? (get item :fill-color-ref-file)) + (d/update-when :fill-color-ref-file resolve) + + (uuid? (get item :typography-ref-id)) + (d/update-when :typography-ref-id resolve) + + (uuid? (get item :typography-ref-file)) + (d/update-when :typography-ref-file resolve))))))) (defn resolve-data-ids [data type context] @@ -273,7 +289,9 @@ file-id (:id file) old-id (cip/get-id node) id (resolve old-id) + path (get-in node [:attrs :penpot:path] "") data (-> (cip/parse-data :group content) + (assoc :path path) (assoc :id id)) file (-> file (fb/start-component data)) From d1cce4461665c2f989537826e9b9365059f642d1 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 6 Jul 2021 10:42:24 +0200 Subject: [PATCH 168/204] :tada: Add keys namespace. A modularized approach for key derivation. --- backend/scripts/build | 1 + backend/src/app/loggers/audit.clj | 1 + backend/src/app/main.clj | 5 ++++- backend/src/app/setup/keys.clj | 29 +++++++++++++++++++++++++++++ backend/src/app/tokens.clj | 25 +++++++------------------ 5 files changed, 42 insertions(+), 19 deletions(-) create mode 100644 backend/src/app/setup/keys.clj diff --git a/backend/scripts/build b/backend/scripts/build index f1d9e03a47..865fed48b0 100755 --- a/backend/scripts/build +++ b/backend/scripts/build @@ -49,6 +49,7 @@ ;; Create the application jar (spit "./target/dist/version.txt" version) + (-> ($ jar cvf "./target/dist/deps/app.jar" -C ~(first classpath-paths) ".") check) (-> ($ jar uvf "./target/dist/deps/app.jar" -C "./target/dist" "version.txt") check) (run! (fn [item] diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index deb58cfd47..835a8acff9 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -50,6 +50,7 @@ (assoc k (name v)))) {} props))] + (update event :props #(-> % clean-common clean-profile-id clean-complex-data)))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index 2125f087d9..da576cf624 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -44,7 +44,7 @@ :redis-uri (cf/get :redis-uri)} :app.tokens/tokens - {:props (ig/ref :app.setup/props)} + {:keys (ig/ref :app.setup/keys)} :app.storage/gc-deleted-task {:pool (ig/ref :app.db/pool) @@ -282,6 +282,9 @@ {:pool (ig/ref :app.db/pool) :key (cf/get :secret-key)} + :app.setup/keys + {:props (ig/ref :app.setup/props)} + :app.loggers.zmq/receiver {:endpoint (cf/get :loggers-zmq-uri)} diff --git a/backend/src/app/setup/keys.clj b/backend/src/app/setup/keys.clj new file mode 100644 index 0000000000..4618fa4949 --- /dev/null +++ b/backend/src/app/setup/keys.clj @@ -0,0 +1,29 @@ +;; 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.setup.keys + "Keys derivation service." + (:require + [app.common.spec :as us] + [buddy.core.kdf :as bk] + [clojure.spec.alpha :as s] + [integrant.core :as ig])) + +(s/def ::secret-key ::us/string) +(s/def ::props (s/keys :req-un [::secret-key])) + +(defmethod ig/pre-init-spec :app.setup/keys [_] + (s/keys :req-un [::props])) + +(defmethod ig/init-key :app.setup/keys + [_ {:keys [props] :as cfg}] + (fn [& {:keys [salt size]}] + (let [engine (bk/engine {:key (:secret-key props) + :salt salt + :alg :hkdf + :digest :blake2b-512})] + (bk/get-bytes engine 32)))) + diff --git a/backend/src/app/tokens.clj b/backend/src/app/tokens.clj index 5c96c19802..efff646d1e 100644 --- a/backend/src/app/tokens.clj +++ b/backend/src/app/tokens.clj @@ -11,19 +11,10 @@ [app.common.spec :as us] [app.common.transit :as t] [app.util.time :as dt] - [buddy.core.kdf :as bk] [buddy.sign.jwe :as jwe] [clojure.spec.alpha :as s] [integrant.core :as ig])) -(defn- derive-tokens-secret - [key] - (let [engine (bk/engine {:key key - :salt "tokens" - :alg :hkdf - :digest :blake2b-512})] - (bk/get-bytes engine 32))) - (defn- generate [cfg claims] (let [payload (t/encode claims)] @@ -50,13 +41,6 @@ :params params)) claims)) -(s/def ::secret-key ::us/string) -(s/def ::props - (s/keys :req-un [::secret-key])) - -(defmethod ig/pre-init-spec ::tokens [_] - (s/keys :req-un [::props])) - (defn- generate-predefined [cfg {:keys [iss profile-id] :as params}] (case iss @@ -70,9 +54,14 @@ :code :not-implemented :hint "no predefined token"))) +(s/def ::keys fn?) + +(defmethod ig/pre-init-spec ::tokens [_] + (s/keys :req-un [::keys])) + (defmethod ig/init-key ::tokens - [_ {:keys [props] :as cfg}] - (let [secret (derive-tokens-secret (:secret-key props)) + [_ {:keys [keys] :as cfg}] + (let [secret (keys :salt "tokens" :size 32) cfg (assoc cfg ::secret secret)] (fn [action params] (case action From 3eb209b6025ed06577cf67c0d9c1e20d18e77ade Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 6 Jul 2021 11:19:38 +0200 Subject: [PATCH 169/204] :bug: Fix import images --- frontend/src/app/util/http.cljs | 2 ++ frontend/src/app/worker/import.cljs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/frontend/src/app/util/http.cljs b/frontend/src/app/util/http.cljs index 8c613bb33c..fe60147a3c 100644 --- a/frontend/src/app/util/http.cljs +++ b/frontend/src/app/util/http.cljs @@ -165,6 +165,8 @@ :uri uri :response-type :blob :omit-default-headers true}) + + (rx/filter #(= 200 (:status %))) (rx/map :body) (rx/mapcat wapi/read-file-as-data-url) (rx/map #(hash-map uri %))))) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 107b092313..abcaa1973c 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -256,6 +256,8 @@ (let [name (cip/get-image-name node) data-uri (cip/get-image-data node)] (->> (upload-media-files file-id name data-uri) + (rx/catch #(do (.error js/console %) + (rx/of node))) (rx/map (fn [media] (-> node From 60b29a3bf58f1df240e2f6e7d40e96eb0ead2372 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 6 Jul 2021 12:19:11 +0200 Subject: [PATCH 170/204] :bug: Fix problem with import with default grids --- frontend/src/app/worker/import.cljs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index abcaa1973c..2b663814f5 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -27,7 +27,7 @@ (log/set-level! :trace) ;; Upload changes batches size -(def change-batch-size 100) +(def ^:const change-batch-size 100) (defn get-file "Resolves the file inside the context given its id and the data" @@ -212,7 +212,8 @@ data (-> (cip/parse-data type node) (resolve-data-ids type context) - (assoc :id (resolve old-id))) + (cond-> (some? old-id) + (assoc :id (resolve old-id)))) file (case type :frame (fb/add-artboard file data) @@ -225,8 +226,6 @@ :svg-raw (fb/create-svg-raw file data) #_default file)] - (assert (some? old-id) "ID not found") - ;; We store this data for post-processing after every shape has been ;; added (cond-> file From e9945235edb19fbea729843f305ae22784525acc Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 6 Jul 2021 15:40:23 +0200 Subject: [PATCH 171/204] :sparkles: Improvements on auth and login. --- backend/src/app/http/oauth.clj | 118 +++++++++++----------- backend/src/app/loggers/audit.clj | 25 ++++- backend/src/app/main.clj | 5 +- backend/src/app/rpc.clj | 19 ++-- backend/src/app/rpc/mutations/profile.clj | 23 +++-- 5 files changed, 105 insertions(+), 85 deletions(-) diff --git a/backend/src/app/http/oauth.clj b/backend/src/app/http/oauth.clj index 72a2d11412..528d90427f 100644 --- a/backend/src/app/http/oauth.clj +++ b/backend/src/app/http/oauth.clj @@ -12,6 +12,7 @@ [app.common.uri :as u] [app.config :as cf] [app.db :as db] + [app.loggers.audit :as audit] [app.rpc.queries.profile :as profile] [app.util.http :as http] [app.util.logging :as l] @@ -22,55 +23,6 @@ [cuerdas.core :as str] [integrant.core :as ig])) -(defn redirect-response - [uri] - {:status 302 - :headers {"location" (str uri)} - :body ""}) - -(defn generate-error-redirect - [cfg error] - (let [uri (-> (u/uri (:public-uri cfg)) - (assoc :path "/#/auth/login") - (assoc :query (u/map->query-string {:error "unable-to-auth" :hint (ex-message error)})))] - (redirect-response uri))) - -(defn register-profile - [{:keys [rpc] :as cfg} info] - (let [method-fn (get-in rpc [:methods :mutation :login-or-register]) - profile (method-fn info)] - (cond-> profile - (some? (:invitation-token info)) - (assoc :invitation-token (:invitation-token info))))) - -(defn generate-redirect - [{:keys [tokens session] :as cfg} request info profile] - (if profile - (let [sxf ((:create session) (:id profile)) - token (or (:invitation-token info) - (tokens :generate {:iss :auth - :exp (dt/in-future "15m") - :profile-id (:id profile)})) - params {:token token} - - uri (-> (u/uri (:public-uri cfg)) - (assoc :path "/#/auth/verify-token") - (assoc :query (u/map->query-string params)))] - (->> (redirect-response uri) - (sxf request))) - (let [info (assoc info - :iss :prepared-register - :exp (dt/in-future {:hours 48})) - token (tokens :generate info) - params (d/without-nils - {:token token - :fullname (:fullname info)}) - uri (-> (u/uri (:public-uri cfg)) - (assoc :path "/#/auth/register/validate") - (assoc :query (u/map->query-string params)))] - (redirect-response uri)))) - - (defn- build-redirect-uri [{:keys [provider] :as cfg}] (let [public (u/uri (:public-uri cfg))] @@ -198,6 +150,63 @@ {} params)) +(defn- retrieve-profile + [{:keys [pool] :as cfg} info] + (with-open [conn (db/open pool)] + (some->> (:email info) + (profile/retrieve-profile-data-by-email conn) + (profile/populate-additional-data conn) + (profile/decode-profile-row)))) + +(defn- redirect-response + [uri] + {:status 302 + :headers {"location" (str uri)} + :body ""}) + +(defn- generate-error-redirect + [cfg error] + (let [uri (-> (u/uri (:public-uri cfg)) + (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 [tokens session audit] :as cfg} request info profile] + (if profile + (let [sxf ((:create session) (:id profile)) + token (or (:invitation-token info) + (tokens :generate {:iss :auth + :exp (dt/in-future "15m") + :profile-id (:id profile)})) + params {:token token} + + uri (-> (u/uri (:public-uri cfg)) + (assoc :path "/#/auth/verify-token") + (assoc :query (u/map->query-string params)))] + + (when (fn? audit) + (audit :cmd :submit + :type "mutation" + :name "login" + :profile-id (:id profile) + :ip-addr (audit/parse-client-ip request) + :props (audit/profile->props profile))) + + (->> (redirect-response uri) + (sxf request))) + (let [info (assoc info + :iss :prepared-register + :exp (dt/in-future {:hours 48})) + token (tokens :generate info) + params (d/without-nils + {:token token + :fullname (:fullname info)}) + uri (-> (u/uri (:public-uri cfg)) + (assoc :path "/#/auth/register/validate") + (assoc :query (u/map->query-string params)))] + (redirect-response uri)))) + (defn- auth-handler [{:keys [tokens] :as cfg} {:keys [params] :as request}] (let [invitation (:invitation-token params) @@ -211,13 +220,6 @@ {:status 200 :body {:redirect-uri uri}})) -(defn- retrieve-profile - [{:keys [pool] :as cfg} info] - (with-open [conn (db/open pool)] - (some->> (:email info) - (profile/retrieve-profile-data-by-email conn) - (profile/populate-additional-data conn)))) - (defn- callback-handler [cfg request] (try @@ -238,7 +240,7 @@ (s/def ::tokens fn?) (s/def ::rpc map?) -(defmethod ig/pre-init-spec :app.http.oauth/handlers [_] +(defmethod ig/pre-init-spec ::handler [_] (s/keys :req-un [::public-uri ::session ::tokens ::rpc ::db/pool])) (defn wrap-handler @@ -253,7 +255,7 @@ (-> (assoc @cfg :provider provider) (handler request))))) -(defmethod ig/init-key :app.http.oauth/handlers +(defmethod ig/init-key ::handler [_ cfg] (let [cfg (initialize cfg)] {:handler (wrap-handler cfg auth-handler) diff --git a/backend/src/app/loggers/audit.clj b/backend/src/app/loggers/audit.clj index 835a8acff9..dbdff89208 100644 --- a/backend/src/app/loggers/audit.clj +++ b/backend/src/app/loggers/audit.clj @@ -7,6 +7,7 @@ (ns app.loggers.audit "Services related to the user activity (audit log)." (:require + [app.common.data :as d] [app.common.exceptions :as ex] [app.common.spec :as us] [app.common.transit :as t] @@ -20,9 +21,22 @@ [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])) +(defn parse-client-ip + [{:keys [headers] :as request}] + (or (some-> (get headers "x-forwarded-for") (str/split ",") first) + (get headers "x-real-ip") + (get request :remote-addr))) + +(defn profile->props + [profile] + (-> profile + (select-keys [:is-active :is-muted :auth-backend :email :default-team-id :default-project-id :fullname :lang]) + (d/without-nils))) + (defn clean-props [{:keys [profile-id] :as event}] (letfn [(clean-common [props] @@ -88,11 +102,12 @@ :cause res))) (recur))) - (fn [& [cmd & params]] - (case cmd - :stop (a/close! input) - :submit (when-not (a/offer! input (first params)) - (l/warn :msg "activity channel is full"))))))) + (fn [& {:keys [cmd] :as params}] + (let [params (dissoc params :cmd)] + (case cmd + :stop (a/close! input) + :submit (when-not (a/offer! input params) + (l/warn :msg "activity channel is full")))))))) (defn- persist-events diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index da576cf624..dc43559107 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -90,7 +90,7 @@ :tokens (ig/ref :app.tokens/tokens) :public-uri (cf/get :public-uri) :metrics (ig/ref :app.metrics/metrics) - :oauth (ig/ref :app.http.oauth/handlers) + :oauth (ig/ref :app.http.oauth/handler) :assets (ig/ref :app.http.assets/handlers) :storage (ig/ref :app.storage/storage) :sns-webhook (ig/ref :app.http.awsns/handler) @@ -107,11 +107,12 @@ :app.http.feedback/handler {:pool (ig/ref :app.db/pool)} - :app.http.oauth/handlers + :app.http.oauth/handler {:rpc (ig/ref :app.rpc/rpc) :session (ig/ref :app.http.session/session) :pool (ig/ref :app.db/pool) :tokens (ig/ref :app.tokens/tokens) + :audit (ig/ref :app.loggers.audit/collector) :public-uri (cf/get :public-uri)} ;; RLimit definition for password hashing diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 947936940f..09a2a636a9 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -89,12 +89,6 @@ (rlm/execute rlinst (f cfg params)))) f)) -(defn- parse-client-ip - [{:keys [headers] :as request}] - (or (some-> (get headers "x-forwarded-for") (str/split ",") first) - (get headers "x-real-ip") - (get request :remote-addr))) - (defn- wrap-impl [{:keys [audit] :as cfg} f mdata] (let [f (wrap-with-rlimits cfg f mdata) @@ -124,12 +118,13 @@ (:profile-id result) (::audit/profile-id resultm)) props (d/merge params (::audit/props resultm))] - (audit :submit {:type (::type cfg) - :name (or (::audit/name resultm) - (::sv/name mdata)) - :profile-id profile-id - :ip-addr (parse-client-ip request) - :props props}))) + (audit :cmd :submit + :type (::type cfg) + :name (or (::audit/name resultm) + (::sv/name mdata)) + :profile-id profile-id + :ip-addr (audit/parse-client-ip request) + :props (audit/profile->props props)))) result)))) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index b40cf802a6..17c52869a7 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -85,6 +85,11 @@ {:update false :valid false}))) +(defn decode-profile-row + [{:keys [props] :as profile}] + (cond-> profile + (db/pgobject? props "jsonb") + (assoc :props (db/decode-transit-pgobject props)))) ;; --- MUTATION: Prepare Register @@ -154,8 +159,8 @@ (check-profile-existence! conn params) (let [profile (->> params (create-profile conn) - (create-profile-relations conn))] - + (create-profile-relations conn) + (decode-profile-row))] (sid/load-initial-project! conn profile) (cond @@ -174,7 +179,7 @@ (with-meta resp {:transform-response ((:create session) (:id profile)) :before-complete (annotate-profile-register metrics) - ::audit/props (:props profile) + ::audit/props (audit/profile->props profile) ::audit/profile-id (:id profile)})) ;; If auth backend is different from "penpot" means user is @@ -184,7 +189,7 @@ (with-meta (profile/strip-private-attrs profile) {:transform-response ((:create session) (:id profile)) :before-complete (annotate-profile-register metrics) - ::audit/props (:props profile) + ::audit/props (audit/profile->props profile) ::audit/profile-id (:id profile)}) ;; In all other cases, send a verification email. @@ -208,7 +213,7 @@ (with-meta profile {:before-complete (annotate-profile-register metrics) - ::audit/props (:props profile) + ::audit/props (audit/profile->props profile) ::audit/profile-id (:id profile)})))))) (defn create-profile @@ -249,7 +254,7 @@ :is-demo is-demo}] (try (-> (db/insert! conn :profile params) - (update :props db/decode-transit-pgobject)) + (decode-profile-row)) (catch org.postgresql.util.PSQLException e (let [state (.getSQLState e)] (if (not= state "23505") @@ -314,8 +319,8 @@ (let [profile (->> (profile/retrieve-profile-data-by-email conn email) (validate-profile) (profile/strip-private-attrs) - (profile/populate-additional-data conn)) - profile (update profile :props db/decode-transit-pgobject)] + (profile/populate-additional-data conn) + (decode-profile-row))] (if-let [token (:invitation-token params)] ;; If the request comes with an invitation token, this means ;; that user wants to accept it with different user. A very @@ -330,10 +335,12 @@ token (tokens :generate claims)] (with-meta {:invitation-token token} {:transform-response ((:create session) (:id profile)) + ::audit/props (audit/profile->props profile) ::audit/profile-id (:id profile)})) (with-meta profile {:transform-response ((:create session) (:id profile)) + ::audit/props (audit/profile->props profile) ::audit/profile-id (:id profile)})))))) ;; --- MUTATION: Logout From 1ee14a76f449c24bab7e1ec126b7f14233d697b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 2 Jul 2021 13:19:04 +0200 Subject: [PATCH 172/204] :tada: Export shapes to pdf --- CHANGES.md | 1 + exporter/src/app/browser.cljs | 14 ++++ exporter/src/app/http/export.cljs | 7 +- exporter/src/app/renderer/pdf.cljs | 79 +++++++++++++++++++ .../styles/main/layouts/main-layout.scss | 4 + frontend/src/app/main/ui/render.cljs | 7 ++ .../sidebar/options/menus/exports.cljs | 27 ++++--- frontend/src/app/util/dom.cljs | 26 ++++-- 8 files changed, 146 insertions(+), 19 deletions(-) create mode 100644 exporter/src/app/renderer/pdf.cljs diff --git a/CHANGES.md b/CHANGES.md index a1fc4eb470..6c7ce28b73 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - Allow nested asset groups [Taiga #1716](https://tree.taiga.io/project/penpot/us/1716). - Allow to ungroup assets [Taiga #1719](https://tree.taiga.io/project/penpot/us/1719). - Allow to rename assets groups [Taiga #1721](https://tree.taiga.io/project/penpot/us/1721). +- Export elements to PDF [Taiga #519](https://tree.taiga.io/project/penpot/us/519). - Memorize collapse state of assets in panel [Taiga #1718](https://tree.taiga.io/project/penpot/us/1718). - Headers button sets and menus review [Taiga #1663](https://tree.taiga.io/project/penpot/us/1663). - Preserve components if possible, when pasted into a different file [Taiga #1063](https://tree.taiga.io/project/penpot/issue/1063). diff --git a/exporter/src/app/browser.cljs b/exporter/src/app/browser.cljs index 90f7cd00bb..9df73c0663 100644 --- a/exporter/src/app/browser.cljs +++ b/exporter/src/app/browser.cljs @@ -71,6 +71,20 @@ :type (name type) :omitBackground omit-background?}))) +(defn pdf + ([page] (pdf page nil)) + ([page {:keys [viewport omit-background? prefer-css-page-size?] + :or {viewport {} + omit-background? true + prefer-css-page-size? true}}] + (let [viewport (d/merge default-viewport viewport)] + (.pdf ^js page #js {:width (:width viewport) + :height (:height viewport) + :scale (:scale viewport) + :omitBackground omit-background? + :printBackground (not omit-background?) + :preferCSSPageSize prefer-css-page-size?})))) + (defn eval! [frame f] (.evaluate ^js frame f)) diff --git a/exporter/src/app/http/export.cljs b/exporter/src/app/http/export.cljs index 8be2f54709..e8b56b6665 100644 --- a/exporter/src/app/http/export.cljs +++ b/exporter/src/app/http/export.cljs @@ -10,6 +10,7 @@ [app.common.spec :as us] [app.renderer.bitmap :as rb] [app.renderer.svg :as rs] + [app.renderer.pdf :as rp] [app.zipfile :as zip] [cljs.spec.alpha :as s] [cuerdas.core :as str] @@ -89,7 +90,8 @@ (case (:type params) :png (rb/render params) :jpeg (rb/render params) - :svg (rs/render params))) + :svg (rs/render params) + :pdf (rp/render params))) (defn- find-filename-candidate [params used] @@ -101,7 +103,8 @@ (case (:type params) :png ".png" :jpeg ".jpg" - :svg ".svg"))] + :svg ".svg" + :pdf ".pdf"))] (if (contains? used candidate) (recur (inc index)) candidate)))) diff --git a/exporter/src/app/renderer/pdf.cljs b/exporter/src/app/renderer/pdf.cljs new file mode 100644 index 0000000000..56efd4e46c --- /dev/null +++ b/exporter/src/app/renderer/pdf.cljs @@ -0,0 +1,79 @@ +;; 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.renderer.pdf + "A pdf renderer." + (:require + [app.browser :as bw] + [app.common.exceptions :as ex :include-macros true] + [app.common.spec :as us] + [app.config :as cf] + [cljs.spec.alpha :as s] + [lambdaisland.uri :as u] + [lambdaisland.glogi :as log] + [promesa.core :as p])) + +(defn create-cookie + [uri token] + (let [domain (str (:host uri) + (when (:port uri) + (str ":" (:port uri))))] + {:domain domain + :key "auth-token" + :value token})) + +(defn pdf-from-object + [browser {:keys [file-id page-id object-id token scale type]}] + (letfn [(handle [page] + (let [path (str "/render-object/" file-id "/" page-id "/" object-id) + uri (-> (u/uri (cf/get :public-uri)) + (assoc :path "/") + (assoc :fragment path)) + cookie (create-cookie uri token)] + (pdf-from page (str uri) cookie))) + + (pdf-from [page uri cookie] + (log/info :uri uri) + (let [options {:cookie cookie}] + (p/do! + (bw/configure-page! page options) + (bw/navigate! page uri) + (bw/wait-for page "#screenshot") + (bw/pdf page))))] + + (bw/exec! browser handle))) + +(s/def ::name ::us/string) +(s/def ::suffix ::us/string) +(s/def ::page-id ::us/uuid) +(s/def ::file-id ::us/uuid) +(s/def ::object-id ::us/uuid) +(s/def ::scale ::us/number) +(s/def ::token ::us/string) +(s/def ::filename ::us/string) + +(s/def ::render-params + (s/keys :req-un [::name ::suffix ::object-id ::page-id ::scale ::token ::file-id] + :opt-un [::filename])) + +(defn render + [params] + (us/assert ::render-params params) + (let [browser @bw/instance] + (when-not browser + (ex/raise :type :internal + :code :browser-not-ready + :hint "browser cluster is not initialized yet")) + + (p/let [content (pdf-from-object browser params)] + {:content content + :filename (or (:filename params) + (str (:name params) + (:suffix params "") + ".pdf")) + :length (alength content) + :mime-type "application/pdf"}))) + diff --git a/frontend/resources/styles/main/layouts/main-layout.scss b/frontend/resources/styles/main/layouts/main-layout.scss index cb4b1c7b2c..bc4ffcb331 100644 --- a/frontend/resources/styles/main/layouts/main-layout.scss +++ b/frontend/resources/styles/main/layouts/main-layout.scss @@ -44,3 +44,7 @@ } } +#screenshot { + display: flex; + flex-direction: column; +} diff --git a/frontend/src/app/main/ui/render.cljs b/frontend/src/app/main/ui/render.cljs index 64fd56ec28..1a7ca192fb 100644 --- a/frontend/src/app/main/ui/render.cljs +++ b/frontend/src/app/main/ui/render.cljs @@ -9,6 +9,7 @@ [app.common.geom.matrix :as gmt] [app.common.geom.point :as gpt] [app.common.geom.shapes :as gsh] + [app.common.math :as mth] [app.common.pages :as cp] [app.common.uuid :as uuid] [app.main.data.fonts :as df] @@ -18,6 +19,7 @@ [app.main.ui.shapes.embed :as embed] [app.main.ui.shapes.filters :as filters] [app.main.ui.shapes.shape :refer [shape-container]] + [app.util.dom :as dom] [beicon.core :as rx] [cuerdas.core :as str] [rumext.alpha :as mf])) @@ -68,6 +70,11 @@ #(exports/shape-wrapper-factory objects)) ] + (mf/use-effect + (mf/deps width height) + #(dom/set-page-style {:size (str (mth/round width) "px " + (mth/round height) "px")})) + [:& (mf/provider embed/context) {:value true} [:svg {:id "screenshot" :view-box vbox 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 ff24325fcf..7e65db893e 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 @@ -36,6 +36,11 @@ (not (empty (:suffix (first exports))))) (str (:suffix (first exports)))) + scale-enabled? + (mf/use-callback + (fn [export] + (#{:png :jpeg} (:type export)))) + on-download (mf/use-callback (mf/deps shape) @@ -111,15 +116,16 @@ (for [[index export] (d/enumerate exports)] [:div.element-set-options-group {:key index} - [:select.input-select {:on-change (partial on-scale-change index) - :value (:scale export)} - [:option {:value "0.5"} "0.5x"] - [:option {:value "0.75"} "0.75x"] - [:option {:value "1"} "1x"] - [:option {:value "1.5"} "1.5x"] - [:option {:value "2"} "2x"] - [:option {:value "4"} "4x"] - [:option {:value "6"} "6x"]] + (when (scale-enabled? export) + [:select.input-select {:on-change (partial on-scale-change index) + :value (:scale export)} + [:option {:value "0.5"} "0.5x"] + [:option {:value "0.75"} "0.75x"] + [:option {:value "1"} "1x"] + [:option {:value "1.5"} "1.5x"] + [:option {:value "2"} "2x"] + [:option {:value "4"} "4x"] + [:option {:value "6"} "6x"]]) [:input.input-text {:value (:suffix export) :placeholder (tr "workspace.options.export.suffix") :on-change (partial on-suffix-change index)}] @@ -127,7 +133,8 @@ :on-change (partial on-type-change index)} [:option {:value "png"} "PNG"] [:option {:value "jpeg"} "JPEG"] - [:option {:value "svg"} "SVG"]] + [:option {:value "svg"} "SVG"] + [:option {:value "pdf"} "PDF"]] [:div.delete-icon {:on-click (partial delete-export index)} i/minus]]) diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index ccdf6be61d..b14de52b3e 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -6,13 +6,13 @@ (ns app.util.dom (:require - [app.common.exceptions :as ex] - [app.common.geom.point :as gpt] - [app.util.globals :as globals] - [app.util.object :as obj] - [cuerdas.core :as str] - [goog.dom :as dom] - [promesa.core :as p])) + [app.common.exceptions :as ex] + [app.common.geom.point :as gpt] + [app.util.globals :as globals] + [app.util.object :as obj] + [cuerdas.core :as str] + [goog.dom :as dom] + [promesa.core :as p])) ;; --- Deprecated methods @@ -45,6 +45,18 @@ [title] (set! (.-title globals/document) title)) +(defn set-page-style + [style] + (let [head (first (.getElementsByTagName ^js globals/document "head")) + style-str (str/join "\n" + (map (fn [[k v]] + (str (name k) ": " v ";")) + style))] + (.insertAdjacentHTML head "beforeend" + (str "")))) + (defn get-element-by-class ([classname] (dom/getElementByClass classname)) From cb731176eb57d63deea3402ddcf34bc12681e83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 6 Jul 2021 16:46:35 +0200 Subject: [PATCH 173/204] :tada: Change print artboard presets to 96dpi --- .../sidebar/options/shapes/frame.cljs | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 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 6f7280e688..c3f5d8a6d3 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 @@ -214,34 +214,34 @@ :width 1920 :height 1080} - {:name "PRINT (72dpi)"} + {:name "PRINT (96dpi)"} {:name "A0" - :width 2384 - :height 3370} + :width 3179 + :height 4494} {:name "A1" - :width 1684 - :height 2384} + :width 2245 + :height 3179} {:name "A2" - :width 1191 - :height 1684} + :width 1587 + :height 2245} {:name "A3" - :width 842 - :height 1191} + :width 1123 + :height 1587} {:name "A4" - :width 595 - :height 842} + :width 794 + :height 1123} {:name "A5" - :width 420 - :height 595} + :width 559 + :height 794} {:name "A6" - :width 297 - :height 420} + :width 397 + :height 559} {:name "Letter" - :width 612 - :height 792} + :width 816 + :height 1054} {:name "DIN Lang" - :width 595 - :height 281} + :width 835 + :height 413} {:name "SOCIAL MEDIA"} {:name "Instagram profile" From c9c24c3464233c02c69932c9f1474afa15dbdc40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 6 Jul 2021 16:49:08 +0200 Subject: [PATCH 174/204] :bug: Fix linter error --- frontend/src/app/worker/import.cljs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/app/worker/import.cljs b/frontend/src/app/worker/import.cljs index 2b663814f5..d318f9d6a9 100644 --- a/frontend/src/app/worker/import.cljs +++ b/frontend/src/app/worker/import.cljs @@ -17,12 +17,12 @@ [app.util.http :as http] [app.util.import.parser :as cip] [app.util.json :as json] + [app.util.logging :as log] [app.util.zip :as uz] [app.worker.impl :as impl] [beicon.core :as rx] [cuerdas.core :as str] - [tubax.core :as tubax] - [app.util.logging :as log])) + [tubax.core :as tubax])) (log/set-level! :trace) From 1894fc7cfa9e693820feb5f7c35bab712e54ee94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 6 Jul 2021 16:51:32 +0200 Subject: [PATCH 175/204] :bug: Fix linter error --- backend/src/app/setup/keys.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/app/setup/keys.clj b/backend/src/app/setup/keys.clj index 4618fa4949..372b83a201 100644 --- a/backend/src/app/setup/keys.clj +++ b/backend/src/app/setup/keys.clj @@ -20,7 +20,7 @@ (defmethod ig/init-key :app.setup/keys [_ {:keys [props] :as cfg}] - (fn [& {:keys [salt size]}] + (fn [& {:keys [salt _]}] (let [engine (bk/engine {:key (:secret-key props) :salt salt :alg :hkdf From 60009476d69e123f0ebd3e579d57c2df1ca76e4a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 6 Jul 2021 16:43:13 +0200 Subject: [PATCH 176/204] :sparkles: Allows drag-drop files into dashboard --- common/src/app/common/file_builder.cljc | 4 +- .../styles/main/partials/dashboard-grid.scss | 1 + frontend/src/app/main/exports.cljs | 6 +- .../src/app/main/ui/dashboard/file_menu.cljs | 23 +++- frontend/src/app/main/ui/dashboard/files.cljs | 14 +- frontend/src/app/main/ui/dashboard/grid.cljs | 120 ++++++++++++++---- .../app/main/ui/dashboard/project_menu.cljs | 8 +- .../src/app/main/ui/dashboard/projects.cljs | 11 +- .../src/app/main/ui/workspace/header.cljs | 26 +++- frontend/src/app/util/import/parser.cljs | 8 +- 10 files changed, 169 insertions(+), 52 deletions(-) diff --git a/common/src/app/common/file_builder.cljc b/common/src/app/common/file_builder.cljc index 348c4fdd32..4a497cda5b 100644 --- a/common/src/app/common/file_builder.cljc +++ b/common/src/app/common/file_builder.cljc @@ -319,7 +319,9 @@ create-child (fn [file child] (-> file - (create-svg-raw (assoc data :content child)) + (create-svg-raw (assoc data + :id (uuid/next) + :content child)) (close-svg-raw)))] ;; First :content is the the shape attribute, the other content is the diff --git a/frontend/resources/styles/main/partials/dashboard-grid.scss b/frontend/resources/styles/main/partials/dashboard-grid.scss index a491f9c7e4..67aaa4ca5f 100644 --- a/frontend/resources/styles/main/partials/dashboard-grid.scss +++ b/frontend/resources/styles/main/partials/dashboard-grid.scss @@ -6,6 +6,7 @@ .dashboard-grid { font-size: $fs14; + height: 100%; .grid-row { display: flex; diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index b6e6e9894b..7dc4975072 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -86,9 +86,9 @@ (mf/fnc svg-raw-wrapper [{:keys [shape frame] :as props}] (let [childs (mapv #(get objects %) (:shapes shape))] - (if (and (contains? shape :svg-attrs) - (map? (:content shape)) - (not= :svg (get-in shape [:content :tag]))) + (if (and (map? (:content shape)) + (or (= :svg (get-in shape [:content :tag])) + (contains? shape :svg-attrs))) [:> shape-container {:shape shape} [:& svg-raw-shape {:frame frame :shape shape diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 8f73075b5d..8a5556bb47 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.dashboard.file-menu (:require + [app.common.data :as d] [app.main.data.dashboard :as dd] [app.main.data.messages :as dm] [app.main.data.modal :as modal] @@ -155,12 +156,22 @@ :on-accept del-shared}))) on-export-files - (fn [_] - (st/emit! - (modal/show - {:type :export - :team-id current-team-id - :files (->> files (mapv :id))})))] + (mf/use-callback + (mf/deps files current-team-id) + (fn [_] + (->> (rx/from files) + (rx/flat-map + (fn [file] + (->> (rp/query :file-libraries {:file-id (:id file)}) + (rx/map #(assoc file :has-libraries? (d/not-empty? %)))))) + (rx/reduce conj []) + (rx/subs + (fn [files] + (st/emit! + (modal/show + {:type :export + :team-id current-team-id + :files (->> files (mapv :id))})))))))] (mf/use-effect (fn [] diff --git a/frontend/src/app/main/ui/dashboard/files.cljs b/frontend/src/app/main/ui/dashboard/files.cljs index 2ec4874139..b982d8ceda 100644 --- a/frontend/src/app/main/ui/dashboard/files.cljs +++ b/frontend/src/app/main/ui/dashboard/files.cljs @@ -44,7 +44,14 @@ (mf/deps project) (fn [event] (dom/prevent-default event) - (st/emit! (dd/create-file {:project-id (:id project)}))))] + (st/emit! (dd/create-file {:project-id (:id project)})))) + + on-import + (mf/use-callback + (mf/deps (:id project)) + (fn [] + (st/emit! (dd/fetch-files {:project-id (:id project)}) + (dd/clear-selected-files))))] [:header.dashboard-header @@ -65,7 +72,8 @@ :left (- (:x (:menu-pos @local)) 180) :top (:y (:menu-pos @local)) :on-edit on-edit - :on-menu-close on-menu-close}]])) + :on-menu-close on-menu-close + :on-import on-import}]])) [:div.dashboard-header-actions [:a.btn-secondary.btn-small {:on-click on-create-clicked} (tr "dashboard.new-file")] @@ -102,6 +110,6 @@ [:* [:& header {:team team :project project}] [:section.dashboard-container - [:& grid {:id (:id project) + [:& grid {:project-id (:id project) :files files}]]])) diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs index c15b2e6641..3bd48a1ceb 100644 --- a/frontend/src/app/main/ui/dashboard/grid.cljs +++ b/frontend/src/app/main/ui/dashboard/grid.cljs @@ -13,6 +13,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.dashboard.file-menu :refer [file-menu]] + [app.main.ui.dashboard.import :refer [use-import-file]] [app.main.ui.dashboard.inline-edition :refer [inline-edition]] [app.main.ui.icons :as i] [app.main.worker :as wrk] @@ -210,22 +211,68 @@ [:div.text (tr "dashboard.loading-files")]]) (mf/defc grid - [{:keys [files] :as props}] - [:section.dashboard-grid - (cond - (nil? files) - [:& loading-placeholder] + [{:keys [files project-id] :as props}] + (let [dragging? (mf/use-state false) - (seq files) - [:div.grid-row - (for [item files] - [:& grid-item - {:file item - :key (:id item) - :navigate? true}])] + on-finish-import + (mf/use-callback + (fn [] + (st/emit! (dd/fetch-files {:project-id project-id}) + (dd/clear-selected-files)))) - :else - [:& empty-placeholder])]) + import-files (use-import-file project-id on-finish-import) + + on-drag-enter + (mf/use-callback + (fn [e] + (when (or (dnd/has-type? e "Files") + (dnd/has-type? e "application/x-moz-file")) + (dom/prevent-default e) + (reset! dragging? true)))) + + on-drag-over + (mf/use-callback + (fn [e] + (when (or (dnd/has-type? e "Files") + (dnd/has-type? e "application/x-moz-file")) + (dom/prevent-default e)))) + + on-drag-leave + (mf/use-callback + (fn [e] + (when-not (dnd/from-child? e) + (reset! dragging? false)))) + + + on-drop + (mf/use-callback + (fn [e] + (when (or (dnd/has-type? e "Files") + (dnd/has-type? e "application/x-moz-file")) + (dom/prevent-default e) + (reset! dragging? false) + (import-files (.-files (.-dataTransfer e))))))] + + [:section.dashboard-grid {:on-drag-enter on-drag-enter + :on-drag-over on-drag-over + :on-drag-leave on-drag-leave + :on-drop on-drop} + (cond + (nil? files) + [:& loading-placeholder] + + (seq files) + [:div.grid-row + (when @dragging? + [:div.grid-item]) + (for [item files] + [:& grid-item + {:file item + :key (:id item) + :navigate? true}])] + + :else + [:& empty-placeholder])])) (mf/defc line-grid-row [{:keys [files selected-files on-load-more dragging?] :as props}] @@ -285,10 +332,17 @@ (mf/defc line-grid [{:keys [project-id team-id files on-load-more] :as props}] (let [dragging? (mf/use-state false) - selected-files (mf/deref refs/dashboard-selected-files) selected-project (mf/deref refs/dashboard-selected-project) + on-finish-import + (mf/use-callback + (fn [] + (st/emit! (dd/fetch-recent-files) + (dd/clear-selected-files)))) + + import-files (use-import-file project-id on-finish-import) + on-drag-enter (mf/use-callback (mf/deps selected-project) @@ -298,13 +352,20 @@ (when-not (or (dnd/from-child? e) (dnd/broken-event? e)) (when (not= selected-project project-id) - (reset! dragging? true)))))) + (reset! dragging? true)))) + + (when (or (dnd/has-type? e "Files") + (dnd/has-type? e "application/x-moz-file")) + (dom/prevent-default e) + (reset! dragging? true)))) on-drag-over (mf/use-callback - (fn [e] - (when (dnd/has-type? e "penpot/files") - (dom/prevent-default e)))) + (fn [e] + (when (or (dnd/has-type? e "penpot/files") + (dnd/has-type? e "Files") + (dnd/has-type? e "application/x-moz-file")) + (dom/prevent-default e)))) on-drag-leave (mf/use-callback @@ -321,13 +382,20 @@ on-drop (mf/use-callback (mf/deps files selected-files) - (fn [_] - (reset! dragging? false) - (when (not= selected-project project-id) - (let [data {:ids (into #{} (keys selected-files)) - :project-id project-id} - mdata {:on-success on-drop-success}] - (st/emit! (dd/move-files (with-meta data mdata)))))))] + (fn [e] + (when (or (dnd/has-type? e "Files") + (dnd/has-type? e "application/x-moz-file")) + (dom/prevent-default e) + (reset! dragging? false) + (import-files (.-files (.-dataTransfer e)))) + + (when (dnd/has-type? e "penpot/files") + (reset! dragging? false) + (when (not= selected-project project-id) + (let [data {:ids (into #{} (keys selected-files)) + :project-id project-id} + mdata {:on-success on-drop-success}] + (st/emit! (dd/move-files (with-meta data mdata))))))))] [:section.dashboard-grid {:on-drag-enter on-drag-enter :on-drag-over on-drag-over diff --git a/frontend/src/app/main/ui/dashboard/project_menu.cljs b/frontend/src/app/main/ui/dashboard/project_menu.cljs index 8967e8a0ca..f6923cc783 100644 --- a/frontend/src/app/main/ui/dashboard/project_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/project_menu.cljs @@ -20,7 +20,7 @@ [rumext.alpha :as mf])) (mf/defc project-menu - [{:keys [project show? on-edit on-menu-close top left] :as props}] + [{:keys [project show? on-edit on-menu-close top left on-import] :as props}] (assert (some? project) "missing `project` prop") (assert (boolean? show?) "missing `show?` prop") (assert (fn? on-edit) "missing `on-edit` prop") @@ -84,8 +84,7 @@ on-finish-import (mf/use-callback (fn [] - (st/emit! (dd/fetch-recent-files) - (dd/clear-selected-files))))] + (when (some? on-import) (on-import))))] [:* [:& udi/import-form {:ref file-input @@ -106,7 +105,8 @@ [(tr "dashboard.move-to") nil (for [team teams] [(:name team) (on-move (:id team))])]) - [(tr "dashboard.import") on-import-files] + (when (some? on-import) + [(tr "dashboard.import") on-import-files]) [:separator] [(tr "labels.delete") on-delete]]}]])) diff --git a/frontend/src/app/main/ui/dashboard/projects.cljs b/frontend/src/app/main/ui/dashboard/projects.cljs index 66e12999a6..768599da77 100644 --- a/frontend/src/app/main/ui/dashboard/projects.cljs +++ b/frontend/src/app/main/ui/dashboard/projects.cljs @@ -93,7 +93,13 @@ (fn [] (let [mdata {:on-success on-file-created} params {:project-id (:id project)}] - (st/emit! (dd/create-file (with-meta params mdata))))))] + (st/emit! (dd/create-file (with-meta params mdata)))))) + + on-import + (mf/use-callback + (fn [] + (st/emit! (dd/fetch-recent-files) + (dd/clear-selected-files))))] [:div.dashboard-project-row {:class (when first? "first")} [:div.project @@ -111,7 +117,8 @@ :left (:x (:menu-pos @local)) :top (:y (:menu-pos @local)) :on-edit on-edit-open - :on-menu-close on-menu-close}] + :on-menu-close on-menu-close + :on-import on-import}] [:span.info (str file-count " files")] (when (> file-count 0) diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 18c51d3ece..ac9a6fd5f8 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -6,12 +6,14 @@ (ns app.main.ui.workspace.header (:require + [app.common.data :as d] [app.common.math :as mth] [app.config :as cfg] [app.main.data.modal :as modal] [app.main.data.workspace :as dw] [app.main.data.workspace.shortcuts :as sc] [app.main.refs :as refs] + [app.main.repo :as rp] [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown]] [app.main.ui.icons :as i] @@ -20,6 +22,7 @@ [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] [app.util.router :as rt] + [beicon.core :as rx] [okulary.core :as l] [rumext.alpha :as mf])) @@ -135,12 +138,23 @@ (reset! editing? true)) on-export-files - (fn [_] - (st/emit! - (modal/show - {:type :export - :team-id team-id - :files [(:id file)]})))] + (mf/use-callback + (mf/deps file team-id) + (fn [_] + (->> (rx/of file) + (rx/flat-map + (fn [file] + (->> (rp/query :file-libraries {:file-id (:id file)}) + (rx/map #(assoc file :has-libraries? (d/not-empty? %)))))) + (rx/reduce conj []) + (rx/subs + (fn [files] + (st/emit! + (modal/show + {:type :export + :team-id team-id + :files (->> files (mapv :id))})))))))] + (mf/use-effect (mf/deps @editing?) #(when @editing? diff --git a/frontend/src/app/util/import/parser.cljs b/frontend/src/app/util/import/parser.cljs index 3c46a9218e..8de2abf379 100644 --- a/frontend/src/app/util/import/parser.cljs +++ b/frontend/src/app/util/import/parser.cljs @@ -201,7 +201,13 @@ (merge (add-attrs {} (:attrs svg-node)) node-attrs)) (= type :svg-raw) - (->> node :content last) + (let [svg-content (get-data node :penpot:svg-content) + tag (-> svg-content :attrs :penpot:tag keyword) + + svg-node (if (= :svg tag) + (->> node :content last :content last) + (->> node :content last))] + (merge (add-attrs {} (:attrs svg-node)) node-attrs)) :else node-attrs))) From 84cf63d1ba9270d374e3145ac2c94905f0c87789 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 6 Jul 2021 18:04:49 +0200 Subject: [PATCH 177/204] :sparkles: Changed export modal progress --- .../resources/styles/main/partials/modal.scss | 7 +- .../src/app/main/ui/dashboard/export.cljs | 140 +++++++++++++----- .../src/app/main/ui/dashboard/file_menu.cljs | 3 +- .../src/app/main/ui/workspace/header.cljs | 2 +- frontend/src/app/worker/export.cljs | 30 ++-- frontend/translations/en.po | 5 +- frontend/translations/es.po | 5 +- 7 files changed, 132 insertions(+), 60 deletions(-) diff --git a/frontend/resources/styles/main/partials/modal.scss b/frontend/resources/styles/main/partials/modal.scss index adb71207fd..7c6a631e18 100644 --- a/frontend/resources/styles/main/partials/modal.scss +++ b/frontend/resources/styles/main/partials/modal.scss @@ -215,6 +215,7 @@ background-color: $color-white; border: 1px solid $color-gray-20; width: 30rem; + min-height: 14rem; p { font-size: $fs14; @@ -276,10 +277,6 @@ .modal-content { padding: 1rem; } -} - -.import-dialog { - min-height: 215px; svg { max-width: 18px; @@ -451,8 +448,6 @@ } .export-dialog { - min-height: 24rem; - .export-option { border-radius: 4px; border: 1px solid $color-gray-10; diff --git a/frontend/src/app/main/ui/dashboard/export.cljs b/frontend/src/app/main/ui/dashboard/export.cljs index 059c91f057..81fdd7335c 100644 --- a/frontend/src/app/main/ui/dashboard/export.cljs +++ b/frontend/src/app/main/ui/dashboard/export.cljs @@ -11,6 +11,7 @@ [app.main.store :as st] [app.main.ui.icons :as i] [app.main.worker :as uw] + [app.util.data :refer [classnames]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [beicon.core :as rx] @@ -18,12 +19,60 @@ (def ^:const options [:all :merge :detach]) +(mf/defc export-entry + [{:keys [file]}] + + [:div.file-entry + {:class (classnames :loading (:loading? file) + :success (:export-success? file) + :error (:export-error? file))} + [:div.file-name + [:div.file-icon + (cond (:export-success? file) i/tick + (:export-error? file) i/close + (:loading? file) i/loader-pencil)] + + [:div.file-name-label (:name file)]]]) + +(defn mark-file-error [files file-id] + (->> files + (mapv #(cond-> % + (= file-id (:id %)) + (assoc :export-error? true + :loading? false))))) + +(defn mark-file-success [files file-id] + (->> files + (mapv #(cond-> % + (= file-id (:id %)) + (assoc :export-success? true + :loading? false))))) + (mf/defc export-dialog {::mf/register modal/components ::mf/register-as :export} - [{:keys [team-id files]}] - (let [selected-option (mf/use-state :all) + [{:keys [team-id files has-libraries?]}] + (let [state (mf/use-state {:status :prepare + :files (->> files (mapv #(assoc % :loading? true)))}) + selected-option (mf/use-state :all) + start-export + (fn [] + (swap! state assoc :status :exporting) + (->> (uw/ask-many! + {:cmd :export-file + :team-id team-id + :export-type @selected-option + :files (->> files (mapv :id))}) + (rx/delay-emit 1000) + (rx/subs + (fn [msg] + (when (= :error (:type msg)) + (swap! state update :files mark-file-error (:file-id msg))) + + (when (= :finish (:type msg)) + (swap! state update :files mark-file-success (:file-id msg)) + (dom/trigger-download-uri (:filename msg) (:mtype msg) (:uri msg))))))) cancel-fn (mf/use-callback (fn [event] @@ -35,24 +84,19 @@ (mf/deps @selected-option) (fn [event] (dom/prevent-default event) - - (->> (uw/ask-many! - {:cmd :export-file - :team-id team-id - :export-type @selected-option - :files files}) - (rx/subs - (fn [msg] - (when (= :finish (:type msg)) - (dom/trigger-download-uri (:filename msg) (:mtype msg) (:uri msg)))))) - - (st/emit! (modal/hide)))) + (start-export))) on-change-handler (mf/use-callback (fn [_ type] (reset! selected-option type)))] + (mf/use-effect + (fn [] + (when-not has-libraries? + ;; Start download automaticaly + (start-export)))) + [:div.modal-overlay [:div.modal-container.export-dialog [:div.modal-header @@ -62,31 +106,49 @@ [:div.modal-close-button {:on-click cancel-fn} i/close]] - [:div.modal-content - [:p.explain (tr "dashboard.export.explain")] - [:p.detail (tr "dashboard.export.detail")] + (cond + (= (:status @state) :prepare) + [:* + [:div.modal-content + [:p.explain (tr "dashboard.export.explain")] + [:p.detail (tr "dashboard.export.detail")] - (for [type [:all :merge :detach]] - (let [selected? (= @selected-option type)] - [:div.export-option {:class (when selected? "selected")} - [:label.option-container - [:h3 (tr (str "dashboard.export.options." (d/name type) ".title"))] - [:p (tr (str "dashboard.export.options." (d/name type) ".message"))] - [:input {:type "radio" - :checked selected? - :on-change #(on-change-handler % type) - :name "export-option"}] - [:span {:class "option-radio-check"}]]]))] + (for [type [:all :merge :detach]] + (let [selected? (= @selected-option type)] + [:div.export-option {:class (when selected? "selected")} + [:label.option-container + [:h3 (tr (str "dashboard.export.options." (d/name type) ".title"))] + [:p (tr (str "dashboard.export.options." (d/name type) ".message"))] + [:input {:type "radio" + :checked selected? + :on-change #(on-change-handler % type) + :name "export-option"}] + [:span {:class "option-radio-check"}]]]))] - [:div.modal-footer - [:div.action-buttons - [:input.cancel-button - {:type "button" - :value (tr "labels.cancel") - :on-click cancel-fn}] + [:div.modal-footer + [:div.action-buttons + [:input.cancel-button + {:type "button" + :value (tr "labels.cancel") + :on-click cancel-fn}] - [:input.accept-button - {:class "primary" - :type "button" - :value (tr "labels.export") - :on-click accept-fn}]]]]])) + [:input.accept-button + {:class "primary" + :type "button" + :value (tr "labels.continue") + :on-click accept-fn}]]]] + + (= (:status @state) :exporting) + [:* + [:div.modal-content + (for [file (:files @state)] + [:& export-entry {:file file}])] + + [:div.modal-footer + [:div.action-buttons + [:input.accept-button + {:class "primary" + :type "button" + :value (tr "labels.close") + :disabled (->> @state :files (some :loading?)) + :on-click cancel-fn}]]]])]])) diff --git a/frontend/src/app/main/ui/dashboard/file_menu.cljs b/frontend/src/app/main/ui/dashboard/file_menu.cljs index 8a5556bb47..31207a0b93 100644 --- a/frontend/src/app/main/ui/dashboard/file_menu.cljs +++ b/frontend/src/app/main/ui/dashboard/file_menu.cljs @@ -171,7 +171,8 @@ (modal/show {:type :export :team-id current-team-id - :files (->> files (mapv :id))})))))))] + :has-libraries? (->> files (some :has-libraries?)) + :files files})))))))] (mf/use-effect (fn [] diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index ac9a6fd5f8..8f1bd7a047 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -153,7 +153,7 @@ (modal/show {:type :export :team-id team-id - :files (->> files (mapv :id))})))))))] + :files files})))))))] (mf/use-effect (mf/deps @editing?) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 43c2828d52..854911c9bf 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -450,14 +450,22 @@ [{:keys [team-id files export-type] :as message}] (->> (rx/from files) - (rx/mapcat #(export-file team-id % export-type)) - (rx/map - (fn [value] - (if (contains? value :type) - value - (let [[file export-blob] value] - {:type :finish - :filename (:name file) - :mtype "application/penpot" - :description "Penpot export (*.penpot)" - :uri (dom/create-uri export-blob)})))))) + (rx/mapcat + (fn [file] + (->> (export-file team-id file export-type) + (rx/map + (fn [value] + (if (contains? value :type) + value + (let [[file export-blob] value] + {:type :finish + :file-id (:id file) + :filename (:name file) + :mtype "application/penpot" + :description "Penpot export (*.penpot)" + :uri (dom/create-uri export-blob)})))) + (rx/catch + (fn [err] + (rx/of {:type :error + :error (str err) + :file-id file})))))))) diff --git a/frontend/translations/en.po b/frontend/translations/en.po index 86b863577c..3c07c0e8a7 100644 --- a/frontend/translations/en.po +++ b/frontend/translations/en.po @@ -855,6 +855,9 @@ msgstr "You are seeing version %s" msgid "labels.accept" msgstr "Accept" +msgid "labels.close" +msgstr "Close" + msgid "labels.add-custom-font" msgstr "Add custom font" @@ -2746,4 +2749,4 @@ 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 "Click to close the path" diff --git a/frontend/translations/es.po b/frontend/translations/es.po index 06611400ef..d6c2bc4e97 100644 --- a/frontend/translations/es.po +++ b/frontend/translations/es.po @@ -855,6 +855,9 @@ msgstr "Estás viendo la versión %s" msgid "labels.accept" msgstr "Aceptar" +msgid "labels.close" +msgstr "Cerrar" + msgid "labels.add-custom-font" msgstr "Añadir fuentes personalizada" @@ -2742,4 +2745,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 991e0d5e5b1e7370a24689369940d1bf679fce36 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Jul 2021 09:23:10 +0200 Subject: [PATCH 178/204] :recycle: Remove classnames old reference --- frontend/src/app/main/ui/confirm.cljs | 7 ++- .../src/app/main/ui/dashboard/export.cljs | 8 +-- .../src/app/main/ui/dashboard/import.cljs | 10 ++-- .../src/app/main/ui/viewer/thumbnails.cljs | 6 +-- .../src/app/main/ui/workspace/libraries.cljs | 6 +-- .../sidebar/options/rows/color_row.cljs | 3 +- frontend/src/app/util/data.cljs | 53 ------------------- 7 files changed, 19 insertions(+), 74 deletions(-) diff --git a/frontend/src/app/main/ui/confirm.cljs b/frontend/src/app/main/ui/confirm.cljs index 030aafd203..a39f47b1dd 100644 --- a/frontend/src/app/main/ui/confirm.cljs +++ b/frontend/src/app/main/ui/confirm.cljs @@ -9,7 +9,6 @@ [app.main.data.modal :as modal] [app.main.store :as st] [app.main.ui.icons :as i] - [app.util.data :refer [classnames]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr t]] [app.util.keyboard :as k] @@ -86,9 +85,9 @@ :on-click cancel-fn}]) [:input.accept-button - {:class (classnames :danger (= accept-style :danger) - :primary (= accept-style :primary)) + {:class (dom/classnames + :danger (= accept-style :danger) + :primary (= accept-style :primary)) :type "button" :value accept-label :on-click accept-fn}]]]]])) - diff --git a/frontend/src/app/main/ui/dashboard/export.cljs b/frontend/src/app/main/ui/dashboard/export.cljs index 81fdd7335c..e89405d3ee 100644 --- a/frontend/src/app/main/ui/dashboard/export.cljs +++ b/frontend/src/app/main/ui/dashboard/export.cljs @@ -11,7 +11,6 @@ [app.main.store :as st] [app.main.ui.icons :as i] [app.main.worker :as uw] - [app.util.data :refer [classnames]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [beicon.core :as rx] @@ -23,9 +22,10 @@ [{:keys [file]}] [:div.file-entry - {:class (classnames :loading (:loading? file) - :success (:export-success? file) - :error (:export-error? file))} + {:class (dom/classnames + :loading (:loading? file) + :success (:export-success? file) + :error (:export-error? file))} [:div.file-name [:div.file-icon (cond (:export-success? file) i/tick diff --git a/frontend/src/app/main/ui/dashboard/import.cljs b/frontend/src/app/main/ui/dashboard/import.cljs index cd9a976201..b8b083e04b 100644 --- a/frontend/src/app/main/ui/dashboard/import.cljs +++ b/frontend/src/app/main/ui/dashboard/import.cljs @@ -12,7 +12,6 @@ [app.main.ui.components.file-uploader :refer [file-uploader]] [app.main.ui.icons :as i] [app.main.worker :as uw] - [app.util.data :refer [classnames]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [app.util.keyboard :as kbd] @@ -144,10 +143,11 @@ (swap! state update :files remove-file (:file-id file))))] [:div.file-entry - {:class (classnames :loading loading? - :success load-success? - :error (or import-error? analyze-error?) - :editable (and ready? (not editing?)))} + {:class (dom/classnames + :loading loading? + :success load-success? + :error (or import-error? analyze-error?) + :editable (and ready? (not editing?)))} [:div.file-name [:div.file-icon diff --git a/frontend/src/app/main/ui/viewer/thumbnails.cljs b/frontend/src/app/main/ui/viewer/thumbnails.cljs index 9c1d09e7ca..26397302e3 100644 --- a/frontend/src/app/main/ui/viewer/thumbnails.cljs +++ b/frontend/src/app/main/ui/viewer/thumbnails.cljs @@ -12,7 +12,7 @@ [app.main.store :as st] [app.main.ui.components.dropdown :refer [dropdown']] [app.main.ui.icons :as i] - [app.util.data :refer [classnames]] + [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [goog.object :as gobj] [rumext.alpha :as mf])) @@ -75,7 +75,7 @@ [{:keys [selected? frame on-click index objects] :as props}] [:div.thumbnail-item {:on-click #(on-click % index)} [:div.thumbnail-preview - {:class (classnames :selected selected?)} + {:class (dom/classnames :selected selected?)} [:& exports/frame-svg {:frame frame :objects objects}]] [:div.thumbnail-info [:span.name {:title (:name frame)} (:name frame)]]]) @@ -104,7 +104,7 @@ :container container :show true} [:section.viewer-thumbnails - {:class (classnames :expanded @expanded?) + {:class (dom/classnames :expanded @expanded?) :ref container :on-mouse-leave on-mouse-leave} diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs index c0766515d6..f48d73b454 100644 --- a/frontend/src/app/main/ui/workspace/libraries.cljs +++ b/frontend/src/app/main/ui/workspace/libraries.cljs @@ -13,7 +13,7 @@ [app.main.refs :as refs] [app.main.store :as st] [app.main.ui.icons :as i] - [app.util.data :refer [classnames matches-search]] + [app.util.data :refer [matches-search]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [t tr]] [cuerdas.core :as str] @@ -170,11 +170,11 @@ [:div.modal-content [:div.libraries-header [:div.header-item - {:class (classnames :active (= @selected-tab :libraries)) + {:class (dom/classnames :active (= @selected-tab :libraries)) :on-click #(change-tab :libraries)} (t locale "workspace.libraries.libraries")] [:div.header-item - {:class (classnames :active (= @selected-tab :updates)) + {:class (dom/classnames :active (= @selected-tab :updates)) :on-click #(change-tab :updates)} (t locale "workspace.libraries.updates")]] [:div.libraries-content diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs index f7f050e22c..af5a3e81ad 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs @@ -18,7 +18,6 @@ [app.main.ui.hooks :as h] [app.main.ui.icons :as i] [app.util.color :as uc] - [app.util.data :refer [classnames]] [app.util.dom :as dom] [app.util.i18n :as i18n :refer [tr]] [rumext.alpha :as mf])) @@ -162,7 +161,7 @@ (when (and (not disable-opacity) (not (:gradient color))) [:div.input-element - {:class (classnames :percentail (not= (:opacity color) :multiple))} + {:class (dom/classnames :percentail (not= (:opacity color) :multiple))} [:> numeric-input {:value (-> color :opacity opacity->string) :placeholder (tr "settings.multiple") :on-click select-all diff --git a/frontend/src/app/util/data.cljs b/frontend/src/app/util/data.cljs index fb0007bb35..7447359fb0 100644 --- a/frontend/src/app/util/data.cljs +++ b/frontend/src/app/util/data.cljs @@ -153,34 +153,6 @@ ;; Other ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn classnames - [& params] - {:pre [(even? (count params))]} - (str/join " " (reduce (fn [acc [k v]] - (if (and k (true? v)) - (conj acc (name k)) - acc)) - [] - (partition 2 params)))) - -;; (defn normalize-attrs -;; [m] -;; (letfn [(transform [[k v]] -;; (cond -;; (or (= k :class) (= k :class-name)) -;; ["className" v] - -;; (or (keyword? k) (string? k)) -;; [(str/camel (name k)) v] - -;; :else -;; [k v])) -;; (walker [x] -;; (if (map? x) -;; (into {} (map tf) x) -;; x))] -;; (walk/postwalk walker m))) - (defn normalize-props [props] (clj->js props :keyword-fn (fn [key] @@ -196,28 +168,3 @@ nm (str/trim (str/lower name))] (str/includes? nm st)))) -;; (defn coalesce -;; [^number v ^number n] -;; (if (.-toFixed v) -;; (js/parseFloat (.toFixed v n)) -;; 0)) - - - -;; (defmacro mirror-map [& fields] -;; (let [keys# (map #(keyword (name %)) fields) -;; vals# fields] -;; (apply hash-map (interleave keys# vals#)))) - -;; (defmacro some->' -;; [x & forms] -;; `(let [x# (p/then' ~x (fn [v#] -;; (when (nil? v#) -;; (throw (ex-info "internal" {::some-interrupt true}))) -;; v#))] -;; (-> (-> x# ~@forms) -;; (p/catch' (fn [e#] -;; (if (::some-interrupt (ex-data e#)) -;; nil -;; (throw e#))))))) - From 65a4aff5fc7d66ebafb285983408558919c5e3b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 7 Jul 2021 09:34:18 +0200 Subject: [PATCH 179/204] :books: Add constraints to CHANGES.md --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 6c7ce28b73..b72a39c443 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ - Allow nested asset groups [Taiga #1716](https://tree.taiga.io/project/penpot/us/1716). - Allow to ungroup assets [Taiga #1719](https://tree.taiga.io/project/penpot/us/1719). - Allow to rename assets groups [Taiga #1721](https://tree.taiga.io/project/penpot/us/1721). +- Component constraints (left, right, left and right, center, scale...) [Taiga #1125](https://tree.taiga.io/project/penpot/us/1125). - Export elements to PDF [Taiga #519](https://tree.taiga.io/project/penpot/us/519). - Memorize collapse state of assets in panel [Taiga #1718](https://tree.taiga.io/project/penpot/us/1718). - Headers button sets and menus review [Taiga #1663](https://tree.taiga.io/project/penpot/us/1663). From e1e825f350c63c611987d43d1f725dcc162ef8e2 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Jul 2021 10:26:04 +0200 Subject: [PATCH 180/204] :sparkles: Do not initialize mattermost error reporter if no uri is provided. --- backend/src/app/loggers/mattermost.clj | 30 ++++++++++++++------------ 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/backend/src/app/loggers/mattermost.clj b/backend/src/app/loggers/mattermost.clj index ffc7305f28..34120a3604 100644 --- a/backend/src/app/loggers/mattermost.clj +++ b/backend/src/app/loggers/mattermost.clj @@ -40,22 +40,24 @@ (defmethod ig/init-key ::reporter [_ {:keys [receiver uri] :as cfg}] - (l/info :msg "intializing mattermost error reporter" :uri uri) - (let [output (a/chan (a/sliding-buffer 128) - (filter #(= (:level %) "error")))] - (receiver :sub output) - (a/go-loop [] - (let [msg (a/ Date: Wed, 7 Jul 2021 10:31:01 +0200 Subject: [PATCH 181/204] :paperclip: Add logging to server repl namespace. --- backend/src/app/srepl.clj | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/src/app/srepl.clj b/backend/src/app/srepl.clj index 71ef20a6d7..9d7c6c8250 100644 --- a/backend/src/app/srepl.clj +++ b/backend/src/app/srepl.clj @@ -9,6 +9,7 @@ (:require [app.common.spec :as us] [app.srepl.main] + [app.util.logging :as l] [clojure.core.server :as ccs] [clojure.main :as cm] [clojure.spec.alpha :as s] @@ -41,14 +42,17 @@ (defmethod ig/init-key ::server [_ {:keys [port host name] :as cfg}] - (ccs/start-server {:address host - :port port - :name name - :accept 'app.srepl/repl}) - cfg) + (when (and port host name) + (l/info :msg "initializing server repl" :port port :host host :name name) + (ccs/start-server {:address host + :port port + :name name + :accept 'app.srepl/repl}) + cfg)) (defmethod ig/halt-key! ::server [_ cfg] - (ccs/stop-server (:name cfg))) + (when cfg + (ccs/stop-server (:name cfg)))) From 1b44fe8fecfd54b7690b0c22278a42cee937cac5 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Jul 2021 10:56:54 +0200 Subject: [PATCH 182/204] :bug: Fixed problem when importing flatten components --- frontend/src/app/main/ui/workspace/header.cljs | 1 + frontend/src/app/main/ui/workspace/viewport.cljs | 1 + frontend/src/app/worker/export.cljs | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs index 8f1bd7a047..e958221158 100644 --- a/frontend/src/app/main/ui/workspace/header.cljs +++ b/frontend/src/app/main/ui/workspace/header.cljs @@ -153,6 +153,7 @@ (modal/show {:type :export :team-id team-id + :has-libraries? (->> files (some :has-libraries?)) :files files})))))))] (mf/use-effect diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index a12d153db5..b7743e314f 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -182,6 +182,7 @@ :ref render-ref :xmlns "http://www.w3.org/2000/svg" :xmlnsXlink "http://www.w3.org/1999/xlink" + :xmlns:penpot "https://penpot.app/xmlns" :preserveAspectRatio "xMidYMid meet" :key (str "render" page-id) :width (:width vport 0) diff --git a/frontend/src/app/worker/export.cljs b/frontend/src/app/worker/export.cljs index 854911c9bf..02fae88fee 100644 --- a/frontend/src/app/worker/export.cljs +++ b/frontend/src/app/worker/export.cljs @@ -190,7 +190,7 @@ (dissoc :stroke-color-ref-id :stroke-color-ref-file) (not= file-id (get-component-ref-file objects shape)) - (dissoc :component-file :component-file :shape-ref :component-root) + (dissoc :component-id :component-file :shape-ref :component-root?) (= :text (:type shape)) (update :content detach-text))) From 16fcc60a598e05820c96192162797e1fdf966851 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Jul 2021 12:11:42 +0200 Subject: [PATCH 183/204] :bug: Fix color picker not working --- .../app/main/ui/shapes/text/fontfaces.cljs | 34 +++++++++++++------ .../app/main/ui/workspace/shapes/frame.cljs | 9 +++-- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs index e75f0407b2..5f2ae54fc0 100644 --- a/frontend/src/app/main/ui/shapes/text/fontfaces.cljs +++ b/frontend/src/app/main/ui/shapes/text/fontfaces.cljs @@ -6,6 +6,7 @@ (ns app.main.ui.shapes.text.fontfaces (:require + [app.common.data :as d] [app.main.fonts :as fonts] [app.main.ui.hooks :as hooks] [app.main.ui.shapes.embed :as embed] @@ -47,19 +48,12 @@ (mf/ref-val fonts-css-ref))) -(mf/defc fontfaces-style +(mf/defc fontfaces-style-render {::mf/wrap-props false - ::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]} + ::mf/wrap [#(mf/memo' % (mf/check-props ["fonts"]))]} [props] - (let [shapes (obj/get props "shapes") - content (->> shapes (mapv :content)) - - ;; Retrieve the fonts ids used by the text shapes - fonts (->> content - (mapv fonts/get-content-fonts) - (reduce set/union #{}) - (hooks/use-equal-memo)) + (let [fonts (obj/get props "fonts") ;; Fetch its CSS fontfaces fonts-css (use-fonts-css fonts) @@ -75,5 +69,23 @@ ;; Creates a style tag by replacing the urls with the data uri style (replace-embeds fonts-css fonts-urls fonts-embed)] - (when (seq style) + (when (d/not-empty? style) [:style style]))) + +(mf/defc fontfaces-style + {::mf/wrap-props false + ::mf/wrap [#(mf/memo' % (mf/check-props ["shapes"]))]} + [props] + (let [shapes (->> (obj/get props "shapes") + (filterv #(= :text (:type %)))) + + content (->> shapes (mapv :content)) + + ;; Retrieve the fonts ids used by the text shapes + fonts (->> content + (mapv fonts/get-content-fonts) + (reduce set/union #{}) + (hooks/use-equal-memo))] + + (when (d/not-empty? fonts) + [:> fontfaces-style-render {:fonts fonts}]))) diff --git a/frontend/src/app/main/ui/workspace/shapes/frame.cljs b/frontend/src/app/main/ui/workspace/shapes/frame.cljs index 12cbfca7cf..6f7504222a 100644 --- a/frontend/src/app/main/ui/workspace/shapes/frame.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/frame.cljs @@ -7,6 +7,7 @@ (ns app.main.ui.workspace.shapes.frame (:require [app.common.geom.shapes :as gsh] + [app.common.pages :as cp] [app.main.ui.shapes.frame :as frame] [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.text.fontfaces :as ff] @@ -76,8 +77,10 @@ objects (unchecked-get props "objects") thumbnail? (unchecked-get props "thumbnail?") - shape (gsh/transform-shape shape) - children (mapv #(get objects %) (:shapes shape)) + shape (gsh/transform-shape shape) + children (mapv #(get objects %) (:shapes shape)) + + all-children (cp/get-children-objects (:id shape) objects) rendered? (mf/use-state false) @@ -93,7 +96,7 @@ (when-not show-thumbnail? [:> shape-container {:shape shape :ref on-dom} - [:& ff/fontfaces-style {:shapes children}] + [:& ff/fontfaces-style {:shapes all-children}] [:& frame-shape {:shape shape :childs children}]]) From 19afc2274a00a478d4ef5a1a969d7a904c97c863 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Wed, 7 Jul 2021 12:43:50 +0200 Subject: [PATCH 184/204] :sparkles: Minor improvement on event syncronization on login after register. --- frontend/src/app/main/data/users.cljs | 23 ++++++++++++++++++--- frontend/src/app/main/ui/auth/register.cljs | 5 +---- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/frontend/src/app/main/data/users.cljs b/frontend/src/app/main/data/users.cljs index 32a81d39db..b1fc5b7964 100644 --- a/frontend/src/app/main/data/users.cljs +++ b/frontend/src/app/main/data/users.cljs @@ -138,9 +138,7 @@ ptk/WatchEvent (watch [_ _ _] - (let [team-id (get-current-team-id profile) - profile (with-meta profile - {::ev/source "login"})] + (let [team-id (get-current-team-id profile)] (->> (rx/concat (rx/of (profile-fetched profile) (fetch-teams)) @@ -187,6 +185,25 @@ (with-meta profile {::ev/source "login-with-token"})))))) +(defn login-from-register + "Event used mainly for mark current session as logged-in in after the + user sucessfully registred using third party auth provider (in this + case we dont need to verify the email)." + [] + (ptk/reify ::login-from-register + ptk/WatchEvent + (watch [_ _ stream] + (rx/merge + (rx/of (fetch-profile)) + (->> stream + (rx/filter (ptk/type? ::profile-fetched)) + (rx/take 1) + (rx/map deref) + (rx/map (fn [profile] + (with-meta profile + {::ev/source "register"}))) + (rx/map logged-in)))))) + ;; --- EVENT: logout (defn logged-out diff --git a/frontend/src/app/main/ui/auth/register.cljs b/frontend/src/app/main/ui/auth/register.cljs index fcc619c112..5ee5ebd1f9 100644 --- a/frontend/src/app/main/ui/auth/register.cljs +++ b/frontend/src/app/main/ui/auth/register.cljs @@ -169,11 +169,8 @@ (let [token (:invitation-token data)] (st/emit! (rt/nav :auth-verify-token {} {:token token}))) - (not= "penpot" (:auth-backend data)) - (st/emit! - (du/fetch-profile) - (rt/nav :dashboard-projects {:team-id (:default-team-id data)})) + (st/emit! (du/login-from-register)) :else (st/emit! (rt/nav :auth-register-success {} {:email (:email data)})))) From 26e9f652b6792de3c3f043af4ff57473b75acc88 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Jul 2021 12:45:33 +0200 Subject: [PATCH 185/204] :bug: Fix color picker for texts in root frame --- CHANGES.md | 1 + frontend/src/app/main/exports.cljs | 13 +++++++-- .../src/app/main/ui/workspace/shapes.cljs | 28 ++++++++++++------- 3 files changed, 29 insertions(+), 13 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b72a39c443..2f8f8a77f7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -25,6 +25,7 @@ - Fix workspace header presence styles and invalid link [Taiga #1813](https://tree.taiga.io/project/penpot/issue/1813). - Fix color-input wrong behavior (on workspace page color) [Taiga #1795](https://tree.taiga.io/project/penpot/issue/1795). - Fix file contextual menu in shared libraries at dashboard [Taiga #1865](https://tree.taiga.io/project/penpot/issue/1865). +- Fix problem with color picker and fonts [#1049](https://github.com/penpot/penpot/issues/1049) ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/src/app/main/exports.cljs b/frontend/src/app/main/exports.cljs index 7dc4975072..d48e5eebea 100644 --- a/frontend/src/app/main/exports.cljs +++ b/frontend/src/app/main/exports.cljs @@ -25,6 +25,7 @@ [app.main.ui.shapes.shape :refer [shape-container]] [app.main.ui.shapes.svg-raw :as svg-raw] [app.main.ui.shapes.text :as text] + [app.main.ui.shapes.text.fontfaces :as ff] [app.util.object :as obj] [app.util.timers :as ts] [cuerdas.core :as str] @@ -133,8 +134,14 @@ [{:keys [data width height thumbnails? embed?] :as props}] (let [objects (:objects data) root (get objects uuid/zero) - shapes (->> (:shapes root) - (map #(get objects %))) + shapes + (->> (:shapes root) + (map #(get objects %))) + + root-children + (->> shapes + (filter #(not= :frame (:type %))) + (mapcat #(cp/get-object-with-children (:id %) objects))) vport (when (and (some? width) (some? height)) {:width width :height height}) @@ -161,7 +168,7 @@ :background background-color}} [:& use/export-page {:options (:options data)}] - + [:& ff/fontfaces-style {:shapes root-children}] (for [item shapes] (let [frame? (= (:type item) :frame)] (cond diff --git a/frontend/src/app/main/ui/workspace/shapes.cljs b/frontend/src/app/main/ui/workspace/shapes.cljs index b62038189d..b310d21180 100644 --- a/frontend/src/app/main/ui/workspace/shapes.cljs +++ b/frontend/src/app/main/ui/workspace/shapes.cljs @@ -13,11 +13,13 @@ common." (:require [app.common.geom.shapes :as geom] + [app.common.pages :as cp] [app.common.uuid :as uuid] [app.main.refs :as refs] [app.main.ui.shapes.circle :as circle] [app.main.ui.shapes.image :as image] [app.main.ui.shapes.rect :as rect] + [app.main.ui.shapes.text.fontfaces :as ff] [app.main.ui.workspace.shapes.bounding-box :refer [bounding-box]] [app.main.ui.workspace.shapes.common :as common] [app.main.ui.workspace.shapes.frame :as frame] @@ -53,18 +55,24 @@ [props] (let [objects (obj/get props "objects") active-frames (obj/get props "active-frames") - root-shapes (get-in objects [uuid/zero :shapes]) - shapes (->> root-shapes (mapv #(get objects %)))] + root-shapes (get-in objects [uuid/zero :shapes]) + shapes (->> root-shapes (mapv #(get objects %))) - (for [item shapes] - (if (= (:type item) :frame) - [:& frame-wrapper {:shape item - :key (:id item) - :objects objects - :thumbnail? (not (get active-frames (:id item) false))}] + root-children (->> shapes + (filter #(not= :frame (:type %))) + (mapcat #(cp/get-object-with-children (:id %) objects)))] - [:& shape-wrapper {:shape item - :key (:id item)}])))) + [:* + [:& ff/fontfaces-style {:shapes root-children}] + (for [item shapes] + (if (= (:type item) :frame) + [:& frame-wrapper {:shape item + :key (:id item) + :objects objects + :thumbnail? (not (get active-frames (:id item) false))}] + + [:& shape-wrapper {:shape item + :key (:id item)}]))])) (mf/defc shape-wrapper {::mf/wrap [#(mf/memo' % (mf/check-props ["shape" "frame"]))] From 7cc9fa6d30a6434dc39d225c5eb4af9bfc627046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Wed, 7 Jul 2021 13:20:34 +0200 Subject: [PATCH 186/204] :bug: Fix constraints calc when parent has displacement --- .../app/common/geom/shapes/transforms.cljc | 57 ++++++++++--------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index aba530151b..c5387aa925 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -375,19 +375,12 @@ (let [parent-rect (:selrect parent) child-rect (:selrect child) - ; If the modifiers include a resize vector, apply it to the parent, - ; to check the difference and calculate child transformations. - origin (-> (:resize-origin parent-modifiers) - ((d/nilf transform-point-center) - (gco/center-shape parent) - (:resize-transform-inverse parent-modifiers (gmt/matrix)))) - - transformed-parent-rect (-> parent-rect - (gpr/rect->points) - (transform-points - origin - (gmt/scale-matrix (get parent-modifiers :resize-vector (gpt/point 1 1)))) - (gpr/points->selrect)) + ;; Apply the modifiers to the parent, to check the difference with + ;; the original rect, and calculate child transformations. + transformed-parent-rect (-> parent + (assoc :modifiers parent-modifiers) + (transform-shape) + (:selrect)) ;; Calculate the modifiers in the horizontal and vertical directions ;; depending on the child constraints. @@ -431,11 +424,18 @@ {})) :scale - (if (and (:resize-vector parent-modifiers) - (not (mth/close? (:x (:resize-vector parent-modifiers)) 1))) - {:resize-origin (:resize-origin parent-modifiers) - :resize-vector (gpt/point (:x (:resize-vector parent-modifiers)) 1)} - {}) + (cond-> {} + (and (:resize-vector parent-modifiers) + (not (mth/close? (:x (:resize-vector parent-modifiers)) 1))) + (assoc :resize-origin (:resize-origin parent-modifiers) + :resize-vector (gpt/point (:x (:resize-vector parent-modifiers)) 1)) + + (:displacement parent-modifiers) + (assoc :displacement + (gpt/point (-> (gpt/point 0 0) + (gpt/transform (:displacement parent-modifiers)) + (:x)) + 0))) {}) @@ -476,11 +476,17 @@ {})) :scale - (if (and (:resize-vector parent-modifiers) - (not (mth/close? (:y (:resize-vector parent-modifiers)) 1))) - {:resize-origin (:resize-origin parent-modifiers) - :resize-vector (gpt/point 1 (:y (:resize-vector parent-modifiers)))} - {}) + (cond-> {} + (and (:resize-vector parent-modifiers) + (not (mth/close? (:y (:resize-vector parent-modifiers)) 1))) + (assoc :resize-origin (:resize-origin parent-modifiers) + :resize-vector (gpt/point 1 (:y (:resize-vector parent-modifiers)))) + + (:displacement parent-modifiers) + (assoc :displacement + (gpt/point 0 (-> (gpt/point 0 0) + (gpt/transform (:displacement parent-modifiers)) + (:y))))) {})] @@ -498,11 +504,6 @@ :resize-vector (gpt/point (get (:resize-vector modifiers-h) :x 1) (get (:resize-vector modifiers-v) :y 1))) - (:displacement parent-modifiers) - (update :displacement #(if (nil? %) - (:displacement parent-modifiers) - (gmt/add-translate % (:displacement parent-modifiers)))) - (:resize-transform parent-modifiers) (assoc :resize-transform (:resize-transform parent-modifiers) :resize-transform-inverse (:resize-transform-inverse parent-modifiers))))) From 163215d5c970dad2784484162fb9f6d9e443d5a5 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Jul 2021 13:44:14 +0200 Subject: [PATCH 187/204] :bug: Fix negative values in blur options --- CHANGES.md | 1 + .../src/app/main/ui/workspace/sidebar/options/menus/blur.cljs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 2f8f8a77f7..f303bdd06a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,6 +26,7 @@ - Fix color-input wrong behavior (on workspace page color) [Taiga #1795](https://tree.taiga.io/project/penpot/issue/1795). - Fix file contextual menu in shared libraries at dashboard [Taiga #1865](https://tree.taiga.io/project/penpot/issue/1865). - Fix problem with color picker and fonts [#1049](https://github.com/penpot/penpot/issues/1049) +- Fix negative values in blur [#1815](https://tree.taiga.io/project/penpot/issue/1815) ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs index fac3572764..3ff9d3f268 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/options/menus/blur.cljs @@ -72,7 +72,7 @@ [:div.element-set-content [:& input-row {:label "Value" :class "pixels" - :min 0 + :min "0" :value (:value blur) :placeholder (tr "settings.multiple") :on-change handle-change}]])])) From 7b9b3dabbe8d5f1354547e2c77a547d2ba333533 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Jul 2021 13:55:00 +0200 Subject: [PATCH 188/204] :bug: Fix problem when editing color in group --- CHANGES.md | 1 + frontend/src/app/main/ui/workspace/sidebar/assets.cljs | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f303bdd06a..ef92386dd7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -27,6 +27,7 @@ - Fix file contextual menu in shared libraries at dashboard [Taiga #1865](https://tree.taiga.io/project/penpot/issue/1865). - Fix problem with color picker and fonts [#1049](https://github.com/penpot/penpot/issues/1049) - Fix negative values in blur [#1815](https://tree.taiga.io/project/penpot/issue/1815) +- Fix problem when editing color in group [#1816](https://tree.taiga.io/project/penpot/issue/1816) ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs index 766c6cda30..8149b52d61 100644 --- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs @@ -798,7 +798,9 @@ edit-color (fn [new-color] - (let [updated-color (merge new-color (select-keys color [:id :file-id :name]))] + (let [old-data (-> (select-keys color [:id :file-id]) + (assoc :name (cp/merge-path-item (:path color) (:name color)))) + updated-color (merge new-color old-data)] (st/emit! (dwl/update-color updated-color file-id)))) delete-color From 73a08fd1197e733745ff07be1bb5c8f8167c5341 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Jul 2021 14:12:47 +0200 Subject: [PATCH 189/204] :bug: Fix resize/rotate with mouse buttons different than left --- CHANGES.md | 5 +-- .../main/ui/workspace/viewport/selection.cljs | 31 +++++++++++++------ frontend/src/app/util/dom.cljs | 4 +++ 3 files changed, 28 insertions(+), 12 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ef92386dd7..f48579f1b2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -26,8 +26,9 @@ - Fix color-input wrong behavior (on workspace page color) [Taiga #1795](https://tree.taiga.io/project/penpot/issue/1795). - Fix file contextual menu in shared libraries at dashboard [Taiga #1865](https://tree.taiga.io/project/penpot/issue/1865). - Fix problem with color picker and fonts [#1049](https://github.com/penpot/penpot/issues/1049) -- Fix negative values in blur [#1815](https://tree.taiga.io/project/penpot/issue/1815) -- Fix problem when editing color in group [#1816](https://tree.taiga.io/project/penpot/issue/1816) +- Fix negative values in blur [Taiga #1815](https://tree.taiga.io/project/penpot/issue/1815) +- Fix problem when editing color in group [Taiga #1816](https://tree.taiga.io/project/penpot/issue/1816) +- Fix resize/rotate with mouse buttons different than left [#1060](https://github.com/penpot/penpot/issues/1060) ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index 4f853229cc..f1d894a2b0 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -286,12 +286,17 @@ shape-center (geom/center-shape shape) - on-resize (fn [current-position _initial-position event] - (dom/stop-propagation event) - (st/emit! (dw/start-resize current-position selected shape))) + on-resize + (fn [current-position _initial-position event] + (when (dom/left-mouse? event) + (dom/stop-propagation event) + (st/emit! (dw/start-resize current-position selected shape)))) - on-rotate #(do (dom/stop-propagation %) - (st/emit! (dw/start-rotate shapes)))] + on-rotate + (fn [event] + (when (dom/left-mouse? event) + (dom/stop-propagation event) + (st/emit! (dw/start-rotate shapes))))] [:* [:& controls {:shape shape @@ -311,13 +316,19 @@ shape (geom/transform-shape shape {:round-coords? false}) shape' (if (debug? :simple-selection) (geom/setup {:type :rect} (geom/selection-rect [shape])) shape) - on-resize (fn [current-position _initial-position event] - (dom/stop-propagation event) - (st/emit! (dw/start-resize current-position #{shape-id} shape'))) + + on-resize + (fn [current-position _initial-position event] + (when (dom/left-mouse? event) + (dom/stop-propagation event) + (st/emit! (dw/start-resize current-position #{shape-id} shape')))) on-rotate - #(do (dom/stop-propagation %) - (st/emit! (dw/start-rotate [shape])))] + (fn [event] + (when (dom/left-mouse? event) + (dom/stop-propagation event) + (st/emit! (dw/start-rotate [shape]))))] + [:& controls {:shape shape' :zoom zoom :color color diff --git a/frontend/src/app/util/dom.cljs b/frontend/src/app/util/dom.cljs index b14de52b3e..f05f2a1a5f 100644 --- a/frontend/src/app/util/dom.cljs +++ b/frontend/src/app/util/dom.cljs @@ -375,3 +375,7 @@ (trigger-download-uri filename mtype uri))))) (trigger-download-uri filename mtype uri))) + +(defn left-mouse? [bevent] + (let [event (.-nativeEvent bevent)] + (= 1 (.-which event)))) From 9b878bd1ccb86f56c9368773167ebb9ccd60439b Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Jul 2021 15:07:36 +0200 Subject: [PATCH 190/204] :bug: Fix header partialy visible on fullscreen viewer mode --- CHANGES.md | 1 + frontend/resources/styles/main/layouts/handoff.scss | 11 ++++++++++- frontend/resources/styles/main/layouts/viewer.scss | 12 +++++++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f48579f1b2..58c2f3df70 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -29,6 +29,7 @@ - Fix negative values in blur [Taiga #1815](https://tree.taiga.io/project/penpot/issue/1815) - Fix problem when editing color in group [Taiga #1816](https://tree.taiga.io/project/penpot/issue/1816) - Fix resize/rotate with mouse buttons different than left [#1060](https://github.com/penpot/penpot/issues/1060) +- Fix header partialy visible on fullscreen viewer mode [Taiga #1875](https://tree.taiga.io/project/penpot/issue/1875) ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/resources/styles/main/layouts/handoff.scss b/frontend/resources/styles/main/layouts/handoff.scss index c41ad640d0..027bbac0d9 100644 --- a/frontend/resources/styles/main/layouts/handoff.scss +++ b/frontend/resources/styles/main/layouts/handoff.scss @@ -21,9 +21,18 @@ $width-settings-bar: 16rem; .viewer-header { width: 100%; position: fixed; - top: -39px; + top: -48px; left: 0; transition: top 400ms ease 300ms; + + &::after { + content: " "; + position: absolute; + width: 100%; + height: 1rem; + left: 0; + top: 48px; + } } & .viewer-header:hover { diff --git a/frontend/resources/styles/main/layouts/viewer.scss b/frontend/resources/styles/main/layouts/viewer.scss index 8f120296fc..7e05a861b4 100644 --- a/frontend/resources/styles/main/layouts/viewer.scss +++ b/frontend/resources/styles/main/layouts/viewer.scss @@ -19,9 +19,19 @@ & .viewer-header { width: 100%; position: fixed; - top: -39px; + top: -48px; left: 0; transition: top 400ms ease 300ms; + + &::after { + content: " "; + position: absolute; + width: 100%; + height: 1rem; + left: 0; + top: 48px; + } + } & .viewer-header:hover { From d2777f5915bce3715710b65023d12dac515edb9f Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Wed, 7 Jul 2021 16:30:57 +0200 Subject: [PATCH 191/204] :bug: Fix dynamic alignment enabled with hidden objects --- CHANGES.md | 2 ++ frontend/src/app/worker/snaps.cljs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 58c2f3df70..12dec0e1f9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -30,6 +30,8 @@ - Fix problem when editing color in group [Taiga #1816](https://tree.taiga.io/project/penpot/issue/1816) - Fix resize/rotate with mouse buttons different than left [#1060](https://github.com/penpot/penpot/issues/1060) - Fix header partialy visible on fullscreen viewer mode [Taiga #1875](https://tree.taiga.io/project/penpot/issue/1875) +- Fix dynamic alignment enabled with hidden objects [#1063](https://github.com/penpot/penpot/issues/1063) + ### :arrow_up: Deps updates ### :boom: Breaking changes diff --git a/frontend/src/app/worker/snaps.cljs b/frontend/src/app/worker/snaps.cljs index dadd825eef..a4926731b5 100644 --- a/frontend/src/app/worker/snaps.cljs +++ b/frontend/src/app/worker/snaps.cljs @@ -19,7 +19,7 @@ (defn process-shape [frame-id coord] (fn [shape] - (let [points (snap/shape-snap-points shape) + (let [points (when-not (:hidden shape) (snap/shape-snap-points shape)) shape-data (->> points (mapv #(vector % (:id shape))))] (if (= (:id shape) frame-id) (d/concat @@ -73,7 +73,7 @@ (d/mapm create-index shapes-data))) ;; Attributes that will change the values of their snap -(def snap-attrs [:x :y :width :height :selrect :grids]) +(def snap-attrs [:x :y :width :height :hidden :selrect :grids]) (defn- update-snap-data [snap-data old-objects new-objects] From 895889d27a2a917f1cee16b75b991c0403410014 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 8 Jul 2021 16:02:21 +0200 Subject: [PATCH 192/204] :bug: Fix local assert when deleting text --- frontend/src/app/main/data/workspace/texts.cljs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/app/main/data/workspace/texts.cljs b/frontend/src/app/main/data/workspace/texts.cljs index d76abc46b1..f928c01cec 100644 --- a/frontend/src/app/main/data/workspace/texts.cljs +++ b/frontend/src/app/main/data/workspace/texts.cljs @@ -68,7 +68,7 @@ (dch/update-shapes [id] #(assoc % :content content)) (dwu/commit-undo-transaction))))) (rx/of (dws/deselect-shape id) - (dwc/delete-shapes [id]))))))) + (dwc/delete-shapes #{id}))))))) (defn initialize-editor-state [{:keys [id content] :as shape} decorator] From 4e439792ec55bcfd687f4d584bdc6e634fc18513 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 8 Jul 2021 15:53:47 +0200 Subject: [PATCH 193/204] :sparkles: Double click won't make a shape a path until you change a node --- CHANGES.md | 1 + frontend/src/app/main/data/workspace.cljs | 8 +--- .../app/main/data/workspace/path/changes.cljs | 7 ++-- .../app/main/data/workspace/path/drawing.cljs | 23 ++++++----- .../app/main/data/workspace/path/edition.cljs | 39 ++++++++++++------- .../main/data/workspace/path/selection.cljs | 2 +- .../app/main/data/workspace/path/state.cljs | 31 +++++++++++---- .../app/main/data/workspace/path/streams.cljs | 3 +- .../app/main/data/workspace/path/tools.cljs | 17 ++++---- .../app/main/data/workspace/path/undo.cljs | 19 ++++----- .../app/main/data/workspace/transforms.cljs | 8 ---- .../main/ui/workspace/shapes/path/editor.cljs | 8 ++++ .../src/app/main/ui/workspace/viewport.cljs | 10 ++--- .../main/ui/workspace/viewport/actions.cljs | 16 +++----- .../main/ui/workspace/viewport/selection.cljs | 3 +- .../main/ui/workspace/viewport/widgets.cljs | 2 +- 16 files changed, 111 insertions(+), 86 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 12dec0e1f9..0692881630 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,7 @@ - Preserve components if possible, when pasted into a different file [Taiga #1063](https://tree.taiga.io/project/penpot/issue/1063). - Add the ability to offload file data to a cheaper storage when file becomes inactive. - Import/Export Penpot files from dashboard. +- Double click won't make a shape a path until you change a node [Taiga #] ### :bug: Bugs fixed diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 7612639031..b8dc5f08ac 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1046,14 +1046,11 @@ :text (rx/of (dwc/start-edition-mode id)) - :path - (rx/of (dwc/start-edition-mode id) - (dwdp/start-path-edit id)) - :group (rx/of (dwc/select-shapes (into (d/ordered-set) [(last shapes)]))) - (rx/empty)))))))) + (rx/of (dwc/start-edition-mode id) + (dwdp/start-path-edit id))))))))) ;; --- Change Page Order (D&D Ordering) @@ -1825,7 +1822,6 @@ (d/export dwt/update-dimensions) (d/export dwt/flip-horizontal-selected) (d/export dwt/flip-vertical-selected) -(d/export dwt/selected-to-path) ;; Persistence diff --git a/frontend/src/app/main/data/workspace/path/changes.cljs b/frontend/src/app/main/data/workspace/path/changes.cljs index c22fc25e0e..ba0700bf3f 100644 --- a/frontend/src/app/main/data/workspace/path/changes.cljs +++ b/frontend/src/app/main/data/workspace/path/changes.cljs @@ -76,12 +76,13 @@ (ptk/reify ::save-path-content ptk/UpdateEvent (update [_ state] - (let [content (get-in state (st/get-path state :content)) + (let [content (st/get-path state :content) content (if (and (not preserve-move-to) (= (-> content last :command) :move-to)) (into [] (take (dec (count content)) content)) content)] - (assoc-in state (st/get-path state :content) content))) + (-> state + (st/set-content content)))) ptk/WatchEvent (watch [it state _] @@ -89,7 +90,7 @@ page-id (:current-page-id state) id (get-in state [:workspace-local :edition]) old-content (get-in state [:workspace-local :edit-path id :old-content]) - shape (get-in state (st/get-path state))] + shape (st/get-path state)] (if (and (some? old-content) (some? (:id shape))) (let [[rch uch] (generate-path-changes objects page-id shape old-content (:content shape))] (rx/of (dch/commit-changes {:redo-changes rch diff --git a/frontend/src/app/main/data/workspace/path/drawing.cljs b/frontend/src/app/main/data/workspace/path/drawing.cljs index 287dd61f00..202266e798 100644 --- a/frontend/src/app/main/data/workspace/path/drawing.cljs +++ b/frontend/src/app/main/data/workspace/path/drawing.cljs @@ -9,6 +9,7 @@ [app.common.geom.point :as gpt] [app.common.pages :as cp] [app.common.spec :as us] + [app.main.data.workspace.changes :as dch] [app.main.data.workspace.common :as dwc] [app.main.data.workspace.drawing.common :as dwdc] [app.main.data.workspace.path.changes :as changes] @@ -22,6 +23,7 @@ [app.main.streams :as ms] [app.util.path.commands :as upc] [app.util.path.geom :as upg] + [app.util.path.shapes-to-path :as upsp] [beicon.core :as rx] [potok.core :as ptk])) @@ -36,7 +38,7 @@ last-point (get-in state [:workspace-local :edit-path id :last-point]) position (cond-> (gpt/point x y) fix-angle? (helpers/position-fixed-angle last-point)) - shape (get-in state (st/get-path state)) + shape (st/get-path state) {:keys [last-point prev-handler]} (get-in state [:workspace-local :edit-path id]) command (helpers/next-node shape position last-point prev-handler)] (assoc-in state [:workspace-local :edit-path id :preview] command))))) @@ -55,7 +57,7 @@ (assoc-in [:workspace-local :edit-path id :last-point] position) (update-in [:workspace-local :edit-path id] dissoc :prev-handler) (update-in [:workspace-local :edit-path id] dissoc :preview) - (update-in (st/get-path state) helpers/append-node position last-point prev-handler)) + (update-in (st/get-path-location state) helpers/append-node position last-point prev-handler)) state))))) (defn drag-handler @@ -66,7 +68,7 @@ ptk/UpdateEvent (update [_ state] (let [id (st/get-path-id state) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) index (or index (count content)) prefix (or prefix :c1) @@ -96,16 +98,16 @@ (let [id (st/get-path-id state) modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) - content (-> (get-in state (st/get-path state :content)) + content (-> (st/get-path state :content) (upc/apply-content-modifiers modifiers)) handler (get-in state [:workspace-local :edit-path id :drag-handler])] (-> state - (assoc-in (st/get-path state :content) content) + (st/set-content content) (update-in [:workspace-local :edit-path id] dissoc :drag-handler) (update-in [:workspace-local :edit-path id] dissoc :content-modifiers) (assoc-in [:workspace-local :edit-path id :prev-handler] handler) - (update-in (st/get-path state) helpers/update-selrect)))) + (update-in (st/get-path-location state) helpers/update-selrect)))) ptk/WatchEvent (watch [_ state _] @@ -126,7 +128,7 @@ (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) snap-toggled (get-in state [:workspace-local :edit-path id :snap-toggled]) points (upg/content->points content) @@ -163,7 +165,7 @@ (watch [_ state stream] (let [mouse-up (->> stream (rx/filter #(or (helpers/end-path-event? %) (ms/mouse-up? %)))) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) points (upg/content->points content) id (st/get-path-id state) @@ -218,7 +220,7 @@ mouse-down (->> stream (rx/filter ms/mouse-down?)) end-path-events (->> stream (rx/filter helpers/end-path-event?)) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) points (upg/content->points content) id (st/get-path-id state) @@ -316,6 +318,7 @@ edit-mode (get-in state [:workspace-local :edit-path id :edit-mode])] (if (= :draw edit-mode) (rx/concat + (rx/of (dch/update-shapes [id] upsp/convert-to-path)) (rx/of (handle-drawing-path id)) (->> stream (rx/filter (ptk/type? ::common/finish-path)) @@ -328,7 +331,7 @@ ptk/WatchEvent (watch [_ state _] (let [id (st/get-path-id state) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) old-content (get-in state [:workspace-local :edit-path id :old-content]) mode (get-in state [:workspace-local :edit-path id :edit-mode])] diff --git a/frontend/src/app/main/data/workspace/path/edition.cljs b/frontend/src/app/main/data/workspace/path/edition.cljs index 94bd92f587..a7ae00efe9 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.streams :as ms] [app.util.path.commands :as upc] [app.util.path.geom :as upg] + [app.util.path.shapes-to-path :as upsp] [app.util.path.subpaths :as ups] [app.util.path.tools :as upt] [beicon.core :as rx] @@ -31,8 +32,7 @@ ptk/UpdateEvent (update [_ state] - (let [content (get-in state (st/get-path state :content)) - + (let [content (st/get-path state :content) modifiers (helpers/move-handler-modifiers content index prefix false match-opposite? dx dy) [cx cy] (if (= prefix :c1) [:c1x :c1y] [:c2x :c2y]) point (gpt/point (+ (get-in content [index :params cx]) dx) @@ -50,7 +50,7 @@ id (st/get-path-id state) page-id (:current-page-id state) - shape (get-in state (st/get-path state)) + shape (st/get-path state) content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers]) content (:content shape) @@ -100,7 +100,7 @@ ptk/UpdateEvent (update [_ state] (let [id (st/get-path-id state) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) modifiers-reducer (partial modify-content-point content move-modifier) content-modifiers (get-in state [:workspace-local :edit-path id :content-modifiers] {}) content-modifiers (->> points @@ -114,7 +114,7 @@ ptk/UpdateEvent (update [_ state] (let [id (st/get-path-id state) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) delta (gpt/subtract to-point from-point) modifiers-reducer (partial modify-content-point content delta) @@ -141,6 +141,7 @@ selected? (contains? selected-points position)] (streams/drag-stream (rx/of + (dch/update-shapes [id] upsp/convert-to-path) (when-not selected? (selection/select-node position shift?)) (drag-selected-points @ms/mouse-position)) (rx/of (selection/select-node position shift?))))))) @@ -156,7 +157,7 @@ selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) points (upg/content->points content)] (rx/concat @@ -221,6 +222,7 @@ mov-vec (gpt/multiply (get-displacement direction) scale)] (rx/concat + (rx/of (dch/update-shapes [id] upsp/convert-to-path)) (rx/merge (->> move-events (rx/take-until stopper) @@ -247,7 +249,7 @@ start-delta-x (get-in modifiers [index cx] 0) start-delta-y (get-in modifiers [index cy] 0) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) points (upg/content->points content) point (-> content (get (if (= prefix :c1) (dec index) index)) (upc/command->point)) @@ -260,6 +262,7 @@ (streams/drag-stream (rx/concat + (rx/of (dch/update-shapes [id] upsp/convert-to-path)) (->> (streams/move-handler-stream snap-toggled start-point point handler opposite points) (rx/take-until (->> stream (rx/filter #(or (ms/mouse-up? %) (streams/finish-edition? %))))) @@ -284,7 +287,8 @@ ptk/UpdateEvent (update [_ state] (let [edit-path (get-in state [:workspace-local :edit-path id]) - state (update-in state (st/get-path state :content) ups/close-subpaths)] + content (st/get-path state :content) + state (st/set-content state (ups/close-subpaths content))] (cond-> state (or (not edit-path) (= :draw (:edit-mode edit-path))) (assoc-in [:workspace-local :edit-path id] {:edit-mode :move @@ -313,17 +317,26 @@ (let [id (get-in state [:workspace-local :edition])] (update state :workspace-local dissoc :edit-path id))))) -(defn create-node-at-position +(defn split-segments [{:keys [from-p to-p t]}] - (ptk/reify ::create-node-at-position + (ptk/reify ::split-segments ptk/UpdateEvent (update [_ state] (let [id (st/get-path-id state) - old-content (get-in state (st/get-path state :content))] + content (st/get-path state :content)] (-> state - (assoc-in [:workspace-local :edit-path id :old-content] old-content) - (update-in (st/get-path state :content) upt/split-segments #{from-p to-p} t)))) + (assoc-in [:workspace-local :edit-path id :old-content] content) + (st/set-content (-> content (upt/split-segments #{from-p to-p} t)))))) ptk/WatchEvent (watch [_ _ _] (rx/of (changes/save-path-content {:preserve-move-to true}))))) + +(defn create-node-at-position + [event] + (ptk/reify ::create-node-at-position + ptk/WatchEvent + (watch [_ state _] + (let [id (st/get-path-id state)] + (rx/of (dch/update-shapes [id] upsp/convert-to-path) + (split-segments event)))))) diff --git a/frontend/src/app/main/data/workspace/path/selection.cljs b/frontend/src/app/main/data/workspace/path/selection.cljs index ab23ae5362..46fd28cb57 100644 --- a/frontend/src/app/main/data/workspace/path/selection.cljs +++ b/frontend/src/app/main/data/workspace/path/selection.cljs @@ -48,7 +48,7 @@ (update [_ state] (let [selrect (get-in state [:workspace-local :selrect]) id (get-in state [:workspace-local :edition]) - content (get-in state (st/get-path state :content)) + content (st/get-path state :content) selected-point? #(gsh/has-point-rect? selrect %) selected-points (get-in state [:workspace-local :edit-path id :selected-points]) positions (into (if shift? selected-points #{}) diff --git a/frontend/src/app/main/data/workspace/path/state.cljs b/frontend/src/app/main/data/workspace/path/state.cljs index 6bb59c4c64..229e46256f 100644 --- a/frontend/src/app/main/data/workspace/path/state.cljs +++ b/frontend/src/app/main/data/workspace/path/state.cljs @@ -6,7 +6,8 @@ (ns app.main.data.workspace.path.state (:require - [app.common.data :as d])) + [app.common.data :as d] + [app.util.path.shapes-to-path :as upsp])) (defn get-path-id "Retrieves the currently editing path id" @@ -14,16 +15,30 @@ (or (get-in state [:workspace-local :edition]) (get-in state [:workspace-drawing :object :id]))) -(defn get-path - "Retrieves the location of the path object and additionaly can pass - the arguments. This location can be used in get-in, assoc-in... functions" - [state & path] - (let [edit-id (get-in state [:workspace-local :edition]) - page-id (:current-page-id state)] +(defn get-path-location + [state & ks] + (let [edit-id (get-in state [:workspace-local :edition]) + page-id (:current-page-id state)] (d/concat (if edit-id [:workspace-data :pages-index page-id :objects edit-id] [:workspace-drawing :object]) - path))) + ks))) +(defn get-path + "Retrieves the location of the path object and additionaly can pass + the arguments. This location can be used in get-in, assoc-in... functions" + [state & ks] + (let [path-loc (get-path-location state) + shape (-> (get-in state path-loc) + (upsp/convert-to-path))] + (if (empty? ks) + shape + (get-in shape ks)))) + +(defn set-content + [state content] + (let [path-loc (get-path-location state :content)] + (-> state + (assoc-in path-loc content)))) diff --git a/frontend/src/app/main/data/workspace/path/streams.cljs b/frontend/src/app/main/data/workspace/path/streams.cljs index 94c03259e4..b67607ae07 100644 --- a/frontend/src/app/main/data/workspace/path/streams.cljs +++ b/frontend/src/app/main/data/workspace/path/streams.cljs @@ -123,9 +123,8 @@ (defn position-stream [snap-toggled _points] (let [zoom (get-in @st/state [:workspace-local :zoom] 1) - ;; ranges (snap/create-ranges points) d-pos (/ snap/snap-path-accuracy zoom) - get-content (fn [state] (get-in state (state/get-path state :content))) + get-content #(state/get-path % :content) content-stream (-> (l/derived get-content st/state) diff --git a/frontend/src/app/main/data/workspace/path/tools.cljs b/frontend/src/app/main/data/workspace/path/tools.cljs index d13fbf7f70..18f262743f 100644 --- a/frontend/src/app/main/data/workspace/path/tools.cljs +++ b/frontend/src/app/main/data/workspace/path/tools.cljs @@ -11,6 +11,7 @@ [app.main.data.workspace.path.changes :as changes] [app.main.data.workspace.path.state :as st] [app.main.data.workspace.state-helpers :as wsh] + [app.util.path.shapes-to-path :as upsp] [app.util.path.subpaths :as ups] [app.util.path.tools :as upt] [beicon.core :as rx] @@ -27,19 +28,21 @@ (let [objects (wsh/lookup-page-objects state) id (st/get-path-id state) page-id (:current-page-id state) - shape (get-in state (st/get-path state)) - + shape (st/get-path state) selected-points (get-in state [:workspace-local :edit-path id :selected-points] #{}) points (or points selected-points)] (when (and (seq points) (some? shape)) (let [new-content (-> (tool-fn (:content shape) points) (ups/close-subpaths)) [rch uch] (changes/generate-path-changes objects page-id shape (:content shape) new-content)] - (rx/of (dch/commit-changes {:redo-changes rch - :undo-changes uch - :origin it}) - (when (empty? new-content) - dwc/clear-edition-mode))))))))) + + (rx/concat + (rx/of (dch/update-shapes [id] upsp/convert-to-path)) + (rx/of (dch/commit-changes {:redo-changes rch + :undo-changes uch + :origin it}) + (when (empty? new-content) + dwc/clear-edition-mode)))))))))) (defn make-corner ([] diff --git a/frontend/src/app/main/data/workspace/path/undo.cljs b/frontend/src/app/main/data/workspace/path/undo.cljs index 8472d5c88a..43dcb09313 100644 --- a/frontend/src/app/main/data/workspace/path/undo.cljs +++ b/frontend/src/app/main/data/workspace/path/undo.cljs @@ -25,21 +25,22 @@ (= :app.main.data.workspace.common/redo (ptk/type event))) (defn- make-entry [state] - (let [id (st/get-path-id state)] - {:content (get-in state (st/get-path state :content)) - :selrect (get-in state (st/get-path state :selrect)) - :points (get-in state (st/get-path state :points)) + (let [id (st/get-path-id state) + shape (st/get-path state)] + {:content (:content shape) + :selrect (:selrect shape) + :points (:points shape) :preview (get-in state [:workspace-local :edit-path id :preview]) :last-point (get-in state [:workspace-local :edit-path id :last-point]) :prev-handler (get-in state [:workspace-local :edit-path id :prev-handler])})) (defn- load-entry [state {:keys [content selrect points preview last-point prev-handler]}] (let [id (st/get-path-id state) - old-content (get-in state (st/get-path state :content))] + old-content (st/get-path state :content)] (-> state - (d/assoc-in-when (st/get-path state :content) content) - (d/assoc-in-when (st/get-path state :selrect) selrect) - (d/assoc-in-when (st/get-path state :points) points) + (d/assoc-in-when (st/get-path-location state :content) content) + (d/assoc-in-when (st/get-path-location state :selrect) selrect) + (d/assoc-in-when (st/get-path-location state :points) points) (d/update-in-when [:workspace-local :edit-path id] assoc @@ -128,7 +129,7 @@ (def path-content-ref (letfn [(selector [state] - (get-in state (st/get-path state :content)))] + (st/get-path state :content))] (l/derived selector store/state))) (defn start-path-undo diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index 48b00825f1..ffefaea68f 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -20,7 +20,6 @@ [app.main.data.workspace.undo :as dwu] [app.main.snap :as snap] [app.main.streams :as ms] - [app.util.path.shapes-to-path :as ups] [beicon.core :as rx] [cljs.spec.alpha :as s] [potok.core :as ptk])) @@ -666,10 +665,3 @@ (dissoc :workspace-modifiers) (update :workspace-local dissoc :modifiers :current-move-selected))))) -(defn selected-to-path - [] - (ptk/reify ::selected-to-path - ptk/WatchEvent - (watch [_ state _] - (let [ids (wsh/lookup-selected state {:omit-blocked? true})] - (rx/of (dch/update-shapes ids ups/convert-to-path)))))) 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 e266380261..aa683f708e 100644 --- a/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs +++ b/frontend/src/app/main/ui/workspace/shapes/path/editor.cljs @@ -21,6 +21,7 @@ [app.util.path.commands :as upc] [app.util.path.format :as upf] [app.util.path.geom :as upg] + [app.util.path.shapes-to-path :as ups] [clojure.set :refer [map-invert]] [goog.events :as events] [rumext.alpha :as mf]) @@ -214,6 +215,13 @@ selected-points (or selected-points #{}) + shape (cond-> shape + (not= :path (:type shape)) + ups/convert-to-path + + :always + hooks/use-equal-memo) + base-content (:content shape) base-points (mf/use-memo (mf/deps base-content) #(->> base-content upg/content->points)) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index b7743e314f..07e73e2870 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -104,7 +104,7 @@ create-comment? (= :comments drawing-tool) drawing-path? (or (and edition (= :draw (get-in edit-path [edition :edit-mode]))) (and (some? drawing-obj) (= :path (:type drawing-obj)))) - path-editing? (and edition (= :path (get-in objects [edition :type]))) + node-editing? (and edition (not= :text (get-in objects [edition :type]))) text-editing? (and edition (= :text (get-in objects [edition :type]))) on-click (actions/on-click hover selected edition drawing-path? drawing-tool) @@ -113,7 +113,7 @@ 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? path-editing? drawing-path? create-comment?) + on-mouse-down (actions/on-mouse-down @hover selected edition drawing-tool text-editing? node-editing? drawing-path? create-comment?) 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?) @@ -144,16 +144,16 @@ (contains? layout :snap-grid)) (or drawing-obj transform)) show-selrect? (and selrect (empty? drawing)) - show-measures? (and (not transform) (not path-editing?) show-distances?)] + show-measures? (and (not transform) (not node-editing?) show-distances?)] (hooks/setup-dom-events viewport-ref zoom disable-paste in-viewport?) (hooks/setup-viewport-size viewport-ref) - (hooks/setup-cursor cursor alt? panning drawing-tool drawing-path? path-editing?) + (hooks/setup-cursor cursor alt? panning drawing-tool drawing-path? node-editing?) (hooks/setup-resize layout viewport-ref) (hooks/setup-keyboard alt? ctrl?) (hooks/setup-hover-shapes page-id move-stream selected objects transform selected ctrl? hover hover-ids zoom) (hooks/setup-viewport-modifiers modifiers selected objects render-ref) - (hooks/setup-shortcuts path-editing? drawing-path?) + (hooks/setup-shortcuts node-editing? drawing-path?) (hooks/setup-active-frames objects vbox hover active-frames) [:div.viewport diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 9fdcdbbf55..8df7cbdd02 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -27,9 +27,9 @@ (:import goog.events.WheelEvent)) (defn on-mouse-down - [{:keys [id blocked hidden type]} selected edition drawing-tool text-editing? path-editing? drawing-path? create-comment?] + [{:keys [id blocked hidden type]} selected edition drawing-tool text-editing? node-editing? drawing-path? create-comment?] (mf/use-callback - (mf/deps id blocked hidden type selected edition drawing-tool text-editing? path-editing? drawing-path? create-comment?) + (mf/deps id blocked hidden type selected edition drawing-tool text-editing? node-editing? drawing-path? create-comment?) (fn [bevent] (when (or (dom/class? (dom/get-target bevent) "viewport-controls") (dom/class? (dom/get-target bevent) "viewport-selrect")) @@ -65,7 +65,7 @@ drawing-tool (st/emit! (dd/start-drawing drawing-tool)) - path-editing? + node-editing? ;; Handle path node area selection (st/emit! (dwdp/handle-selection shift?)) @@ -158,9 +158,7 @@ {:keys [id type] :as shape} @hover frame? (= :frame type) - group? (= :group type) - text? (= :text type) - path? (= :path type)] + group? (= :group type)] (st/emit! (ms/->MouseEvent :double-click ctrl? shift? alt?)) @@ -174,12 +172,8 @@ (reset! hover-ids (into [] (rest @hover-ids))) (st/emit! (dw/select-shape (:id selected)))) - (and (not= id edition) (or text? path?)) + (not= id edition) (st/emit! (dw/select-shape id) - (dw/start-editing-selected)) - - :else - (st/emit! (dw/selected-to-path) (dw/start-editing-selected)))))))) (defn on-context-menu diff --git a/frontend/src/app/main/ui/workspace/viewport/selection.cljs b/frontend/src/app/main/ui/workspace/viewport/selection.cljs index f1d894a2b0..b8282b5d8a 100644 --- a/frontend/src/app/main/ui/workspace/viewport/selection.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/selection.cljs @@ -364,8 +364,7 @@ :zoom zoom :color color}] - (and (= type :path) - (= edition (:id shape))) + (= edition (:id shape)) [:& path-editor {:zoom zoom :shape shape}] diff --git a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs index b2851340f3..1fe2dce1db 100644 --- a/frontend/src/app/main/ui/workspace/viewport/widgets.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/widgets.cljs @@ -47,7 +47,7 @@ shape (-> selected first)] (when (and (= (count selected) 1) (= (:id shape) edition) - (= :path (:type shape))) + (not= :text (:type shape))) [:div.viewport-actions [:& path-actions {:shape shape}]]))) From 55d2acdf13baaa578b26c6becfc8a837dc36122a Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 8 Jul 2021 17:37:21 +0200 Subject: [PATCH 194/204] :sparkles: Incremental area selection --- CHANGES.md | 3 ++- common/src/app/common/geom/shapes.cljc | 1 + .../src/app/common/geom/shapes/intersect.cljc | 6 +++++ frontend/src/app/main/data/workspace.cljs | 2 +- .../src/app/main/data/workspace/path.cljs | 2 +- .../main/data/workspace/path/selection.cljs | 4 +-- .../app/main/data/workspace/selection.cljs | 27 ++++++++++++------- .../main/ui/workspace/viewport/actions.cljs | 4 +-- frontend/src/app/worker/selection.cljs | 14 ++++++---- 9 files changed, 41 insertions(+), 22 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0692881630..9106e1b2e6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,7 +14,8 @@ - Preserve components if possible, when pasted into a different file [Taiga #1063](https://tree.taiga.io/project/penpot/issue/1063). - Add the ability to offload file data to a cheaper storage when file becomes inactive. - Import/Export Penpot files from dashboard. -- Double click won't make a shape a path until you change a node [Taiga #] +- Double click won't make a shape a path until you change a node [Taiga #1796](https://tree.taiga.io/project/penpot/us/1796) +- Incremental area selection [#779](https://github.com/penpot/penpot/discussions/779) ### :bug: Bugs fixed diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index 24165ff5cd..c98eba760b 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -208,3 +208,4 @@ (d/export gin/overlaps?) (d/export gin/has-point?) (d/export gin/has-point-rect?) +(d/export gin/rect-contains-shape?) diff --git a/common/src/app/common/geom/shapes/intersect.cljc b/common/src/app/common/geom/shapes/intersect.cljc index e4dc2cc562..4b0593dc17 100644 --- a/common/src/app/common/geom/shapes/intersect.cljc +++ b/common/src/app/common/geom/shapes/intersect.cljc @@ -302,3 +302,9 @@ (let [lines (points->lines (:points shape))] ;; TODO: Will only work for simple shapes (is-point-inside-evenodd? point lines))) + +(defn rect-contains-shape? + [rect shape] + (->> shape + :points + (every? (partial has-point-rect? rect)))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index b8dc5f08ac..37c26e0f1f 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1843,7 +1843,7 @@ (d/export dwc/select-shapes) (d/export dws/shift-select-shapes) (d/export dws/duplicate-selected) -(d/export dws/handle-selection) +(d/export dws/handle-area-selection) (d/export dws/select-inside-group) (d/export dwd/select-for-drawing) (d/export dwc/clear-edition-mode) diff --git a/frontend/src/app/main/data/workspace/path.cljs b/frontend/src/app/main/data/workspace/path.cljs index 57f2af43b4..ada2f22a82 100644 --- a/frontend/src/app/main/data/workspace/path.cljs +++ b/frontend/src/app/main/data/workspace/path.cljs @@ -28,7 +28,7 @@ (d/export edition/move-selected) ;; Selection -(d/export selection/handle-selection) +(d/export selection/handle-area-selection) (d/export selection/select-node) (d/export selection/path-handler-enter) (d/export selection/path-handler-leave) diff --git a/frontend/src/app/main/data/workspace/path/selection.cljs b/frontend/src/app/main/data/workspace/path/selection.cljs index 46fd28cb57..3fed3f3651 100644 --- a/frontend/src/app/main/data/workspace/path/selection.cljs +++ b/frontend/src/app/main/data/workspace/path/selection.cljs @@ -101,12 +101,12 @@ (update [_ state] (update state :workspace-local dissoc :selrect)))) -(defn handle-selection +(defn handle-area-selection [shift?] (letfn [(valid-rect? [{width :width height :height}] (or (> width 10) (> height 10)))] - (ptk/reify ::handle-selection + (ptk/reify ::handle-area-selection ptk/WatchEvent (watch [_ _ stream] (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) diff --git a/frontend/src/app/main/data/workspace/selection.cljs b/frontend/src/app/main/data/workspace/selection.cljs index fe72eafe20..eaeb4b9723 100644 --- a/frontend/src/app/main/data/workspace/selection.cljs +++ b/frontend/src/app/main/data/workspace/selection.cljs @@ -45,7 +45,7 @@ (update [_ state] (assoc-in state [:workspace-local :selrect] selrect)))) -(defn handle-selection +(defn handle-area-selection [preserve?] (letfn [(data->selrect [data] (let [start (:start data) @@ -59,10 +59,11 @@ :y start-y :width (mth/abs (- end-x start-x)) :height (mth/abs (- end-y start-y))}))] - (ptk/reify ::handle-selection + (ptk/reify ::handle-area-selection ptk/WatchEvent - (watch [_ _ stream] - (let [stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) + (watch [_ state stream] + (let [zoom (get-in state [:workspace-local :zoom] 1) + stop? (fn [event] (or (dwc/interrupt? event) (ms/mouse-up? event))) stoper (->> stream (rx/filter stop?))] (rx/concat (when-not preserve? @@ -74,11 +75,16 @@ {:start pos :stop pos})) nil) (rx/map data->selrect) - (rx/filter #(or (> (:width %) 10) - (> (:height %) 10))) - (rx/map update-selrect) + (rx/filter #(or (> (:width %) (/ 10 zoom)) + (> (:height %) (/ 10 zoom)))) + + (rx/flat-map + (fn [selrect] + (rx/of (update-selrect selrect) + (select-shapes-by-current-selrect preserve?)))) + (rx/take-until stoper)) - (rx/of (select-shapes-by-current-selrect preserve?)))))))) + (rx/of (update-selrect nil)))))))) ;; --- Toggle shape's selection status (selected or deselected) @@ -214,11 +220,12 @@ selrect (get-in state [:workspace-local :selrect]) blocked? (fn [id] (get-in objects [id :blocked] false))] (rx/merge - (rx/of (update-selrect nil)) (when selrect (->> (uw/ask! {:cmd :selection/query :page-id page-id - :rect selrect}) + :rect selrect + :include-frames? true + :full-frame? true}) (rx/map #(cp/clean-loops objects %)) (rx/map #(into initial-set (filter (comp not blocked?)) %)) (rx/map select-shapes)))))))) diff --git a/frontend/src/app/main/ui/workspace/viewport/actions.cljs b/frontend/src/app/main/ui/workspace/viewport/actions.cljs index 8df7cbdd02..2fbfbac085 100644 --- a/frontend/src/app/main/ui/workspace/viewport/actions.cljs +++ b/frontend/src/app/main/ui/workspace/viewport/actions.cljs @@ -67,10 +67,10 @@ node-editing? ;; Handle path node area selection - (st/emit! (dwdp/handle-selection shift?)) + (st/emit! (dwdp/handle-area-selection shift?)) (or (not id) (and frame? (not selected?))) - (st/emit! (dw/handle-selection shift?)) + (st/emit! (dw/handle-area-selection shift?)) (not drawing-tool) (st/emit! (when (or shift? (not selected?)) diff --git a/frontend/src/app/worker/selection.cljs b/frontend/src/app/worker/selection.cljs index d0a03d6319..d93fcfaf45 100644 --- a/frontend/src/app/worker/selection.cljs +++ b/frontend/src/app/worker/selection.cljs @@ -84,7 +84,7 @@ (create-index new-objects))) (defn- query-index - [{index :index z-index :z-index} rect frame-id include-frames? include-groups? reverse?] + [{index :index z-index :z-index} rect frame-id include-frames? full-frame? include-groups? reverse?] (let [result (-> (qdt/search index (clj->js rect)) (es6-iterator-seq)) @@ -97,7 +97,11 @@ (case (:type shape) :frame include-frames? :group include-groups? - true))) + true) + + (or (not full-frame?) + (not= :frame (:type shape)) + (gsh/rect-contains-shape? rect shape)))) overlaps? (fn [shape] @@ -151,10 +155,10 @@ nil) (defmethod impl/handler :selection/query - [{:keys [page-id rect frame-id include-frames? include-groups? reverse?] - :or {include-groups? true reverse? false} :as message}] + [{:keys [page-id rect frame-id include-frames? full-frame? include-groups? reverse?] + :or {include-groups? true reverse? false include-frames? false full-frame? false} :as message}] (when-let [index (get @state page-id)] - (query-index index rect frame-id include-frames? include-groups? reverse?))) + (query-index index rect frame-id include-frames? full-frame? include-groups? reverse?))) (defmethod impl/handler :selection/query-z-index [{:keys [page-id objects ids]}] From 741d3050adb0e04d849e7d1052e0499347acbf2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 8 Jul 2021 13:09:12 +0200 Subject: [PATCH 195/204] :recycle: Small refactor set modifiers --- .../app/main/data/workspace/transforms.cljs | 88 +++++++++---------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index ffefaea68f..e503884de8 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -227,7 +227,7 @@ (declare start-move) (declare start-move-duplicate) -(declare start-local-displacement) +(declare set-local-displacement) (declare clear-local-transform) (defn start-move-selected @@ -342,9 +342,10 @@ (->> position (rx/with-latest vector snap-delta) (rx/map snap/correct-snap-point) - (rx/map start-local-displacement)) + (rx/map set-local-displacement)) - (rx/of (apply-modifiers ids {:set-modifiers? true}) + (rx/of (set-modifiers ids) + (apply-modifiers ids) (calculate-frame-for-move ids) (finish-transform))))))))) @@ -396,10 +397,11 @@ (->> move-events (rx/take-until stopper) (rx/scan #(gpt/add %1 mov-vec) (gpt/point 0 0)) - (rx/map start-local-displacement)) + (rx/map set-local-displacement)) (rx/of (move-selected direction shift?))) - (rx/of (apply-modifiers selected {:set-modifiers? true}) + (rx/of (set-modifiers selected) + (apply-modifiers selected) (finish-transform)))) (rx/empty)))))) @@ -537,49 +539,39 @@ (rx/of (apply-modifiers ids))))))) (defn apply-modifiers - ([ids] - (apply-modifiers ids nil)) + [ids] + (us/verify (s/coll-of uuid?) ids) + (ptk/reify ::apply-modifiers + ptk/WatchEvent + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + children-ids (->> ids (mapcat #(cp/get-children % objects))) + ids-with-children (d/concat [] children-ids ids) + object-modifiers (get state :workspace-modifiers) + ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)] - ([ids {:keys [set-modifiers?] - :or {set-modifiers? false}}] - (us/verify (s/coll-of uuid?) ids) - (ptk/reify ::apply-modifiers - ptk/WatchEvent - (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - children-ids (->> ids (mapcat #(cp/get-children % objects))) - ids-with-children (d/concat [] children-ids ids) - - state (if set-modifiers? - (ptk/update (set-modifiers ids) state) - state) - object-modifiers (get state :workspace-modifiers) - - ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)] - - (rx/of (dwu/start-undo-transaction) - (dch/update-shapes - ids-with-children - (fn [shape] - (-> shape - (merge (get object-modifiers (:id shape))) - (gsh/transform-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 - :flip-x - :flip-y] - }) - (clear-local-transform) - (dwu/commit-undo-transaction))))))) + (rx/of (dwu/start-undo-transaction) + (dch/update-shapes + ids-with-children + (fn [shape] + (-> shape + (merge (get object-modifiers (:id shape))) + (gsh/transform-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 + :flip-x + :flip-y]}) + (clear-local-transform) + (dwu/commit-undo-transaction)))))) ;; --- Update Dimensions @@ -649,7 +641,7 @@ :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))}) (apply-modifiers selected)))))) -(defn start-local-displacement [point] +(defn set-local-displacement [point] (ptk/reify ::start-local-displacement ptk/UpdateEvent (update [_ state] From 56795f8d263bf66013be44d5d1a7f2766ef5f48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 8 Jul 2021 15:20:43 +0200 Subject: [PATCH 196/204] :recycle: Reorder functions, for more clarity, and add some comments --- common/src/app/common/geom/shapes.cljc | 64 +- .../app/common/geom/shapes/transforms.cljc | 360 ++++++----- common/src/app/common/pages.cljc | 1 - common/src/app/common/pages/helpers.cljc | 9 - frontend/src/app/main/data/workspace.cljs | 37 +- .../app/main/data/workspace/transforms.cljs | 611 ++++++++++-------- frontend/src/app/main/refs.cljs | 3 +- .../src/app/main/ui/workspace/viewport.cljs | 4 +- 8 files changed, 560 insertions(+), 529 deletions(-) diff --git a/common/src/app/common/geom/shapes.cljc b/common/src/app/common/geom/shapes.cljc index c98eba760b..8771ff2e30 100644 --- a/common/src/app/common/geom/shapes.cljc +++ b/common/src/app/common/geom/shapes.cljc @@ -7,50 +7,12 @@ (ns app.common.geom.shapes (: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.intersect :as gin] [app.common.geom.shapes.path :as gsp] [app.common.geom.shapes.rect :as gpr] - [app.common.geom.shapes.transforms :as gtr] - [app.common.spec :as us])) - - -;; --- Resize (Dimensions) -(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 (gmt/matrix)) - shape-transform-inv (:transform-inverse shape (gmt/matrix)) - shape-center (gco/center-shape shape) - {sr-width :width sr-height :height} (:selrect shape) - - origin (-> (gpt/point (:selrect shape)) - (gtr/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})) + [app.common.geom.shapes.transforms :as gtr])) ;; --- Setup (Initialize) ;; FIXME: Is this the correct place for these functions? @@ -164,15 +126,6 @@ (assoc :selrect selrect :points points)))) -(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})) - ;; EXPORTS (d/export gco/center-shape) @@ -187,17 +140,20 @@ (d/export gpr/points->rect) (d/export gpr/center->rect) -(d/export gtr/transform-shape) -(d/export gtr/calc-child-modifiers) +(d/export gtr/move) +(d/export gtr/absolute-move) (d/export gtr/transform-matrix) (d/export gtr/inverse-transform-matrix) (d/export gtr/transform-point-center) -(d/export gtr/transform-rect) -(d/export gtr/update-group-selrect) (d/export gtr/transform-points) +(d/export gtr/transform-rect) (d/export gtr/calculate-adjust-matrix) -(d/export gtr/move) -(d/export gtr/absolute-move) +(d/export gtr/update-group-selrect) +(d/export gtr/resize-modifiers) +(d/export gtr/rotation-modifiers) +(d/export gtr/merge-modifiers) +(d/export gtr/transform-shape) +(d/export gtr/calc-child-modifiers) ;; PATHS (d/export gsp/content->points) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index c5387aa925..9df0fede59 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -15,11 +15,13 @@ [app.common.geom.shapes.rect :as gpr] [app.common.math :as mth] [app.common.pages.spec :as spec] + [app.common.spec :as us] [app.common.text :as txt])) + ;; --- Relative Movement -(defn move-selrect [selrect {dx :x dy :y}] +(defn- move-selrect [selrect {dx :x dy :y}] (-> selrect (d/update-when :x + dx) (d/update-when :y + dy) @@ -28,7 +30,7 @@ (d/update-when :x2 + dx) (d/update-when :y2 + dy))) -(defn move-points [points move-vec] +(defn- move-points [points move-vec] (->> points (mapv #(gpt/add % move-vec)))) @@ -48,9 +50,8 @@ (cond-> (= :path (:type shape)) (update :content gpa/move-content move-vec))))) -;; --- Absolute Movement -(declare absolute-move-rect) +;; --- Absolute Movement (defn absolute-move "Move the shape to the exactly specified position." @@ -59,6 +60,68 @@ dy (- (d/check-num y) (-> shape :selrect :y))] (move shape (gpt/point dx dy)))) + +; ---- 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 paralelogram 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 paralelogram given by the points" + [[p1 _ _ p4]] + (-> (gpt/to-vec p4 p1) + (gpt/length))) + +(defn- calculate-width + "Calculates the width of a paralelogram given by the points" + [[p1 p2 _ _]] + (-> (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 "Returns a transformation matrix without changing the shape properties. The result should be used in a `transform` attribute in svg" @@ -117,98 +180,6 @@ (transform-points matrix))] (gpr/points->rect points))) -(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 - [center modifiers] - (let [ds-modifier (:displacement modifiers (gmt/matrix)) - {res-x :x res-y :y} (:resize-vector modifiers (gpt/point 1 1)) - - ;; Normalize x/y vector coordinates because scale by 0 is infinite - res-x (normalize-scale res-x) - res-y (normalize-scale res-y) - resize (gpt/point res-x res-y) - - origin (:resize-origin modifiers (gpt/point 0 0)) - - resize-transform (:resize-transform modifiers (gmt/matrix)) - resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix)) - rt-modif (or (:rotation modifiers) 0) - - center (gpt/transform center ds-modifier) - - transform (-> (gmt/matrix) - - ;; Applies the current resize transformation - (gmt/translate origin) - (gmt/multiply resize-transform) - (gmt/scale resize) - (gmt/multiply resize-transform-inverse) - (gmt/translate (gpt/negate origin)) - - ;; Applies the stacked transformations - (gmt/translate center) - (gmt/multiply (gmt/rotate-matrix rt-modif)) - (gmt/translate (gpt/negate center)) - - ;; Displacement - (gmt/multiply ds-modifier))] - transform)) - -(defn- calculate-skew-angle - "Calculates the skew angle of the paralelogram 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 paralelogram given by the points" - [[p1 _ _ p4]] - (-> (gpt/to-vec p4 p1) - (gpt/length))) - -(defn- calculate-width - "Calculates the width of a paralelogram given by the points" - [[p1 p2 _ _]] - (-> (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})) - (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-pathn-temp`. @@ -257,7 +228,7 @@ (gmt/rotate (- rotation-angle)))] [stretch-matrix stretch-matrix-inverse rotation-angle]))) -(defn apply-transform +(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 round-coords?] @@ -310,7 +281,145 @@ (assoc $ :selrect (gpr/rect->selrect rect-shape)) (assoc $ :rotation (mod (+ base-rotation modif-rotation) 360))))) -(defn set-flip [shape modifiers] +(defn- update-group-viewbox + "Updates the viewbox for groups imported from SVG's" + [{:keys [selrect svg-viewbox] :as group} new-selrect] + (let [;; Gets deltas for the selrect to update the svg-viewbox (for svg-imports) + deltas {:x (- (:x new-selrect 0) (:x selrect 0)) + :y (- (:y new-selrect 0) (:y selrect 0)) + :width (- (:width new-selrect 1) (:width selrect 1)) + :height (- (:height new-selrect 1) (:height selrect 1))}] + + (cond-> group + (and (some? svg-viewbox) (some? selrect) (some? new-selrect)) + (update :svg-viewbox + #(-> % + (update :x + (:x deltas)) + (update :y + (:y deltas)) + (update :width + (:width deltas)) + (update :height + (:height deltas))))))) + +(defn update-group-selrect [group children] + (let [shape-center (gco/center-shape group) + ;; Points for every shape inside the group + points (->> children (mapcat :points)) + + ;; Invert to get the points minus the transforms applied to the group + base-points (transform-points points shape-center (:transform-inverse group (gmt/matrix))) + + ;; Defines the new selection rect with its transformations + new-points (-> (gpr/points->selrect base-points) + (gpr/rect->points) + (transform-points shape-center (:transform group (gmt/matrix)))) + + ;; Calculte the new selrect + new-selrect (gpr/points->selrect base-points)] + + ;; Updates the shape and the applytransform-rect will update the other properties + (-> group + (update-group-viewbox new-selrect) + (assoc :selrect new-selrect) + (assoc :points new-points) + + ;; We're regenerating the selrect from its children so we + ;; need to remove the flip flags + (assoc :flip-x false) + (assoc :flip-y false) + (apply-transform (gmt/matrix) true)))) + + +;; --- Modifiers + +(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 (gmt/matrix)) + shape-transform-inv (:transform-inverse shape (gmt/matrix)) + shape-center (gco/center-shape shape) + {sr-width :width sr-height :height} (:selrect shape) + + origin (-> (gpt/point (:selrect shape)) + (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 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 + [center modifiers] + (let [ds-modifier (:displacement modifiers (gmt/matrix)) + {res-x :x res-y :y} (:resize-vector modifiers (gpt/point 1 1)) + + ;; Normalize x/y vector coordinates because scale by 0 is infinite + res-x (normalize-scale res-x) + res-y (normalize-scale res-y) + resize (gpt/point res-x res-y) + + origin (:resize-origin modifiers (gpt/point 0 0)) + + resize-transform (:resize-transform modifiers (gmt/matrix)) + resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix)) + rt-modif (or (:rotation modifiers) 0) + + center (gpt/transform center ds-modifier) + + transform (-> (gmt/matrix) + + ;; Applies the current resize transformation + (gmt/translate origin) + (gmt/multiply resize-transform) + (gmt/scale resize) + (gmt/multiply resize-transform-inverse) + (gmt/translate (gpt/negate origin)) + + ;; Applies the stacked transformations + (gmt/translate center) + (gmt/multiply (gmt/rotate-matrix rt-modif)) + (gmt/translate (gpt/negate center)) + + ;; Displacement + (gmt/multiply ds-modifier))] + transform)) + +(defn- set-flip [shape modifiers] (let [rx (get-in modifiers [:resize-vector :x]) ry (get-in modifiers [:resize-vector :y])] (cond-> shape @@ -319,7 +428,7 @@ (and ry (< ry 0)) (-> (update :flip-y not) (update :rotation -))))) -(defn apply-displacement [shape] +(defn- apply-displacement [shape] (let [modifiers (:modifiers shape)] (if (contains? modifiers :displacement) (let [mov-vec (-> (gpt/point 0 0) @@ -332,9 +441,8 @@ (dissoc :modifiers)))) shape))) -;; TODO: looks like orig-shape is useless argument -(defn apply-text-resize - [shape _orig-shape modifiers] +(defn- apply-text-resize + [shape modifiers] (if (and (= (:type shape) :text) (:resize-scale-text modifiers)) (let [merge-attrs (fn [attrs] @@ -364,7 +472,7 @@ (-> shape (set-flip modifiers) (apply-transform transform round-coords?) - (apply-text-resize shape modifiers) + (apply-text-resize modifiers) (dissoc :modifiers))) shape)))) @@ -508,49 +616,3 @@ (assoc :resize-transform (:resize-transform parent-modifiers) :resize-transform-inverse (:resize-transform-inverse parent-modifiers))))) -(defn update-group-viewbox - "Updates the viewbox for groups imported from SVG's" - [{:keys [selrect svg-viewbox] :as group} new-selrect] - (let [;; Gets deltas for the selrect to update the svg-viewbox (for svg-imports) - deltas {:x (- (:x new-selrect 0) (:x selrect 0)) - :y (- (:y new-selrect 0) (:y selrect 0)) - :width (- (:width new-selrect 1) (:width selrect 1)) - :height (- (:height new-selrect 1) (:height selrect 1))}] - - (cond-> group - (and (some? svg-viewbox) (some? selrect) (some? new-selrect)) - (update :svg-viewbox - #(-> % - (update :x + (:x deltas)) - (update :y + (:y deltas)) - (update :width + (:width deltas)) - (update :height + (:height deltas))))))) - -(defn update-group-selrect [group children] - (let [shape-center (gco/center-shape group) - ;; Points for every shape inside the group - points (->> children (mapcat :points)) - - ;; Invert to get the points minus the transforms applied to the group - base-points (transform-points points shape-center (:transform-inverse group (gmt/matrix))) - - ;; Defines the new selection rect with its transformations - new-points (-> (gpr/points->selrect base-points) - (gpr/rect->points) - (transform-points shape-center (:transform group (gmt/matrix)))) - - ;; Calculte the new selrect - new-selrect (gpr/points->selrect base-points)] - - ;; Updates the shape and the applytransform-rect will update the other properties - (-> group - (update-group-viewbox new-selrect) - (assoc :selrect new-selrect) - (assoc :points new-points) - - ;; We're regenerating the selrect from its children so we - ;; need to remove the flip flags - (assoc :flip-x false) - (assoc :flip-y false) - (apply-transform (gmt/matrix) true)))) - diff --git a/common/src/app/common/pages.cljc b/common/src/app/common/pages.cljc index 0e69c9d53c..fdf02cfa33 100644 --- a/common/src/app/common/pages.cljc +++ b/common/src/app/common/pages.cljc @@ -66,7 +66,6 @@ (d/export helpers/merge-path-item) (d/export helpers/compact-path) (d/export helpers/compact-name) -(d/export helpers/merge-modifiers) ;; Indices (d/export indices/calculate-z-index) diff --git a/common/src/app/common/pages/helpers.cljc b/common/src/app/common/pages/helpers.cljc index 7ab687dcaa..2c7507243e 100644 --- a/common/src/app/common/pages/helpers.cljc +++ b/common/src/app/common/pages/helpers.cljc @@ -465,12 +465,3 @@ (let [path-split (split-path path)] (merge-path-item (first path-split) name))) -(defn merge-modifiers - [objects modifiers] - - (let [set-modifier - (fn [objects [id modifiers]] - (-> objects - (d/update-when id merge modifiers)))] - (->> modifiers - (reduce set-modifier objects)))) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index 37c26e0f1f..7c0a57c7d9 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -1139,33 +1139,6 @@ (gpr/assign-proportions))))] (rx/of (dch/update-shapes [id] assign-proportions)))))) -;; --- Update Shape Position - -(s/def ::x number?) -(s/def ::y number?) -(s/def ::position - (s/keys :opt-un [::x ::y])) - -(defn update-position - [id position] - (us/verify ::us/uuid id) - (us/verify ::position position) - (ptk/reify ::update-position - ptk/WatchEvent - (watch [_ state _] - (let [page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - shape (get objects id) - - bbox (-> shape :points gsh/points->selrect) - - cpos (gpt/point (:x bbox) (:y bbox)) - pos (gpt/point (or (:x position) (:x bbox)) - (or (:y position) (:y bbox))) - displ (gmt/translate-matrix (gpt/subtract pos cpos))] - (rx/of (dwt/set-modifiers [id] {:displacement displ}) - (dwt/apply-modifiers [id])))))) - ;; --- Update Shape Flags (defn update-shape-flags @@ -1811,15 +1784,13 @@ ;; Transform -(d/export dwt/start-rotate) (d/export dwt/start-resize) +(d/export dwt/update-dimensions) +(d/export dwt/start-rotate) +(d/export dwt/increase-rotation) (d/export dwt/start-move-selected) (d/export dwt/move-selected) -(d/export dwt/set-rotation) -(d/export dwt/increase-rotation) -(d/export dwt/set-modifiers) -(d/export dwt/apply-modifiers) -(d/export dwt/update-dimensions) +(d/export dwt/update-position) (d/export dwt/flip-horizontal-selected) (d/export dwt/flip-vertical-selected) diff --git a/frontend/src/app/main/data/workspace/transforms.cljs b/frontend/src/app/main/data/workspace/transforms.cljs index e503884de8..713371c7ed 100644 --- a/frontend/src/app/main/data/workspace/transforms.cljs +++ b/frontend/src/app/main/data/workspace/transforms.cljs @@ -24,18 +24,13 @@ [cljs.spec.alpha :as s] [potok.core :as ptk])) -;; -- Declarations -(declare set-modifiers) -(declare set-rotation) -(declare apply-modifiers) +;; -- Helpers -------------------------------------------------------- -;; -- Helpers - -;; For each of the 8 handlers gives the modifier for resize +;; For each of the 8 handlers gives the multiplier for resize ;; for example, right will only grow in the x coordinate and left ;; will grow in the inverse of the x coordinate -(def ^:private handler-modifiers +(def ^:private handler-multipliers {:right [ 1 0] :bottom [ 0 1] :left [-1 0] @@ -45,13 +40,16 @@ :bottom-right [ 1 1] :bottom-left [-1 1]}) -;; Given a handler returns the coordinate origin for resizes -;; this is the opposite of the handler so for right we want the -;; left side as origin of the resize -;; sx, sy => start x/y -;; mx, my => middle x/y -;; ex, ey => end x/y -(defn- handler-resize-origin [{sx :x sy :y :keys [width height]} handler] +(defn- handler-resize-origin + "Given a handler, return the coordinate origin for resizes. + This is the opposite of the handler so for right we want the + left side as origin of the resize. + + sx, sy => start x/y + mx, my => middle x/y + ex, ey => end x/y + " + [{sx :x sy :y :keys [width height]} handler] (let [mx (+ sx (/ width 2)) my (+ sy (/ height 2)) ex (+ sx width) @@ -95,8 +93,193 @@ (update [_ state] (update state :workspace-local dissoc :transform)))) -;; -- RESIZE + +;; -- 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 set-modifiers-recursive) +(declare check-delta) +(declare set-local-displacement) +(declare clear-local-transform) + +(defn- set-modifiers + ([ids] (set-modifiers ids nil)) + ([ids modifiers] + (us/verify (s/coll-of uuid?) ids) + (ptk/reify ::set-modifiers + ptk/UpdateEvent + (update [_ state] + (let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {})) + page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + + ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false))))] + + (reduce (fn [state id] + (update state :workspace-modifiers + #(set-modifiers-recursive % + objects + (get objects id) + modifiers + nil + nil))) + state + ids)))))) + +;; 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) + id->obj #(get objects %) + get-children (fn [shape] (map id->obj (cp/get-children (:id shape) objects))) + + shapes (->> shapes (into [] (remove #(get % :blocked false)))) + + shapes (->> shapes (mapcat get-children) (concat shapes)) + + update-shape + (fn [modifiers shape] + (let [rotate-modifiers (gsh/rotation-modifiers shape center angle)] + (assoc-in modifiers [(:id shape) :modifiers] rotate-modifiers)))] + (-> state + (update :workspace-modifiers + #(reduce update-shape % shapes)))))))) + +(defn- apply-modifiers + [ids] + (us/verify (s/coll-of uuid?) ids) + (ptk/reify ::apply-modifiers + ptk/WatchEvent + (watch [_ state _] + (let [objects (wsh/lookup-page-objects state) + children-ids (->> ids (mapcat #(cp/get-children % objects))) + ids-with-children (d/concat [] children-ids ids) + object-modifiers (get state :workspace-modifiers) + ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)] + + (rx/of (dwu/start-undo-transaction) + (dch/update-shapes + ids-with-children + (fn [shape] + (-> shape + (merge (get object-modifiers (:id shape))) + (gsh/transform-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 + :flip-x + :flip-y]}) + (clear-local-transform) + (dwu/commit-undo-transaction)))))) + +(defn- set-modifiers-recursive + [modif-tree objects shape modifiers root transformed-root] + (let [children (->> (get shape :shapes []) + (map #(get objects %))) + + transformed-shape (gsh/transform-shape (assoc shape :modifiers modifiers)) + + [root transformed-root ignore-geometry?] + (check-delta shape root transformed-shape transformed-root objects) + + modifiers (assoc modifiers :ignore-geometry? ignore-geometry?) + + set-child (fn [modif-tree child] + (let [child-modifiers (gsh/calc-child-modifiers shape + child + modifiers)] + (set-modifiers-recursive modif-tree + objects + child + child-modifiers + root + transformed-root)))] + (reduce set-child + (update-in modif-tree [(:id shape) :modifiers] #(merge % modifiers)) + children))) + +(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] + (let [root (cond + (:component-root? shape) + shape + + (nil? root) + (cp/get-root-shape shape objects) + + :else root) + + transformed-root (cond + (:component-root? transformed-shape) + transformed-shape + + (nil? transformed-root) + (cp/get-root-shape transformed-shape objects) + + :else transformed-root) + + shape-delta (when root + (gpt/point (- (:x shape) (:x root)) + (- (:y shape) (:y root)))) + + transformed-shape-delta (when transformed-root + (gpt/point (- (:x transformed-shape) (:x transformed-root)) + (- (:y transformed-shape) (:y transformed-root)))) + + ignore-geometry? (= shape-delta transformed-shape-delta)] + + [root transformed-root ignore-geometry?])) + +(defn- set-local-displacement [point] + (ptk/reify ::start-local-displacement + ptk/UpdateEvent + (update [_ state] + (let [mtx (gmt/translate-matrix point)] + (-> state + (assoc-in [:workspace-local :modifiers] {:displacement mtx})))))) + +(defn- clear-local-transform [] + (ptk/reify ::clear-local-transform + ptk/UpdateEvent + (update [_ state] + (-> state + (dissoc :workspace-modifiers) + (update :workspace-local dissoc :modifiers :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? point-snap]] (let [{:keys [width height]} (:selrect shape) @@ -113,12 +296,12 @@ lock? (or lock? scale-text) ;; Vector modifiers depending on the handler - handler-modif (let [[x y] (handler-modifiers handler)] (gpt/point x y)) + handler-mult (let [[x y] (handler-multipliers handler)] (gpt/point x y)) ;; Difference between the origin point in the coordinate system of the rotation deltav (-> (gpt/to-vec initial (if (= rotation 0) point-snap point)) (gpt/transform (gmt/rotate-matrix (- rotation))) - (gpt/multiply handler-modif)) + (gpt/multiply handler-mult)) ;; Resize vector scalev (gpt/divide (gpt/add shapev deltav) shapev) @@ -185,8 +368,43 @@ (rx/of (apply-modifiers ids) (finish-transform)))))))) +(defn update-dimensions + "Change size of shapes, from the sideber options form." + [ids attr value] + (us/verify (s/coll-of ::us/uuid) ids) + (us/verify #{:width :height} attr) + (us/verify ::us/number value) + (ptk/reify ::update-dimensions + ptk/UpdateEvent + (update [_ state] + (let [page-id (:current-page-id state) + objects (get-in state [:workspace-data :pages-index page-id :objects])] + + (reduce (fn [state id] + (let [shape (get objects id) + modifiers (gsh/resize-modifiers shape attr value)] + (update state :workspace-modifiers + #(set-modifiers-recursive % + objects + shape + modifiers + nil + nil)))) + state + ids))) + + ptk/WatchEvent + (watch [_ state _] + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + ids (d/concat [] ids (mapcat #(cp/get-children % objects) ids))] + (rx/of (apply-modifiers ids)))))) + + +;; -- Rotate -------------------------------------------------------- (defn start-rotate + "Enter mouse rotate mode, until mouse button is released." [shapes] (ptk/reify ::start-rotate ptk/UpdateEvent @@ -218,19 +436,37 @@ (rx/with-latest vector ms/mouse-position-ctrl) (rx/map (fn [[pos ctrl?]] (let [delta-angle (calculate-angle pos ctrl?)] - (set-rotation delta-angle shapes group-center)))) + (set-rotation-modifiers delta-angle shapes group-center)))) (rx/take-until stoper)) (rx/of (apply-modifiers (map :id shapes)) (finish-transform))))))) -;; -- MOVE +(defn increase-rotation + "Rotate shapes a fixed angle, from a keyboard action." + [ids rotation] + (ptk/reify ::increase-rotation + ptk/WatchEvent + (watch [_ state _] + + (let [page-id (:current-page-id state) + objects (wsh/lookup-page-objects state page-id) + rotate-shape (fn [shape] + (let [delta (- rotation (:rotation shape))] + (set-rotation-modifiers delta [shape])))] + (rx/concat + (rx/from (->> ids (map #(get objects %)) (map rotate-shape))) + (rx/of (apply-modifiers ids))))))) + + +;; -- Move ---------------------------------------------------------- (declare start-move) (declare start-move-duplicate) -(declare set-local-displacement) -(declare clear-local-transform) +(declare calculate-frame-for-move) +(declare get-displacement) (defn start-move-selected + "Enter mouse move mode, until mouse button is released." [] (ptk/reify ::start-move-selected ptk/WatchEvent @@ -255,7 +491,8 @@ ;; Otherwise just plain old move (rx/of (start-move initial selected))))))))))) -(defn start-move-duplicate [from-position] +(defn- start-move-duplicate + [from-position] (ptk/reify ::start-move-selected ptk/WatchEvent (watch [_ _ stream] @@ -264,45 +501,7 @@ (rx/first) (rx/map #(start-move from-position)))))) -(defn calculate-frame-for-move [ids] - (ptk/reify ::calculate-frame-for-move - ptk/WatchEvent - (watch [it state _] - (let [position @ms/mouse-position - page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - frame-id (cp/frame-id-by-position objects position) - - moving-shapes (->> ids - (cp/clean-loops objects) - (map #(get objects %)) - (remove #(or (nil? %) - (= (:frame-id %) frame-id)))) - - rch [{:type :mov-objects - :page-id page-id - :parent-id frame-id - :shapes (mapv :id moving-shapes)}] - - - uch (->> moving-shapes - (reverse) - (mapv (fn [shape] - {:type :mov-objects - :page-id page-id - :parent-id (:parent-id shape) - :index (cp/get-index-in-parent objects (:id shape)) - :shapes [(:id shape)]})))] - - (when-not (empty? uch) - (rx/of dwu/pop-undo-into-transaction - (dch/commit-changes {:redo-changes rch - :undo-changes uch - :origin it}) - (dwu/commit-undo-transaction) - (dwc/expand-collapse frame-id))))))) - -(defn start-move +(defn- start-move ([from-position] (start-move from-position nil)) ([from-position ids] (ptk/reify ::start-move @@ -349,19 +548,10 @@ (calculate-frame-for-move ids) (finish-transform))))))))) -(defn- get-displacement - "Retrieve the correct displacement delta point for the - provided direction speed and distances thresholds." - [direction] - (case direction - :up (gpt/point 0 (- 1)) - :down (gpt/point 0 1) - :left (gpt/point (- 1) 0) - :right (gpt/point 1 0))) - (s/def ::direction #{:up :down :right :left}) (defn move-selected + "Move shapes a fixed increment in one direction, from a keyboard action." [direction shift?] (us/verify ::direction direction) (us/verify boolean? shift?) @@ -405,209 +595,83 @@ (finish-transform)))) (rx/empty)))))) +(s/def ::x number?) +(s/def ::y number?) +(s/def ::position + (s/keys :opt-un [::x ::y])) -;; -- Apply modifiers - -(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] - (let [root (cond - (:component-root? shape) - shape - - (nil? root) - (cp/get-root-shape shape objects) - - :else root) - - transformed-root (cond - (:component-root? transformed-shape) - transformed-shape - - (nil? transformed-root) - (cp/get-root-shape transformed-shape objects) - - :else transformed-root) - - shape-delta (when root - (gpt/point (- (:x shape) (:x root)) - (- (:y shape) (:y root)))) - - transformed-shape-delta (when transformed-root - (gpt/point (- (:x transformed-shape) (:x transformed-root)) - (- (:y transformed-shape) (:y transformed-root)))) - - ignore-geometry? (= shape-delta transformed-shape-delta)] - - [root transformed-root ignore-geometry?])) - -(defn- set-modifiers-recursive - "Apply the modifiers to one shape, and the corresponding ones to all children, - depending on the child constraints. The modifiers are not directly applied to - the objects tree, but to a separated structure (modif-tree), that may be - merged later with the real objects. This way, the objects are changed only - once, avoiding unnecesary redrawings." - [modif-tree objects shape modifiers root transformed-root] - (let [children (->> (get shape :shapes []) - (map #(get objects %))) - - transformed-shape (gsh/transform-shape (assoc shape :modifiers modifiers)) - - [root transformed-root ignore-geometry?] - (check-delta shape root transformed-shape transformed-root objects) - - modifiers (assoc modifiers :ignore-geometry? ignore-geometry?) - - set-child (fn [modif-tree child] - (let [child-modifiers (gsh/calc-child-modifiers shape - child - modifiers)] - (set-modifiers-recursive modif-tree - objects - child - child-modifiers - root - transformed-root)))] - (reduce set-child - (update-in modif-tree [(:id shape) :modifiers] #(merge % modifiers)) - children))) - -(defn set-modifiers - ([ids] (set-modifiers ids nil)) - ([ids modifiers] - (us/verify (s/coll-of uuid?) ids) - (ptk/reify ::set-modifiers - ptk/UpdateEvent - (update [_ state] - (let [modifiers (or modifiers (get-in state [:workspace-local :modifiers] {})) - page-id (:current-page-id state) - objects (wsh/lookup-page-objects state page-id) - - ids (->> ids (into #{} (remove #(get-in objects [% :blocked] false))))] - - (reduce (fn [state id] - (update state :workspace-modifiers - #(set-modifiers-recursive % - objects - (get objects id) - modifiers - nil - nil))) - state - ids)))))) - -;; Set-rotation is custom because applies different modifiers to each -;; shape adjusting their position. - -(defn set-rotation - ([angle shapes] - (set-rotation angle shapes (-> shapes gsh/selection-rect gsh/center-selrect))) - - ([angle shapes center] - (ptk/reify ::set-rotation - ptk/UpdateEvent - (update [_ state] - (let [objects (wsh/lookup-page-objects state) - id->obj #(get objects %) - get-children (fn [shape] (map id->obj (cp/get-children (:id shape) objects))) - - shapes (->> shapes (into [] (remove #(get % :blocked false)))) - - shapes (->> shapes (mapcat get-children) (concat shapes)) - - update-shape - (fn [modifiers shape] - (let [rotate-modifiers (gsh/rotation-modifiers shape center angle)] - (assoc-in modifiers [(:id shape) :modifiers] rotate-modifiers)))] - (-> state - (update :workspace-modifiers - #(reduce update-shape % shapes)))))))) - -(defn increase-rotation [ids rotation] - (ptk/reify ::increase-rotation +(defn update-position + "Move shapes to a new position, from the sidebar options form." + [id position] + (us/verify ::us/uuid id) + (us/verify ::position position) + (ptk/reify ::update-position ptk/WatchEvent (watch [_ state _] - (let [page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - rotate-shape (fn [shape] - (let [delta (- rotation (:rotation shape))] - (set-rotation delta [shape])))] - (rx/concat - (rx/from (->> ids (map #(get objects %)) (map rotate-shape))) - (rx/of (apply-modifiers ids))))))) + shape (get objects id) -(defn apply-modifiers + bbox (-> shape :points gsh/points->selrect) + + cpos (gpt/point (:x bbox) (:y bbox)) + pos (gpt/point (or (:x position) (:x bbox)) + (or (:y position) (:y bbox))) + displ (gmt/translate-matrix (gpt/subtract pos cpos))] + (rx/of (set-modifiers [id] {:displacement displ}) + (apply-modifiers [id])))))) + +(defn- calculate-frame-for-move [ids] - (us/verify (s/coll-of uuid?) ids) - (ptk/reify ::apply-modifiers + (ptk/reify ::calculate-frame-for-move ptk/WatchEvent - (watch [_ state _] - (let [objects (wsh/lookup-page-objects state) - children-ids (->> ids (mapcat #(cp/get-children % objects))) - ids-with-children (d/concat [] children-ids ids) - object-modifiers (get state :workspace-modifiers) - ignore-tree (d/mapm #(get-in %2 [:modifiers :ignore-geometry?]) object-modifiers)] - - (rx/of (dwu/start-undo-transaction) - (dch/update-shapes - ids-with-children - (fn [shape] - (-> shape - (merge (get object-modifiers (:id shape))) - (gsh/transform-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 - :flip-x - :flip-y]}) - (clear-local-transform) - (dwu/commit-undo-transaction)))))) - -;; --- Update Dimensions - -;; Event mainly used for handling user modification of the size of the -;; object from workspace sidebar options inputs. - -(defn update-dimensions - [ids attr value] - (us/verify (s/coll-of ::us/uuid) ids) - (us/verify #{:width :height} attr) - (us/verify ::us/number value) - (ptk/reify ::update-dimensions - ptk/UpdateEvent - (update [_ state] - (let [page-id (:current-page-id state) - objects (get-in state [:workspace-data :pages-index page-id :objects])] - - (reduce (fn [state id] - (let [shape (get objects id) - modifiers (gsh/resize-modifiers shape attr value)] - (update state :workspace-modifiers - #(set-modifiers-recursive % - objects - shape - modifiers - nil - nil)))) - state - ids))) - - ptk/WatchEvent - (watch [_ state _] - (let [page-id (:current-page-id state) + (watch [it state _] + (let [position @ms/mouse-position + page-id (:current-page-id state) objects (wsh/lookup-page-objects state page-id) - ids (d/concat [] ids (mapcat #(cp/get-children % objects) ids))] - (rx/of (apply-modifiers ids)))))) + frame-id (cp/frame-id-by-position objects position) + + moving-shapes (->> ids + (cp/clean-loops objects) + (map #(get objects %)) + (remove #(or (nil? %) + (= (:frame-id %) frame-id)))) + + rch [{:type :mov-objects + :page-id page-id + :parent-id frame-id + :shapes (mapv :id moving-shapes)}] + + + uch (->> moving-shapes + (reverse) + (mapv (fn [shape] + {:type :mov-objects + :page-id page-id + :parent-id (:parent-id shape) + :index (cp/get-index-in-parent objects (:id shape)) + :shapes [(:id shape)]})))] + + (when-not (empty? uch) + (rx/of dwu/pop-undo-into-transaction + (dch/commit-changes {:redo-changes rch + :undo-changes uch + :origin it}) + (dwu/commit-undo-transaction) + (dwc/expand-collapse frame-id))))))) + +(defn- get-displacement + "Retrieve the correct displacement delta point for the + provided direction speed and distances thresholds." + [direction] + (case direction + :up (gpt/point 0 (- 1)) + :down (gpt/point 0 1) + :left (gpt/point (- 1) 0) + :right (gpt/point 1 0))) + + +;; -- Flip ---------------------------------------------------------- (defn flip-horizontal-selected [] (ptk/reify ::flip-horizontal-selected @@ -641,19 +705,6 @@ :displacement (gmt/translate-matrix (gpt/point 0 (- (:height selrect))))}) (apply-modifiers selected)))))) -(defn set-local-displacement [point] - (ptk/reify ::start-local-displacement - ptk/UpdateEvent - (update [_ state] - (let [mtx (gmt/translate-matrix point)] - (-> state - (assoc-in [:workspace-local :modifiers] {:displacement mtx})))))) -(defn clear-local-transform [] - (ptk/reify ::clear-local-transform - ptk/UpdateEvent - (update [_ state] - (-> state - (dissoc :workspace-modifiers) - (update :workspace-local dissoc :modifiers :current-move-selected))))) +;; -- Transform to path --------------------------------------------- diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs index 22e9adebe2..236f4a0af2 100644 --- a/frontend/src/app/main/refs.cljs +++ b/frontend/src/app/main/refs.cljs @@ -9,6 +9,7 @@ "A collection of derived refs." (:require [app.common.data :as d] + [app.common.geom.shapes :as gsh] [app.common.pages :as cp] [app.main.data.workspace.state-helpers :as wsh] [app.main.store :as st] @@ -239,7 +240,7 @@ modifiers (:workspace-modifiers state) objects (cond-> objects with-modifiers? - (cp/merge-modifiers modifiers)) + (gsh/merge-modifiers modifiers)) xform (comp (map #(get objects %)) (remove nil?))] (into [] xform ids))) diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs index 07e73e2870..a0fa7552c2 100644 --- a/frontend/src/app/main/ui/workspace/viewport.cljs +++ b/frontend/src/app/main/ui/workspace/viewport.cljs @@ -7,7 +7,7 @@ (ns app.main.ui.workspace.viewport (:require [app.common.data :as d] - [app.common.pages :as cp] + [app.common.geom.shapes :as gsh] [app.main.refs :as refs] [app.main.ui.context :as ctx] [app.main.ui.measurements :as msr] @@ -64,7 +64,7 @@ object-modifiers (mf/deref refs/workspace-modifiers) objects (mf/use-memo (mf/deps objects object-modifiers) - #(cp/merge-modifiers objects object-modifiers)) + #(gsh/merge-modifiers objects object-modifiers)) background (get options :background "#E8E9EA") ;; STATE From fb7751eaaea1e70cf80a7186df483d6cf484fa2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Fri, 9 Jul 2021 12:01:41 +0200 Subject: [PATCH 197/204] :sparkles: Apply different resize vectors for h and v constraints --- .../app/common/geom/shapes/transforms.cljc | 67 ++++++++++++++++--- 1 file changed, 58 insertions(+), 9 deletions(-) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 9df0fede59..011918583d 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -330,6 +330,31 @@ ;; --- 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 resize-modifiers [shape attr value] (us/assert map? shape) @@ -387,13 +412,19 @@ [center modifiers] (let [ds-modifier (:displacement modifiers (gmt/matrix)) {res-x :x res-y :y} (:resize-vector modifiers (gpt/point 1 1)) + {res-x-2 :x res-y-2 :y} (:resize-vector-2 modifiers (gpt/point 1 1)) ;; Normalize x/y vector coordinates because scale by 0 is infinite res-x (normalize-scale res-x) res-y (normalize-scale res-y) resize (gpt/point res-x res-y) + res-x-2 (normalize-scale res-x-2) + res-y-2 (normalize-scale res-y-2) + resize-2 (gpt/point res-x-2 res-y-2) + origin (:resize-origin modifiers (gpt/point 0 0)) + origin-2 (:resize-origin-2 modifiers (gpt/point 0 0)) resize-transform (:resize-transform modifiers (gmt/matrix)) resize-transform-inverse (:resize-transform-inverse modifiers (gmt/matrix)) @@ -410,6 +441,12 @@ (gmt/multiply resize-transform-inverse) (gmt/translate (gpt/negate origin)) + (gmt/translate origin-2) + (gmt/multiply resize-transform) + (gmt/scale resize-2) + (gmt/multiply resize-transform-inverse) + (gmt/translate (gpt/negate origin-2)) + ;; Applies the stacked transformations (gmt/translate center) (gmt/multiply (gmt/rotate-matrix rt-modif)) @@ -448,9 +485,9 @@ (let [merge-attrs (fn [attrs] (let [font-size (-> (get attrs :font-size 14) (d/parse-double) - (* (-> modifiers :resize-vector :x)) - (str) - )] + (* (get-in modifiers [:resize-vector :x] 1)) + (* (get-in modifiers [:resize-vector-2 :x] 1)) + (str))] (attrs/merge attrs {:font-size font-size})))] (update shape :content #(txt/transform-nodes txt/is-text-node? @@ -538,13 +575,17 @@ (assoc :resize-origin (:resize-origin parent-modifiers) :resize-vector (gpt/point (:x (:resize-vector parent-modifiers)) 1)) + (and (:resize-vector-2 parent-modifiers) + (not (mth/close? (:x (:resize-vector-2 parent-modifiers)) 1))) + (assoc :resize-origin-2 (:resize-origin-2 parent-modifiers) + :resize-vector-2 (gpt/point (:x (:resize-vector-2 parent-modifiers)) 1)) + (:displacement parent-modifiers) (assoc :displacement (gpt/point (-> (gpt/point 0 0) (gpt/transform (:displacement parent-modifiers)) (:x)) 0))) - {}) modifiers-v (case constraints-v @@ -590,12 +631,16 @@ (assoc :resize-origin (:resize-origin parent-modifiers) :resize-vector (gpt/point 1 (:y (:resize-vector parent-modifiers)))) + (and (:resize-vector-2 parent-modifiers) + (not (mth/close? (:y (:resize-vector-2 parent-modifiers)) 1))) + (assoc :resize-origin-2 (:resize-origin-2 parent-modifiers) + :resize-vector-2 (gpt/point 1 (:y (:resize-vector-2 parent-modifiers)))) + (:displacement parent-modifiers) (assoc :displacement (gpt/point 0 (-> (gpt/point 0 0) (gpt/transform (:displacement parent-modifiers)) (:y))))) - {})] (cond-> {} @@ -606,11 +651,15 @@ (gpt/transform (:resize-transform parent-modifiers (gmt/matrix)))))) - (or (:resize-vector modifiers-h) (:resize-vector modifiers-v)) - (assoc :resize-origin (or (:resize-origin modifiers-h) ;; we assume that the origin is the same - (:resize-origin modifiers-v)) ;; in any direction + (:resize-vector modifiers-h) + (assoc :resize-origin (:resize-origin modifiers-h) :resize-vector (gpt/point (get (:resize-vector modifiers-h) :x 1) - (get (:resize-vector modifiers-v) :y 1))) + (get (:resize-vector modifiers-h) :y 1))) + + (:resize-vector modifiers-v) + (assoc :resize-origin-2 (:resize-origin modifiers-v) + :resize-vector-2 (gpt/point (get (:resize-vector modifiers-v) :x 1) + (get (:resize-vector modifiers-v) :y 1))) (:resize-transform parent-modifiers) (assoc :resize-transform (:resize-transform parent-modifiers) From a77863d3c5088d30bc5cd15642b01176eb0b7b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Tue, 13 Jul 2021 11:26:03 +0200 Subject: [PATCH 198/204] :bug: Fix constraints for rotated shapes --- .../app/common/geom/shapes/transforms.cljc | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/common/src/app/common/geom/shapes/transforms.cljc b/common/src/app/common/geom/shapes/transforms.cljc index 011918583d..0cf6fe8427 100644 --- a/common/src/app/common/geom/shapes/transforms.cljc +++ b/common/src/app/common/geom/shapes/transforms.cljc @@ -520,12 +520,33 @@ (let [parent-rect (:selrect parent) child-rect (:selrect child) - ;; Apply the modifiers to the parent, to check the difference with - ;; the original rect, and calculate child transformations. - transformed-parent-rect (-> parent - (assoc :modifiers parent-modifiers) - (transform-shape) - (:selrect)) + ;; Apply the modifiers to the parent's selrect, to check the difference with + ;; the original, and calculate child transformations from this. + ;; + ;; Note that a shape's selrect is always "horizontal" (i.e. without applying + ;; the shape transform, that may include some rotation and skew). Thus, to + ;; apply the modifiers, we first apply to them the transform-inverse. + parent-displacement (-> (gpt/point 0 0) + (gpt/transform (get parent-modifiers :displacement (gmt/matrix))) + (gpt/transform (:resize-transform-inverse parent-modifiers (gmt/matrix))) + (gmt/translate-matrix)) + parent-origin (-> (:resize-origin parent-modifiers) + ((d/nilf transform-point-center) + (gco/center-shape parent) + (:resize-transform-inverse parent-modifiers (gmt/matrix)))) + parent-origin-2 (-> (:resize-origin-2 parent-modifiers) + ((d/nilf transform-point-center) + (gco/center-shape parent) + (:resize-transform-inverse parent-modifiers (gmt/matrix)))) + parent-vector (get parent-modifiers :resize-vector (gpt/point 1 1)) + parent-vector-2 (get parent-modifiers :resize-vector-2 (gpt/point 1 1)) + + transformed-parent-rect (-> parent-rect + (gpr/rect->points) + (transform-points parent-displacement) + (transform-points parent-origin (gmt/scale-matrix parent-vector)) + (transform-points parent-origin-2 (gmt/scale-matrix parent-vector-2)) + (gpr/points->selrect)) ;; Calculate the modifiers in the horizontal and vertical directions ;; depending on the child constraints. @@ -535,6 +556,7 @@ modifiers-h (case constraints-h :left (let [delta-left (- (:x1 transformed-parent-rect) (:x1 parent-rect))] + (if-not (mth/almost-zero? delta-left) {:displacement (gpt/point delta-left 0)} ;; we convert to matrix below {})) @@ -584,6 +606,7 @@ (assoc :displacement (gpt/point (-> (gpt/point 0 0) (gpt/transform (:displacement parent-modifiers)) + (gpt/transform (:resize-transform-inverse parent-modifiers (gmt/matrix))) (:x)) 0))) {}) @@ -640,9 +663,12 @@ (assoc :displacement (gpt/point 0 (-> (gpt/point 0 0) (gpt/transform (:displacement parent-modifiers)) + (gpt/transform (:resize-transform-inverse parent-modifiers (gmt/matrix))) (:y))))) {})] + ;; 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-> {} (or (:displacement modifiers-h) (:displacement modifiers-v)) (assoc :displacement (gmt/translate-matrix From dc22c2763ecc0a57b95a65b828d25decb5f7b3cd Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Jul 2021 13:24:42 +0200 Subject: [PATCH 199/204] :arrow_up: Update dependencies. --- frontend/deps.edn | 2 +- frontend/gulpfile.js | 4 +- frontend/package.json | 10 +- frontend/yarn.lock | 685 +++++++++--------------------------------- 4 files changed, 153 insertions(+), 548 deletions(-) diff --git a/frontend/deps.edn b/frontend/deps.edn index 2958789ddb..2695bf8460 100644 --- a/frontend/deps.edn +++ b/frontend/deps.edn @@ -23,7 +23,7 @@ :dev {:extra-deps - {thheller/shadow-cljs {:mvn/version "2.14.5"}}} + {thheller/shadow-cljs {:mvn/version "2.15.1"}}} :shadow-cljs {:main-opts ["-m" "shadow.cljs.devtools.cli"]} diff --git a/frontend/gulpfile.js b/frontend/gulpfile.js index 428bbf9a7f..03c4f1c27a 100644 --- a/frontend/gulpfile.js +++ b/frontend/gulpfile.js @@ -8,7 +8,7 @@ const gulpGzip = require("gulp-gzip"); const gulpMustache = require("gulp-mustache"); const gulpPostcss = require("gulp-postcss"); const gulpRename = require("gulp-rename"); -const gulpSass = require("gulp-sass"); +const gulpSass = require("gulp-sass")(require("sass")); const svgSprite = require("gulp-svg-sprite"); const autoprefixer = require("autoprefixer") @@ -157,7 +157,7 @@ gulpSass.compiler = sass; gulp.task("scss", function() { return gulp.src(paths.resources + "styles/main-default.scss") - .pipe(gulpSass().on('error', gulpSass.logError)) + .pipe(gulpSass.sync().on('error', gulpSass.logError)) .pipe(gulpPostcss([ autoprefixer, // clean({format: "keep-breaks", level: 1}) diff --git a/frontend/package.json b/frontend/package.json index 5ab1d3d14c..e5f371b46f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,7 +23,7 @@ "gulp-mustache": "^5.0.0", "gulp-postcss": "^9.0.0", "gulp-rename": "^2.0.0", - "gulp-sass": "^4.1.0", + "gulp-sass": "^5.0.0", "gulp-sourcemaps": "^3.0.0", "gulp-svg-sprite": "^1.5.0", "map-stream": "0.0.7", @@ -33,7 +33,7 @@ "postcss-clean": "^1.2.2", "rimraf": "^3.0.0", "sass": "^1.35.1", - "shadow-cljs": "2.14.5" + "shadow-cljs": "2.15.1" }, "dependencies": { "date-fns": "^2.22.1", @@ -45,10 +45,10 @@ "mousetrap": "^1.6.5", "opentype.js": "^1.3.3", "randomcolor": "^0.6.2", - "react": "~17.0.1", - "react-dom": "~17.0.1", + "react": "~17.0.2", + "react-dom": "~17.0.2", "react-virtualized": "^9.22.3", - "rxjs": "~7.1.0", + "rxjs": "~7.2.0", "sax": "^1.2.4", "source-map-support": "^0.5.16", "tdigest": "^0.1.1", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 439290c174..a0ac4205e6 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -70,11 +70,6 @@ ajv@^6.12.3: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -amdefine@>=0.0.4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" - integrity sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU= - ansi-colors@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-1.1.0.tgz#6374b4dd5d4718ff3ce27a671a3b1cad077132a9" @@ -99,23 +94,25 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-regex@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" - integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== -ansi-styles@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" - integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= - -ansi-styles@^3.2.0, ansi-styles@^3.2.1: +ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" +ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + ansi-wrap@0.1.0, ansi-wrap@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" @@ -134,7 +131,7 @@ anymatch@^2.0.0: micromatch "^3.1.4" normalize-path "^2.1.1" -anymatch@~3.1.1: +anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -149,24 +146,11 @@ append-buffer@^1.0.2: dependencies: buffer-equal "^1.0.0" -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - archy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -208,11 +192,6 @@ array-each@^1.0.0, array-each@^1.0.1: resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= -array-find-index@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" - integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= - array-initial@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.1.0.tgz#2fa74b26739371c3947bd7a7adc73be334b3d795" @@ -302,11 +281,6 @@ async-each@^1.0.1: resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" integrity sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ== -async-foreach@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/async-foreach/-/async-foreach-0.1.3.tgz#36121f845c0578172de419a97dbeb1d16ec34542" - integrity sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI= - async-settle@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" @@ -337,12 +311,12 @@ atob@^2.1.2: integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== autoprefixer@^10.2.4: - version "10.2.6" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.2.6.tgz#aadd9ec34e1c98d403e01950038049f0eb252949" - integrity sha512-8lChSmdU6dCNMCQopIf4Pe5kipkAGj/fvTMslCsih0uHpOrXOPUEVOmYMMqmw3cekQkSD7EhIeuYl5y0BLdKqg== + version "10.3.1" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.3.1.tgz#954214821d3aa06692406c6a0a9e9d401eafbed2" + integrity sha512-L8AmtKzdiRyYg7BUXJTzigmhbQRCXFKz6SA1Lqo0+AR2FBbQ4aTAPFSDlOutnFkjhiz8my4agGXog1xlMjPJ6A== dependencies: browserslist "^4.16.6" - caniuse-lite "^1.0.30001230" + caniuse-lite "^1.0.30001243" colorette "^1.2.2" fraction.js "^4.1.1" normalize-range "^0.1.2" @@ -425,13 +399,6 @@ bintrees@1.0.1: resolved "https://registry.yarnpkg.com/bintrees/-/bintrees-1.0.1.tgz#0e655c9b9c2435eaab68bf4027226d2b55a34524" integrity sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ= -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - integrity sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo= - dependencies: - inherits "~2.0.0" - bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.9: version "4.12.0" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -641,19 +608,6 @@ callsites@^2.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA= -camelcase-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" - integrity sha1-MIvur/3ygRkFHvodkyITyRuPkuc= - dependencies: - camelcase "^2.0.0" - map-obj "^1.0.0" - -camelcase@^2.0.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" - integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= - camelcase@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" @@ -664,28 +618,17 @@ camelcase@^5.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== -caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001230: - version "1.0.30001230" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001230.tgz#8135c57459854b2240b57a4a6786044bdc5a9f71" - integrity sha512-5yBd5nWCBS+jWKTcHOzXwo5xzcj4ePE/yjtkZyUV1BTUmrBaA9MRGC+e7mxnqXSA90CmCA8L3eKLaSUkt099IQ== +caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001243: + version "1.0.30001244" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001244.tgz#a6dc49ad5fa02d81d04373ec3f5ceabc3da06abf" + integrity sha512-Wb4UFZPkPoJoKKVfELPWytRzpemjP/s0pe22NriANru1NoI+5bGNxzKtk7edYL8rmCWTfQO8eRiF0pn1Dqzx7Q== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^1.1.1: - version "1.1.3" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" - integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= - dependencies: - ansi-styles "^2.2.1" - escape-string-regexp "^1.0.2" - has-ansi "^2.0.0" - strip-ansi "^3.0.0" - supports-color "^2.0.0" - -chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -694,20 +637,28 @@ chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: escape-string-regexp "^1.0.5" supports-color "^5.3.0" -"chokidar@>=3.0.0 <4.0.0": - version "3.5.1" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.1.tgz#ee9ce7bbebd2b79f49f304799d5468e31e14e68a" - integrity sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw== +chalk@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" + integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== dependencies: - anymatch "~3.1.1" + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +"chokidar@>=3.0.0 <4.0.0": + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== + dependencies: + anymatch "~3.1.2" braces "~3.0.2" - glob-parent "~5.1.0" + glob-parent "~5.1.2" is-binary-path "~2.1.0" is-glob "~4.0.1" normalize-path "~3.0.0" - readdirp "~3.5.0" + readdirp "~3.6.0" optionalDependencies: - fsevents "~2.3.1" + fsevents "~2.3.2" chokidar@^2.0.0: version "2.1.8" @@ -771,15 +722,6 @@ cliui@^4.0.0: strip-ansi "^4.0.0" wrap-ansi "^2.0.0" -cliui@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" - integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== - dependencies: - string-width "^3.1.0" - strip-ansi "^5.2.0" - wrap-ansi "^5.1.0" - clone-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" @@ -847,12 +789,19 @@ color-convert@^1.9.0, color-convert@^1.9.1: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -color-name@^1.0.0: +color-name@^1.0.0, color-name@~1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== @@ -953,11 +902,6 @@ console-browserify@^1.1.0: resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.2.0.tgz#67063cef57ceb6cf4993a2ab3a55840ae8c49336" integrity sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA== -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" @@ -1051,14 +995,6 @@ cross-fetch@^3.0.4: dependencies: node-fetch "2.6.1" -cross-spawn@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-3.0.1.tgz#1256037ecb9f0c5f79e3d6ef135e30770184b982" - integrity sha1-ElYDfsufDF9549bvE14wdwGEuYI= - dependencies: - lru-cache "^4.0.1" - which "^1.2.9" - cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -1159,13 +1095,6 @@ csstype@^3.0.2: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== -currently-unhandled@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" - integrity sha1-mI3zP+qxke95mmE2nddsF635V+o= - dependencies: - array-find-index "^1.0.1" - d@1, d@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" @@ -1221,7 +1150,7 @@ debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.1.1, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -1277,11 +1206,6 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= -delegates@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= - des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" @@ -1399,9 +1323,9 @@ editorconfig@^0.15.3: sigmund "^1.0.1" electron-to-chromium@^1.3.723: - version "1.3.741" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.741.tgz#dc1024b19b31e27fb2c8c0a1f120cb05fc6ca695" - integrity sha512-4i3T0cwnHo1O4Mnp9JniEco8bZiXoqbm3PhW5hv7uu8YLg35iajYrRnNyKFaN8/8SSTskU2hYqVTeYVPceSpUA== + version "1.3.774" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.774.tgz#4d6661a23119e35151646c9543b346bb3beca423" + integrity sha512-Fggh17Q1yyv1uMzq8Qn1Ci58P50qcRXMXd2MBcB9sxo6rJxjUutWcNw8uCm3gFWMdcblBO6mDT5HzX/RVRRECA== elliptic@^6.5.3: version "6.5.4" @@ -1416,11 +1340,6 @@ elliptic@^6.5.3: minimalistic-assert "^1.0.1" minimalistic-crypto-utils "^1.0.1" -emoji-regex@^7.0.1: - version "7.0.3" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" - integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== - enabled@2.0.x: version "2.0.0" resolved "https://registry.yarnpkg.com/enabled/-/enabled-2.0.0.tgz#f9dd92ec2d6f4bbc0d5d1e64e21d61cd4665e7c2" @@ -1529,7 +1448,7 @@ escalade@^3.1.1: resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -1874,57 +1793,21 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" -fsevents@~2.3.1: +fsevents@~2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -fstream@^1.0.0, fstream@^1.0.12: - version "1.0.12" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" - integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" - function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - -gaze@^1.0.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-1.1.3.tgz#c441733e13b927ac8c0ff0b4c3b033f28812924a" - integrity sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g== - dependencies: - globule "^1.0.0" - get-caller-file@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== -get-caller-file@^2.0.1: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6" @@ -1934,11 +1817,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-stdin@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" - integrity sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4= - get-stream@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" @@ -1976,7 +1854,7 @@ glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-parent@~5.1.0: +glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -2024,7 +1902,7 @@ glob@7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@~7.1.1: +glob@^7.1.1, glob@^7.1.3: version "7.1.7" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== @@ -2056,15 +1934,6 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" -globule@^1.0.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/globule/-/globule-1.3.2.tgz#d8bdd9e9e4eef8f96e245999a5dee7eb5d8529c4" - integrity sha512-7IDTQTIu2xzXkT+6mlluidnWo+BypnbSoEVVQCGfzqnl5Ik8d3e1d4wycb8Rj9tWW+Z39uPWsdlquqiqPCd/pA== - dependencies: - glob "~7.1.1" - lodash "~4.17.10" - minimatch "~3.0.2" - glogg@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.2.tgz#2d7dd702beda22eb3bffadf880696da6d846313f" @@ -2153,19 +2022,18 @@ gulp-rename@^2.0.0: resolved "https://registry.yarnpkg.com/gulp-rename/-/gulp-rename-2.0.0.tgz#9bbc3962b0c0f52fc67cd5eaff6c223ec5b9cf6c" integrity sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ== -gulp-sass@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/gulp-sass/-/gulp-sass-4.1.0.tgz#486d7443c32d42bf31a6b1573ebbdaa361de7427" - integrity sha512-xIiwp9nkBLcJDpmYHbEHdoWZv+j+WtYaKD6Zil/67F3nrAaZtWYN5mDwerdo7EvcdBenSAj7Xb2hx2DqURLGdA== +gulp-sass@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/gulp-sass/-/gulp-sass-5.0.0.tgz#c338fc021e450a51ae977fea9014eda331ce66b7" + integrity sha512-J0aH0/2N4+2szGCeut0ktGHK0Wg8L9uWivuigrl7xv+nhxozBQRAKLrhnDDaTa3FeUWYtgT8w4RlgdhRy5v16w== dependencies: - chalk "^2.3.0" - lodash "^4.17.11" - node-sass "^4.8.3" + chalk "^4.1.1" + lodash "^4.17.20" plugin-error "^1.0.1" - replace-ext "^1.0.0" - strip-ansi "^4.0.0" - through2 "^2.0.0" - vinyl-sourcemaps-apply "^0.2.0" + replace-ext "^2.0.0" + strip-ansi "^6.0.0" + transfob "^1.0.0" + vinyl-sourcemaps-apply "^0.2.1" gulp-sourcemaps@^3.0.0: version "3.0.0" @@ -2223,13 +2091,6 @@ har-validator@~5.1.3: ajv "^6.12.3" har-schema "^2.0.0" -has-ansi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" - integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= - dependencies: - ansi-regex "^2.0.0" - has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -2240,16 +2101,16 @@ has-flag@^3.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + has-symbols@^1.0.1, has-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -2319,9 +2180,9 @@ he@1.1.1: integrity sha1-k0EP0hsAlzUVH4howvJx80J+I/0= highlight.js@^11.0.1: - version "11.0.1" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.0.1.tgz#a78bafccd9aa297978799fe5eed9beb7ee1ef887" - integrity sha512-EqYpWyTF2s8nMfttfBA2yLKPNoZCO33pLS4MnbXQ4hECf1TKujCt1Kq7QAdrio7roL4+CqsfjqwYj4tYgq0pJQ== + version "11.1.0" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.1.0.tgz#0198f7326e64ddfbea5f76b00e84ab542cf24ae8" + integrity sha512-X9VVhYKHQPPuwffO8jk4bP/FVj+ibNCy3HxZZNDXFtJrq4O5FdcdCDRIkDis5MiMnjh7UwEdHgRZJcHFYdzDdA== hmac-drbg@^1.0.1: version "1.0.1" @@ -2402,18 +2263,6 @@ import-from@^2.1.0: dependencies: resolve-from "^3.0.0" -in-publish@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.1.tgz#948b1a535c8030561cea522f73f78f4be357e00c" - integrity sha512-oDM0kUSNFC31ShNxHKUyfZKy8ZeXZBWMjMdZHKLOk13uvT27VTL/QzRGfRUcevJhpkZAvlhPYuXkF7eNWrtyxQ== - -indent-string@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" - integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= - dependencies: - repeating "^2.0.0" - inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -2422,7 +2271,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -2526,9 +2375,9 @@ is-callable@^1.1.4, is-callable@^1.2.3: integrity sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ== is-core-module@^2.2.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1" - integrity sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A== + version "2.5.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.5.0.tgz#f754843617c70bfd29b7bd87327400cda5c18491" + integrity sha512-TXCMSDsEHMEEZ6eCA8rwRDbLu55MRGmrctljsBX/2v1d9/GzqHOxW5c5oPSgrUt2vBFXebu9rGqckXGPWOlYpg== dependencies: has "^1.0.3" @@ -2591,11 +2440,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-finite@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.1.0.tgz#904135c77fb42c0641d6aa1bcdbc4daa8da082f3" - integrity sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w== - is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" @@ -2762,11 +2606,6 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -js-base64@^2.1.8: - version "2.6.4" - resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.6.4.tgz#f4e686c5de1ea1f867dbcad3d46d969428df98c4" - integrity sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ== - js-beautify@^1.14.0: version "1.14.0" resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.14.0.tgz#2ce790c555d53ce1e3d7363227acf5dc69024c2d" @@ -3074,7 +2913,7 @@ lodash.pluck@^3.1.2: lodash.isarray "^3.0.0" lodash.map "^3.0.0" -lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.15, lodash@~4.17.10: +lodash@^4.17.11, lodash@^4.17.14, lodash@^4.17.20: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -3097,15 +2936,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: dependencies: js-tokens "^3.0.0 || ^4.0.0" -loud-rejection@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" - integrity sha1-W0b4AUft7leIcPCG0Eghz5mOVR8= - dependencies: - currently-unhandled "^0.4.1" - signal-exit "^3.0.0" - -lru-cache@^4.0.1, lru-cache@^4.1.5: +lru-cache@^4.1.5: version "4.1.5" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== @@ -3121,9 +2952,9 @@ lru-queue@^0.1.0: es5-ext "~0.10.2" luxon@^1.26.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.27.0.tgz#ae10c69113d85dab8f15f5e8390d0cbeddf4f00f" - integrity sha512-VKsFsPggTA0DvnxtJdiExAucKdAnwbCCNlMM5ENvHlxubqWd0xhZcdb4XgZ7QFNhaRhilXCFxHuoObP5BNA4PA== + version "1.28.0" + resolved "https://registry.yarnpkg.com/luxon/-/luxon-1.28.0.tgz#e7f96daad3938c06a62de0fb027115d251251fbf" + integrity sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ== make-iterator@^1.0.0: version "1.0.1" @@ -3144,11 +2975,6 @@ map-cache@^0.2.0, map-cache@^0.2.2: resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= -map-obj@^1.0.0, map-obj@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-1.0.1.tgz#d933ceb9205d82bdcf4886f6742bdc2b4dea146d" - integrity sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0= - map-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.0.7.tgz#8a1f07896d82b10926bd3744a2420009f88974a8" @@ -3162,9 +2988,9 @@ map-visit@^1.0.0: object-visit "^1.0.0" marked@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.1.tgz#b7c27f520fc4de0ddd049d9b4be3b04e06314923" - integrity sha512-5XFS69o9CzDpQDSpUYC+AN2xvq8yl1EGa5SG/GI1hP78/uTeo3PDfiDNmsUyiahpyhToDDJhQk7fNtJsga+KVw== + version "2.1.3" + resolved "https://registry.yarnpkg.com/marked/-/marked-2.1.3.tgz#bd017cef6431724fd4b27e0657f5ceb14bff3753" + integrity sha512-/Q+7MGzaETqifOMWYEA7HVMaZb4XbcRfaOzcSsHZEith83KGlvaSG33u0SKu89Mj5h+T8V2hM+8O45Qc5XTgwA== matchdep@^2.0.0: version "2.0.0" @@ -3218,22 +3044,6 @@ memoizee@0.4.X: next-tick "^1.1.0" timers-ext "^0.1.7" -meow@^3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" - integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= - dependencies: - camelcase-keys "^2.0.0" - decamelize "^1.1.2" - loud-rejection "^1.0.0" - map-obj "^1.0.1" - minimist "^1.1.3" - normalize-package-data "^2.3.4" - object-assign "^4.0.1" - read-pkg-up "^1.0.1" - redent "^1.0.0" - trim-newlines "^1.0.0" - micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" @@ -3261,17 +3071,17 @@ miller-rabin@^4.0.0: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@1.47.0: - version "1.47.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" - integrity sha512-QBmA/G2y+IfeS4oktet3qRZ+P5kPhCKRXxXnQEudYqUaEioAU1/Lq2us3D/t1Jfo4hE9REQPrbB7K5sOczJVIw== +mime-db@1.48.0: + version "1.48.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.48.0.tgz#e35b31045dd7eada3aaad537ed88a33afbef2d1d" + integrity sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ== mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.30" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.30.tgz#6e7be8b4c479825f85ed6326695db73f9305d62d" - integrity sha512-crmjA4bLtR8m9qLpHvgxSChT+XoSlZi8J4n/aIdn3z92e/U47Z0V/yl+Wh9W046GgFVAmoNR/fmdbZYcSSIUeg== + version "2.1.31" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.31.tgz#a00d76b74317c61f9c2db2218b8e9f8e9c5c9e6b" + integrity sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg== dependencies: - mime-db "1.47.0" + mime-db "1.48.0" mimic-fn@^2.0.0: version "2.1.0" @@ -3288,7 +3098,7 @@ minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: +minimatch@3.0.4, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -3300,7 +3110,7 @@ minimist@0.0.8: resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= -minimist@^1.1.3, minimist@^1.2.5: +minimist@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -3320,7 +3130,7 @@ mkdirp@0.5.1: dependencies: minimist "0.0.8" -"mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.4, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== @@ -3379,7 +3189,7 @@ mute-stdout@^1.0.0: resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" integrity sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg== -nan@^2.12.1, nan@^2.13.2: +nan@^2.12.1: version "2.14.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== @@ -3426,24 +3236,6 @@ node-fetch@2.6.1: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== -node-gyp@^3.8.0: - version "3.8.0" - resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" - integrity sha512-3g8lYefrRRzvGeSowdJKAKyks8oUpLEd/DyPV4eMhVlhJ0aNaZqIrNUIPuEWWTAoPqyFkfGrM67MC69baqn6vA== - dependencies: - fstream "^1.0.0" - glob "^7.0.3" - graceful-fs "^4.1.2" - mkdirp "^0.5.0" - nopt "2 || 3" - npmlog "0 || 1 || 2 || 3 || 4" - osenv "0" - request "^2.87.0" - rimraf "2" - semver "~5.3.0" - tar "^2.0.0" - which "1" - node-libs-browser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" @@ -3474,39 +3266,9 @@ node-libs-browser@^2.2.1: vm-browserify "^1.0.1" node-releases@^1.1.71: - version "1.1.72" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" - integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== - -node-sass@^4.8.3: - version "4.14.1" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5" - integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g== - dependencies: - async-foreach "^0.1.3" - chalk "^1.1.1" - cross-spawn "^3.0.0" - gaze "^1.0.0" - get-stdin "^4.0.1" - glob "^7.0.3" - in-publish "^2.0.0" - lodash "^4.17.15" - meow "^3.7.0" - mkdirp "^0.5.1" - nan "^2.13.2" - node-gyp "^3.8.0" - npmlog "^4.0.0" - request "^2.88.0" - sass-graph "2.2.5" - stdout-stream "^1.4.0" - "true-case-path" "^1.0.2" - -"nopt@2 || 3": - version "3.0.6" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" - integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= - dependencies: - abbrev "1" + version "1.1.73" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" + integrity sha512-uW7fodD6pyW2FZNZnp/Z3hvWKeEW1Y8R1+1CnErE8cXFXzl5blBOoVB41CvMer6P6Q0S5FXDwcHgFd1Wj0U9zg== nopt@^5.0.0: version "5.0.0" @@ -3515,7 +3277,7 @@ nopt@^5.0.0: dependencies: abbrev "1" -normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: +normalize-package-data@^2.3.2: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== @@ -3556,16 +3318,6 @@ npm-run-path@^2.0.0: dependencies: path-key "^2.0.0" -"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - nth-check@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-1.0.2.tgz#b2bd295c37e3dd58a3bf0700376663ba4d9cf05c" @@ -3583,7 +3335,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@4.X, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@4.X, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -3709,11 +3461,6 @@ os-browserify@^0.3.0: resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= -os-homedir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" - integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= - os-locale@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" @@ -3730,19 +3477,6 @@ os-locale@^3.0.0: lcid "^2.0.0" mem "^4.0.0" -os-tmpdir@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" - integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= - -osenv@0: - version "0.1.5" - resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" - integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.0" - p-defer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" @@ -4159,7 +3893,7 @@ randomfill@^1.0.3: randombytes "^2.0.5" safe-buffer "^5.1.0" -react-dom@~17.0.1: +react-dom@~17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== @@ -4190,7 +3924,7 @@ react-virtualized@^9.22.3: prop-types "^15.7.2" react-lifecycles-compat "^3.0.4" -react@~17.0.1: +react@~17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== @@ -4224,7 +3958,7 @@ read-pkg@^1.0.0: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@^2.3.7, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -4246,10 +3980,10 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" -readdirp@~3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" - integrity sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== dependencies: picomatch "^2.2.1" @@ -4265,14 +3999,6 @@ rechoir@^0.6.2: dependencies: resolve "^1.1.6" -redent@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/redent/-/redent-1.0.0.tgz#cf916ab1fd5f1f16dfb20822dd6ec7f730c2afde" - integrity sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94= - dependencies: - indent-string "^2.1.0" - strip-indent "^1.0.1" - regenerator-runtime@^0.13.4: version "0.13.7" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" @@ -4318,18 +4044,16 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -repeating@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" - integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= - dependencies: - is-finite "^1.0.0" - replace-ext@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-1.0.1.tgz#2d6d996d04a15855d967443631dd5f77825b016a" integrity sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw== +replace-ext@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-2.0.0.tgz#9471c213d22e1bcc26717cd6e50881d88f812b06" + integrity sha512-UszKE5KVK6JvyD92nzMn9cDapSk6w/CaFZ96CnmDMUqH9oowfxF/ZjRITD25H4DnOQClLA4/j7jLGXXLVKxAug== + replace-homedir@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/replace-homedir/-/replace-homedir-1.0.0.tgz#e87f6d513b928dde808260c12be7fec6ff6e798c" @@ -4346,7 +4070,7 @@ request-progress@^2.0.1: dependencies: throttleit "^1.0.0" -request@^2.81.0, request@^2.87.0, request@^2.88.0: +request@^2.81.0: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -4382,11 +4106,6 @@ require-main-filename@^1.0.1: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -4425,13 +4144,6 @@ ret@~0.1.10: resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rimraf@2: - version "2.7.1" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" - integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== - dependencies: - glob "^7.1.3" - rimraf@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -4447,10 +4159,10 @@ ripemd160@^2.0.0, ripemd160@^2.0.1: hash-base "^3.0.0" inherits "^2.0.1" -rxjs@~7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.1.0.tgz#94202d27b19305ef7b1a4f330277b2065df7039e" - integrity sha512-gCFO5iHIbRPwznl6hAYuwNFld8W4S2shtSJIqG27ReWXo9IWrCyEICxUA+6vJHwSR/OakoenC4QsDxq50tzYmw== +rxjs@~7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.2.0.tgz#5cd12409639e9514a71c9f5f9192b2c4ae94de31" + integrity sha512-aX8w9OpKrQmiPKfT1bqETtUr9JygIz6GZ+gql8v7CijClsP0laoFUdKzxFAoWuRdSlOdU2+crss+cMf+cqMTnw== dependencies: tslib "~2.1.0" @@ -4476,20 +4188,10 @@ safe-regex@^1.1.0: resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sass-graph@2.2.5: - version "2.2.5" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8" - integrity sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag== - dependencies: - glob "^7.0.0" - lodash "^4.0.0" - scss-tokenizer "^0.2.3" - yargs "^13.3.2" - sass@^1.35.1: - version "1.35.1" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.35.1.tgz#90ecf774dfe68f07b6193077e3b42fb154b9e1cd" - integrity sha512-oCisuQJstxMcacOPmxLNiLlj4cUyN2+8xJnG7VanRoh2GOLr9RqkvI4AxA4a6LHVg/rsu+PmxXeGhrdSF9jCiQ== + version "1.35.2" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.35.2.tgz#b732314fcdaf7ef8d0f1698698adc378043cb821" + integrity sha512-jhO5KAR+AMxCEwIH3v+4zbB2WB0z67V1X0jbapfVwQQdjHZUGUyukpnoM6+iCMfsIUC016w9OPKQ5jrNOS9uXw== dependencies: chokidar ">=3.0.0 <4.0.0" @@ -4506,14 +4208,6 @@ scheduler@^0.20.2: loose-envify "^1.1.0" object-assign "^4.1.1" -scss-tokenizer@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz#8eb06db9a9723333824d3f5530641149847ce5d1" - integrity sha1-jrBtualyMzOCTT9VMGQRSYR85dE= - dependencies: - js-base64 "^2.1.8" - source-map "^0.4.2" - semver-greatest-satisfied-range@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b" @@ -4526,12 +4220,7 @@ semver-greatest-satisfied-range@^1.1.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== -semver@~5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" - integrity sha1-myzl094C0XxgEq0yaqa00M9U+U8= - -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -4569,10 +4258,10 @@ shadow-cljs-jar@1.3.2: resolved "https://registry.yarnpkg.com/shadow-cljs-jar/-/shadow-cljs-jar-1.3.2.tgz#97273afe1747b6a2311917c1c88d9e243c81957b" integrity sha512-XmeffAZHv8z7451kzeq9oKh8fh278Ak+UIOGGrapyqrFBB773xN8vMQ3O7J7TYLnb9BUwcqadKkmgaq7q6fhZg== -shadow-cljs@2.14.5: - version "2.14.5" - resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.14.5.tgz#f71d9bf1e292d452ecc76ec0a061fdc895af36b5" - integrity sha512-+tn4f8bSD2P006bsuVAmQeBRSzNM55o2oPT2SfDYDcUYVuqevxnxuDc2xza+c2UsA55bnYezP71CSkFDC56Osw== +shadow-cljs@2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/shadow-cljs/-/shadow-cljs-2.15.1.tgz#9f4b642efafeb84480396f46af2a8e59351d5986" + integrity sha512-X0ueBJksdBg5FIuFOFguyZtAP9gzZZI6lmednxQ/eOsN9tGhpTXh5Y8/7lGzkfIFXxONe9THZx4f2m4JX5jBYA== dependencies: node-libs-browser "^2.2.1" readline-sync "^1.4.7" @@ -4728,13 +4417,6 @@ source-map-url@^0.4.0: resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== -source-map@^0.4.2: - version "0.4.4" - resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" - integrity sha1-66T12pwNyZneaAMti092FzZSA2s= - dependencies: - amdefine ">=0.0.4" - source-map@^0.5.1, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -4821,13 +4503,6 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -stdout-stream@^1.4.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/stdout-stream/-/stdout-stream-1.4.1.tgz#5ac174cdd5cd726104aa0c0b2bd83815d8d535de" - integrity sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA== - dependencies: - readable-stream "^2.0.1" - stream-browserify@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" @@ -4873,7 +4548,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: +string-width@^2.0.0, string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -4881,15 +4556,6 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string-width@^3.0.0, string-width@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" - integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== - dependencies: - emoji-regex "^7.0.1" - is-fullwidth-code-point "^2.0.0" - strip-ansi "^5.1.0" - string.prototype.codepointat@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz#004ad44c8afc727527b108cd462b4d971cd469bc" @@ -4939,12 +4605,12 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" - integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== dependencies: - ansi-regex "^4.1.0" + ansi-regex "^5.0.0" strip-bom-string@^1.0.0: version "1.0.0" @@ -4963,13 +4629,6 @@ strip-eof@^1.0.0: resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= -strip-indent@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-1.0.1.tgz#0c7962a6adefa7bbd4ac366460a638552ae1a0a2" - integrity sha1-DHlipq3vp7vUrDZkYKY4VSrhoKI= - dependencies: - get-stdin "^4.0.1" - supports-color@5.4.0: version "5.4.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.4.0.tgz#1c6b337402c2137605efe19f10fec390f6faab54" @@ -4977,11 +4636,6 @@ supports-color@5.4.0: dependencies: has-flag "^3.0.0" -supports-color@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" - integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= - supports-color@^5.3.0, supports-color@^5.4.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" @@ -4996,6 +4650,13 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + sver-compat@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" @@ -5050,15 +4711,6 @@ svgo@^1.1.1: unquote "~1.1.1" util.promisify "~1.0.0" -tar@^2.0.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.2.tgz#0ca8848562c7299b8b446ff6a4d60cdbb23edc40" - integrity sha512-FCEhQ/4rE1zYv9rYXJw/msRqsnmlje5jHP6huWeBZ704jUTy02c5AZyWujpMR1ax6mVw9NyJMfuK2CMDWVIfgA== - dependencies: - block-stream "*" - fstream "^1.0.12" - inherits "2" - tdigest@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/tdigest/-/tdigest-0.1.1.tgz#2e3cb2c39ea449e55d1e6cd91117accca4588021" @@ -5185,23 +4837,16 @@ tough-cookie@~2.5.0: psl "^1.1.28" punycode "^2.1.1" -trim-newlines@^1.0.0: +transfob@^1.0.0: version "1.0.0" - resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613" - integrity sha1-WIeWa7WCpFA6QetST301ARgVphM= + resolved "https://registry.yarnpkg.com/transfob/-/transfob-1.0.0.tgz#c7fc27a5b5430ad486267ae666d923f74a0ab320" + integrity sha1-x/wnpbVDCtSGJnrmZtkj90oKsyA= triple-beam@^1.2.0, triple-beam@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9" integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw== -"true-case-path@^1.0.2": - version "1.0.3" - resolved "https://registry.yarnpkg.com/true-case-path/-/true-case-path-1.0.3.tgz#f813b5a8c86b40da59606722b144e3225799f47d" - integrity sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew== - dependencies: - glob "^7.1.2" - tslib@~2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" @@ -5440,7 +5085,7 @@ vinyl-sourcemap@^1.1.0: remove-bom-buffer "^3.0.0" vinyl "^2.0.0" -vinyl-sourcemaps-apply@^0.2.0, vinyl-sourcemaps-apply@^0.2.1: +vinyl-sourcemaps-apply@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/vinyl-sourcemaps-apply/-/vinyl-sourcemaps-apply-0.2.1.tgz#ab6549d61d172c2b1b87be5c508d239c8ef87705" integrity sha1-q2VJ1h0XLCsbh75cUI0jnI74dwU= @@ -5485,20 +5130,13 @@ which-module@^2.0.0: resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= -which@1, which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.1: +which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" -wide-align@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" - integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== - dependencies: - string-width "^1.0.2 || 2" - winston-transport@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/winston-transport/-/winston-transport-4.4.0.tgz#17af518daa690d5b2ecccaa7acf7b20ca7925e59" @@ -5530,15 +5168,6 @@ wrap-ansi@^2.0.0: string-width "^1.0.1" strip-ansi "^3.0.1" -wrap-ansi@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" - integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== - dependencies: - ansi-styles "^3.2.0" - string-width "^3.0.0" - strip-ansi "^5.0.0" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -5576,7 +5205,7 @@ y18n@^3.2.1: resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: +"y18n@^3.2.1 || ^4.0.0": version "4.0.3" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== @@ -5594,14 +5223,6 @@ yargs-parser@^11.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^13.1.2: - version "13.1.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" - integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-parser@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.1.tgz#7ede329c1d8cdbbe209bd25cdb990e9b1ebbb394" @@ -5628,22 +5249,6 @@ yargs@^12.0.2: y18n "^3.2.1 || ^4.0.0" yargs-parser "^11.1.1" -yargs@^13.3.2: - version "13.3.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" - integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== - dependencies: - cliui "^5.0.0" - find-up "^3.0.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^3.0.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^13.1.2" - yargs@^7.1.0: version "7.1.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.2.tgz#63a0a5d42143879fdbb30370741374e0641d55db" From 8fb8a5d89a497d502626ec0a04e6d99c44b76236 Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Tue, 13 Jul 2021 13:47:43 +0200 Subject: [PATCH 200/204] :tada: Add release notes dialog for 1.7. --- .../resources/images/features/constraints.gif | Bin 0 -> 772801 bytes .../resources/images/features/copy-paste.gif | Bin 0 -> 217531 bytes frontend/resources/images/features/export.gif | Bin 0 -> 196468 bytes .../images/features/group-components.gif | Bin 0 -> 399767 bytes frontend/src/app/main/ui.cljs | 2 +- frontend/src/app/main/ui/onboarding.cljs | 105 ++++++++++++++++++ 6 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 frontend/resources/images/features/constraints.gif create mode 100644 frontend/resources/images/features/copy-paste.gif create mode 100644 frontend/resources/images/features/export.gif create mode 100644 frontend/resources/images/features/group-components.gif diff --git a/frontend/resources/images/features/constraints.gif b/frontend/resources/images/features/constraints.gif new file mode 100644 index 0000000000000000000000000000000000000000..e320861cecebcfc2d7fca814172b1de313f8bbcd GIT binary patch literal 772801 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

h3n{t|?_R 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 FGlHk10?C<EGbA^0(lU73Tw%^3)p&Y{t!(y+zC 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

Bk_cuKd0eZ~$3*m|!wS+VDC(uEafSY4e(fjD-7 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%0AR!ONfNaO$44mK$U|JdECGEDK7Ezwox zfQ((~&Ry43Cn1jBPA%PUMR~87s5DeiY9zl-@01*%& z4IW{T33&|e;0_)k4FJJ?@1P9q_Y9~&2~Oe#g7^%E0Em@f2!=oj1egj2VU(u9W2IOj z%FtO-;(;MJg3%^Z#Z^Vf_2tMjU7v}AxyXZA6OI{%8tB-LWg!+cp%Dy$kRJh&0T~e* zL73^l5DuA#1vw4w0EqvASPYaPl3#!d{+E-_KnY-A2μgg^=CAdIX*Eu{D&O8IuK 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≈q0wr1 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�TNGOj4Ia>XYE2tP#_osI&g;D@&?&H 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%0A4?45YyStwpY^UK6s=&Qz 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

#v}p}2QA=$ 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�E-hmU}2%(8;b-_7bOi_R{ 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

Y{IpiR#EIHp; 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%0AR!ONz+`vV3C;ipHi0+KVHy_G8%70Y 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

  • S7u{IL zsH9C;PnTC)L>6^N-@V5F*(12teuke3fQv%}8pL&Y#31MpZb*>34(G;&Byd4yS9F6M z^Vsyg4`OU&6Y!h06{iBpDIjy^R%8|j+sW+l+ua?cM*jp@ISdJ4W`$A%$Hu5ecSue0 zB*Uj_LY{Z1fp}z`hbnCN_{VI_fg@%n-5AUaM$&(g$?m9Q*g=N~#$zjCWjj?S9k=7h zGlfSyABF1dB>F!NA{l(xZ~$Twg6d^~>7lW*kZZ(sZld;k6$<2>PIPpKW@^vIpa8}0>I#NKL)(O zlG8a=vOIdjJp02y5HvOYySnqsI^LVWasob))1weIc=!?MV zLqU*J!PU#Y8Vtft)4;w$KEMLM4-`Tryto_8y%O9#3A7vvTtN+6LMwc**w{I`^S&cg zz$>&u2UI-iGd&*sx(Z~$FHFPNh&P^_zwh%xH4MTq%)!!AKmT*QDg40!j6*(TLL&S$ zBTPVh>q8ox!zUa=J1j%h3p;fp#4m)w4*!(FBy>auG{j_s!X1>t6uds+YeY(1LP(@P zB-Ff5#6L@fz&gyB^&67*L&H*hL^j+)H|)Yee8oy^Lg+h09VA0d>_JYnKwAXGSmZ!a zgu`GYz*8i>^i#z^c)e7|fo z!6+Q3O$@+JEJrz<#z3q$Yjnr$J4a09MRoK=b_7Ox1VM4EM%wGg=CemcWJX2wL1+}n z5d23$oJWNuK7!oEMC8Ur6hKdWNNj{hE^Nqv#K_u<$W)w^RTPp|tU`}0K!%jZjyy@w z1Ic9+$!285XS7I`T)>p{KvJy9y#I5_{yWHDbVZ$nznhfAoD52;Ez$8OZai!{pO z8_N1CN~a__q)bPTTgNjr$*I&ns2s(uoV=>cM5V0AcKphE)W})n$Qd-tu?tIlyh?q{ z%5P*#h(t>SRLQlZOPq7di5yFWq)EMeO1q4~s0_@b#6|PF#DauLpWH{FG|blPN@Ijf z%0tWtRK}j9$iFO0%8bmxjKsq1%zNWYgVfBQ)JoAbJjo=-)Z{eMWK7d^Ow?@6w*1Uu zWKG(9Jid0!%qU>2SEUU1)v0oAW#4h00ecu-K4{pWKQjLPVVGK9I^<6P=*5t0514E zSprZ32nPlL00WrM2og{N$cG>}fc0!ovpY{LU{PPttNL6?4l~cBKu-XWPr1X$?NF2{i>( zkh(a^7}Fsv(m#FCCSB5oDAYqO9_);X=46oPw9-fg(Mc`4ifD&Bh=2#+06n0Hd%yqz z-~(cK0S3hfd1wR@cmN5|1A6!e1xAfFhs=Xn=uYU4*g&on$=#1;EwJ%Ne9ga#;pHu#5RT~>OfR#A|LJHUVpD1vRkh;}#w z448m0Fw=%50C{+X7ob)$;8QyF1+(J^S#Z}0h=FY&It5jLhs6K|C{H(4OXSRt<8pH_W@ z48Yw05P(If+8A{Jk5$^|{ayg*;C~2*+r`}jXx)4W(h?;AwPjl@AOH$Dh|w*87kJ*- zm0|b%2YrBp=N(<%WzeCm-zLUi{ViN4#Rp|*fB)-6;3kN{3Vgb@vZ z82>N@D464b1>QU+-U1DP3RneV2w&gLR0miETyTQ?jRy_pSA6i->`hh%@K#{3W50ES zSik_Ztq6X=1oqwIG8obIEL2851Tm1^4W~MlJ?TNaYk(T0?aMVu0lX-~dnn z1lg_LIL79Nb>V8gh80C&`ZZ8Sc!n|X;iu0{?*H ziO^R7*nl3J-VGoCRK{Ndz=yNl0D8a&AT0nth*f$3QnX#^=M`E>MrBG?Qgy{%KTuNy z-~(Wt-s-*HQ^o)W=-^U@WPGsNZID;9O;7y&X7${LOqEZh4&U`e>wRG2XI)|?RnXF9 z)iQqO4WvewmCC<%)%UpOHD%z5i0hICQWhlu^dx`+z+)wL==H=0eZYbSXaTI1U7^iv zzqJK=IM;No2sysm58i^Lecl#mQa9j+9y41VKIpZj*$I|w5e0w~sA46()`__5RL0|2 zy=x`KY`ms~SS@8O-h!jXgbm#Ss0M)d%|HlKR8&l3T%Ax|tY_8{fTikw{Z}uJ zU*COE?LC0#J?r#b&t#3^Yt?It_S67iVI58IQpVmk5b4hm)t9Ai!#22>^UL)S^)UNE%p6eK{S~G27*;Va)9o-FB@CEhS6b0uU?%{8a&xSSfSzgc- zj^hb#-J$K<13gub-Ej6UPzI&dMK}m^J^`o>0R0wmHp1L{?90t1OAEcy@7_}J-ca%` zY4hG*^p5ZeC;=I`a~W8KUTH0s@8G5HtA^sT4uF6~Trmg$MgRYC0JmNapX7-E z>o$Pw7=`kSyN8mF-nG`$2$x~du5f%lTPQEOStMYhG+;T!P?>-AZ2w;D^yE<>Ez`t46kngw znN4kcAnpb51*E2LKLq9(pJhdt>WPqtJxE?TC}{TVa+*)_YhQ8{t_YLo=t4bEl;`NL z7kK6N*`fAVFi&@CW_KrackGtwcxU5z_wI|(Qf@v30yuy1_&`s1UAE;BEm0(mfmNUeCT?A)MgT_8gzdHX(w20MhjNOz>p3n0 zY(NB9&SgRH-x!btG1ySf25kYbR$)HmV&>iV75J40>>}LAREPP+QB!d~w?oi_dMMnM ze*OQoZA~pZgXaKCpan>9gVhG}$~I7! z0!jq<`gPuQ`t+|S)2(dzl0ZzE#lF-0E_2vas#amaqpm)r_4XP^`Wt*8F@OWQ?UEe} z{9k<8+Zz9KoN~7zV9P%hU4+j*a1c-+J_-RqS4sUjK$|p_O*WQd_y{&1ZouI)3T0!F z*1!q;1z{g;@g#O4K>%3B*hu-rc3XW504AVvV>PD{V$t!VjApk9u!WBn8M$6{?J;?h zN-VhqlXo@Ul+!Xh1(l^wLn+xM0ZKK))G|&%C6#$sW#y%sNPQJneim^-&p+*)LBRtY z=#dctkC`OTBQFFnK@6z;1KnT$^h3e{h5sGafMPPDrdnhR5Zc;JOsEqO2X0yVaAtNHeHY-`&sdmkal1>s~jR+QR;r|prRdO{93+&uT3 zU6kF?WYf@B7Hu?8NLvol(oH4vl+)LyS-R`5yRG%seRU1H?YC3MGu=_z?cKEES=;w{ z>ET|ymnU^4ukp*fjybbFZ$72xR$?jo=tLQR`k1Mgxwr69GatUxhCfGpvf`s}yY8Ap zd-v~pm-qeO>C^v_Nz>t6zy5_g-yZZYaZYH|3!U|PLOR@OuW8ouTkH1cKsL#)e9?Iz z1+%rjXZ50Y()tdy#uGok5&y4(=|dh@lqbRy+OL29V;|5uS0$nq@FxenT>`ncI{7sa zg*x0J505uD7HSZ09Sj}_duYTX8c~HS#9#oY2g4W|5PQX|q3RZRo*W`Ei&{+L4>1_T z_j!B?7jrIPLQ9S8gPK~2Ikmz?~hlMX4pM3ORwZ-kvAtq49{A~TuXV$$wrxxu`J z@mhdXCN;^(OHleUX#Y-B zrb68xOwlP*KaO#zN(CHFL&`>Oma>s3Wtt6*iIt^dm3c*_Q$O%HnhvUrd}D^Q@?%_unG+4K1J)=*fpt{ z05z&-*ZI}ghIXt>oF`AaiCTQBwyLZp?D$X;4hE2=09?>Y1YaWrR$UHj8u7<-w@5yI zL;?e|5vz3LB9WV#(~`8crENjWToNJ$Uu4-pcelcq7UZtIT6ymS$+s1J5aR`ZS)5no zixlcYcUd1T?MQtq)l>#{r9qt zFl=Raj|K1{9VvWUKU82KjhhB>(5Uyy`j z#N~r;*8iwOUj`r9C@~K4&OR2O9hd`DM$v~DLH0CYbzTD8KE_#S5`C5^!D`+jzzusd1b)LTEvAq{hAgZ)`a`Ud0l0N$LSI zB`6nVkMwzVOpfKHL-ORqb~+@44z)v7e)f7eH(v!5((C+d%~k zNPrI{;me6M@+wM4+hG#eh&)`jD|Ho05N>c;xP?uC9gxJ^;D#5F7cnDL6F}eLE(s=_ z4gUZ?w;KcR-rBXZ9X7|LhGP2o(*r7CtZm;(6ybmfzZU%u79n>w7$=gfX4VeLlCq8KJddR z7#}qt-)<00JRkrn;KMb=npAjDiDj12tp z9$U2*A)Ul=HNY=!z$Kmx9KhH6`J3GAgCJB45zY%mBwuv}#&ZFgxDkvpuvdTx0$MnR zMC5}C0K(Q;BQr9L^=(5uEP#xC+!TtPAe>(~yb6HfgEBBmh!lqP83rck;RZBDGHT<; z?4g_#;>Q%?skK4a5M)Ux+G>!5ukD`;@Ij*47)d1LCGI0Jatqc8B>w_Hp#c~R0azo< zZI;YE+K$y#ELsP*EYn6f-Y@D#K#E{|Y@JE`WBIVxo-6NgEgo1(C73P}7f6BYsThmYRzwg1)~r^a5`mU$fZPn*PPIS5>S8|j>2kYgaj@cx0vT>0!U|aCv6}~ z26$uv#HI+qfIf1U4T={I(p;o*nveOIMgW-`XoEFmLv;eD404!E>g9Wl1f@x3(J5O$ z41ol=fthU(p`m?piT=7X}QTFPK!$O9yt!3A^;G{C^S0BNhe$EkT)az-hZJuLA zre!)VS~$2s5oCj}x#yhL>7GR7HjX7ULg>S#g^k^V9~P=xQl@pt);8h0Lm}dRH{405?R!5riXyUZ)Fy=sA8& zoKB^Z38)+8jH5+gh|tFkf1T2|$t1SkVVM?Ifzw1~Mw+7vd$H z5oBWoW?lLSa45sApoBD3z#YV>5ER0#xM^_|BEtkEQ4%Kt48(C7=xR`>G$_DF5f$sV8nrflAd>ymavzkN+woa3P?E$Ysy0Fu~;#i|@A zpL)n50Pz;Zda1>NDeASA#=Zxswq6FfLH{)%!lou3jM$b$Vzo(m`e zUbb%xWbgJ?<^rHXHsIk9uA%cnFZU`MxzU3>z(H|bgBgfIJyh>?0&kX)L_b6@J;Vd> z3NN@yBn1?KGaws$El4Yt11>x)FNWj|%GG|R?RoX;{WRAPl0*Rt;fW~k{`N28`XLm) zUN;P{E$qW1xPd)fgSUc)2$OJ02uBHM82|ZUYgSCRR$B8u?|!_;KXQn-o=w-CC>*HZ z`Egke=p-B%@rway1kZyM5AerfaQ_e21**j(a8U1GL}Dkb1Dr8i2%LjG#4r|w06N%1 z`@SzJ=1hDTpcoX!0xp0axC1V9LXRn?nbj^-RjldZnsDh_biLWHeN3oMl=47`O*1H4_r2>==FhP9z`v6w`9w!;v7fC@l@ZpZ^GKR^r=+t`u7 ztBBpwNdzZ1fG1O{VQK;lw4a<1K*B-HG1Tf;-0IAsZAr3l3yTe~HgV6jTn6}Z4FkwC zSHL`sEQwZHu)V+os6eU_!2itn9@V+bJuj0q6hN3U+pXa6Wt?&<8<|MRBY%qb5|`R;Uw!FXS=Fuq&f=6!<*1skK8~Q8O;;a| z)x@=2n~mf&4pLMN+W2bYwDnuJja}OhNWirTx5qw@S71ZTuaVnZ$*VscH0bg=zaC*b*Wh6E?EzkS>x7f)-C>2F77sQ&o*KO_h%)xb1#)~M>1a} zcWz^>m{Rv^K{rMTw|RAUbwBbfPOK!~E+t>AC1>n-=T_O`Y7X+Jdt;S#GbVBy4Q0iR zW$iAWam1%tSbJ%$?6y}=uGrfsAE0?0`Md+W(B0c`D@VXt4N5hu3J#|F_J1p*WW$)Z zV)6;+XA1Z8aQA2KVE9((S%&|aMoc)Bfo4rsI9~RbJd^o)`9Oi+F5{x1T4buM+u35QLo(dJ9(voKfqFxn;KdrHu0+vU<98TKbOXOrj_H zroT<6Q#!hX`gBk!o3m}in)-QTcav*3dvmw5uVV-pLh)T5!w6f(AqojRo@LZnC@fyt zh0Wb*82?|m!5u`NQcLuUiLyk`frO-}Ufx5Dlp%mk9>XJm!#4oLBcUKDheV7Wwn03% zh77nN!YV>^#`8ze4c$0+9yV_-hY_CPA>PMVf_@PI?g^Z@(U&3-6*IU!B*k0&B4GSP zi~Vp`;HETP*wom`jq_FigT!AdA9@?(A#vdy9!1YRw#A{rV;kg4et+V-c`rF`pSEsC zIsa3g1Yhj%D2#0so?tX~Vp>oD2dn}xz`$Z`p&-h^5(e?do#Hr*LIpsg{7nP0dHk!M z=LIZ=Qc}WZf+F%OKl49$=@TP71h4}F2ZI(dDm;T2JYhUk|Mk-Ybs3@kqk{GV#I}C} z3I6jZqz!?5UpjS*J$|DW=%IE;0400`h!!<^6lqeU1(`BU2dh@DUA=k{2sR9BF|EanX~jA$ z7}Z|8a@B$bPifkvP1&*~@J}K|i%0(D3lhQ_u?%hg>4URlp1~j!+)#s)6W=HYCjaN0 zAfPNmh;8`tY2@h9uY*<3#0;1`@SvQ3_dX~fun!IwkS+TD4Lp;u;hY3_8h}SHUIsVS z8X>R+*8shLxye+kgaKpajU7Jp9v9d_@16`687YI}j>AYTrP^AM!GFYoN-Cp+U>G&owA zZy?lItBkMF!21UuK{go90B(j-W&@CBOOFep{2{;=#XiI2Jc2^o=MFj)!2c&61GYJc zFCquJ2Rs6j+~7Lx_81Z&eCk3dJ2pkJGaS#Tw9_9KY?$AtlJys<%oo{DO!swSjxtFDUq zDn=R2LhIE;6%BRRMc;}~ID9Up2U#RXNq`>Ib^uL?Es#VtA3>44#{p(Ndlbq=Z>n+u zTloBTpzM?}Lj_;5r6jwXSTm^FYYlkoI%S|0(oVlv(>Gc;;KWBCEVzIV&P_X=ja5F1 zr7a{RAC<{qBM}|Rw=$iCYe|3$rmI*t35b-E#}at7U@lM_IOU;UjsF#9f*zz*!mNz3 z5JL?)43Xy!M+_F|v{Gzl#bsK25iF|6GONa*ZOk#9t&mjx7LoxP8x^? z>hsJXG8p@Vn$RRlpFI{JW8=WRL}(y+AUWfLpx}ab@d4J^<8iS+*kUyTD!)AQMgM8% z5gAB;X!1`XBAh5zU3FTmm}ia9)~~kW$}1SfTKX)fp=RpsP>&`3pFU4NVIVTOPH$eT z85a-o4uXGM+jHE>_{*DL4W7yu$zGm0>{pgMZfS{(n@r63(JYegbbcH+UcMKKOYMlqF9c7#aI0jz_q zvL_UA!Ypj!t95Uy>QM`&J6}vynA>2LA4d7RkN*lQR>Mo>t&XQFUM+7%uuN!3ts^R^ z8B6v4tuE7 zYv)1DV#@%7S=wW0pz-d7aA_LKcLj!lS=6c)vUcb#zvfNwLC?v27 zX22l<{1L=H{Q(nt$O8`Fiidvrs|Ppo1^*w!z??j=aD;s{q6qgih&x@ljeSHybjlC_ z8Kyys47{fwFo;1mx^W2mZI~V^<_3#Fc1(MK*fZYqlOQUvje6i1rrn(-gXBsTRc3{i zNOUFh^!hRzZW%5+?(vWD5#%CFLoaQ36ebK{4Q_Z3B0K4cJ8;nnf%a3#L^iTNuyBG8 zhxjw;!bAxQ)sGTL3Ln`~R-NxG$g~oqf5OSlZqoak^x;Dq2oME7h=H6A2D->co)=4w zabsu^BL>`v={`3&>QZZv0u%wp3s^H>RMgkg5XnasIsp%2NFdSiu`!kf8r)Hdn$)J= zD{>d4++H{oXFdF3bfrroEuYA`NB>lw61n?rw(w&QWGDjxI8Ys;K;s1uP{ASj_yPgQ z0lss5i#z~9f&xI0f^LGw5E>u>H$2KB7pOoaa2h9mV1k(aAVHU=8V`Hp`@v(p4r&mz z4^3d;-zlk{00z_!Dnx(-m0%w}243)kTdf^u0C^E?3j@Bzyy9Oif^O2EnO}in6+Zpalg`e zZjoM7lo0|$U|<6Gd5g&lZsL9p;Q@5Jx@WYbiFk8h5%AgLKw?gSq|<{QCPz6W_^uLZ z6hJfuQyc|fl6NUT9ZAMyIsXhApn^U9;~gZR3qLeLf(gW64|E~G(UZ>AdFi7OBzQmt zA}A3##3>vkC;bIVK!()dgIk(_fdG{L4}OCjf}j^YKl1qi2?Sn+JokA(AL*AZ*n{+@ zM?HefOzpN<8`}_lF1Ne=brXdfyBk&7cFlhjV*%2*YeC9Aza9Uh#Gj1wmnE#;mXx79 z>bTxdA?{`=5(NN};sEgl|J<+tj3xdA@H*NLq~Z#dyo|2G?5@`DSAa)$7!XJ9FM1Ni z{OnI;NKjZfPyv;~{uaef;0loNr9{vJ5ZtOLT;>5^5Gf!q2P3cs6A%PNhKand+A8qc zY$n?xiu`=W+aypa(Em@P#;v26PztAz3cJrJZfpmtPz$H6LB#IMaHZ^8saMEs3(L?9 zgK+XlCrd z5P8IG4D+fGAMp`$Bv|$#6J!k%zYY%B0{o0n{OXYW?yw5;Q2og55=+q(O~eOJu?i27 zc)qZ%#1OAGP!(s9774%}YVo5ou_-o@xsGuBl2AmJa0^8d3fT`Aj}aM@F&AOR3okGX z88H@{M;WJ)8kJEOnSvL0hz_Ca6NM2Eok2w~>Isu53ail^&+!~tajsVB88Z;=&=DTv zu@A4YAhNN!wEvA8xeXMlkQm9)808Tl12PhskqBK81J8~O1yUgwvb*R}X5^3)w-FeF z@eKP>yO@R{Gg2cZP#rCh9T(9Z6LKR*k|YzvAqT=9ebE#3(Hlb%9EnC8^^g=x(hMb$ zD8d0+Xz~v`G7(=98uzfwbh4Ce(nk701#Cem&F>@~1R{H}6Z0{;_Hha=au3TbDVdNc z-RCOZkS7hY9TCzIw-OC?@I*GK4aIU99Wo`Kaw4Tt3aN4?(J}y;!XNGc?uvlpqULZ) zFXS+R0S*8F5&%2+lIBLk0f1o$3LpgJ;SM6eBcO;cV@)ofh9D8qD?t)0WpOeW1nj;m zGPxmcrvHcK;%(mIfdLo*00zJpMpNF9GEgp|3?9G%#=-9v0EJeM3hHh%PjW3K5*Xc* z3E{FFC-X$wLNU038Zu!UNJTi_rKwE9$Z7-e5@RuT4twr_0@yJ0B%l-0<2;`8BrK6Q zO|mju(HSvw%)(MGIkPhhf+V1G8{WaGP-6?CV>|;08vMIF@AKS7e`SB%IuI7ga86%P~2nA z#{Xnw%0;$J@G6(GHzV{nCv+++G$u!JIAhe5*rFeXz~CwcBn}4?SmSjt$4=tIPQXWX z9AG8p^MDoyBF@xFxU@@W)I2}ZMjH`FbCi}Irk47`a?li)asnrzXcQjk7~TT5&_-=u zDtGi{luE`TQ~(3aF(}0{P-m%M`|6muiVR#}rOL=mXlp*=FH$ijt6*v90Y zjvtiat9*56^|WZ;@mrzCuZU%KWUg*Us*U<0_8uSxsw4ZjhUEy(;EuFW;Qu3AC50bi zA>OzXH`*jh!F6LJvSZ2B4AVAU*H-=jDSM1Um@1|J3=m4mvH(?(WDi$Z1J-B_cmIrq zcaK6_uOk6l5C&DyxbOjYxm0Xd*C~C|V_(-?FBDxzRd#bwN8O=4fV6w35O@JLaYa&k ziPA*)p&Rx9PR4gEGj(j)vPwS|CPP+B)fRqB7LYI@17d7{vC>~XGI-BdJ!6z{ZzLWp z_hJG#Xr?!ZSa)1m(ta&8RIAc~Jurh;ae#aBAaAr-)Ax821Z+3BL?F1KB={Gvw+vyI zdrcUI$+v?qQ+O3OhHsbyRX2jGw{Cn24#Xe0?$`5C6D`msm#SmwF*| zf}^rigEM?tBhAtiYquvG4zG!*pDZ$3%M9^y%>G}7>nz;2zfY)&31~>HjVQ)b`4pQ;VO`6 zbdcjXgeSR(5t(Bb8Hmod{akp6HQA4k_=AacU`ZKac~}LOm~ohw@3?F~xsgNpk!3lV|JayoxR9sWftOia zeL0VR_p6zWnUIy(pJn%+t@n)eIhd_@WCvQAoF$rw0UDEY znWIH`j4PU%*V&sbSdAk&q)phP=NY6|dOQodhr1b=zgeT*8K!sHq0JYf={ctnn4;}? zn=d+up4t3F`KMQTr;*sDlbU!nmW+qGnTtB36?%=G+MbvCl#v;$Mfw^wm78Drr1zMg zyE>b-`j%1ItbrP;Nm{B+x~5SYlKXhAe;2Kfd3fs@Q@?tC4f>Z6+NP~qrS*EEft7%H z`lAVZuXPcuiF%@p`s%wD+_}Y^#dF-EcN|2hJF>0Y%FTSto4me98_s93%ZvNV zk2}ok9LMFH!0nsQPw~zdoXWv`ume5H$^6L?{SpUVyp3D1Tbt2wG0P)*(zz1N_glnY zoWx`N(y6i1yZh5?GSlDM!jT-qaeCBgG1OIj)!(qxH+$156vpBE)hS)k0sYp`(9wB( zyghx_&)m-ud(ne^3wwRddtA$Y+}H&@*Xg|3wGi3M57J9K+MRvanOfJcU4~6tzfFDC zue8>coZBI>)ytiOy*7CTGeXAv$-!U=P zQTxa_J;@zB;CWr%0Y2dya^U@X%2B<__x-K@AsFhw36`7&HlE`yg3>ZM{<$mZt;O@C_xd)usx4rEd z1QuqT@FRcdhrxT*UB}y7;tgHWhyMskq8`yBUlJa8;cfhcZHNXSU-Ah*D&OYUUCpUI&m&#NA2{%BpYX#z zmX;o`NgwTFr7CWG_~qUiSU(dRJNfTj*}IF`EB*^Eo$wz(^r=6#Egg_!`{OtgP9yXr0+%p;Thou>`_?MJBVQO`JMmp=&$v z-xt1s0}CEZ*x^%+4?I={Nzx=rm0P*Olv#}CUA#McMqC`Uu;R78wV_w0EC7ZT(*|nkzzHJtF+1$D{Q|8>cm#^QBxsxkjF1Yjo zX^BxJ9afL6WZh)h`S+h@5V04Yff#0}6nf{W*B5(>x%U`+u*o-@eYN42oPKg8Lsx%y zaaUYh$7yKejf6P|o&R^7P1j&#*HLEKc6m|wkcBuVsia{XmgXS=Acn}GYh0Bm8-28) zc$SK}vFMwNz{Qx?j7kZ(oGLDXUprismJMkIKmFj?b1mAf^hsxoMonVn=7PMJ~Hov*y+tShP<~ORY%OVq0jI zvUVHVw_w7kEB|r4+NWSN`MQfL~ z?pJWQyBhZexf;hj7`kmfOdY!tze{m%DR{N`dSNCD&ky5EteA=9hg^%Ow7a0rAzuiHrMJ~lf-!3to9!rf`L-GWgrzkEX+xv$+9`OY}gaZQvpj`hhmpK6}Z%1APT>#{#MgA#dhJqM~ z0LXxgB|Pw45zNR0_fkOBitS!5$08A*(ApcC`J$1|vKIgscB3mYK7KH$)i zMbd*G&kzFx4nQ`4WI+#(j0FSU=(^JUjv)(zSo)4QxKOokRL~k-*KCN90pdd*a6n`O z8i0`nkb#r+fCdQ;;J?hZFpF~A29!SHz%h1$9s~f!3wEgyZ-P^tigW`!t~m!xeuMzk zBir+?=goam0}ujPTO-`?IC17Ej!^NK(f(1#+AS@EO&i$LzO*|rA?$;I{Ims6)naS4k--HRUxqHy_0KqFBgh6o06gqP*J2<5^=+YARn2O?xVo~dy6nGfoYCBE6kIy~Y?*phAUzZK9vVKt z13)=aXuRNZ?EtvglB^L1Dv->DmNEhG?Pz@1{Lonr^7rsjH!pLW(d`7^E0e5dVlJo6 zb)E{G+x4k;(>dN?y>q_N6GpuyG5`>0K#H$H1xywiuK18E=S*tW1|R>x*O)kfq*cw+ zL9*TLU^c7Rdj4C9hkDcuD0QM6u1ejtK_>j4|iLU_Qn=kva9$TKw>ZNM(fPms2wcS=BRc zi)B2<<5&yGam^ul5lBZ9Xi=GkBDOXdxTb;Q0zyr(Mo^+PVjyuvFoe%Q2KJCSpoR)z zgIbSsTN>d2oLCPlwMmZjNsCj7sze3^poxq#inss;V52w+Nk#wyz);tOPzUx! zjrVwgNLhnuS%p|*mv@1T!ciU6M=M7)S%gjnArQNSHe*zg^i+`qaaz~IO%~Y^(Nho| zcWoP4ISxnwlf#lH$&%g^LjmwcV{}>{xssxFRm7N8PZ(#&m_%;&6Ymp<29a+UF;*VQ zl5cbX!xR7xh)AOIkw*!WdDfI@^O0jjOvo`tHMx?khLcozFMyE zLxFW*XMJ{weREbgR48wbnVEn2I)MoygLzS!WtcdIm~5$Xi}{wCnVRGH6sSp-#h6}D zh?mY3nHQs)sR@Cu8EX*ZFP2xBX{j<9=s`SYo5cA<2tt=mXpFImM6-FBVON}fiJR*Y zV-&TJBiE3M@{lN(Ya4hJlLDRKnLOx*oJ2I4>f?2mi6_h$oA5xaq*_#%aooczA zj0jTf37(MYoRX=WJ}H~+#hY|vrq6-6})LESg zN@EK;pS7Z$o5!7vXrs3|jDEwD{&|-&s(m#2oJ49bI7*!(N^-vmHJ;UZD*B|E*`iCN zE=fw68XAVi0 z@1dabiIxp&rJVPsMbUwM8kbzUj4ui&#|WTI+ND*vsLt}IXu6}bMwmW|GC&%f5_y`I z%BV&vrXO*A=P8BhNvSnDs-=Rdx*4cEI;d+}sBLY)2JNkrFW{NdAh2O+N!z= zLTasKq36XO7x1op<6 z0rC&m%CGfu9uY7N*h;V3nxXf4q>E7pt?95z_&U={u9Ro41zVyAtB47Us{!&26u`0N zavmSMll}RtV0y9L3K9*=vN+;uPM~g5DzYScA0>ODCo80Z@ek7=0$_==X9BS=3$t}u zvG&TX7+V$au(L`_H7@oA<9ZM|I;Gf|u7*mqMLQ39@C83`wP`{FYP+^6 zM6qs=x}3p^udK=$lJEsqi?;vc(YAmaxPrU3PVfb;kgtF`B7&->2Fs@QnWBDcoB!|` zbQ>|-nwOFLu3hW1RBEe_8>*Qbxs|(2QH!~uTdbR_tcKXRV0)`#d%B|Qt1t?zm5H~o ztEH(su**8KKLZ+q0_6kPOuYeOu!u6!letrIb57F zOt9OV7AESwLmRC>ET<0a!ls+V#2Lie%ep44t_thKO1#6Q`;$Iw#hMAlB)q;;9JWZz z#WuRcJv^gioS9y%zMbkcpL)Y-{G?{A#b_MIe!0dy+N@#hx?^m|`8mguYP^0Nmv~I8 z`pdrjs}r+;!-FijZZZo{OUIMi$U)P!kUYX{tjBJA!VkL0luV$~amlkAzy~u6w)@En zW69~8$LPAp?OO|;jLP8|%8?w%rYy^ZETy8Hyyh1F+n@q(iHw)*m&5m5n#%viLh0Nx> zFgf}S)@;BujKGMjz_TC@?VOsbAr97@#mKVG>5$LvOr6#c&lEhwXi>y~@x6-ttpe?s zs-X@2OuXt`%H;6S^^&7TYtS=X!9?7>3w^|Rp%cr1(RZvG&0rEYLlayF95vxlwIH!3 z{VW>Y3o7l+QCz~|92NvH7cgQIyTBb=JJZUX8uLI4j)4+UVI7na6UN}u!2r~`pcAzK zutZ%dIm!+UqSWva8_it9wL$=D+0!oa(>TE$GJVw=^BvP5%1TWDOg$M-{VqVw9onGQ zSe>KGu+{(9($!z_)%1)qV$I2LJJ=`;=InVunU)s+1#8O_>c>my_=8i)G{I2csU%i01Lx#+NgaaIXVwJ z!O=qe+1vusAq~u)RSN{*3cY~Xxt-UXF%P})3MH|llJV5BO&3soFgx)Ix4qm+lB4jD z3(#E)t8B>5{LG+A+pypYxlqpBT`Avz4z=J4t$h;8TP4&@7uJnLL7f-r-3tNT-Wa0M z-;mz(9n`#>$(p>=CH)m)LfWs;3an5I)}Y(~uG>ak(5%o3&>hc9%@x)y+4)`3m{Hpg z&IY#-!Cj9LR5UvWPfZQ!H5K*n-%W)G% ze&jb1)lnVfu22f9km2%;74I<+v;*R>feduP(l8AR>CFnPkP4vi3OTOiM9vpTe&%OB z5NEFBTd@nQ;N(!wVo(iR4>7Ov^gbwM5 zp6CzZ=(0ZRw9e?R&gztI>6otRF;3qH{x85C+{{rHq8+wdp574d=K~Sw?^EcoP89#H z9_`YO>yuvTx}NEA9^LPu9-kiS-VW8r&f~103aD=Bf==y{{_F}N?d;y}?T+q*Zta1- z>)9^e+kVi&J}ctkC1i~Z#gOB|f!@gO<)}^&%+8nUeiZP|>YpI*^N#JU5an)7B?EEg zSneVizdPZ==i*N8sc!BKpYRs3@Fb7!4R7w34)GG7)O219UR~ohe&Y)sq&g1ErY`Q| zUhoA03I!4JB3}_DAMXu6^eCV5PY&h1e((0}?HT_Y4X!Jt{t7tn@kKxML;v#!5%gM5 z=~nOX4}bI#pY$W*FMu8Mc|H(Olj8({-6THs9{=Z9PZ&i%_m__8ssQ$`5aa(4bHA@~ zUJ9(A3XhNX&^`IS-q#`%>NNh>E+Xn?P3q!J_=Ruytbh8b zj}WR~{Hx#kuOIob56LV~`#cKlc%<+9o-*n><^ezWTrTyk0Q#X%0C!&yu0Q+`5&Yy| z{>bn8u7CGOpW$K8<;*|(OYRZ`VEbTE|6hV~b<+8@{rSl*`T(IS)j)y-3?4+7P~k#` z4IMs&7*XOxiWMzh#K^GJMvfglegsL7pemBAOtxy}>J=SMU5UcYKKKerV5D?5K7=Dfu6{W6(fK%7y+6xa}lUDivY`4u3Vu^ zXtL>Awr$4Mww<*O#;U zmOa}lFJ8HwJ%0urI-_0ErA?RI%nGI1l`LJtlqq@eFrA1K2NjA`H&UgdAwG>Nby?M` zS%=x=I(b*FT$K&NBJhfUS%YYOU&o$ZyXe&2RioUivbA5BfMw6tX}Bj)_KXi-#d$n( z*X7N_UdEi+`gZ>P{qy%OKmos7jkWP!V=gwq(A#M}poAJ~Hwpip>Fqy*fHP<+;;@1& zIhhFfsxq<0f-XAgEJQIy6|*y|MHgRuCC2eudaXPKYhn-?^=@m=J;&m+NiwhO3(GS6 zVq7svC6|QgMJJzx(!dz|qVXjg1H&=C+8ARmw+SVz&_dvv$#5B}ka5g7L*~G$ zg@6&6q_a*tE21(_J!h$rIl!>ANz0vdT#P+KhieQz$b=ly$|CQCG}0CIq_omX`Lxnc znE=fZu`a#*QnxV0bOx$24YR5^oCrXUrZ#sm=*@%TTxd>7Z^bn(OZVin*IpehFTopa zVsNK}4n>x+R3BTE(fWwA(pPD7rM6m!cE$EmX@7Op&p-dqE7rD8Lj`lG6vsT3xZ>2@ z%Aj5h`tTrGt);i#&Va=?Q*ZyoGD|@lOh!0En@Lt(X62iYz9Idzw_%4N$~R((yWMxw zegpki+y_A=_oz`xg^En!P&H=VRo8SEp?Du=xn+v>g;`UJ`^9lK+Df)fV1jYmd1u88 zjt^nU6yDTjqaThrX{EiLnPy^x)(K!9A?$d!kN-nA-S^ft&Y*S?a`kAj$5y#%vxgq~ zX{d2#?AV}wf)MT=fexBjv?U`uZ)(lvyKjr{UeM;7r5>2$s*AcB>r#UoIniRUI%t@K zzSg^PvH{1OSiv73v~7eS1ReCb4|D8b$WXRCbzJ{3XT9~nH?K|OjUznVV{=!`dSt2y zSQhfg6{1{q;UjfDZOt);I%+hzG}9oeLw6=_pnU8-=+iYHK6?v`=id0^t*1?S+2J<4 zcA^p&(|M6~PqZ-K5n5Gy`5`^Ne$BzxQhfK=3)xGmqnFZSexWMlf zkaL}5-t1^+KE&NFNu>&z+YZtg0+fsZ??Ye+BUM2BJrIHsoXYBxfyks%G0sGS=oFNZor;Z&S=lnJiTX965x zs;&~lC3Z0`O$4J8qc$kpIEaT;q~Q51s3iXknhGIO9AdzDhma$B(T<>rVIK8p!jh5E zJ!b6ELCDoY7dA$VeY|5LFS18QHj?4}W2uKO0QB-SWBL*3Fs)P^`Av#iIDA@u@ zQkGJZlKf*7$rqKXg|1+!JSCb$IZK9=(w4WpWh^JDN}i$7l4`uuQ=GRDI5I?zv_z&v zbh%6@rjnPdT;Jgcc_?T8F`39@W;V6i%xWSFn!mIfCYjVmHxl!j=X^^wo#{IZubE)1LNZXD`VF&Pz&4nCgp2^XNHHgxqt7`D~{<6CzDlBD9zUy`w=TdQgOh zE}Hzb9j^WfP>6OkBMy}ygd)Pw(0TvVq;CXiO7*!(0$?g;IG^ICX={sEtOnSw1AqwT`ENu!@(5*$MOhsx^59m{nLKUYB8EOR|wo$3h)T&pF zOVzrH&aajgtPja5QIE#ev5L~HiX&HBm1x$tW+bf?5~p1Cx>vsT^{zkcYhVRCSc3jF zuY^TxVikKw!!FjbkAKmdAZMkEfv-G(H0w$m^wQ|fStV~)4H7w#%_U5beS zRG?Im0Du980*oOvV7Sd-1{?~I1sFVl1A3^&aV_zTAQZq2C?>@V0w94K6Cf5aFmVwI zBaspuzzA-+D>N`505?zr5@cY;9ta%V9Z=&DI?g~5lA&Y<40r&yfI|RIKmchRU;wA^ zF_4ElWQQCA01wE7$SjOuI?r^smQ-&A2oU7}h!_~k{Qw3Y0A)BFpaB-Z00SJbisQnz z#y8e(0Rj!)KR-YO7#RP6B_v`5BLg4{-9>L&m0W-#?BXZXm;(k5P=FG+ffx*UKm=Az z24KK|0Ri|x1;BiO_-gme$qm4zGyRZEG@t|@hyZ`*TxVuO^sXFcudE9IjDeYzC|Pgu!BZ3gH{kR0|U%JfG-@N4s6j)OKYcZXGD3x z1&Bc^q9{wZo3k+jSiS(r01rR_OF+F05CL)1gN9(c2yi=v@Hkk3zkIVK?HaHHFfcc8hEX5@ z;UWVmn*bZ=vIOA3ATYrS_yrJqfUN_7GpoTHoUMfbzAs>ir8|VIsyhorL&^HQr|1}{ z%d?hJFoy#MC1`*QPytIAI}R9v2Z(`HAb@B91WXHsM-vKCP_sUiEXAg|fO~3K?M%nW(6uh){(HaKNK;KS>Nm zr@)|6i7>XJhND_WsNgEbISPXq#-nnA4|oW+lR%d`LtSLY3k0yW7`q}kzXmWGCo3oVvWrzoe{f z63k^XC~x9Ra`8*RRLslzhsJcw$Arwtl+4D&hk0NJZD82LPxKyEV%FW?KE#Bl! zZ78F{u|>5a&gSGS$Vgs!suk}Jf%~6Ku|PYkOgH@P$AMc%}*}n22c2pK2X#}Wz)JIiRPp}7k*v@Z= z3Bdr=8^uyW)lx1!1{HzSP!-ih<%UYV)Gxgm7Nwm*<<#sn)Fc_zSbhK0Ni|MOjSV#g zn2p)gRy9&CO$1n#)nJWO=(N;R9gH;<(>CQ*7=2YG5msTPRNnjs*uc~!y^a52)n%K5SWkn8>&RDN-B(fVS81gM^H5VEjgWOU zSkmHFg&5g|cuZ#yfdj~biB*Vi;4~3n*@e&tL*O|M5QV1jR7PD3Ki~#>_y>D9hF`si ziyhUB-OX><*h}r$f%OuQ4O!51%!i0tlXXmZNLfjE2uTPwhT#7PK>&aWP_+T@1*XVZ zoy~|v;M2$C28UtoLZ@EOsZ{6;5&qWfC%8j+J$HbGQbCa zIJY(+TO=ibW9S0{(Ah^d2uFonQvF#(J%)e42ShDZI=$S3=vjQ=hF=v0z&!?fzz4@| zTT*@7(Tv-3J=SzhR;Rt&&Dz_D$XkYpfD*XfhuGbR*asC;+>Bt{fB4kORaE3{RONM4 zKcI)keOvpXJlhEnUEs)6HF7MK#)gmD@C>Tk=uX+8w#v z)mw*f27BvWhsXi|c!+mkMO#>086^PYJqXDaR_FbPL`eUH$F&7~PzM6&*U|L@O7(=& z-CjxEUJ3SuK0sYhIN;SiU${-*YDLzjbyD}mE%06>2o^qw ze}D%7m^X>wy@uEaDKh{h?q3wG;mH-?V0GTq7256%+J4OjgFxM5$Xt8yggVw>2|nOB zF5f;_;{I&n*kxKMmf{MuV!o~7#uR}Fh*^jD22W#%eNZq5;NKahVbvYi%2i%&um?JB zT%YA!1x|!T&|y!|VS^xDBkqIgwdA(#u00|LMYe+A+Mj%MsNi0R$re^^^Q#$ce`<5r%` zS61IxW?#Fd<-4QeE4F2leN0ZP172PTc*ueSFa!Klh<(65jCSECB<>-}MGhS5X zB>22NH(5wjHyEkVzgiol0 zEcgI=9bDh2ScHISw$SA2t!bNPRGf}Xo#y{&a`xz-Ccf6ZhzQi$d8L6vSYYgU>Hv=F zrnug05L$?^>Z{IZSKjGy?rC%OYLT1Frl8jY5COd5k7l*lxTe*kt!ogLT@lV}yCdnf z0Irp_kT})XxbA~QAlAcn;=~r@#eQFSW(y-|>d1EK6`^eNool+@>bst0&OS;m{Rc!% zk<#u1Qoiie)@+|&EW>_6#n~Z@V7u)%O4I0_WQv4RFNXZ{jxa2A2x}|KpBUYzL?C2bWTD zR&eWP@CxVfF_uvb&+G*!@DA^92)At!|7a3daX7v321h(rKV>3E%7>NAg2P(qZ@u0Y`EF_~j%Qaw(mMomN_bRaO4R4qK7( zEFJQ?o{5gl@(q8B<~gP$>hdW^(s>ArtsVx=KJqjVa@RHnzkua859Bzv5V4qrd64rW z9rBfE@(jP$RrT>d&v7Yj23KGS*l=*VDyc4^J4JWPxlQ1CH3~q zgkm@LV@LL6SN3Axgl69aPJs4k{{&E=_EN}o)!1}LC-f_g^!^z2VOLCN7x!@|_i`Wi zXh(Nxr}k=31yxXuT_5x+g>ztci|b^ShhPPU_ylnO%XIhme+PJU|Ac8@cTli)cTb6I zHxE?3Yc1~#VULJZClit_;W7!lSlcKSNV+? z2zP(?Y}a;%m)&G___$!Rk{?f%*ZG}acaCUzmtPH-UwB;g*a^Dvi0DV0Ure57`jq#G zkw6Vica6U|Rhb`Hny>$Pxk#c(S9-t{iLVFyuowH%&wwaphj1<=o|Dz=T_^*EDzZ>Lv^GK3S_NS95 zfB&)s2xkHZ5-k5{a3Fz%3JC=373^WKVZ(}1EQai2vSk`IHfwg!nH?4jN0KaQ@+3+E z#Zt0t>GCDam@;S5tZDNm&YU`T^6csJC(ximhY}t6?}=FFNmbMEZ@fj-o=~uBgo6PhZ8Su{5bOD3vrKjMltp3jllaVuWtQ1_Uzh+GG7dDx8%tj zTk{pZ{XGBr^y=3m@4gt>bByBmx@K?xKK}gr|GJMmt)emEtwvu#)a`d5f(a_P;7k3% zrHw?81t{Kl^)YxMh8b#@o`Y|RBb8kgvgT2N=52T)iYclHT8B-M6A@JcQbr;{2CjG` zjydXxR*Ou%2;zj%5h#$2I~sW;l1b9k<5J?l7^7-gkw>J7N?Lg(mPsKqN9FlYgh37n zXOV+C`Y5EF75a}jyY!-`M2br3XQZ8a`srYkR(dI-P%cViqoAs~DteRJQx-R|$~r5p zwc7t0t34flvx}vewt4D6sj@mOvBpixjVEdKu`ILAI{PfN%X(rDKHYtjORrA`877s* zdiyPLlOEGnwB?%1EH~C-ODdbjxXD_ue}X$Ny_y~S*SYyZOD%5K@=9u=e8Sspy#*VL z*rbWzt1rXSGWSg_>-xHFg#;gbF~)8c%1vMmH(V`g5KG*y!1M{5G0Ivlb<;P!)bTPX z-;^b8#~$mOZpaa5D;32fTAVV_D!(<8BqacFG|~XjAjw!X17`Eh{p!qagfdoqa?n|y z$P@uwpQMyC9{}*d7sK>IKrf(}5rNV&pQMi=Box4bC^w1QEKYuKL(f0<9Ha0|Q0M>4 z4akdqLkqT+mTJ+vtzoS-h55^-boZqtr_TBAnC?8Tj~vMFDNJ zekTFP^g#gL&?fXO_vrSW>@ok~lWe-Nj<3+(_;91JDF0+n4?f!4-Yo8;eKX6{6Zige z@B*ly;<1E)C&>Z;dLh4*90CBFL&-kiP`&GIscYj3-?A|1EDb)(5A?|1_6V>Ie3*j( zwm6DC=w}awZKH3wdSCqH_p0;V4_$>*%>MdEG733PUIP5#eU8$g1)d}VMiBoZOD^z% zl)Qrk2(SeOnPfqQc&}j|{6``Zu{~ShLmdPl-241cElwh4guJK-Ox0}?`4o#95k07%%{eLl+TOh zd!GpZag6dQg^YdE;x<57!#=EMdL?6y2K2FG|4{pZ~y~@ zGKxJ~WV9YxE)UiZa4q~B;j)ns`fYN2o_J;=%BV$Vj*^r&j7tvZ_^Uepu9e4l8VTAI$ zUn#M9N`R$uVBP#CM2GXruT8XTNTC4$;Bn59IO70WC<#9zakL9$&Hx`=QlSpo$SQ8I zS$-e@J_?6PF2)pv2^rx%|G_b7uF-GROekEkiOOxNGNMMkPDMqN)TErv34t65JVbB+ zw~b^USUAAZu*%DtxXzKnq+<6RsW-m)4RD0BNBXjukoMs-n)p~@L9^#ZgkCeK3-uol zeYeV^@|8B>RO(YpVoM{y<0RjZD$|PMw2^o-RXSjDEaBwo#lJxl14$vUgDO}T7l8`}Rv&+;~0LL3@QKJiBa zfZ}Clz$gGjpggZEb|J6B$qBOoIF`^Be|5DiX0_R~-S)P-vxy5${)oV=ejp2HYg%ic zR^4UcOn=!O>UQ<&-TA_%ayLO*77ELwjn!rwLIe;)_BNZJ~Nud9OTWK8O?4EbD@N4;tKz_`OS7#v6k;jW^Q`<&VKf6 zp4VJvEC0FB5Dhey*-U6dGa8SG=CY#u+~`T)@zHbcGNm)k*GXAJ&zc>yq%|#Stxakg zWBxRsN3CjBPHGsn5Ok_nE$aYIY985L*W#=;;92uJa;1K+;@G5X6!*H=8LM?IJe{fD z$=BGx25e!%;dl>O;_Or7Mu4otI6_|x}#paf517=YWMPU;*q)vsv8aez&8{ z-3wNYIyToMbk*gzX|Z~X%Kwi1wS~#4{i;DFMJvcw+6%)jtp0*f)%Vt z_qXRbkzwn*-yT;|CPFUqk(0dSCJ*^0QoadJu)P1}p8y5SOOXnT*PGfpgS3!3{_&mX zNaa5NInaat^Oh6c|)1M zLkE`ky)#}vfPXyXk1qJZtD@rcUb^8EM>SIcD_)KN{C*-Y`oB+p@~Jp|`qTgScJ*7oI8D`&Q{8{RdR!s@`P0At_P@XU zsZVYGMNIRX-#Eu<-}b>D0mer89UuZGpaLQw{!QQh@!zZ6T()@0>rIL06(9x@2k>nm z2X>$be&7bGn+U4gERGJpt0@WzZlA|aXuGE5>U!V{XzgEK^k zggjyt#$G6{VyF}fJ~+lH{$TqVpI0;kE8b#GObR?0LyWZG7=FZ}4aGd*A~90bomhh- zzRPa_V;L5sGm1s6okob{RUHl>PbiNqp2RskBR49)kT0X z+Y6qBIi6!TYMCka%pj6uP<*07a1J~=snH3)`5~MbWcbFGDl{g~nS zUsZ}_GrlE}HJEy>mnTTrZGoHOjip{17*j5ce@P8k8YN-M#4r?Qf_P0$6xVT?K^>eI zdTC{URTo`iQ*K!sW9B7fBFSRJ1U%SMRvmz9hE5&m7jvZ-Vsd7`AkM$!T3cGHf7p_Uw+-76u zCW;`KHzt)uA%!FafC&F!CP{n(+bq{el+Nj-&gwL0kEoPQ#Ew!R*-OkNU@pbs2qv+4 zr<{Q2ax4{6fJ1V*rb!@A1R&@_K#%lLkM(dB_Gpg=ebpn?)B2Rp`FxR1r3*f>5B*?L z{LD}NOqO->r?QYM4>DXhI;6GbH6g%u-53&;&_n1!)o^trYk`(g#tf2$7Hp zp^yr(kPGQlGzrlRsnIB%s8Yxojl~*^nu&N?PD%j45z**Eh`8KR(s4#8MnHJMcX_AlOgEA2SGnEfMJyRR;6g55Renu&uV%n&dNuh>kNopyV zF2o0XfIe0gJGm216pAR#laG?pJ>?UevZ*@s(?10i0Spve8Prb|>z?|js3z-`lxlc7 zSW=MIH#{ra3>Qg+)JT=oNzFu8J?f9P6tlckO&!Hd#T3ZA%8=&NP7xG6_!O~*%&|tA zyXt13g6HuNfC$V3m%_sbENDqs6;=U&R=p}krql*mCBb-!lrX&kWR#w(^jXte(I_HByLu%jua1Yy@WFi*KyGR4X{l)F(`M4 zEm)B)(@NKDJ#E@X?b@p7QM&D!z(shOK@OBw1kivH9D*TcCYo%eU(6+tB^uQt?z>`b zf*nmAsKsGkE`&)X4i#>niYDh`2)x2X=y0v*PD)=^*I#BX&Qa;=D#^0Ggyeb{n&2l_ z%BJm3E$-$np0$|e1}|-SC4gye@ft6N>DY9cg^2l>dr0k+78UgFrR<)?t$Z)|%8Ka@ zZ}$K0+V&EpnmMobp6@=U@9nNH`=VoIaA#bcWA2^R`Hteb68EDW#WH{RQJ+HhOE;|} zu^Yc(LcB4%l*%r!0^u#4x~cHk(c=}bG3cEfAP2I@`CQe#T`5@J+?|paf3Y89 z9&0D|g_|0q^2d#yE=!)*sh%un=h$WPDdY0g?eZ?0Ueft8`thG#q9hRBGBF!l=}j{= zSF<(aUg|*~=8<3(ucZTHr0PbqtTi7wmvc6UpZU$*{;6=UxL#O>WH^iSs*N8#*RwXK zvjcK7JG3mGLEB4=QOVYwNfuNQ#Z97H{npPqcS#eBqB8%%Q04G^&DT{JjzFO zP<2&XwZ~~SS-mLyNVloi$v?wOs#UBVuA?2*_8nbz8gjtktz&|8-r5 zp;W6zUhDNy^R=o0wqpNvMM%gx;`LXLNHq>KVrN@pUp8iEwr0!WU3>LZzsY1%c4ZqI zXP35VpEhbgp=T4eZ-Dk5iuP#pnrhEBZP)e_5;kssHfV=-SjTq1LboxvU*Ilkc6A zKRMb(xs*>Gl~*~>T{)J2oR)8Szje8n!yA}~Ik}BFnd|tA1NoVMnVGM-v$eUK7aN?% zIj_w*oj19X-?@P2xtKArMjvc7^}BBeZ4xYdzY-wdT-Ub zt$P-(=Xzi5IoQu zyTAWCz$5#-D|@&Hyulwl!Y91KFFeCHyu(BLz)O3*KRm@(yv1KU#%H|7Z+wE^1i?={ z$A`Sgk37kjyvd(Dg6pKjqdd#Eyvx5l%*VXGk7dEjyv_gLJkIC5&hI>o&qU4hJkSTd z&<{P)2RzM#e9J=SNv)^GjEUp?118=+;cciSU?9nfh8ov-0wZ#_x;$P0vgPK1*lU^fB+MC z0UDq|6Ig=ZH@@RPzS1Xy;-i5VGyxA-08NYl8t~0gY=ajtKIDhK=#T!(BR=QTgB=jT z1;~U2FahTyg)dkE>CZmx*FMJoffppFjGizxuB~`?tUQ zzd!uPzx>ZX{nx+xkHhO9#XK|t;lBhxfbjBuHWJ!}JQKnS6k|jtUsPGy5=Zi>$2r?ucI@HUEL<>*0#VqKHM@yqe zktS8Tlxb6^PoYMYI`w3Sfd=uRVPW$J4FF~i^^^$Wi7%n-kR+tKmTg}cO$B`#jzMQ2^n;(po z9?qP4b?eu$XQ%8u0`%b1wTBl!o_u-p8o76mZu)$C_wV7ypPin7cM*~v$k)H0e}Df< zBX9`15`_EUhadh+P{9QmY;d9viYcuaNjP!P!V59X&^!oB153jXK@3qu*`|R({Vj z%brcUw(Z-vbL-yCySMM(z=I1PPQ1AB+*pMVA`=%9oaYUrVe zCaUP7j5g}%qmV`_>7ZqiaYU-(|rmE_ythVavtFXrZ zD(kGY)@tjmxaO+suDtf@>#x8DE9|hu7HjOW$R?}ovdlK??6c5DEA6z@R%`9G*k-Hk zw%m5>?YH2DEAF`DmTT_0=%%agy6m>=?z`~DEAPDY)@$#*_~xtczWny<@4o;CEbzbt z7i{ps2q&!Y!VEX;@WT*CEb+t?S8Vac7-y{U#vFI-@y8&CEb_=Cmu&LMD5tFQ$}G3+ z^2;#CEc47X*KG66IOnYM&OG<*^UpvBE%eYt7j5*>NGGlI(o8q)^wUsBE%nq?S8esx zSZA&E)?9b(_19pBE%w-Cmu>dhXs50A+HAM&_S8Pi! z`s%E=?)vMn$1eNqwAXI??YQTz`|iB=?)&e+2QU2a#20V;@yI8y{PN5<@BH)7M=$;K z)K_o)_1I^x{r22<@BR1ShcEv4<-fe?(K1Sd$r3R>`j7|fssH^{*bdhmlF450`| zNWv1D@PsH#p$b>X!WO#lg)oev3};Bg8rtxNILx6AcgVvY`tXN945ASKhe*UC8u5ro zOrjE($iyZ(@rh83q7TiOrsjt$i_Ch z@r`hdqa5c*$2!{aj(E(Y9{0${KKk*GfDEJ{2T9068uE~cOr#K8TZGhwqMn5@s-AVYG2!ye%1JnSDIdpeC@jVRT~VeGKZ)y^w3<^MQK)9J{VmX zkh=Vs7++=0p}sg`cv+u5US%Ubx;XLrvLO#vZKtfhG?RJRSf*L+@O*S>zU{K9F235y zR(*Nt=hEe;mhoyApV8%SHr{M=XKCy*VQ?P) z#Sx+XAqPc>42lJTU3(-I3MNPp4|UFWdCQFbcIgm?e+UXOrJgapvJ8VB(T<}R)B%5l z>bh>75y3AJ(F$}04<28;f`d>XK>7w8|DP+p0~|<-*a8t}BRLhdLlq>DBwsl3grJB} z1#N5y>2ki!k7lewIj$Ebx3{0ir5 zZNH4OhVn0^+W(J`WZ?v&cKO3%L3gKr|JdS|^s+ywt1ub;RQ*38`N~Agx=G&*9RA&l z9bEbRzmm|skfFk4Jf>s55!0O(XQF=BeG?m_>B5fR#{3#`?n^?kpSEz{Ep+~5_xb+` z`9Uh{UPxzK?{oBJzsRTmTgaf(Pwq>mPaHmb(v15so{ZGiFzv(Rh z30dU$SgqwhAte{y(yRR^q~%NN|Ab^y7!UhTNHJZ~cV_l1{uB#ggSYAH6N~ zKOsjuR7=oyOI-VT=yUtBTvw{S&FC<(!?J|P$A&iYnsWC-!q^M{6EahyDBvF#{2hbk zy^wc)6^A8x|6189?uAsN7y6%&Lsd%$`Br$`_d*UlCIv8zu=j*5*|NzB9R z;z~MVUTkJ}+f?$oC3+D)l-t%&|DM|^2i^X(Uox86d>kPPE8RAvMHC+13;FW<>)1(= z&7hdFjwL0P>AEW_r&HbE-mxUW(|@!!0d((QpY~ByzC&7Hh@|jT`n`9g>!iFFQt!c! zU-5#pZ(}*I%{kKdLSE_Bn_g33JbHN^InjM0EhfJg(pKcc08IU=VMLN~&tpnM95yp` z7oZzA&Lw?&mZWtrq>}ar;jvleKUuGY`6k!5=j}@W!euF$QUD&i*^ z!s@OrQd2n)1-~`!1|p$;Me(pn8Nv@;UVFJ56E{2TneMWs4@yIwEQr+jbe!y{aqlKgTV(4nclRhS@k+BhjAl@G2TpwL|r znoRimBl+)M``us5^^I71g(2Ini(%Hl)AYzcOM?mS8q9FteH`MpI$qBe*T=t?*DiTE znjgsWTW`{u|0eRq=8r3c5O^|5*O7jP-T)xU2xY0E5SWv>o8A6G_&*Pb)JH)Vu_BOp z_Z_+g?xBI9zfYjd{!7A=^TQdorovfHv3QJC<^*#{Wl?_lNY1H^P@XnZe(#~r{h~FV zz}v|Cm{T8X3~OL>)PO`T^sU#aC7HW9hDrmV;CdQLj5?L4C_qG0M<>P6I^|r2atbkX zhs7VdS*hfSC1`8Z(rnq8{xK+v;#Yw~%*!HJu>dgQCxXCX9aqM{Q$^_fyWQ%mY()=J z86f{Q?H7Y$R@qRjopAv?B>kJjYDN^SW0M*Uw zGac?`OgQm^t=AIxZOrBk3ooB2p~e9 z6-2iQhln!KFo!y%ME0UMw}u8(;6fry^S*Y9qV9!6OXomw zHsrhPh_)UyAj4S!tvGac@`Q3zXomenh-ypdgQ2z$nsd>GX&_V43c<-|OIU>&a9 zNk|C1?_NoSE3V2}C=Nvk!(E~>i#Z&Ce;U#r3J+#2I|UF|p~+d(u~bw8Bz7HnpL6mC z>XzdQ2bh%VL$&~s|JOO0*BYj4%Z}A2MBotdVyU1OZVEqH zHjMcfdK!k1B!8|H=6X&V@G_9r%Dkjyg57JH!98=#k-wn7p`ie(Zjx8Gg^biF0QdGs2v9q;<@#8xoGwXu%i?KtYe0U=q5_^SB0Ka&-uRY@8JZgxWDzp6&%Zw;>bp<;4eE?*!Cm zktEA68IeayE}_|3(-an2F;ATmmJJoi91uHZ?jcpFI$pC&fU>v0dEs~@8Nhe%PAbpN zwgF}4q}L0FKrvzfGUaN_>UWC(|1!=24u*i@P67Z(K%KvVFEYxzc|TH_+Kq?g;Sx+= z*!@aAQ6gbo-CL1tUv<_-mr`Zhdk%G;t?uW4_wS!{c+4P>AK@>=lE?W>qNm*6AtEiy zjyO_7#^XETNl!k&82KB%(gEEM-K4B7!xdL#@TfP_UwHC9n8p+y%y%D?hb)AQGIhah zMzg^VM99dplqgz)AZz%7=G*a8z$$=1nA5QD7q ziFh{Yg>>stvZADN4uiWC=anb=a$P)5cIMcxcXo(yAw8-poHMJhL5^>KFvzr0om^^@F9V#Y>wi)t>r_m1WDCV{6k^#0$=lg z{rOk?Kw0u~lE}t$>tPHEvZ_tIrcL}U5%vw`U%G46{J?tk+{?e4Bnb;3s&j|r0j{S= z1k*s&>rQ`8tx2?P2~Z?s93DQlPCzVB4GXaJ^1(cR5%CodjDW%10OSA=Xq^z2Z@b9> z-+t~#I4!DJYVo*KpRv*KtW@Sg`3)&h^xBqq-`pMxz%N1(yanLDJ%!r4L9CE?KH89- zM7Kg42O?jGmb7P;pGYzRAQ%#;loxP|B5)K3x|ox!{DLA+0c7iVtSa7KkpPyffIBxB zgbMU(Dm2tC5J~+?D2in24hbe%kGNciABVzaAz|{qem@3+&K~QkG;2Hb!T)S}X|!Op zlSce zetwt3-X86Uir@Ht+%X>EQL}}rl^yvPzrL2R1-lqw49U9^-ft*aABrG84?i9NeT9rg z!jtCL4PIU+z#r48UcWlC^SeratF$j4pPB?ll8{g(I&OK6pow=-uqilUCN{Cg08h^? zK?jvYb&$|@C&6rjd7D-CHvBUB^m#nKeDbtqGR_O33vIVe6!AI=hD8v4JcY)?Q!MMi z3MdHqi=-dW=Zy6!3=iWUQzxUN;fnUDmj*(|!Nd-##Jf|lCkM$JQ0Pkpo*oh$i3&C> zh2cjLTUcAj;K#3edj7bwV1Du5W*B~OA#+?ue4tI-f+G5uml$!HD2$K$86G~CoV*)A*0;$tjX)YKeHbs>FGhuy5L$5!-2I57RB|b*=p1he%kD zWOme@J^HAQ_!tG#Rwc+>Pn^KVUq=z=zYjj;WNB9oGd~EjT!bGZVUk?AJrW|<+PPX8 ziEp(NChTKPB4H%&vzT>0V7x!%XM7;~_~Gxthb^(h)Ajc~dATX7X$nZf#(JW{Em-!V z&cg4EtAi}nL3kgFHdSUc=-|D+FT@swD~ZILMHEQu7rG<7+Z(f&G14Y-6VV-vc zC0Rajy_mA(HX3a!SX`#xpQsWjkWfjgJePo0+lpw>x&(Do^5J#G0#NkgH0{3IW|pG= zBBr9=@c!N<5%r}@C8)AQs7 zudQCjpPc_sLpGoR9b2bJNKl+tCrn#=Em1?m{_&R@?ZQ%nP8PY6QIo!7qsk}&i+c6B zRIRLD1p`=4SNfBXdy`F8opKfdb7d3pXp>l$EK-J{9%1=R+ z1j3`AYVsPz{2R=&8fektKJ6_`z?OtREs2CJhSIH0M|ENTl~&d*a+1yC!7R4DEqTkN zsnTsl%XQUmZJ1GC7h1bQ2YuWAXbwV&543HKS)}Fn8zlbiZV;cksrJ#4wpMsY4Q;a< zxl{pprlnE)h$Cs&a>vASM+3Z*TC5_2u(^hyy`zDNtG#1|2R`|ybLx(^bCaj@i+X29 zyFzVjXA~h*;8N$w5qyXC^LcD*w}01uR@b{N@mK1!85dpuXtjRPVsVT=OX*>+DrG;U zd>*K5i2n1L_!y2y*G(qV;TDUHz7W-GkPt2wS^k4%Fz!as_0V{B2BdV~w0A!jZfEG| z0V8_&Ng=0Z`|aH*c1e^42WA0k&+IUN)8fO4Eo&N!XJMbO2~%C1Pm0X z5314)dmRsn-1eV&bO0{~m_vw&EWqXz*4zXN!ZnYjz=IYWsiag)YD@{^W^Q%y5m@Qumrf=K~_@sc>6pxZ$U zOSs48Ff-{SXk!W<2)`Wj{6;rM>&!zj4v(DSYVMg?zq>UAX-?6s4w1vSc`)!UW^$VX zIO{laDQ>1XYI2=-G#d@~(}BAQ!38_Hc8$NddwsE^8!wL&Zn~XDbaFBWlFG-!6~0gH z922b}i|t4F8&ZhpvC~;Na}d(`r`237maqeq^k#X*=_zc&jmL3p;E5*u9U1wAIotyF zb?dmmr(*slf7q9qPfJ$H+LALDZDF}2O5Ztbh2|m%9H)P}0CrzUl${ubms^eSujLKA zX694)JDsvR>&yJ5jH;AqdYzfI!gj}lKT*H;Ks3G-_Oej8t3({UDo zyA&x1!%RD{Of^h64a+!8UpXdL`?d2wjPOrPZCbCgr^;^ipDxqM32&SCNKGt4Yg*Wz zZWd~;4y=oPi?2AggqhKPRj=96eYNH*C35W6g5Y0h`@S_AzhJEe=*_FX{?4})+-%sRnA`2H9x-lOpcsQYsT6U>iEi_QCkd$!RyG~A06*@Jjls6v`S-weNAV{|w5dEDGJH!x)4g&;6;{V@ z9ME5Rnn%WuO{;qt-S;_ik31NNIIPq&m%ar{hx(jKY9xyA%I**%|gAYd!d8Oho$9 zgu>AeJRfx$#F-rM%}+I&tPC#PX|3Kj>~2iY95V%Q<)yF9Ng5i?dDamBzMTs7OMuS4 zFrOW>e_Jm&`HnM9I5u}~B`8gx2v4F=J=wy51dWbgp?N^1#mFa`EiWjZTxUPgcX~_0etDh$ zL>Wi^#`EqEC)Mqd1v;Yg4F8UA6GZa8{?T=NB4N`cWAg_ze*Pc1^j~;S|2DlN-Ur-q zmJs_0(Dk}%znfeoHsu-mAPe~D_*Dc47xsUqgw8e|-KT_f`ot&mq!Q>Q8NwduSgX8c zn*!n=x+l+G^|er+sm59VUJE;sRf9f64+c{-J0LQHJ2e%a9T_l^4Gbv)=RB zP}sxkhf^BvVE_8RKW~=8hJMX|6{+Rh_G)*bwO4F{-f?%Fr#&G{l5@{eIf>u-CiT#} z?2WdzTi1X4kplzX#PhKz!_JA-&u{Ky7| zOP6LE(aE1C@1VDnN=W_A4@zN?+#^!QZJPk?Mh{yjS9}f@Wc8a z{L9p#L_%G9IvhDd&W;U*k;1MmX*HT0V)+7b-+E-ps z6Cu6(O!1j0rH1OS4p*_zq=vAj4Quq4rUd9?g!Yp+Rb?CpVm#+k#q}(R;qM)UwRvYy zPJxkznF_W?Ph8{Q7W^jLBt*q#Mn65s@!=0|d#uR|E)55~NWn6E@g8ZEJ+)xCy8tA6XGFVCrS{j!PeO_uaL zMefFwbv=}I5_H}R(&*w$Kj^kAj0)Z5WSy2gxPtz(Wx!m}ac>l9y=<{4H*j|0iS8`8 zmciklDAsQhAK351$dm{i^1q?{ou7)9$|!d4Anw}{|F-bejM1&^cMRHtL8dR4gq_mP zHIn%G3O0#&wUcp2sO+HhgO=wfCE6>;&y`GjZ&}`b1^|a!R$-fbDZr^S(E<7;>>(OrSred?i#IOixT`k z4cA;^wJ&}f&8)n;HV~F#y5H2rye{ZNMULjDMv7$U}EY(r)B^aSp8XZ zrxD*B!NpD*Wox44YqI{}?)4N#;?|YU-XHNEMJXGi5~VVqvD~BM)p=s9&;SAXza^_diIweaUc%gt)ea)Cz1e!F{1tY~xWFVYPhtuZ3;N8DOu$CpIJ3y9Nte zcsdrhqJ~PjvV@3FK@&wVlda?Uq+njB;B)GJTDRw5W`}`>b$!0C$yAS!yLJRmIvZc~c$s^4le zKbKR3NjT#Lo@kXID#~4zgO582(v6d!#e^0696tC(Bh^bdUIi>|qs2MW=M4>6Yf}vE zB95^3NJEsX9=#Y&6>5M;3}M&@L(=;+$jit>zR3sbSJ3hQeC=zcmBK*4rmr^pj`FXA zzUfw9gfEddEv~9^CFcXdZk3{Bs3H>`DHOe)U&qnMG45s_O8Kpd&PWMz_uPbes)fC% zM7+3A>uFkBZkvwlyYMK+2WUO{0`l?(c|Dcwp%A-onA#thAerX-Mt8!oI^KIg<$ zMG4m%J4b8q7-@fGh*R;9o+0+TuG0r5Rg;R+D9Jmf#LgDIZ#>d5EsxSOr8=@d3t)AE zs2im$k%zxz0SKoBV??fB(SZ}I)D6%R2i)8BGo!pXJ|m0!L7%$7^iNGx33ZhG8l#`m zMGGtnN|F+Y!7+U0r%sgyW7y+gk~qrxU+r1Gp32ZIZEnY3|2ubqy;i z)cou6$?k7pePI69b}!5~Q8~xmVYyYsQxTL*!muI85;s#%`VgYNFVDo#o&Y0JmN;G) z(taePzdfVbYCCRf*sT1KLLp6mX52%e!jU{a6Pgy$n-eWHlt3#Dsz|_!l+48;#O{}` zxinmpzPLrdhMUt5GNY^sd#r2Omk>|!Mp9oH?%)pGO#RdR*~+lFBT%T)ek-5Kd?!R~ zWi0ijU#7%Q)_H)rA?@KWI~I3C%e{^5#R7jZ{--y_YGi-3rzZG3c}TxIjM|iElPRto z(};Gu?tUd3zIf6=yjg= zZWM~FJ3fRmyQm+qJD9S4*GdlCeiZRk|1U$_kK_}_H3icu7h(Kyq0T-wWX`9i-e~8R zB+=HADgy4$)gyZ`S^eAWSjXe1UFu~f*|rhNmO+?=a1tMm`MPpX>*_^AA8)Fb%hT4(h_u!z7lxDT zc9XP(mIbE()`0LEs!|4+iH0Q^roUNmv>mVJPi)5BKnScRFZNC5U$z5OyZScAg~3d~ zh}f0z1Z~?zQ^az0-L+)DpOB}2ReLtW7Q_Nzme_4{m;F3pS49BU`_?Y~_Zc`i;J&iP zQU7~%ExkE}CRfutlpK-HYZ2*&14b<#fln5)rK^E2eLB8g8DHl8)gg&g@)7xQslai2 z1E&l~8d|3R6E(nxKD&k}KdZ%UVXoX1By5 z&>_2#!vx@TzAvk%FHxkWd89(v21M!9=uOTZwF5jS zM5lNR3jRfm^p9}61%D)3@Wmo_!N0ymMIL7%V!aZLqb-k8OZz1(g}i=1OD1cMQlgoep$t3m7pIP0rISCr~ z`Bjm6-N2cSoHHv!P%Af`uUy?x$esKGbAvAA6))PiEEPTiLMBpi0WiVC(m#%rxc(?b zRkm|F^^uZB!+#Evrs7jpDaH{NX;d*QeuN|-d;LES4C;mje0eNBTpE2V&Jc&Ai@4ur z>5W&E=qgJU9mepq;BckmNL}|rLI?3z#2V_e=7c}WW6JbLls&fa7aqoS16bJ$`qcEI zJnxUKW8!l4BE8ljMnr0wJpE2G`G0>8#Avj>FAz=@gt!+9C!15xt)(hw-=Jl7`B%TpCc3z#a&=$ow&hWY!kB@CEj)+qa8>gmjg*QV^4vfp zr7=*83{vzxN~2FBQbaZ3Za4`nnuvp-2H7)Z$=7M@9gJlD86maebfr~yYag_*R=gM` z3s{d_$;#d&)ao^eq!1ZzY3$?s$vJm8+$16#5zx`i-M{Eb9$@(?)b10*RFXR~(g`&d z&l)Lm5Mjp&zHha$q=#;1;~)T`nf4he9Z*M=VFRP#f+z`#DqKtJDfbyI>PDp>8svVJ z>RF28wvEMeMA7*d@xzPijF`#K)}M-ut0-GSjTV?+D3100oHT-l38J)z;&2fA)0rE% zY}riso?3paf;m|eL{$?6ofBMrP@l302P~c!Yp{9?SQi;?WKg(jmHATSNhE5>*Pr`x ztWhW6i5PX^lS1U(O@|D*Hbzn%;u5SD#sdModsxo|K%lhh4+d(?CB}X#ji*k%Kh`*2 zsdtakaazaa>D0NA#0>#;9EE;vRpbWqBt5W?`;{$E8Hm{-7b18y{eUk_Q%IxClgI`O z8k+q=pQFL3GR1`)&cHA*%XaSZ_C=76&PfRR>j?%p=~y*FJ=1k;%p;6iz^dl6+AUzd z_y{kzO!#)x7fklB?2qh68=$`+-nH$l`pp_AXFwE z2M3Ol&BQ}b~M(+E6?bI9lg(p0l2Si2wo(*`NDSMB z!F@R!6yVx^;m;X=tu*|aX8aCxBwhWQKNqG${pHy^vMsUrjP3_@2{Ii-ku%Ra;gF7YFR`LT3jc^N^4PZ(9ydRFAt*9+>#b-~VG zIk9SpmLdkb=k(hvhamk_uvtrIJ=#lpf4>vX@Fh>lz{FsF*(BjKb?w4<{Eix;Qebfx zc!VRO$)qg`WE2ab90EB3T3-Pqb+N|aepAL@QfysCjiOnc0L%le%=F^*`cN%Y<%>B{ zk@@k(H&YoTUYP?^;bi<4;d8h)*obyEz?Fhw+t1ib&=lIYC(8b$pY;?8fZV@13SqGB3d6ruEgP^{qIo9h*O3dV!Q{Elr#LbJC^wL!XVv`NcLa zJ8^Z*BV2;z6AxL*eo1r$R_l0&={~L!jUfN0Ir8in=Q$nGcdG~m#Duq(Ely%6>+Z`( zK?NPKWMWfxl0ixpeVR zW6*V9Lwdei60iNOusn~q3^u6I7YIdKJ=dU`oDvfL%+f zX{Q(|4(C&QWtfjbRH-A^;1FOd?pUCGrr74DwWA#0a!~hyy6=m4!R^4u+eV${thjb; zly=#_Out#tbkW4=A`Oc5^}BWib-KW&*a&isXpRcmpGU(Lmn5%yK@8+d8%ibO=^0NH z@|Od)IO{?v?YI)j^$)xp+2jh3=d9$kau?pe=fWPyq(^v_9nBxuzoXE*%X{BqeQD0i zP}f}te#{oEeT^x$QBe(MwSHzXv`g5U?(`GW7UK=^i^8+QF`cNJq)fwM|MbweNllg>BU2ag3LbfvkiQx+^(}WQh;PkjwjEe#;3R>|<7}pkavQ`{ z;|hS^j6l^eAM8Xm2_|Q?u^^4*(^8ZBH$}(~JjW-rt_RBnd_7ioES#I(CYJ0IUnZI| z>T}LoKSg+68>bKVk>G?`VMmM{{9&twH^+}Pvu11@?=o+xt zDn+LcF_(t44RAJ%P3({BgGItqm@Y)R`}j!eLeL8Z?$`*`p%ocXD6P5sl~~mFu&I6P z2oC_HipBHBx_b;cr{n5<6SG+>(S8FvaUW4FAh4)y4I}sN(0tT@8Uk^Ao^kN4Ej_Er zwdU|kiL@fC(qR~ReJ+yuu-?PwB}pQVuEZ}pYB2Dn7q|(?=KbSRYndV4*+d`ohB=`@ z%nX0vM@A>Z?2hYWF?ISY)_j)9xtO-wtxeTB9OYAH9J}5-0dDZ)PTc2VCOzh?kGYe) zrXbE^hwD3bVwdMZ!oW8d^KTr>MOwovc%fWwUR16=`h%w14~Oah;#|={+)+fK4;UHV z7?~3t62Lra=scKxxN8@UBCK&D1thI3k9kZMcHQUWlv9T-Kq8>u!?gD9pEWb1wa2#6;FB0c#2PlB@She4&?xyKB4Kb*7F zekuzD7MxOWVP2{%5P;GjGRW*-?nHR*AmiSaf418X9g#e#Vi%BDH25d~ZStQuEuu;E zg^|9`ud$<-K60CtG49O_7#g4c?ez-RsR&bnZL=R|cjkBQt(~7pfzv!99Hse3hh1Gj>61#MK*Ui*(kgtYbICUXD;&+$) zP!l)KV|BRBBTFT zc}?=6GyDQNhH0K=k@h_uP-^g0O)f%*pRPyu?y{-NfN>}bUhL~M+TG5#Vj{Jz2fb3} zhqAXhQ<;@{*E|mj`XOjKcIfqKet#G~a@upJH5mhd(@OMh?pqW>&J)BMwdmh)8xAJ@8TaCN*#-f?-gob%vnG27@*NHGNsp~x9H)B(PAb4~M0yqj!BofLX#Xbanp!C!ZXUp_W zSAtd#kUG(ODPpCL#v0T$WQ+;{y{2htGuk~^YheZ_1*Xo^8Nrqho*H>!4^v!tj zwQLsR#d^2i2bbE0I8$w#AuX=c}Y)q8VGm(8Bd zI5Y}*Ay96+npU!zQI`l>Z-cSO?l1|_=(N!qKH&AK@DtfvIKmj+cGNX-V)6FX2&-SP z`|TOj3;0-<#7E?Vedm-AFrCce-+A3fJ>fNBpFTZiO~fXk1WbIIu{`wsx?4J^-;N$f zm-31CW`etUFIjjV=se*o#jclyGcv^VXK?RZ_px5Bg%-`BmdHr)4tc@=LbH*f2NHte zFJC$d#@3Y}ak&Ea)Gu@~T&J5byYD1)e=pRfXbcL*URWDt6HYawIclbph|&8}^g!fx z&0N!oS6*}u#etWA4T6Pc&UZrs3LliPEKKn6n>-0id5~<}X(|E29Ad`>(1d%OgPNg~S^C((IbdZvqdAAd8R6Clc05u{s%tq2t zHz3g=NPNd0+Do^aNZz=s+bEK&61>8_{*qXw46FtdA==)dsQpB2Bhnl1p#iN>K?Bx1 zI0W2{EKEO@yWNFAbf3frwRIoWNJqmh1)=5hS%uJM! zjwZ&M$9SA#9zI2kyvi+#60ZxXI0rKV3$%0m$q;T>H1u~6C2ml~7}zpn?Y7!o<=iy1_VDbNgDaJ3d_J%g9l*Oj=wlsfLF zm_g!}7rk{yA#unq3gX=X)zP>9mO0)J4g6(A3X;yKl$s>?_`AJ}aDhh>5?r_&9lPW0h zlsVRFg&9NkGnr;aKJ?)xhBUa-tyFH&3Y?42#^KTG=K-OAP*I5V`$tV3g76)FHyun6 zav{dUC8<B4)r@A|WBHuwP%D8an z!Op+mn-t|`OU(9X-t1>O zSC6ZvvMIoxiAq#IG0|jKC~Ot`NM=qFN-KB3glR2 z5lGc?|2;DdCBr-r;cZA3i8W4$*jI}i%KqT$ zc14DLn%buH2iCNGpB(?$iSaTp5ib8e56aEWGwZfCyOXlH3v}!F$w%*gRJ8HRu4VYp zAdpuI%vR-&I^wLtg3Z3RQDmbaf0_(vY4rW27Kn)@;}r~@@QBtQ)So3NTy0TN{%)Ol zc0QYv-EK=1WPv3_s&=zKmJ(Iccy-JrRVEKJHy63^Wf%%eg+Qx)Iw3QzAull9w+x8{ z#9hjO*%|HJXVC-h7Z`(W1P4dMzb|0%xij%1klJ;G5`p z{h3KaF*R7HZ?0uPCO}geive4JgcHYA57L|c)_S7g)L2~O)DN|u&!&slzE%g@?4XXi z*Y>8h|H|TeBNE!GGv6yw*U6+GQRYJyu++05tfzN4rhU_V4=K>a57A{AApMm)>-AIT z1Vu_SCFg0S#fP~s2oK|?l{2QJtn)s;n23q$ds0OpU4;=x_6k^|nd&I6R`Vt4C#j9Q zso_{-l@P|{$lCz28$axNTnI%+ZrfZ8@x1o%6j8-92sVcZPy(dBztlHmezSw6CPE|4 zK~me{z;l}AMIEvAVTw=;H+vX51T>wOKd`zgaIpFXxoq|n#%mc{XbpqtuSwM@D8p%l zSD@-SsWfh8R|gve3Ej7dcdt_+W2w(>Ozwlh-PGH`%xEQIDN8Sr9R2b6;W8tarVs2T zYb1##y*vG;a1@1D5_ip}v`#+yFYW;Dof(x!?4XxLPIR_cG?TX=<3q`{hr7&*U>eAY zPLcN7e4K))2uNTB&AtxR=+qOW*?JJ%O)b$)Ne$G4TH1NNW2(V2mMTjA&_`q>FPmn&c{7up|+NP{KP+*CVnp z9JygC^|Rk{iA=&5BWkD6K=D$h-jctSnOmq>S1zsoyUFY|J%7dCTq9jEg>sBfGT2>E+t`>eoCWNxaodag1oG0`AyZ$Vv;JLZq-tkCXm)`nBaN`5(o4; z2^C#WmMH3#tje+EFOdi+6-ADkQOrs|-ZkT8`}u5_>UF}IAJe%Kl)#N0_1Lm>N?trOv^D>pOcEkrHCI zS7-S&Dn0xny&~BK5u^ZdcyLq&eGVM3nykjmolqYnCt!*@m~uVfVu}jvWg%%%SlME;8{=UfLs-EWVFLx}5{y*YYE(&Y_AP79dyM!BS8at|Y#XQiQ=3=zO#Ty#XG~_) zLL<};4@Q1xycH`}S|9z%*Qd{*7^g^z#4D7TeTJq3OV@#(B3^g-2Ee#GgAHf%Yt>OTiSQ?k&p^0(YfhddQ66r9@7BTylPsg;$j#3@@ z4!gLl&lH%-LVh=ycDx33beY+@TRpQ2=~TsK+HvGuSNfc=FNp=oj>U^$hIA`nb9EGV^1M)IWZiB4ehgOskJyOspm=9F%iTrG*?NAp2C+3; zVphPTkrk;rdjc*+86N0@o4;7ft@m5Kf@zUE=WODhMgP}n?KFJ|Q;`R839y`+ zgjp?wB0%YRI2p;qBpCL5Ka-sHg8)72Q(nQ*$cM!|Bd)SEhsRpx{si2lR^}(B@lRwf z^a+q;Jc?_EXJlsXZ0+{d6*+7__&5fSIg~LhQ9|!MXKr_vrONAxXXFtoIP}L%2%)>h z^;!Lms_=yajk5CY2k8O_Z92W@?q+>k(j%^~=X2s192ePn9_{X*Coz3!tycN#rX=G* zp{}NAE1YF1$j0H8!X4&)&bOAUAQ6cJb?m08qD{?dc(wwA6?@RE)H7bj5LLxNgWr** zlJu-z2)0#ljrAaP_=MqDro2>Ek=|OlJmjdrLeTL?TtRNFZagX-b5H7g+zo}?cWq}Up9FO4qFL$Gji=KjXC92DrW%RBZpzu-A z;3J#WIr=AS+)#PeW4BWejHDapiN};g(MSF&Q6ryn=CYr(oM`USb5#K#-0cJHRZM!- z%i8U8E!)uhnd`vYC$-FdIi}8{Dm_f;p~9)XW_gw}+@b^MPfCboU6Ql!RPMshe+>zN z?J$jgUmPgro_$L2l4=2+yf}BrQmBsk>6jA6;>lG$(!@fBcx~7#=62Tz}qWgt^iE#HM8zDvr~9A$Znbn5-#fzFu>fh_j!#9u+a*`CoR8oU-$?M zXG#+kqCe^@3#xpPtlpJ~i)vc>RTQ@@aWdQ-@U=n`&M!6#3M56%KAUmo>6wN38E;|9 z0zL>ML451G2#Mcp)}dUAexleJiNxLr48~WiS#-ND?MbzYh~pVH(&cQRDx-* z_GMa&!XqBLlIz$2F`!jrt#Y4EPq`F8>STJA;lNgI-5ae(W+Wi>26IZb5X{=b9@(Fi z^X*`Hc$(LOBgYQ(Uixx*(KM!{Y*%PV#7^NUd(&-?9lyhI=BqoWtUHr-S968b=1wDF z(KRx#l~6Ww--p`Ox~*JHB};`O2+b+AKS%K9$_K{RO_`3fJArS8;@v%&{&ZE|+;Aga z)+Sa|CT>?Fst3Y^l}5NqoO<~p-bmChmDML_S6f{&J9;8uI?c{JX;)js^Kz_PWKl3u}NR69X!1 z|5W<>R@L6hh321kb}%c5tmv~1CN+bf3)_)I*zthZ_e}j$de{H>omlvZJ1ObReku?S zl&SkkOD!onixEH0jmY4PMH}}|;5S%*=Y@It+WZlCgGo2KT6`<1%Yx?vsaQ74>-gLn zG$jXI4$S!=32ra{4EeLP^X>cJ&L=Zm)3qjIL=RAkUy7shn9(uc;6%2k@$cE3%I0`% z6D*Zptz)Cf)1I;bnjNr7T^rnrRH*ey+S<85m<~N()%n{JJ zlV?w#KY<1nI`m&rqeqd>gH7_wx^mSt_$jQ@Z$uV2B2b@pe*P_t*9 z5#UN-8G&TTj1?m=EEs`ayK>cn#qMlhzkdPuJUY*(Is)ntvolN}-QvYP)A39fIkLHQ zISE*1%sA&|o#xbC2ItOQv}mCm=?X>Q4S{#>2oh?zWKy?s=h9`XD307ujT&tdaOv_U zfp6M$B40@$C32qMjN5D;n0j@p6HisVRJu>^=7p8kdVBXiv131M zn0|f5hw;;otaCDB%9}U4OhC>iyUZPm)Isnva>g0QG)F=i2q8sai|q{`%Fv~oaW2c| zvYdY7=(pd#@utL(LW1tN=CpAMIpkElNk(ms6X(4fum6iLKBc(xPAaOb!b&Ty#47Tu zKTyC?sI=B<>#exvsw+Lc_)^l!_l|<6F#M|IiLb}_>q)cy{-dlS#@zXkqHMk)r<`%H zaYr73A{B>j_(owq}eFSej@`^Dp z81(Sci#!@eXS)t$<;tiZ`A64Rp2EMl{qTd}FfG}Tm7M3;O*iA@V1 z*s$%h-2@#`S>O&uG&vR(g|S)bY78~hQL*E(sY-#oDywC->dMF>87a%!uGG@WNx0;q z%dSrMI#=GHvh3+p!1fC;-^|F2H63>*nutMbs{dielxQxAql-1(NXU_VetkzaEpULO zo(91QbWop^HB`o-6n!qbMxC4YTuHavw$kv%iz`#9Hhl`-PfZ3D+oBN5S!Z?b6RONt z{R1#3ixQ;hvWdVE6wYdRxdmZ1SWC#)XkKGTizt9;us6$c5|o@}m7SI+Xrqm8+D5^t zS;?CRTT12cx)l;!BF7aANqL!6*DZFVd{@0L)t>jL#}wn4XZ!+-3^&{eIAtQHamSiyc%dVQ&N=5ibQ647 zLy=%7PJ@|oy20&3*TEi9?48J-PTI4NbN2Y^*fvVr?%-AxZw*OYbps1o@RlyN>cQT`Z$#AQx4$vMqG(feE9HhC`E%M+40ssLBHn4#S#J7UbX#he9*^?H!fsJv9 z12-9S9ie{XvEtAUcROL!iXwO=lu^oemTFmfU`9Mo1rd4JN=yY02PU6EkBZcz2sjo* zxr<@L6m$TB26|A!@TpJ(CNQH9O6Ni?yumi(AOJejVMDLQk3`KHTm4=nyF-zTiPY&| zQU1p*0m?001`G>X8gQ<>E$~V0lK&FC0M|z=4NfuXU`Jz6<(^k{jUtpYM>ftOjyAcY z8m1`36;^-)Ac&9vD9iy6WUvAcK)?eA*^>__wg!oe11A^Z;Kq8=F+GB;c0L>#5G9GF z-KDHMMFbwEh9|S)CDT1@Gn}ff8ObS9ag(0(`xMGBo$mX5tFO$<~?U92pbkI>sT1DF8tyKJWoMR{(}nUI7Rmu&D{A&`R>* z00&un6HZL>%hv@nc84Nn5C08yOr&NsN7&(AGr#jp;U!U;OJr(bteL7P{)L;+8|hcS zR?=^{0~>=NVJdO((iQYT4TG4%6>?xx53FDcI4}WEai9fUu#Y64q#XP%62JL9t9}BV z*6m7mRZ;y>p#cn1L+SR=v6#zRi>#<4{YE9lDidcq$)t6>>bU7;W~7w+#`L~{(mbqy z06g`93cC;pW^_ZPQjnzw2;c!T-gOD5lOZoD!kd=3kpOA4(N1i1++hOLP$7YqPiBb$PPYP2M+$X~?)%5$U^8){JDOJ9qFE)*gf%oxN7 zWf{-insEtQxJ84?vHwqV@a9c3$)P9_{IS^q)KM#$&S7stqwf9$p^g2~AR$}8hu&?X zO=2Ks9~j;Vn}t;1Z4b~yJ2V3PH78j7NJ?$MPM5CW2BC;XH5?R*D|Dfiy4CMpqd<)f zl_aMqFj$GS%(X*U5S_zvgbrtx+7k!Q<;gL>mKoC+I^t$tPG#;a;Y!l4e9iz zciz&9Lwe#QKpxnc)|bY?#xjh?DYc+mJ@Xd_YoNwYNxNbLng{h z+K%l;KxLyN3HE1j7VYRpD+9<=6qvO~Z!daAhg_;ZQ>o+|=!?6fcNsG_M*& zD@L$3-~Pm3%%P2L$m1DruqzI3 z9^)Q3LjNK!39Qn*@wJl_9MOGOwEh$>?MFje8%nk^#NUSLi91xX2<))lKl6CW@nbpej2CT*5 zpb&O}4R~nKICK+peduB~dpbiDmav30JflH28k(Tql=p%2J<$jU{EJFtMe-G2qeZzK<+rZ3!zLQ7Z$)wZw9M6PiYsK@~w#^Ds$p!`gs zLI2hv8(1(MqCp<;pfwn#o=l(&qM;n@F1XGCz&awy4z1aaOZbcq$^j(YL|9S!dO~ihTuK@E0GbSwo1u4@gZqvphq9E|HHjV-- zu<9;QGS2Kw&H}|UW3)WaBVeLUE@B7eVG{CS5+*^0a^o26VH&I<6dR^tBEbz;;0lJ! zB$AB4K!O}HM(7YFhnmb;o-8DQ;=%Ig0GY1&oX&`xF#4i!`K*ZwGtm+$(ag?m_y4-? zH)w=%z+ngPED$E)5*p-_xWOEz;S2+z74>8f*oh8)iY9D?LCOL2K8gRBg%5us0HX}) zpe4ebvAYa$p%^d`9gwm@#j-SR0yQfVB{88gaPwj%C{WDRNGtPV$DhDK9SE*L>}?VP zL58v+LD<9{x*=HJfDk4I*wo-+0?Y?(!bHX)7t;pOifbdXLDB+Hf1WG}>Bq1dWgo9? z30DT{o=}OnOA2oW9;k5p8Zzr#Z7C}vlsK`_z|JH*uR(615GJ7zHYELoE*d7G5=c@u z@XBI%X%|DUL}qBzYim!eM5CaR5u`KNn7ca6lEJ+%%ApbAwAQ6)J ziUJ=NvOhk~dFV`I5AZ%#~Y+w*> z1qTHM(Mlqm(u*au!5Vsl1zGSohGU0*B9E+r0B*Ano5LDFt{Ab*x|$H0s7m^n@n+_M zn~;(xkg~ioQ88&Ky+q6Fz9BMuNjGkjD+2*5u_3*ILF!O~FgJSy_ z#Bnw#8_+=~BqG4dVePts96ELF+5r+Off}B{Hb$@<H8$;sCxSFtnIk{($Vd4RKojp!^RnU$ z=m9bA(;8J{AJthg@M8~iD7MelHmH-R;SeBU5P;!#aRC~DffNV;5=h~92|yNv0TB?P z5_H0MA;D%Lp?H6n5FSAd03i*=;1LMm494IL&Oi#J;0*GB4z~9T?f`sg;0#jX<-{RB zXR}AO)=^$0S?zaOB`gxdcH5McTCK`j;{$Cq@h~%QN=a(>t`vKyVGw#3d7nWTWPt!Q zK^CHy03IQN5#bRYA$kwNW^J|-K=>MfA!ySeasLzd3=+3{rGN?)*L)vg48Hdal;8_~ z!Oh@vHaSvVJ+LCcfn0Gx zdPzYRvbYfpK__J45$>Q78i9G!Kocxjg9%^^?%)oDHiHQu4axv~WmpNQUU0_owj5|AYm?(1or8xT7U_5rh>!Fbr7G%#63l#aRE#pT($*(L zi)|11n<#=@I~WqQm=vP4yXWy z-&hO``VRj248(v62%wjjUfv`u!FXd~o&kXX?m3oG84&h)4p11NcOsYXz@X!} z4DO%|#Gs)gnpTlnq6gZSzu?bsFrx#^M50*$ONNnoct4vp%=%H{22^$H=Hdo2<1&_< z%NZ#QvnM(^ig^N*v8NoU0T>{d6#oz*5)6T_#rP5C`3?YKsT)Cq*;owb;0~a`pO;z; z%AgE%qL<4+sLOx~Dtig2;GvZO3Wi{yy|*;2!H{>gIbbtCf7IG|n6OeJtSN7QgL7Id z8EmlyTT_jlQ%yN#Ma^dAR-OSElvfk>x}E`{5zg2U1e>s9`3?->4EnjL31AMG`mvdt zjwhP{D!UBmIJ1oz3X}kRsem-!G$gWG8({OfS`(zbx`(GNo3TY=|1nVu6r3MLoRgTX z+uA7L0UegJwrzK=BQjm>7O!Jr6e0l-0AUZ5yRc;;v7dXm(-)}4zz__&pCMb9g_;VQ zS`3z&3hqD&X!Qji8VaZYm;b4t4y@WZgd?=A0h+BL8~PNrLz+cM7u;N1S}A$EEcuep zmcMNqx0N$csE1tdTEHWr$o;vV8)1SOT)B;VjVF5!9Gk+E`obMssij-HL3{{?Kn&z~ z5YQ9F#XCQBG+LnsNkpyB_4>ccDQTMq!L2!O(}B z6a;&x8@&|>A4@DO#Nd62-TEi;{3p(2_t3$U zy3v4y{vJzbZl8kr*>@|9o+>cBSSb0?%N>+Pq^895)75bU`* zb=};E)7o|49TMrhi8;C6-TvMMWmCfyz^_>Lp&=F;VH6NS6Cwfl6C1Dr`<~ZWClFz< zg&(^CLdlR9WrirBat6i_0s~(-G?=BFH3HcxW+Onr8%By4wGr?La-&FVBqwS+sqLJ| zl`LDjeE$hErp%T9YTB%6?NzN@yL$NyHf$JyVnmA>BN}vAFrU44<*EhC+03a_t6C*+ zGpkK?1XOzM3YLJmvF0$A6+8AExwT`nnFCj@95rCTpxGrbs?jJ-ks2AAb0@H-N1FUT z3S4H6A;e|Mh*2VN2o;P}s3?KqF!Kw@btGD>*wNd`i{DU!oLDk-Nz>Z6UVRNarqEkOVj0#&F)4g$e|nTB}&fJ-*th}aQ}tex}`YO1Bi8hJu4 z`Vx^^*+!#oLG|`qaLWu=oN}5P_Xi4$t^^%M(^Yp}cH2Sa-KVNrmDQ16t(P85Yq4dH zTi~D*mzH&L>1CIZAQFio!WvRoBe4+h$YBJOaR(sI&QS&xazeJy5HyfLz-2Hff&T#s zBy5{WIp7c=PB!9*^9=#%c4Q4oC(49qimT4cl8id>RAW##5|!hQO!*iUy#kBmXiaII z$7HMo%*rssV}Vm8IOLQgkFNK5NlGKX0y1ovjWj}vA;}`vth0?RF(I|tD)FthLv;HB z3}b*IF2v@Fqwb)D%8SvVhibF#z>5}K-4{N`i(w8z!-`5Fqj+1>Qlx5)C?r ztz<7?1Oo{&F)=hFKu<9?+Nr&6=R^j^s0<@URXe(KbPP^lp?geW}w=E7vK#RF8Uf)?0Je zHTH$hCgge5SY+oC+M#VNYKzNUc0~Ya2tyOZAO!*ZLabQGLKA#D3}Mg&h{QDOK~RWR z;?6(-ZxLWyF~baIa!`(RnS=n=$XuY@#WR;6%0&YFl<1B|x}um)N2vQ3cYxGGArZ|; z2pgM9X0?%qPRabXRV3tb`@K`sdcKVt#}M(8#eF^TLy{z1eX4kU&#Nebxz$L47G4Q~W(b0L9ONrI-y_RVHSK(vt%`&Gmq9Wk0gB3e3{c);|$ zCyKUX3&VI9m-_5Vi_jp&D#gUXSdxM;j}TUY01+5lA_5_EFvx`z(h!Ak4^J=1L*+&S zG|~7mhk-1~IJtQhL;eGPiJa6T>4&LDCWko!7?k`%s^l;i1&<- zM9EyH`EuBj)U2)AYf&S9u`I0F9;Z_j@F*oiiZxSlEwe-0iYHa7q);~LoZ^sJJ-YEWxDg;0c#TCYK*qsGbZ`!2 zJR?34k(i*K!7*pJ&}0aZR4GvJ1v$V4Ick`*8dh~9%>VqO4p+n4?|3y|Lb;|`QwK04 zhBu-G`({ej8kUwq(VVkV+uG){C1Uutx1>Nsv63}QG8V>J&LS$amK&{u9AvRlfQ)0u zFbj!*Lzx(;kVUoi!I;oT38(Wr&@|@;C z*OhQ_jk*v+7QV#~0pcl=j1`7hegT{&iaJ#065uhANi4N2v>9e-=i^$Fge(kEx8xCPu*zy)W5fVmX&sgd zFR+Y5q9+B31d7V*O3@6noK-}fI?Z1motXJ`Ub3!k+cF^|=v%1hmq)TroUIS_grGK_WDSfzY8F)2=WxAKuIiYXfKGriq6$ z4dRQ6*pZ@*nj}vcBxC58nHHtD*yXkl4O$Pm+p2o^<`c%qj#HB@35ob!RoWH zblobiZ8p5&a#`5bE^K$F7d2>4G5HOiw!8!L2HTiu30ztZT0+qq60cAeWc`DX5DhY_5K{u}5jeeHG^>z12R7_yL{ z>uLVQka6rV#5z+fXernlBy=NBkrP#YBmp(@oKts z*etW^)C+xrISL;L%bn=SWnXobp7afCcgwpm7qz>-_3qpt-JB&djtvGtB@?Xk=CJq5 z92WS(Cq5MpF?b(=Ig=%6QI|f@)c<%F_jsPgNRsDiCH8Tm)<2m?QrV|j1cW;^CKBIA zcmsHPR&*|LQ871_Yg*(bca|T?B1<1LY{N5H{^1x!wRcN3At>WiFhB*kfP92Xbsaaghfb%M>vFAWq}r_71R`FST|<=VrJvEgcz|z=HY##H+}=ih2>XU;&2Wk z2n?+!LA(VG#wT@AR{tj z0y4m&AkBgpb$E*cM|4o|0xLjAj0Pf+#f8Lp5-ZX($|#V_Xbne_hzhufo7RA#MPj5C zi77^bm1v34cM}NXYBI)F=)gd2fp#O9N;q{a$HIC70tkLXEI$?th!BhKXlw#k21`~# zK<9UVw*oK_ZPF$${n&IYQYeFXgh)sdm^G2l_-#WZh1WNcU{f&McR&~^MH=Za;*bv6 z5EtbzhBUQG?c`f;C;uSGA}sZUJZpka%_EOXg^M$(1bYYr$ERKW2#ABCbTl-CKUswB zB9sZ}J|6~o3+afIXJV>?VwhKInwOMbH&J3I66uj71OS!ZcOKa=G4=3ry8;cG(0Ul5 zK{zEQ81XS|2ndnzEY9vC5cjAA9|DZ(VxDdph(5@CvPqlmauSTUd5Tz33fYL0_ehm@Vjp#R6F8U^=$rL& z6Sm0`=|G_Afd3BeaG+5#7VdQ=4>XSLRGAc1bF4@p_$Dl}h(^$|2ggQeycc|bHv}sn z1A|Zx;&M6IcrKinIULv$Tqq8Ad7DiLX5>YE;WmZT7^6fopaoi>7*U|;kf82B7E_5U z^`K5{7eRERK^n9{CCP@#gQ5PRp~u!3mBD|fIZWYVHfzI)DT;DkT48S4kDs%I@~LSd z)@h#RaSth&lqjJ8IUX~?4s*&5>p%eRfTskY4(h-So+npyQD!4nUjO-<+qV*QimIu)4hNbJ z?vM`ZkpB+czz*c#4sunBTGV=mFhRWaMae^wZul|7BUpL&Ah#$jG?T@U)EpW1Am0_h0F$V5H4o4Ct2>Yy5!7yYRuAi!#p~{=1 zdbhy25f_WGtGcmM`%UxUu4A~0mg!DA=3BeASHVhTCHV+Fw?<}5vp^>;YYPK;P!HyC zBtYt{b6Yk(OAc>Ztp%f(jJR>yIs+Y*pCFgCE4H-aS`%E>5>G3*Qmdy%k`D7wYlzE= z@(K+EP(d1$AMyHUE&DNKf`6llG8^F-hh`z1%Vo-$sn9wT4MVL%`LNR$v3onQq`H)U z3%Iz8r|z&0duk7!>${`4H;F5}8ssIc2u1_uS7Ag{%}bAv3Vit|sWyNM>fjEH`Tscu zEV^SSt#(_DLVKnfm!=Jg0;To35-3u#JG)Hy58F!sPfGynaK3s94khRB31dieC#-&5yKBJehIa#hct?+Hlz&VHsnA9X_3*Je8@kxL5~NEG8hpAH zd#$Sbz>oNspAwj{J8~DSedGEPs=5vy+_53duI_4;@cVYWWrn4AO*~Tr?_LiJS!6n)3ALjquvIpMhU)WwzNxx5lK9!ecZ?H!2b>&8^Yw^ zuH8U$KlWp*WXN`f$OA$ke+EK^(4EKz2SAs1Lm(%X5j|tj4MJ)T1$@S6Oin%vv*n#$vRz8-tdae=rvbys*r z3}56WVFVyj8Zym;e|_h7`)m+P=PZ^j0xcsOtiVgGQJVFQprtDZ~1 z8o4lE?b4&W77FasS>c2byPFFgzNhTBs$8e7T+T!d5AN!|z+edX0XJU4CG;C6g)uy| zm%nV3xewxCS^dvCD5M4oq)7s#F0IC&oTI2~%|y$PkEqSu3cKA5uHTHo)2yoJ%dQk% ztIx2`jc`+3)J4O4Q|!z^7nG=ranGZPhg5Kjyfl0Vu?Zz=4j&7o@1Yh149#ggD`tJx zH<8xfyVhH0%0c|rsw~&$46E!~tGGaOd98k{O>=LTH@)o3vz?2!T^ZB@8E5bdB}$-= z-K<^F*iS^d2ko0?nsJeL*?t*;+&qaE?Adu-6Q4Y%8>|kijQ^+bFu<@H;CPyTmU%32 z6ASHRL3So4bC9TJG*}4M7=eQj$aW!;;fwX~wv5e~Q1sFWygO^W-wD0k(|F-x=72}{<+`${SOW69djDsFrKF#3#+v{7x0`Ps%TSx# z!J19M`R&bHZiP3Yg#Z1@0zT2C?GD`_j(SGC60}NC;J zGcnZ%>e|c z%-lHz=ak`lh6)uiRLUrUp{86pcj&|sh&Zw0M#zz;E~Q-AGDxSHQ_h3+GG;HD!*n7` zmTcMSX3YpVW8K>IYiEB@TsCP`HGyRWk|Enkz$byALt#y(UEKI_0xR)R_4+lcR*TNB zQX5;=EU}BS2*i4u))0Y6clLfK0RP3}oH+#e3jRxYOg+Tj#*O<%zue-F-Uw*arj0mq z00R_Bvz9yqDzuw&+NqeHev(NjqKp!7LJE`9>?x^;V@I8Jv{EM>=hCU?nX#^cMwqiS ziA57xR7?x5IU+%iF20uF3q8IJG6VOZO`Nk2)F>xX@QX6mPsc=clE_8}B13{~4 zw9-ynCbiXI(~`A6W?<4bi*nnox8H&z>OUsa1W-A6F8a!%bfR;v9%_J*- zu#-iY?GS0i5Mc~?WJbO$5@W9%seppVA(R;8Fm&V-vK(>s2p%tw#FKFd^Q)|r#f3VG%De}Qr>xC zlZ}FV3Ajznef#}axc`9F7F@Z&L3GZYuoym(6ar*%oD`=?d4!bo2mpitjqeyEFaiWx z4~m1RRe~C@%VBxtlP$7Fb!^TKT=2CsD{$SKbGn(qoLLo4LfByjT28}<7BMWxg*j10 z$DMXMtwX22+(yz!_=2de|LV}eBTRR$0O+=?is*n99dRWJXx%$5u`B0cjlt8F^Go%LpVd{3gRdw zP+Kl@%xH%o+o>vd5853I`*$IHz(aV>VF%)H z(W^W`V@1Rw*8f{7W{5^)PKjqYgA)8=J%c<-gG2a2>A)AlX^aqs)2PN4$+1EkE(v}A zP}lmV)IPakDSoe!4gL0ZKYo=7U}1zE;Q)BJ5y^spvPe;$#?lB)7=jdlc!XUpm$5V4 zK|Nwn&k*=Bgo7ZY4&^Wz7d_HNXQ0p-*k}_S1?Ixf)J{P-^NH>vLq(1{^~ zqQgZusQ)pIf07V>&S(Zfn~|+=mJ5`ovPld{sk2t`E}`d>)jXs^PFRl&M*hh{bdOG+-J3DL5+Y;LrbYCXxb)9A!n)&n(dP2`>NTRHfGa)+w`0M zZW^r`v*w@p!4Rnq36KcMQDo4PL_J(BC;a3n> zj{j@uk&k5o1Tma;mQj!bkdY`uBP^E}M&;?lF9Xaos(DzJs#ci5&`)R#e%QyV}?Mw~+q)KQdmD{$1VF`?wEDWKE zwcvvTs!e)i=g<)=LcsSH1 zG@IN}j%PfY0Jy*f0U+^cNNC~_1b9ReA)z_yS%eBOPy!8@3j=D2U=W~C#VvZFlWCX( zAFU?0onf71Su=T6e6@AM10*Ya*rOf_!^OKH@k5VK?hzxe_Z=RQhC4XXm;d~x0oN{z z=1<&W76tyN!WnMyqm=e-*iIvbs~J>p*Uz>2H*gS&w-h<85%5+hf` z^C;Lv7?i*wm%VI4#y|&ZFhkXtTKLg%jbxgIH&wKzxOvU)a2&dW8cX;1Af6uesXwCX zXQ1E@2=i&Kcl`%r7)02|u8y0(onY49IZv}q&7WS=jYB1RH)E@KHO(sH=brm4mX3=< zJRRzgsKX=jK!-Zif%J74#32p=dQjlP6|?9DIp&dg%r^<`FI=*CCtlv%U%a9Un>#Dm z4f?y2{t>E2{qhIFde`GZ5}&}uEO=3l(BeM$B+%FyWvRZRH97K+$a6u1)K?1M`e3%C$yuBnWJM9a?k?6jik}#jz zHVX?nZK^zR+aD-|nJ4VRSlK-#Ybf6+`~G%D^cpf-#bG-9K@rN!aPGQ$oshwoRIQ+Ez2`HLjR0Jzw$%1yPZIMGD*zD zFfzms1ix*IjoJ7!N8Cb9EX8l4L>{z6GsHwwY(;Q_!zt9WDvZ2E6p%*LLOpavUF;oH zBtt-4#lG4_VN@tsJVX!-zj+xyMl3<|dqngbMrdpyF^sq}BridXMr@3-4#cxg+_O() ztvejLJQTESEXO0$LEiI4RkTJ{G)H$V4o>7bZ?wW0u$OQ&!Smz7cicw~48R>!N4;{g zVB|-F48v`_w(pzA3adj=#6@x}NQk72UYs=_Y`ciO$TvL5#*@QD6v4?8$8gI%9~tDp8P?r1j=IU#-Z%9S`3g|#L8#< z%9Z5GUu;UBRLgygN?Dx43adw>49SOt%Zp^ob?iwX)XQ>|OGKPX@ncAu97T}?%!uU6 zfb>emjLWcuNxD?TkHkl3)W^pROr8A0zI4pYG)KWa#&4|3WxPtfM9RtheS=+bjQ`K$kx0~XoO9Ll*!7h$;9MM%|ypBL`h(LOX74z-yBQvD@y=5 z%hSxt<$TS}ti;B&$m$fv=A29sv`4GN!|6=R?f;C%+)PR3EYD2r&W@x?I}A^dJVEMI zPwu?VRLsuZq)$y`Pr^(_=tK_bluz>1PhC9EI!&1(eFLDWz>49*C(O%s*G3iZntB}5dhOBJon`HWE+ z6;b50P#jgz$P`NNBunrtP4N^^%_^0AOQyGRa{Mpeb@tGC037EiC`VpLBrG%-NbA4h}fNE9Pi|_|5a9CgH)>IwLF14FBjm|d(RdQ7cX?0j`MJkohhdpT4cm0Qb z5QG45*K^5LK{x<>E!I)xO#Iwe8~-9$e+5{9EjN`|)|0i>4*OYbZPbR8{k zg^fLR)gm3&LIl}{UE1Dh!|u9Qg#8(qmD#OrFnw*uoP}76_}75_B$B{d;X~SmHCk(B z+lZZ5tNmDwj6+rJ&jy`TSf$&4P}-4Q33+&g7kB^(AOm_x326<0Be;ekXn-S7*;s&q z2AF^%INX1bhI5UEJHUVkhyhz@2QipjH@MYu4FYYoR?J0!OP~S_5P%}!24IB-C1`*G zPytl<2ZjXz1327xV1NKPfxZm_#f65(oq#Ra2TdS>&b0+{SqXhG1QIBK7}$dY=v82q zh2o8YZCF2D_1amv1}cbv+W!>=mB8Eqh=xYMfVt(Luysln(%GLe+YNwL1q|H5EnLg} zR{`JyVt9dJ#Rqw41QH+s3DASX4b}u`hcdu`0;pVQfPn|#fJGoe#5G-X-BkkshTDn0HA;a004_9fEUOAUQfmc zWoUo^7-RuxgZ0B)0kDBRP5@^e+nU8r5*6F`h1it%R|1${XiW($4gfChVr%`^2gm^i z0DwZy2lLQk0#Mw4=-vl7V?GXm4`2W-K38983OsfIU(i*HK;r-?fE|eDNJ!>AJ^)$n z-2*6LWdMKzNa24y07$lE0U&`shUTCBhkgixaL(q34d!9?;zmAKYhBqs7G-=0Rtb0k z-)#XV1}rD$(r@%sFkR6bWv0?SfNizcm<@m!I0P{OfB-1xkgeV@XoYPE2j7i>V$cKz zD1b${+&vZmH~%08fF%G900lrG=@Yo)szu!`F5qiu*g9TWUS))57z0+$-QE3%MF!q% z4S)(z1vrT00w4omz~=?9g{F{(NM3+2NZ;zEgv+%T2{?p1;A2_V+JATl65!P^AO(Oe zWSS=F7?6Zc@TB%lPxFCam5|?k24|2KX_7AIc?JLuID}4c2TTxFGGK-|V1PpIUI35) zPC$fY25D1(f&++WZVu{TrRr5627Dd>LpIt~zy&8@-gr1;;Khd%CIETHfS4`XtwX_wFP>Z z=Or#zxfSXF*n*V6Tmoo;dvybDSUB88WZv~%sdnUT#Rrxq;f3DSVYOV9g@c%eZ>`-2 zIGES^9`Rw-TG>YBEuaV4PGbYtV=vZdW4&2iW?zL3Y@Th3^sexAy@+{60OrMq+}!{c zU)KWg1FfZpb=6%q-s8umUx_7vZFTElJ@Sx606tj1R|Z((7LR9q$kuJ; z@Z1IR;HB1W*zxto=V~?cZ4hw+_+R;+*U{bqwavVX6-|27Tk@W$jXv642G~Eo^R5Qt z^Z%CCP3B@aK7r>ZX#FzT4{)l@V@Xemc;MstO>Ea~aHm#e2@l@Wh3ZllZeg8i zkac9cj@PnQ+HIC?cy0r&C1qA;X-012E!gv+HOb?=(m|zV`dz_757?$K+chQt`6hs_ z=4A!I@nPQLcm`*Db!s;D-6wxRM)zj7HsR0i^;2em1DF5~j)zVD=@kER+g@SLvhltyX0}Ttv5pDEDS_MeP=R>}uEWuYJY3jYW(dO+dfQNzC-~KK4VO*hDwl zdC%PfAOIQ2co}$HUhi;aW?r}k08B?*3McRt2V`>h<#yQIJx=69W`L)T>Vo!WRR3pW zTCZaPSLSU->R~lkyB>2}C-{5Kb!-M+UibB9U5QvD)nm=nTxMHo_ho5ccK&|$F^=}+ zFxv?z0gab|MX+{CuylNWYUtKyXjSxWaO)NifE31uYY>9~c;@-#^5l4Db`Nulu=^Co zW4jLQxRzzyb=!QGRf*^Kpic9O1$wQu@q;(U*rdgU|IqSwcp46TgXZVc-Uh#g*AXw| zmelWF7ja(o1xs&tpmy$#7w!Ns`+ty!Js4*>7VQ5bA0tfw5obg+BU&b$X1r+R+$X#@0?`;L}73}9O=5Sz**=wIxvj5lieGXaV zCU{xBYTQqbc7OuP_62qpSX#e(F^BiPA8;a+{x5dhm+xrm*L=6teSn}uKpz|c0x0nl zWKbZ1h7KP>j3{v;#ftwfV$7&<9;{lqcJ=BNY}l}3#*igTwrpjyW(1tUj45-bvp*;* z;>_vL83C6BmJvvX%vdo3!-5g$wJTRGSnPB{jVd)~Mt?yLEC_RGtDbs-4g`oHNetK) z|NLP&;9wiRi_#bb;9!XsBsXpW1laPAAboB020DH!T<1X4i|6(>;>KvXVWJB7Z0v!ewYj{(x|L)g=xYBAOBq3!119w2LL2s z`?^38dhY};AfpMx0R(>;=fyX5I(3Q~FN?>i~m5e81LC3Bigb8^COQ280XWS3nG_JVQrp_Z>t)U00wNc&mIF(Q;>6kO~%M_2Ncl8KmGkTTZ*SO=L<#z>;ngiMPP%7WNRe^SAJ#f zm6iZ{&?C)m67=?4a0oKgo|D$S_Z~;vbw`p(D5=DfOEhsA(;qWDnG;Vw0VNbsMj?e> zQ%{B2=5$pB0l)!p$+=cOP&Ae&hi6@t+C?4)aDbrA*|Hd3Ti{dUWVZj{bKGEsVPutB zGD_1>pp8L@Pd-;MAn1l{jglt-7ckI(X_7i1<7xqzhRk9_4GemK0D2jkmz&E%d0lo?R{5QH;}uh0Fy}4R)OyTv+fbAl#n;epzipr? zo}%u_;y;kynP&hDEcsP}n{MdfTM$ZkRjL*S5C8#U7C+aVfkE3-U(}8m zbr*3Q!x;G2kc%p!72}MBc^&})MPL#@$4x&SHTScAPchb$Pf)pZDc9~zx5J3bxZ|Ce zWr=26x*1JuLV=@=$!9?U8k&Zd9ySFqgAsw7PS#Z&=`{cAYg_=K2MgnnydkZG8&qKj zQRuLTuuy|Cw8(o>#~qQ7gmvL@owcx)9@@>2Suu1H2$2>QjYPykxrs;-iwF@HO2ma0 zGa*Hs_{1Q}aDW8ti0Te_5}9eqW(<_cmrNJIpG@#fLQ_-Gu;|7hYJ_xeoFg5nQbX;i z4u{fe9eH5qLv7VDkf&Q8L;xtrLuycq7bzgjzL>z8jd3P6F=NlrXr`d0iD(!^BqyB| z$3t1MlcF>o>hO3&@92p zCNEgaW72SRhe9SZ+f+(u=`oc&^dl<)+00sIL?ZvQye2m%qR4;X(u-KyoRk=(!?xmGm72szO<@5m1}c>I!2*N7p26kZbQNAUPNja zu+(Jhd+Y1Q<&u}6TGj2&ylPtCTGze;o@0E~I$zBWIKiY8Z(I?JRl16{vD00zQ59_A zEZz~CmR03}k9*+{3z5Nx)um$1$=JI-7QpQVF^dQ3EK{PDxE!vjhg)pp>fqPGpfxW8 z?V8@No;JWY9&&pMT-**@lgLW$posrJTi*S0wZElJF?*M6WdXie!>yfhW@~)qFQ-t8 z0oC!7a|>M*Q@6qa%m|6{w1IhAEFmqZ-Hhby&y3)M7%w2dH7|TT4I)p>K#{&3}4sUfD z-@gtvM1U>qcR%FQz6f=bZTtUV=bkspD)TRu)l6rL^}_{~p{fU^kpNz&d)K(ub$A={ z>1_;~0QnHY;i4^sX3q%F%}(~Z+8szjU)#_s%cexakr*iH`ysBfaH1Prf zu_YtQ2mvYf?1pwwfdUm!Oyb5qHG)KFy1>OMWz&kArG;)gJX z&;SX<<{y~9y&eM700#dA>>s`de)TYq0SV{)l;AFb85(%;~|P>KPG6e3~O@P37F0-|gMG&D_-GSBdq~iJ@A< zZQaBDnhWIKpTI!{5C{~UiV!g10e0XhGzdK;gxxGdB&0?>2!I>p2m%z!C^!H&P(vor zgNGEsF|@NSr?G!5X%q92}s)h~Ye_#utWRp?Dz}B7zuxU!c)LK72tZU_&IVMU9+* zJ3NEej2s_mB1XibCT>aySOYO+fQm#)5J*5LP{SM~20oA>Dk#ng2wj#{oAq%Q_8poE zX@t#j-mC-xF6yF++`|P_KsH1n~mHS)B`Sv0wyvFNXA1ZZXzg(q77oe zB0@pAs39A=p;7@MnN?k>Ntk*S)e-KR@R2|RDCJtjq_DZ9Rg|6wDMP#^1n;cK0@wm8 zGGPn2O8KD>M5>@NSY-!hoEfNqFWe(S2!d78n=Oo78%W1nPSI4}T0ang9Kg+554{vRIon&BPlGtj zC_G}z1zKD5!^z#FQ{ICEh}%(S#91+wfzepDsoS;zW~nIUn<;~55~IguKy2XKKI9t4 zu_gajzGYjyCFn^9+!Us+2m%*G%E|4+9neAfLF6_7nqCMZhZLvB1R6#pglP~!ILITV zSq1?}rWF=RgSg5GSjL+%r=$I0ycK6=RvKK=C%AxGasuXBL|g`rpghV7(lus{Jmv`c z#kw@*U=n~>{-BsymBH;8lno`2saP@!p9{EwHdsS8EU5Ab-^oea`sC)V$=O9j93{x* zMPNet^}`Ujje`K92z-DDz`$58Mgq9Q9Q?r#CA8(bAg5F+&plSp;dLn0 zl%{8h!4|lNS;Xj!*657H`62OWcU}oZF;y}U~TtIF>0}OP{x{MskLFWHm z(ug3KMqkRKX!7D`Y(ad?=m&hjT1=lZR^N8LmNPc!jI57%qQW#-12+ICGCIta{^f7N z!46>DV-kSG4Qbe%+nILFn`S_segLwNP-MGR`qb$Cc`vv~Hi5 z+8}^>9VJXDjlP*rI_P)-r4T+>bYb0u6=h^0VUV^(XHw0YhN#6jBDy`Mu8w9ySSvr^ z0u-c5uudj^5`YSDLnIslgO=!p_9#}a3xUq1ds7)dfr_odV#JjKrVA87Hgw;>5r8}x zLIxaxu!0)CAzePekhNm0$4Q*UKH~t5*{DHSP?;K1TSV*W!)~$U#aHT+Y7ck;P_g zd8_Mske{pzzgnT;%E)h)ZLFT?$U#bJR+@CQB^gemMieh1n8hfBfeQ#=mWF3yaOQ{d zB3V9WOtJ;q&ZF{JhG$BH0x;-5`0x6}=dIa^c99QaLA)xYhuCy zqe`#*HiR@J2J1FSZmxV{Hy{F5 z?!zOvfjwM<5)y<1cpENUWCvmb0Tix8N*q2kF){988Tg_3*qZ;QC}H0GZrBv@<%mHJ z#A%IaaXD}?n`M9-99ul>aWzD-Cw?ImBP=bFTm{oeBpie(cEUPH<7b4xIoQJxV?hX@ zgFW;E3>3jMTq6})#UhG9gJ|Re=z%-nLMK>gYr@$17Mh^0ZyfT^ZxcY-aboUtD|u<31@1k>^~ z$igm?rVA*62p97)Co}gcaWkO%B;_Jpk6qouOedR=wshyj({9P9CgM(U;&@rZuRT~+)+T~h^X>Vpc`8y zM;C|OjzR$}q1a)7OlL&nk-+etX#=Q0OSWE0uk`lfrQ&43d`KKW#6n8{qJu!go`tFD zk#*dB4)R^>s20KyFb+33$Ucw)2~1D*^r=~`@?XIwwyCc}=NtD0Z9~ML9<;?RcaQY! zfSnm^qVTj&dv!{4KqojzJDdUdI0&p_f+z+?xtxV&CNNv|4IrW@^;8Xua zR8O^ju80F@0t^@=vMIGp4{8oSDAkGU7!hH)5@lZf)|xd06nRUS@D26Gc%CzR*NuI1aOn4j&wk-HeZcH-)Min7KI14frzdERyy0 zi(i?j?Qle=xWaV{!&$WE+PICwTr(bckS`gIpE#6}D@AABxsJDyzxa?FID$brks*16 zTHUFw_zz!nmESn^>8yOEZ}xdPh+TP^)fNAbbBm8>?vt0fl}S0z*7%#V*qH}qgki3e z3mFo!D^#SMXWNbkQJ~S)NVooZrNOd1lmQ0(2e}37jM!fY@nDaH6y9tB$T+(@(rBfV5NMp`zU&MB8(*dYNyo1&hZkUz2^_JX%58Xoq9Yj3( zt~bQ3&l*KUy3yEqcQep;Gr75n_nvFqg2o%m*{cusx_dyJ5*i&pkOEYy#=G(HZT+~Sg%|*m%L-)Gq z`&m3ftnjJEiBGzpHznbK2$fyt{!p zD_>uczcRk-8VBc_$|*eH{yT$9BMA}w6oDiNNxck91aQ{8>)txdZ@9&OyRUmDsRKR5 zt(%?tyrxU;b_XG;8I+nc3!Cft$s7CqRl=SX_52;5*PI_%9D)AdQNJvy?qPu# z+!&Vpr%_0oir3Cyn6L%kKV=3gy%11@;6HhHTpmHFup+b}4>*W2sqfK?2Nk&ebOXrc z7J~Soy(nM?%8`p(xpwvXHK$3jV&}oCm1|e8UcrV9D`pH?vSiEXHfu(}Szf(+`8xYE zqXAgKgSk2*aF@U`0?Gf787oF$STF*;cIB!CiydLjn=gu0R-lM8XfTfa%NHbsHDVc5 z>Vvam-amgqCb*#nM*%&2qa2`;H|NB^dlxEXqnA&J8+Onn*xJ2754Af2D8)o6hrqoL z3L3)Ul5nAG*s^Kc#!aszK?CsU#mnHPS|bFuWEw#4o=mk$7)m2o4vhd?5~|G} z7g0K6hyd^!dc=)vh*`j>doZYAm1m&X0H57@`z^Sc=-CI36Wn=550mowqLXeSabO#n z3fO_3dorL1KmGXQj7P%8V#u0T&Pq!ywuT{~m?YmyvaQI}qRcW_4C|4~9|fNdNE6*}pt!-r2l z0i|bB3HV@D0#v#=Bu24NQfVIuC8SWs&_*k5pE&2N(?J6fG%CFqP-zE>6MkBN4|#n}k)$0!wDCm|)!w^%9F~=a249d$ggO^{E2oSL% zOcJoxJ_2?dn8Y1Unp7%$q$w3r`tZ5Off{eDNSO^7+D8yBPUC{0ekQ^aC}qr0!52s= z0gnKV7efE3;fHg*QW=ChjmfozY90{|jq%Y33oh=|xS@^{%8dY#`RFP=mOJdwpP;i_ zfKf2#i?||+tsNL>Zn>%`rgtL!H-Qd;=7L0@&$ftPc)tx&+;NXoa>-(v4CcwoE}OD# z9~t~HA!$z{pal|x>uw?BRGQi-d<4-r9s`>CSL189X?YHqWOKomdnk-4oI_)htxf{& zphE#xOPy3D#R>Af@yD;+c#p(Wib-*n4U9?TMfSPiA%ax0+W$eOhU3LE=pD=j|qPMZf zBAfqAZ_McLF+F5M&p$%9jy)WXRf{Ut%a%vLLs{qpBKW`uV5BHUaV!H7oZuEbmyx86 ztR&J%fIH^!hchgKVy03^%U-jvln^9zxx(1MCiOgqU?O!QVi-eQbA*IsP=i}=&_6(x zffyWvZOLm8P>2YOGkgF8Qn4UI`jG)jKv04d+y=nhqk>r_PbZK#!v(1GFZ=xCY&#@C z5B~?iAHgksazm1m9Ame(;7u+4<69e<#vm}BWQh2{A?p}~Lwq1d4vAXS4L9(w0*HVh zFsNL?PLlx%ioi<*fDuDtIJ$)FU?a~^!4@?}NJL&~ivPewBWFh`F2G%q1mxs!uXGN66R`ThOxF9TbL}JjtB~rnr(A za%Uz*BNl`xh!ZGrhzya3Au`~#rU7M8tEdM{v$IIyL`qTg@#D^TD2o?_V1WPVhY*}4 zGD-*#jovZo8d;jee17F^LW<*B+A_x^)$wk5i_#vi8bDnQF(DgJDFFXI!uBb6`XiL~iD~8~eEIpqxCEfdV*10`NwLAM``W zK**3u1)>71ssJWn!ZPgc>Lf8N2}yZ&xX3~;XFOynJ-~a@js{R>x#eiK7UfN?q*5B& z<>o%mIJjA!gs8pK>q@zhU)x&MxeFQ#9Err$xzUkHcNFX1GAzxp3^D&|TQJ?F2oGcJiM5BWj@ zXZzkKT)w!|xyYTTa`Dwad}dg~3{i#v$S@63WWY}SfI$(av6?n?2onad1~>2;4g#2} zOL}kzE;<2YD#9QJ*|^mM8OYY@JFerRyD4Mc#k9QBN786#z@XA2&5{i+0XwrrkX+nj2A2fRiY+! zvhz`ePQZg05PN6&fI_(e+~}paiyj9ikKtEMj6lo#y`;8Zd#X>tqfQn81*k#UN=E zAdx_W5l0mdg$GD}aq?vdeS-pn2B<(37I_B=N+S@6#9$Al*?i}#K16*qf&@!J;%5?B zl5hxM5dyhD3CQ4`i8(+MFu2J8D^C=I?cRq*eSicozo|K7fCGmZ+gx3<1$+2@0>B5S z;IkSx4-*`$B0e~YoeRvc@M90ipbRjeC}yuJ9zX>?D;{h@4pM2x2tXbHfdoY27@)@h zhoJG?z#Ba!V{Y>`4ZWZt~Ab z_D@O>O%f>)6T2dMG(!?Y&?_$S53OntQ?V4Uu$gm8}B97?JT+%SO@UC6n#kd5mUjELW^d%FwksG_Qe9mm!F2@_k(QN?E z{yY)H>?jnOi>%1e9p90B{KYG23LU?yBJqeFXObt$@fPW-C+RRBnbDli@EJ>T4T&qmtdX8r zGVp>DDx*>=`KTrZuB#|g9d+^|r;;nX(kuUMDC5Q%e@mUB(a%m2x0sU9vaz|o(knO|8AK7pzQ!=@*Q4aMIF%#1v<#HnHQvdE!F(Xql zNs$akk{BfBCTkSRGsvNHQp&n}ZTrBUxT zQy_DbE$8q#Gm)VXKpeZ%9*OfN3+^$IQ#|4B7DXsMyOA%&Qa0^Q4W$tU29qpQ|MFftP$KCZr)Znv))i=!gj7 z%gDo$BFIa0sb*;DOGf`g1=93ev5HNt@-*L6D|eMvfk`YNPXgQkJyev|!ed;2X(lWJ zizY~+F6f&=3e=!!Rx|WHW3yUk^I9!cGqv?T*D^oz6;kn`9mK%1Ov42T%a~-!5FF(q zVyuA}gy}{hb+m~g>cOaxiew=*B5$=g9TQIZ?^|`{tZ)Ge+zd<%KprAx_DR2s@~a!#}66=y*e zmd-ZOewIV~6%_xKX!F&?HZp2d;=CFxMO{j24J5bVc4CbOTP`BNqBUcSa$_kqFaZ)f zaZ_YB^=wU|>7M^C>QaybqRZNj0oG)+irl~)+5_oOWa(b$VCZ#4I$<4RC?34zVuh4E z-L!92vmNEsN|L8n+`t~LVInMI6ez$aK9?Jyt~6W=6|#XJ2mm@ptrL(es)R2bD(Hw# zcQ+pQQ76|gIkr;S=~DA4Q<>CTJJ%$Da0YACys8R~knaIvz$kogQK$j~3-)yp>P}qM zsrVrll%h}CgF^6jclkCaeHUwdIQlas3>vL>7b31uhUsJRj_JEbN*gN;tH1ThNg;6LH@8XK_>bbDeq#)el~asy)n6HyhtZf4F(Cu0u8-eI zisO@tGczz;m~68+k(GFo{dhxvn2duMG7-3vw{ejxw~@n^8bP*>HT8}=8I?!VR`)iO zKlE=?8J6kiljFE!%{N;ybz8A`WMi3^!zqDVSyx}Vmxp;xVUu!cd4<>IeBUX3Vc2Ym z8JgYblKB;whjuli8JmYxw~CZ}1G9xmS#wQ!eY2UI{briS*f@jvn#-A;A#$9!*>X+N ziXs15mpQkc@41IdLL!4USFL%7@tK%snL2AZelQoDU$|@mTA|$;NPxCHd)JH?I-3Pr zg)jGGaXDngIff;=oPRl;{h6b&nWDQ{o<|v@OIdw4I;2JVkNG)}BYLHWd8FgHHg6ey za~VJFIi{o8qlMOQ*LkO5d8R{|KIu4|Q~H^MnwNXJpMUzFmAa3I8Y%Jan@{?vof)cG z`kVn7qBl9KD_N?+wy3W-r%(B-W0|R6+N;kxs^9pS2RfPM$EKMXr|o#0**cZex~tI> zukkpn<=B=V`Jf|tm-`x(^}3AL+OP-Nt;d(9>C>cdI-`+#v4y&&A3Cx5y0Q_tu~Yw; zq-)x!5n7Tr8>}yzcQYGcNxNtNnxK_=o(H?0&04j=7_l{3vuB%YjoGn@8cF9moSHeI zal5Uz+NJk8wS}8kSv#)V>9ws|v~ocO&f?^ z8K}VM9~)Z*+qu14w#{2|#k-oF8orY|yBT@AEmdskxvcNHzU_OuPrJH>`M*O{ zxh=Z8W81$A{Jd8glRuii8C*>dT%HrWzg1eoCtSWET#z%|!0#KB^P8QjTC!0Zt35nm zH{7Q?9L3v{!o$|W?Ha>dTt91@yq}uJTl2-OGsZPKsdHRBZCssk9LPg+$JhUxtY!Si zi#$1n9Hg22IgebwBb%-#`^2HV##OwjSscrC9K?|s!IRv_yFAMSe7uL8%z0DF&9}Kr z{HlXH&7)Y$)%v#MJTljug?*ckfP1vB0-<{S!o5#ic&o+tTT+xL9c7aroT-Ql17 z;$0HnNqpWt{k=Dy<1;?cMII*6Tf4>jEGPciD_+t~-rxm(#R(qf50c-_oqo_gmBG%-odGP;H&-UdlTjHTji%*;a|S#dtT8BNrHQm;y zzT36lAlaSN#om(-UcL4E){Wlm$J6JtzU|Nc=|eo~M;zzLN9Pwj?zi5Dm44=#e(x!B z?!mmm!Jh4Xc^UGc3Z$I|8sG67-w6hR$gAY-0l!1w{ z?W4VoD#8pHAOO_;09wEG10Vnnzzm>R7_Xkr+x_x?Bpr0X*j?ZEf8X^ZAQNampUnrJ zdD~-$p5nRv@MU=#AW7eW{{R?32CU)qD?i+&c`Q=l0+e0)zaIbqAPe9j_XpqWMcMF8 zUzY1Y`K|r?4}th?5&Ln!n8#xCz5V;8|41m`{A(Q`oCzFAkl>#g1QIG-$grWqht3Ez zOSp_cGGxYz5f~PXK(Af7YQbXnu%toJq5$&6^7k=-dh515Sl0 zg9f$ss#dODy^;nSHjF^Ar^PZBW6Ck<$B?ih{uC-y>(BqMV8elCU~s==&+6gj!(=YNL)WS&jCwr!T1bFY;7x-DGMbeZ1GyLVY%0)Ue{ zV}!7>VTu+pYUJo~^~kcYW#8V;y?gE4l03T&eRMR_(oat_O`X(wYnrc(c%M(de$CvQ z-?Qw^^>_K;b{T`0UVQnb++TuO_8el-Ek>PV*F{!`cJ@_h;e{pTR)=_PkvA8B=tcDu zYN}zSA8()OfZ>WPdWTQLTF8KVE8?2*f@P`d)`aDD3@&~8ibUTPzn@~)|@ zK+H9Z@2Jt*+3&+7W0>u>xsF$`tOtvmuzL#!)o{r)f4A<$`&9}r#%k)wrn2__=xn~E zN;`7RNOvc2%%H7ID6LkG3vABE%pHtt!|823Xqz`WTVE4)? z#u!to5;ud3bVOz;+UV6VL-6DZ$4+1ff4F z^3Q)SB;Za8Acq4^kZ(IdfGzle2>={G0rdDsIPzz$$lc^Kw6mV&Y{kJla<4-8QHTKa z5XXOD(SMssNEpMo5H+&#eJ%gFAzA>C0DA0@00B4xKJbAFY=Mu6;`18MHbt*C1&wTp zbK>%*1(s^5>nxT0hddtf1wMoW1K;yv|AHWZ01oghy*yw5!g7`YY=eP15dax*u>?9& zFfH18Tho9ew<>LLYJRMm$y6AC9mKI6Sug`CM!|qY0$`E8Jfkrgc%Pjlj0S4d;0lssLZ2)OX;*q~C;KP3c z5Jd|;x(%Vs@vo{yBx+SFz;co{i#R=jP&jE!oSt(Ls^!NhTo^z@21Oa7r6S6*IXP<9 zZmCks9d$PcRd;r=lAIJ{8f}3^0^mcrU~QpTb#ej%1cj3YaBV{HL4bIy)rJR9qk9dh zT%ClVwpiT99@_tql7#g2uYqNNP3i022ok`U&Nbu~0XbQBRaTUN16wJ1bh?3>?TM`P zj1=Lg69DA!A305n11@of68J%$LD1+f*PGGbN+SXcSO9t3afT4YQ5xbsz=~V^Vt)jo z0IB7NCQ2{{5(Gd2=fy+=6ub=r~`V;OJCkcQM$U+G!An!defX>;dVeF0kxF|ByTfB$`%pBVI!cl1H7tED#^eG z0HCGc#M|Ps)`N#keShqUKd3gg1VQHX+ z+ieVh1I(+8UOE~B0Wg4jSC~#Dr+eKllAbh^+A{26PJ6CiRCm3b6F3q;%4iwkP51kA z^#cE0*@p&zjE#H%021IH6hDC6b4voE3nG3N-yy~U3x}q&SBruG_sDI*ag$e=Vi#6< ztT%k1CBD3gwg&CYMH@^YCOQBRXn=mNL4}zjyyvgwrEHC@lZ_`Ej+{Jzv(r6Z02$=IRdWy7E3dz1QwewEXrSPCmc`)(T!| zynui0;HcgGY(wHl-QWmUH~|>G?JJspqY~eDCh?*817v*TBBe=vwO~0#a?z(WmzOgs zcQGpmQ0GHypciun6-8hNF=w#>_8?1-Pz8M8KOcn&1)xcR)d2isWLCFliKY`eCTjmM zKmj^N06gYnW<*=?C4z8(Zb~KtVSoh(U;tM{3@{J@1PB0FrU_ccWntue>PBKlHFg+O zcGtvauJK2fhad5^NzX6_W*`cCHd8ya09yuITefE>h;<(IgD`*rsP+%gAOo)TNMnVB zVHgBzv`~Pc0(78mSQZH%zyKNNb)FUjvNdXwXMj~0Df?6s{ltKqmsuQkJe`$1pCxk? z$R#`xg64%q=QRqD#t-I@5(vit!u59_;Uaia18iB4Rx>$3AO-wKvh970H|PE3d9Es z2@O~m1D(KCCKY%Y^^g%ck%6TX4zLRLFb~uq1}wRe^)L!UsCzgeh9w4=REYnSS914=i23*$ z`>0`#cpx42c?88iP(+Zz2uLdMCtSN1$wGclElz&EjQy3^kxkuTBl((sNuaryqlRw(QRtYhiyv7j!=$dRd znar6Gnn{^9MCKzl5ZIU?hHKulo?X<0 z?{x-M1NxY)RFD(;JZ09K-IAMXwwwQ%Gn5`QqWGDB7Al@c z#E%-99H)dlsU(mS2$?Rro)yHRWM`DfxS~n9j6Lc(70OQ+I-WF|n93ngjQL@=7FtL8 zqV9=QPbj1+igqaHn^pQaNveoTI-`ggP@Cmp$|HIO`lY<{qeV)T4SI}Qx_xZwIAQt_ zi^!y9dOoNVi8(5P1j?gz3Ogmbpw{;-@tJ*n^rkI3sCsj!4w0v1%5tUyiDMCvfC{1_ z=ADjOGHz<31cRdTId2hKZ=L!uklGNEdRYz#GysaD0_vC{wiP0Js#sH~Y;k6Bs-^Qu zq@XG@wtA~Ds-!Z?s!7x-915!*Dy!-ltZVbBhI*pAs+<44%BA7yrOrw-sfrR~YOLj1 zFR$vSv6`M#nypuZtA#mD)0(2yN~h-PslzI!#rjXjs#)hLrD`gu?|QDp_@0RBpk}wI zzuKt#imLI7r`<}g-|CNOI*B_PqMTZ=yn?RbBdz}`r|nv*5^J#dSh4zOSqkf)iuoxG ztD}Jmu^Wpt(3-0Zda=Apsw^s@Df=(mx)7@xvNZ}AP}-sBIi*LE2@yN9j*=U-P>kz3 zst_8pwJNkiv@Yq8u5NLdl#qj$vPsLLK&!RS^eZC3V4x&A zAlrcT$|K1tuFFcUk+BwJOS7Ai1!xPTSxT)i%cB4D;1hMLDiL!CdE2FG3u_tbq&6C^ zmnw-StErv=4;K))=&~6fpbo91t1ip8VauY(K)Fr(D>V?UAB(t6YNl1|uqR6`+8_f# z3%cBQ7BX-S)S|gx%eex}sQ)kvAMm=cJB+`Q1$V`=`-!2aTdBzj7MS{>vzobwkOlJb zyIw*A)LXsOYXo0#2uEVOECaT=%cAN~2U%dM(%T)@d%o!F1Yf`jy6YQj+lVdKr;Yok z%v-*ci${CQu6=92`+Bo%yReU#x{uqu{9Ca2E2@gxR0V9V{tLekShAT)zzfX4+*?fv zEQJ#stPPyFg!8uZJFY&P!5e$PM|;8aDZ>Aqy1~awwGdptC_KUyjC~LKy)Rs+D!ixW z*}}|P!v-9~i8{jy%)>UEu>UKvPkN?NYPAn5#I{Ppo2$Q_%fw%L!&3{c@(YlT8^Kac z!7Y2cP)x&HJfc+Gscn1*v|x8o`-p>Pbq zooA_7Ousw4$AhZI-HW?HOvv}y$40!kbL_&3%%+BH!9a|`k-VOaoU;f5$YHU(gM7(H zI>|B|u!#)H(38oEtFuVVv-o%Xf^i`@74wOvDbH z!%3{VkPFP+6U%r!q}GbeY;(>+IIi?99B}#fSXGrA*C^1I13C*5tj(T- zM@B@+vs??a(9GnFG~5iXkIX*e+|HOh9_KvDN4d`Q{5tP43#N<1Wh~8rI?wxj%Jz)F zm8{O}T+k2G&;MM-3@gy#JkAWgHs*}b>8#Hcy*CccpDS#|x$GDOz0v%9(3pbH>%ktY zq0uC5(K_SL9u3g;8qzCG(JfswpD_)!;58`C5h@)LEPc~S)6vE&)BhRItTPMfAk>}n z8P2fNCoRjU@zd&D5llVQmBJ1%UDOcmw%+^|o}AR;aMj!F7R#{JN`ZYDG1a#+3mQSz zI9(~{fYy^r#-_x$fegYQkr4m2u+VUgE^dJjA(0CzXBUN%eIOAMwNSW!{V&6!(+_Re z0qkMXF&zYu8VP|FjNRBI^BLq|F-Q>%b(L+Hu_$1VGobeIBQs9>>krBHatX?bwy_4WLaIH&WY@J+fEa z$qA982_et5Fucv}FrOg~ut3~5LI9_o5dxwfqTSZ8z}nxy-z)jy#X z+8xfb5Dc6B-mCH%>;MbnjbV0S-p8HZ54zF>-n9V!D6bt11zy0uBqW5EuUjt`X;b7s+iaiQVC_u+$;mC$C)$B@WeGJumi65FY%&8qVCS zK>)DO3c29mG43dVtqrZ<3KPByp*`9te%cs;5vc9c1i<7#4GXT&3f2JPMSdZ}k`BS3 z*H5$=mY@jrH~My@CgjTz6r4j#!eQQknZZ9 z?wio=o4^V14)34v37}B#r2y-!;0k;$7qlJ~ww~(%uj?$%;IH5cpRNj}Q19XX3Goi^ z?r!eqZWHT1@f82?4KMEsUka)4>5;zGH%>fe?kU%e-Qe65`@-R!-U)#IU;{Fii z9`QHv@b0b<7a#BRp79$G>HALDHsa*Q@b3XH@IH+w+aBkvfba?*?g?@8@hI=c7|~MYKurJzuktLv5THN+FAw#VQT6k_^cs)v zS#RP;LF@leP5`-n5q_^2#<1(FAre@Q^en&h^A7i7QT0wg_jLd4N#65JE-PKH5G{up zqGC2}zSrBH<=wvaY|jvHulO<{_w(-fOAiWl&++;`;ZYqRxbF9OF(6WICF^|^S6=uB zp9-ar@Tq_LiBI}0!T2{1{HTBRtl#smk0Y2>=^L}-Jno@Gk`N%l;f3z`tg!N_KmaW- z{FRaV8DIBy-}*4!ps?Tfc(DwBU;9y*`<|ZkEZ_a!{}te0|Msr&&mQah&hM08J^-<- zSu+9!ogGA&P@%IwC<;D=7*XOxiVvL?BuHRc!DPsc6(cZ=KrjNm2<*y5pw=t`uwD^p zMS%Ym0aXN26)aUz=T4qIef|U*ROnEmMU5W)2}>Bi>XUT=;O}#T5${996m0$yF_9#q1TVShAhbb}Z;t`&-z>$_}I3%PwBMuK4=> z3wSwj}$m zN@=BnTp~cXm?G*eJ`XmCEC* zFZ8GqF-ReYY_CKkN&L|{^RU`4v+1a_E-e8?gUGF?&K^_0S9W004onn%{bN`YOu?YzMOCuF$=1N05Z!g=m4fbS-;FjXQj2) z49&xJ$x7+e>QYRrGwZsy_#~D_Km)~*&_WMIv{GB2g*L=or=@mRUJd2SPR%w0Hoz(W z2`uo_(;O0Y%j{kw;6X1XG%3tgRdO|2dFO32D|?~CcEvmK4ED2R7$mmO?raov&|D7A z*3eh!Ww_zt?u9twd{_MH$xQq8S4K`F5;q}6LM^woQjsAbwo@T$SJjDHooHc)Uxrzy zh+!#6VVkqf_TYmn_HV3i(egJUfdYoB#<{{1FGqxNUdd*gV}=^$nWuL8W~O!Kx#ypS zO@_~ntL%7O_ZAeHBa%&iNWuvVQaNh3-!6*joORwgYi_>_mg|X{3D_;R;_{AQ{{~f7 zXSfeXTqU^|*SGF%x!pQrjlm69T-4qpn^dz)hVW#y*M6wOq@Zn`VB8*;q=&*D0? zh&UaP&!UeFv|z$3CO!9$vWk_|?K@2!$+@C!lza(lv&F=4V6I z`2+|z_CfAq?~~G$w#O+3^6h&a@d)i|H`0y4(pSjS8L&_iqiFm-y zur7h$0$~U{){yutD2FCIVaZZBkrf`%i)~sW1!vgA8rqOpJ46T?58}iB(9kG?#em}& zyZA*q#wUz-lp_s+=D`m}(Rfz+n1`&`sqJO)AjRgrrVDI~gH36WSL)0>OIB{Q4p z%x6L~ni4TkFpD)xwGi`9--PE;!Z}ZIj#HPTG$#Xjf=c8GNq^bgA4EtNPl6t0oXu?K zJ@MH$ea^2m>Rcy26PlKSR+J|P#i%_KicbYbQ<@y3AC^S4%83B~luH&xsZSO-{(u)d8or&oofR6Y7pnbI$iYx*i! z-MUwy{Ir4k+p1dE%2&ctM6Sg8D`3+q!mi?#B8n|6VQad>!5RgsiH$5@58KDTT2`oz z%Bw^mn^}0a^>v+9jDaAMSh|unk((tRYg_9Re$F64uElMwSlb@e=60;I#q43YdOvxE z*0Es=VA++zY4wZW+^bEP|7>XMeL)x~ahwQEsS-gUeG-vw`Yzj$5ome;)J&0TcS zyI%ITmtnQNt$XD=U;5%Tr1qOnii$L2 z4-eQuHAZUJp+eZl$K~*9iNSyeeBc5UuE2&%LlF-%f&kB`1C1B!6C*%RK_@QAGoWyQ z0|0;l0?x3+cYYql9VLI0T5sbXOIE|2w;RW zfH8yyOrQwOICBBA0D}i`Ko8Yu@|gn=f@v^=6fX$>fCSt?kXXQ=&_yr?n|ERa0DNF< z^eeCc+&~RTkii(EXh4?%5CUdELJ18hK!&vp04{?W69-5#Fh~Gq0(AP*q25U*Dtw5b zsCUlKwr<$O%z!;R1J-*Mb^tzrfd`P`%m-+I1u(#X8GD!y)PTdJr!k5H6kyu|V1%$A zAOZ{wfX`{H$QJ-WY9sVFY|y|!0gA8-f%KUK2Pkq8u5oY*Fkr;bpaKH|kO2-@Vh;yM z00CkQ=7k4<-u5)m>GMR^e;%k00VqG z1jR_T9>T!@0$73oaCkTr(tL|Z1Yie|uEjr2XMeIkRF0+=pqONAj6OkEa@TuoXZuSw3geg0<;@M)DTfN4IV=3H(%LG zm8>88jdhB5!~QQvOE+ zjnMGdM=Ii}ZE*k?z!_Q&KnxBs{!E-=^3_;4@P`a&Nr#~B0tmzS53velJmU^W2DIt* zKJvgrga!aWIe+82g#d;ZV1No(H^^K61!st{0^oo-0DwIMza5wc0{Arpn1&z zL!baX3$*1U08%>yoH~R9=(ok1qQ=6$7A&Za(V_LT1Yrm`H;9IdtAJB*hF3r|q^kgD z(1b73gGigawtIjg=m=I=24+CH-*bZoNr;Ewy9f}xDGWg9qMsBif??1y1|YF((?NEt zfMk${0FXc^2!J9uh`1xOSg^Yd%!E~NLghO>Ghl!^D4wiSL2>ae7xY7T8nA_!wmm~P z$V&!cAOHZ!fC~V=ZzF&QkhmX=u>h!mZ#y)0n>Y{HGflvN3=jbZSO7hcqJrqVD@2(h z6THL-#D##hE<1oPa{~s%fCISyfDllI01$v2n8iJdhIC^wMu-K8`+!&jGfwnGP!z>V zDu_BjL5j!%0BAj(h_gSW##)NPYfOki??l>GP}sT!`3ffJR$2XJCd#pnwLb#5ur# z2>^pXU5P&~OF${=+du$Da$b<(_y^8ohQ*@{)>al8M$vV{z`hxze z2)@%go?N;3YRRp9!6?-K3C@d3X?!Y~*olF#vbPC2m;{}w3@XMVF0O>jZ}Q5oOt%n_ zGiQs-ywuB1n#R5K%fD=)?pjH}B+SAjoxU{8#8k|v!OO*T%*T8$q0&moq|C~k6~eR3 z%+$;_Y0S;^%+IW%`vT3;BuxS2!@(@g)Ktx*K+M&2&DRX9m4nUMq|H4%9+s=k-1JN5 zs;t;t!MD`S;M`5#G$pK5N#R7!mV~X?lC8R8tmK4FxLnS;%1ObD&g+!U=?pIYTS4sf zPM3qLxRNW>>dfyn&u0rypE@fZlFajTPXOB~p1LZ(V$a`v&-?VTf6&kUBnLCBtmOjV3OY(_JOjhzL`Aa1(A&)@5bZX5|KC zHC8J{2VboYU^UP+qSRvD)iC7-Pk59*0M~F8*KzFwPvC}!kX5f}k+N9T$q`mw5P<{8 z0&s{{qAj&=s0e*91QKvSQ6P?)t%!Q92tVKkdiV!>IEF+BjLD@hprG~9r zuH?_1$lC*u1c~TRN$|Le5Cj02fLR;>Ul@+G)d_p0h(#b-{>0bCuw1q+*{#^we%X`_ z``OP0-Mr-q)I9`zn27#71Zbp)b|3?M@CPg?fHsI-E6rVs5LW`&UUKDz$n^x?#RqT2 z*YCB5h$Vo>#RqPnTv7N3Za4;dzz3(*UdzSYAUy})^;J!A*Hj|jtu$VqD1i(32hsii z&kry)o!AF7vtHql-TDRBhX_~h6<0ri++zsWZNLX}=mT3Q2z!uUd!XEHh+L9oU-#8d zk{w?NhT8kR(fp;_jP2RZ9p3+~#{Kk(126;S)rtN5f&)0)ig*VSKmZSRVClAb|0WS^}_Qr_EjhNMldv;t~Fb zKaL1A?%f#XUmB)cICiMV@3OW?TZWVC9wn;yxf@ zMaGA0VB&|6+Vk~ir9xp zfIK6<-X#s@GoIk?wOKF@-}wCp@~vV5ScC%J<^PP>_Kn;To?jNO*)$f>HQry~%~Rpc zuUz~kk%B5m_z*+7k01XD? z_vPYt{$zyS@GAAyM3*m|&i;1&%(9Va{2rX5(Y_+^u#&9KO(pXx=w-hC}#-IxvGzjDX}t zTR$#}wBCtlH3!6SYy5@ldT!&n?O3|rXQ71%05AhdhycZ701>EzARcUnCJH6iiM7rK zs3wfXe&NP8>ENAf$&O?HtOx+eY!Lv3uxOOCu9e;WUbj{4|G;WYj%?SKGfvit3Nr%F zzU?rLmfdCIlI?97W^Ml!?$@TWXq*+ErWG{K*SEgv$JXlUp3*Qy-&xt+h;{DnmTTz- z@9@TMMnPj9KJU0b=>z@l^-j`b-DV=$<^AUG{@&$dum>LAZpKFc@A*b%`=-+Z&f5i7 z@CM)F2QOd zx8s~GX5W5n^_KE1_lYLAa=8|AEeCU$UQ%(Wa_H7^ED!TE$J-#a2L3qmAct}_m+~-e z1~+%|&P8)MPxCOv2e?+(UzOW0*Yhk7Q)_5!umJRa-SI)!az4+6GOvzf#^>N>^d47k z75Uv^kn}~b^d&EJtg!PgH}FaCbTALoX>f(CaFRP`*gP-)bsOh%ZRmwoxQrM!^Zpg| zSJz~qM_DtA>Y~KV< z;P!6+gr$Irs;G4qxpiER^Jr&TZCCeoSNCprcW-A3sF(_?P<30s8f zfv@#d_w9pkb%r3+jZbEe_xSpd3?&(Pa=-Xd&3Kj9WR~xEPdJ{pBU8xh24^>-T)FZWeIF1xa z$q#Gkm7IATh1H&uKMhJBdzPh^xyKc!PkU{#m`DxYEp_#{FV;x`{J;l%x}TF?!3?$E zmIJa@eD@f*_xo41mdKZUUa=RaLHxB}9cL2MNqytT&)TX1eTBIi=}7$R!5Ar_`9bx3 z;tl=KAAQod8`F38dkWSosruFTSjJ(T-a#F$QP>*#mg*CG+|OFwUmV{7p1TpA>j;?Y zb9~}=SX3H@_URTl(j~;=`+SG~&*k%Ua0XCnCN0hWQr&({4pVkO2JBG_&Vti-Eq}=l z(|K41GFn&z&Hi(Lf1r*3WXPLG0*GY-mo;nu=IkKCgMVf;AmH#J#E23nQmkn4BF2mw zH*)Og@gvBPB1e)eY4Rk>lqy%UZ0YhPOp^a*(yVFoAG=}0iXrRSbD+S22^0Q7K_Mp6 zq)L}EZR+$X)TmOYQmwl3CRUo$j1@Cx3|X?UK$i_AI>Rd3v})I~ZR_?e+_-WbX3aXU z*v_6|lMO6ru&lG9a|aVHZ1^zZ#EKVFtc#UEv95Z>5)=xU(OJftH*@an`7>z6j&FY3 zDS2;S%R>c+ZteOt?AWqrHy&M+oX*H!DF^+#Iy3Fy!iN(tZd|GDn(1`r1h`uFn>>HZ&l z;5j#+U#p?eAA$)gxZryKdH0=w;;D8JW(-<*A%+>Y=3p z`q_sk+&n~NE^RV*Wp7!=SSO>6+IZ(+e@e=ypac-wrj@8As*s$HdirUGkP)hRvPR~Zm!{)A+b+Va<7pn9^fHS?A@qxY5Sfr03Box4bC^6}a zk)##*!3{nC*mF!GF7@j#z=aC@p^}@bn=sCx+9c7=5kyeOn*ZE-$O92LQ;{G5On|`w z1NZ_{w-ilE(IUv!R4Gd@@ApDGvVWm&+t&x4jK6P zgGB*tK<(bX7WGNM-+yxdqp~M`{EJVXl@4x?tOTHp4>v7~@((x1(1TCk6KQRDva>9^ ztBy+s)@j+L(@9fCK!frRnE&(wfEoB$v=1D4@68?7n)CV4?tc1_@-cjFgO53V*y4~q zm)q0wHYy_<`Q$Spi@4NUb3Lx-Ux%LP*rt1LC+it~AVT;R(Ii3y7rjFQ0>{5Jaz%bR z)O(-5|C2}}d)k7JI(>v2xgTytfXOY-6ZTjh0rPY@Pqa)To4a0e&XGN4p^H=ByI_=1 zml4>>&U`ef0|2%~5q-cR06P$wdvfQ%-OX=LfZLrP2!J?$5uktjQXvFq;t2vW?;n|q z$OAj~IV3fUdyB&VAQFM3L5u*P4&~!nO+a|GiR@z!2Qa`kP{^Ghu8@T#Bf#%um%lzV z?}qruMjs&ZFUEPIjfvnP0WQZy-dRtG+Dp}&Y(}~y@{vX}(MSYn@I;90%m@dd$UY)* z00+45cF9p;7tF4mitHI_(y<6u&#|lla=ID#yreb?`1h0jRc1Xwk371kFGq? z5-pO25Vox*-vEFSC=!WzUBG7spvU|8b`es&uzr^l7v);EKYWM_a0D3M0Vy{?QO0p) zrR>)!9h1sC7I8eIdnGtg6wAikQUvpWVohiOfQd;2A6PiRyx@t&VB(Hse`=xcP^L1L z?MZRT%%9``l*d3(;A45u^kD;mSjVnS5OjF#mDr9L&WU2^Y_<8vApk%FxmC*v060Vk zrvkr^u<#GNMJeRh%fhwNmOeCWO=T-!)3Y;+&}*Y`A(mk_H@Lkn6|Lz~YhWX* z9g7l{gL3(gSmm<0mLuZ@RQRZ^D zyKd=hb{!jA$a=TD=8Xz?k?Ys(p0~a3Mb2(NTS4W%x4!mui5+>n-Ywm?zy1|Te&s7( z3;wsj2A&o}!`oZ-9=O2{zOHSysbA5__Q4kZZ-9xYUH)FU!~M9Tels5lm;suKollVqJ< zHL!yXY*!Qe)v=a!DlpxOP3QP)f?gG4_Pf6gZV2`DMbVi~z7rm# zely(P|5gR2{k&{}%WPL*3(>)wr0|WSsNwxC1;|65ijhM+;a-rb60qq=O`aI!9mn!>Aali zR_Bk>v##~5x1#GiSM$@s{qw%+dO`~6(bdy_A*Qpv?QUnf)4%Mwl<&gl18a_x>oYp@ zsJ-v#@jBoG5BRsU-RZM9e2x<@Gfc&&o0ZXfwEZsm8$y~b6)XvulteA zg-5+iecY2jJ$^W^`qisG@y%X5rgEekpHQYim#I4Hb1xj$^S<-0PdV&CZ}F)6B#*Wy z4|#R3{Nen*`M;Mp^yU3|yL;$6y73BH2?Ch2FTeZMaX$EgzxdFHzWOcy!oIGxzkP&z zzx~+R{`QAarMpLlCzA;}V9MkE{afe9>2L;foS!jB$nlv4{~ciexd%HeL*v;WG8mj+ zKwk18AO*(8k4=O1bp^{s;O$kQ2M))NHAAX=#r=s6@_ir*(#C(xLo$pWSd4|B2;k5klo?bjR+S1G9o43h#?*#0X|_R zZX#0P$i=}S4|1X?vc!})RKaZur|{hN&Zi*}lBa(5Io>-6UT#Chkhm*yLGa4E+7NaS(7BW^` zihUaHW#cotM`m$jzi^g&paYwsVla|pk2%+@9Y0sz!x(9A?HOeDTgMA9rx(>x75J<>3JkwjFDP1xc|kX3{=+b(A1i23)`Sm+LS;9h>%3kjosjl-gG5Tex(adQlBi2C4G|PID}w6#N$NHG*ylq z#g9=&5SWP@ud(H)xTQp70Rd=L+c?7kVCL-9&R*izT{)%;;Zrf~iTwo6o(xa%xKHvF zR1ZZH^w7lgG>$@<5{S7JNsc3Beu-&eMEVR8IvGLwSj76Y&rXg;PtuVW{iQM8&lv4b z|NKuP1Q6@+kO3hO;lvT*5T#k7Qe@88JN9OmSf)h(G?8!;(g)Gf2$j$YC8uMMB|fE* zY5q+N&Cu4=kl_@j8r{SWQBxURQ%C0JDXJxJl4l0}=0#Z6Ma={V*=0mj(G_J8eAdNs z&L@3-Q5X@x7zJp!pizIaksHBLW9klWCYX9r*d!t-_-MrVC@6#x01XtUL?qH8H40}q&!imCDxN)dX=RA1m!-Bkv~NaKs^rQyb)|36wBD@fTCzx z?&hAJq8LtMpL!$fNWudg zfU7=)Gh_ix9zq{Fs;$!JMHJOh-6|^O>a#`Ef%dAe{*hEU#5WuO(9{79gzH2CKo*FD zMiOLK$*M%0R9F?|i5^#|E+w`yid#Vhxq8$@(tr^hf;QL{v(96`93*z7s1aT3yavgy zp69&8&SAO7F5(tF#+RKwNv67TltP-W?M1X>-ZsBM` zY`_BRw6^1nk!*+AmYJ%>18J!?QLAqMj+w#AY<+EHS`Zi3q*uHWn9q8cJgNnnENz)Q zEYXH+!5-}_KJCPcnAEN!)mCdhUacBt?amr4*KQ)7Zdllg;@A$@WR@)t;o79-5R0YT5aC0t$Ta}l(uc(F5-&?uG|i;-*SgB&>hqoDB|+r3I-XI zdD`JV?i~JyGfdFr5}Dvy?i#LOEr6Lq;vuhk?gy@5FT`8C@~r9Btpe_XnxSRm4%*zd zE&^WQvZ-#-TC45qt$WPFG`xbJecQaH$~A&*@A4n{Eid!VAB60JE0jXF(HY~K%QAY? z)+X=Jp&Iv=+OS>QvCUeo5wGz7J)5u8uJ*#5u&wW^pFFN``(4PrFbu!2&E?$Ef!*AJBmI5hP$*&x`yJB# zaKvd{*TLQ9p>X;AVZj7q>Gp8KZJiS<-Qd9;*rA;CQ6Ca7T?; zOJ4XnU-VV6@Ks|VC$SU%7H}b7T_R6%B2V8U19Br1U<^6~4@a`vSu!YFa`Vn#0FEFa z((x5~a_)`tC|BRz<%VRO@*~HgDyJPQH?J$3Un%3DEDv%X)^gwFvM}dzDF+}g??w7? z;0_icFiT!BKXc=G1rs7O49>D9HuKd%Gc?zq29n@_hzEUavgu(n(Q&gmpK~{3AMGJ? z`h_$6B}6Ba^TtUtJ=Zfm7oQNa^H{j^JCCzGZyY`cG(NL4LANt>h;#no?LYfkK{vEP zLtsHrOh5nfLUWu$7xX@7^yB%3u}*ZH*l|T)oknYP1adS)cr-1CG|`Q8MwfI1t}05W ztx8K9OSiO3yR=3B#55->^h_JtP5*Q=Y=llPaZkS)P~Wsj3^h*|HR>t#88dYS4mC4F zbyUan$WpcPNi|lZpiam0R`Z`$e|1%(^i_-Xce%kfaToV&A2)K_wsMnOT{kzr^)_^i8gNf{uMM|#8=7%vw{|Bt zcXu~)e|Mq(J-2v=nsk@9nN>G>zZrI~H*Z_@RJ-?kleKQkcY~d`eIprp=XaUmH-Gb( zdjI#4wYPxR*mf5dYhktm3 zhd75rIEk|ug`aqVUAT%Ln1;9be|0#FuQ-U$IE|0EjmNl&1E&Qb!9nDBcj>fDLxj)t zI6?q9a0xjfSA@?*fRP_LlFycs+s9KsM3XzYk3)HsOV*SNB1K&JlWX~w16G$u8<=N# zm#4X!uQ{8yxtqT^oX5GG&pDmfxt-rRp69uq?>V3Mxt|l-nftk*4?3Y2x}hIBq9?kd zFFK?DH@c(aIcXC>pg%gLSGuKNI;Lm3rf)i@ceI z7CD-)I;_XKtj{{F*Sf8*dMJLmsNXuT_qwnDID}3Os=wSi-PB zJG4i;v`;&=6S_4#gd~K41ysZZ@PHRE0xJB07uZ3ym%F*2JG!TPxr+l0IK(;pK@)TU zMa%#r*aRB}m zz1D9%*LS_we?8cTz1WXE*_XZ9pFP^Az1pum+qb>jzdhW?z1+_|-PgU{-+k3LfiLg_ zp*jRAKtM!vfEQpz6I{TUmzQ~fK-Aa7Io-!8Dn$6Cq%A85Frp=o;bL!m5v!~CWI#a4d@@E^q zZH%5=pa8U~)2C3QN}Wozs@1Czg+?HP3Ln0IU{Ud4_{0L%vuM+*UCXwu+qVmac1VJc z09dg2o_u%!x3Ay7fCCF2O!(l8!!wB3v3nP+mktZPpeanbvgON|Gix?zr?VY2lNsN& zqG7Y?)2LIcUftB0YiFmL&=dV{n^M-ebL-yCySKsD&d}JQcNd?R5eA>2BJjtz^XJf` zOP>n7nHMjAd@*V=<6wueXe>?tA5Xr#`STfp|3TA)2jqlJoS~mjzrOwPz^BD#nB>0y z{{RM<8F0xo!JmK#CaB_w}nci!sfH>urT++DAm&+tnrA1G6Y37+Of*IyOV@gycL-L_1=bRF%DNvgam5ETC z5v@t*pMav4%%I6EWG9|*VyR`Ij5Zn|p@kN7D3cB8*-@m9W~!-M$aIQKLXZ-qW|dYZ z^ya3hrpg*Q1h7ibIpo0qN{%+ugeuUel$!clfflKw>aV~K*4RJnI78^IU?N)3slhhu ztZBo36DqDt?zt<55G9k&v)p#O*0AJEn{2MjDjE@{-lnTARl{lnX)@H7YT~sKO|$O3 z_&znPe2~Ulu4`F3gl@hB7pzlb@EC(kzvIeFfTacvZ1BVso3wAI41Y=P!{%1(@y9zg zMouvbXS@u#5Fbxw6pY}6M}Dv2!RRgX%nkod5aj9J zCD1(cNZz&Khqo;@u5+J_ujlwoW3T8cGR!?al-;2`}s1NP6XAzdf$z1MRXd z>#oE8lkBzIZhJbsIKqY&JxH=nT{J$uOE0AdkNM&qGt`Um>LUNT@&^|OIswhI0}nd4 zID*F-KJ-w{Iq1NXeR10#ZaLMNyV*m?*Ez5dU>?)32Kv&6mji+h9uS1bICLRDHbh|s zarj0$%;Ak{V9!O`>)-#r_a!RPOMJV?#qkV?Km{)FCO=!>10jeGW;h}c(_29geqj!4 z@PZCkn1VO|Ui7qFh|qL1>)VoC(~N+jP=)1VVFF)RlVL^U7UK{|1Z@|MN#L*xI9LH5 ztl^A2L?H@5h=LkEn4%(zaD-zsq0z9#5GWS#9|xQw6=(9ZTZG~k(eTDPA~=rtv7rk- zz`+%MaSJu{00?OWgCx$u#uB;lh>25TGnnYag&40EE5su_rZL1lS`1;)FohQj!HaRQ zLmk*~gd^S%1svcY5QDe^BqJ#aYkY$pD|%WjjL1DW_KixLG!`8bl0r~MQIym8hAB@8 z7IvIt9H+QM9M}+tNif43(Fg<{*x&;K-~bFju)!mNpaxS|gB;)ZMkPliOk#F#7yyi$ zGn%OXHH0)XlzJ!5H$u3j&Da zqZ(9EJ>M9$e1>U>%52C#pGg3AOhX(49Z6%>!3n*)`&(j zhQSL^Tp|xwu&5U18ihE}ArFTz#Vmed4R3ry9N_qdHuUGSyF7IPQY8RYt=bpQ5n!+k zK^|ctQcq%PwHSOGUP>J@3xK9$8m=O%N1nz$b(oeN=}2ulvSA8x&UFoIpaUJ$a92tH znBo@3_(ovA;Y-RI7OII|>_7AY2n-ZJ1)}%|KcMk~2XLSh^zcUz1`rA;3?LfQb;J-F zP=RgyV;{1BK?5e>2I43q07WP+0rVh@7Zjif#Q8@%lz;*9&PF0nbKPVQ@-u5p;~e-c zPA!Is$3CcbSK2eI*dprnZoTcteH@@rg{B0u;YMhBKJq3~lVz z8ZLB*Q%Qi>uPhcHm>91T3b2KLgo6PE5CH)eV2gYNfiD-RfOHQKfDd3G06X}{JuvV9 z3LKySZTN>W4nTkmM1aT-@IU|xAcudvg9Hr_0R^DU)W{Y`zX-|ie<$)ZSP;1XnBE&3 zN{_aXT-0L0VQuh(Aq*A?6JT4aHHVTr4p9I^bs#HUI9fFjW16|Y#1 zMNa-~W=DJ3({8!74~*@EXd9s9_%_Zr(inD_;~arL$49;Ki$Z9F7jHCsDH_5?R_1K8? z2#s(DfN%$8a0X&f22`K~kYF#_KoBTKX;oNQfjCcffky}teT>mmfp`$IXmZTPf`3?u zF-Sr)Sa=9wXN-7*kLYZYC~ebMi9JUS=0FX?pb3az2#?T-VlawB00W1hfAb~~D28I8 zH67Nsi_-^#aCV5`lZe5%h>b`q#(0dR2NvegD&zof;?NDy5DS{n2#;V0b5I6KaE&jZ z3Xz72-Pn%MVL-4ph?B^R<@j>H*iQ!`jE(4Dq2rFY$b<4YkKj-az(5S7kdKIP2Xi0> zN}vQ(5DWv!jop}Hmh^&xr+tXHOzAj`+=h@8X^|KIX+ZSYc^oMW_}B=I00<*V1w$|f z%&>F=v645}k__o`GPp@JICC`FEsk}QI7x{AFc0Z~4(Py)nM)wl$7 z5KZyqXprU&eszl(f_=RxXE7&}5vh(hXq8uqjQ^kxx3dnZ^$fBQ3$gGBh)|Z*I0T1K z4(LD-^Ei(c*+wpDhz<#rz<8Ha$t{lMI&is{mxdRAxen?;4YJ^qnqUZ?NCm2S3kC6* z3BHJNj{eVbK-nfWZ6ISrlZnNCw2x8n}#&<(L*njtxoN^l0u@R;J@4a@lrjR``s z*^p1UPnT&B5_yB1$(vY-59}bCXRzQ32UM=~`Ym?>55wRJ+sdm^N}Hu>5aS~do%IUxTCWCc z6;GoLy}$~tkgnWXO#h=eCv`##0XcR?0P)%i1fUA3PznM83ZIY=n{coss}Y%SvYD_6 zDyy;tzzHtP37-H8GFu9%pbD)2&;PbDhDy;-Su&uxf9P6nT|WJ?lJ`w3G^wKmJJ4lAl8 z^{6Q2v!*H#pacu!S`bD%5TH=Dd>au_O8{t#wjV3Apil}lySA;6uIie#<@l&rgN_G* zHWoV&cIyfnOSFBPxg9~cg?qTHV6}?7tkq^rw7H-VF}VW)wgSNln%lY*!MTP@vsL@K zI2*cd%RTq>a)t`64*^yJQLwA)y26_fvFov^aJ)6UwzTUCSBtfb+pC$RNy#KYj?<+C zV1vV(y$qoW+^Y%$@wv4Bi?hwUt8V+YY`VL=ix5gvHx2>38mqm-3%)no3iQjo4Xe2O zTD}l#Iy~z);ZqU5YY=w}zr$<4_ltM1kigqYzPZrAyU;(-TMS|&y^mX_4Z%4C5Woby z!33ee2|NqrE2_`CzuMO~FjqGJ`w;AFog6&89!wA*48qqtU?QBg5L`Kkb2(!Jb5=q; z1)(%89K;Gi!=Ti_4*b9n487j-t0#=Y)VmO$BE(W$XGW|-NNhZ~>pzIIH%yE&T&oaK zJjG&csz^K#S&YMNv&0h1!vv8!WBkUai@P|CHfb!u3X?Mg`&rM9IR^pvBG=AJ%CX;0|m$vA;<(l$i{HT;u1H%@x@R9#BmDAIT1B-O3CVL zI(B@=1dz#Wyva{Y5n=4d1Wd@Me8-<+EB?aDe4H&Ep)=Mh${ZoVz0ALtOv?iCF31u$ z4r4Qo!xP335xne~zU&adY!R0X5z`FKZG#XJJk7(bFau#LdE78pa}Yi(60eL9&8(-_ zjLzvC5uLIS)tsl>JT=2i5T24m=yJ=t(#kbatO9Y)2hq+Jxy}JC&;{|%0^`pJoe=Rn z(A&(=^=!|cGAN<~Cdf3-`<%-;0nmX;&;-!X2hGt0LD2`X&;tR|UpmhO(ai#_$|tok z3gN~g;U~8L>d`1|(#7o0B~%c{G7vcZ(FX0ADt*!sO#t_t&lY{r$sE(1d=oV7sUW@3 zBwZ0ZoyzF)(_hNcM(rs%J=Ni?$q^ybJ0a5A>Cpwz&{FNs3Q^Tljnyk1)HpZK&wSRy zJkh8+Dil%9JVDm(NYE(#&r$5v22nyVUC}vR(&zlwasALl9o0vDEOw&Q6Cu`PZ4KMv zq!x)6^RN!qfGG$q*rI|=2w~A)-6{Xoq-pvy@E{KIanE5K*^(XBP5lwo`ViKD+4qST zEGgJkO(hRQ5EvcKBb^M+P^)0t80pX?;0)W!k|_L)6Hko{3Mr^yK@MTe+kVm{+3hfF zoeaqTpqrEWGtN-UJ&ni^q1Q7p-H8eo=aANhT`hzyFG)Qprk$!!vkc_@tz?qII#DRd zz>@a;57yw`13@-qQ{a9=-^j2GxNW6BgU?^R%9B0YB(c=Da;$jK41pafy}c}=E!?TR z)l2EApbFvUohSUg6P$Xh?Cs4~jVxkfB|EL)wN2v1fmuu};pa`^G!ZGr(BNo_7ni)@ z7v2oMf~_VGTIG#)VxeDc;Wmv5K||7SqtIhaK8iqunJ$-_c{tx01JT6|WCA16*9y3w%)Mj2B{QIkFp3#cz zAqXKcn4agFP5|EX%>&cjOP(~14(oXFKPIK)&aKfmA?UC!4s@J2MqTS`BIdXr41E3Q zyH2_dv*Uj*6Sqzf!j9pZepxQQFotc_-Yx1;!|b{~eYxA~!Aef!Kgnl@( z?l%8i>q=hj;T{ZdZR2>c3)+tA=dS8pIuhSL*Vk^W@2>5D9pcFz>Wn@NmD3AzUFv=I zI63^3eU8hJTN9pK@NroX_<-6V--;UY?(oj(uk7E89`BFS3!Xgk2#Gx(Ul$+$>J>nK z9@-PZ=6>=2vdlYCHypnp(|16G)jR}2VZP#QwKK0Z503xv4J14@|7_2Te(&7V@>r_b zvs3i!13^Sz5EBLupyL%`S{%91xc&R@){^cx@$&%^^kps(kw_56JrL;t_F|79gf2pZ zY{y2;(*v*a+_MW-FCgku<`Y&B5wsA#q7Lbx4o$lByzw(~tfe}y%&T4#Y=0lq_x4_Y z5OdEyuT)`8pnCP%U>44hwAxzzx^A17TG_j3pIIAMs zN5I`U0tC>ROSf)Zw{;CIUc{JD<3^4hJ$?ikQl!Oy|Gwqg)vH&qhQo#tKt{k=!WiXYyas=9&)-Ju(okSscH%qHPsQwW=5)~(-~M5!_u%eyrHE#5?yb0^PUK7qOh z9$X|+r5c?=l{%0!X6BhWdtE1%A-Z$u&{;E$TD5TQ-ANL!HMj0vW61XQ{R>jCvr|vJ zd)!4p@q7BhlP>Kz8LH*wnllfyh#hs(8StEQ92zGYQ}VEgjV|K&1)usRY^OfPf{^1(IsY&NupxG49I(K1#=$X~OBiIM#|N1)F-W4| z!l#zlRwB>5WcXUfFTeyNOvsDold{U7;sQxOiz;)mx#qSaU>$)P8Zey$$6=bYjoMH+s*r-NAL=RQer#+KAuSq9ATCY7jsXVpTe+ZZdo?Lg;H6ty_ zZIHd9>+X)_Ky-n=3T0(`(B3Os&Yw?rblMbf46 zTJTdwW&tQe6+uN$K-1N-!feegv~c*c5sS=alp9{eoRHW zJ|bZ`a<;paToOI?1e4WDYswZfWQ!PXx+#-`of<34DlH4NTP`UCwT9kcV~v_`CZLBj zLK$mOy5I8|0D3@$zonFB(@-WrH3zro-q2Nd@(EG2?CFMy`S&t(=M~BEbp& zbn6}!pW@~o9KQue$wdcF(OfJaZ$q}3eDz`|3-c}+kLCv6Y<%E-cO!ZCyPHeBFA{K| zc?Lvl5>5jd_>DCZ2SbZ#ai|stHKG{{9qpsH?;mc5znsqwg9fqaxj?5MN>=PPVoWghk*5In~5uM0LLgNbWM7pBz%0f*gcv9tznkP*wl#|^W^Rj|3(=s^HH*;2(lEq1Bd!vd zLmjr!fvCG1LDr$5JKhlw&(z}{r*%qHijW}&|C%B!eR(CM%%c{H+|a}rnYj53cGLChKg8CT$0nL!cRHx>}78| zVn(@cRE>6R;N4{8l%=er8mNV;BkUQv5Esa$kZLlLdQC$=vdw=S5e=r zY&dz^NS{8EdFWKGXFsdAql#7_Rhh>+obj@Y*b@(|UBeo*uwcXD0IMdL!X)aEUb9rk zA?eU9I@+6Gv&Lt*FDb4>1tnazx;4L#X%|tQ+tKZ5&vXD5@FnW0T?3OZ4jf$Y`VPB< zDTHFe>o7~Q2!LTCZ#c3!jqgq=|K>uDz!0b>Uhb_9$gq*&O`FEaRb`&G5FHUV5m?cItF_bX^%)DtSUe)^*GCeYxUU8-> z(Fsd}2-grAlh4Ku(t@bMYl88rp&M!@fH{V&TTNRB9dCZ?WOXXe%hq==_uZ_XB`f7B zPkFENa7SKoWsi{{HOm8ujzaWti%^`DwKX1PvkFFlDfA$#SY1I622qdrVZXQTF&DzB-=qb#Oc~>&}~s>T2k!goPi`h=$1>;pJwIQm_t+le8S{ZyTsmke@Nj zkO9XI3?pb93Ad=IlT7NKD$l-8E_%-!Vh?)EgC6f}ny&a!y;;L+(0N_XI{3j?UK3yv zDG%kqBoC^CO=|^*T>(r5sq-Lf8Xjg!uV}h~4Y=QQTdIVEU?w$S2t7N!)XO#y3%U_A8d!imID-tPptWsCj*5di23oqP(xVJl6In>ZMfan8YG75U;m zQ-h>b1HbRl6?(7;dVmKfY#Q{NGQ3K+EHMaJgFnb|ysZl})F_|AI;DnqupY<*Od}A8 zD8OQgqNd6N|1h`$0=R-JPzxZ`2Gk2K)eEuKo4^X}t!`?OzVo|=a6$}3zsR^aivvLw zft`(82wf;StqTPDJFrQ-F(ooK@$$FK8-#@@xJtRf^b#T*@uW7K0x7rxFqp&6lDi>P zvTmIl95I=!R%01TX*sAaH^x00OT8g2ovHh{Qhou|sU5vv9n#apc2L8z6k(2aZe- ztcymPa|c_r2Y47sjB^*>Q^X(g#gQU_dsI2WV!_wav44U=9FPP~#K{{h!0Pi!FdzUU zAj&BSN^0o=P*TMpoXAqMyUfBnC@TrRss;pX2uWxNQ@90Y+?l)CsP(J05loP@WJxa@ zr8gL*JX!++fGs}~!zLnt6#xU70x|;NhK9IHpNy8GoF>1tnI7;Ie1m7oqu0`c@LB;%Ya$0*0R<$e zfm8>b%#lKPLw@YbP~k@)pcWt*%mJb-R-`-nI!ucU5{x`DzWNJhh`-JZg-eLPXmE#( zQ@4UZI?SxKG?ED9Bml^HJn{ieHo(s9R3F0C=X_LF_>%qU@JTIvyueDFJYuGn7v)VnjKHWQlp5u*3&aO^khd-~xs?nBJpIV7d>76j z%aF*Sc&Z0pIKd_jECLwK>qH_S*v={KKS~1vN!UEGD9@JRg*>;Xx@Rx;{F|EC1H-xRvw zEY+k?3V7&Baz)Ok!qWh~1ZG^k%6y6=<)}l8j(OEOslo%o%GWOFqe)9m@mhgFU$LeNaXSg-~Dy zY~TllC?J&`2!U9xcD3rL+qhYHGrygf=FU?M3fMT6NLzO7k)oPr-DD~a4z|B5t1Z_UrxD~_iS zH;ITpR{c=|(1lyL1zxzcohhK82s!vEgvUccm<3JJ%ud)k7%@~-M!f`gI0tDX&P-5) zyd?k>B}&?r0)vTNYqi!Dy#j$6Oc?b>8P!9n+}Ms)H>3+N6I|XOrMFwq1!joeje^&9 zt2%e^$?J85>0QG7 zUBir8#FRxiyG3CdDO2##P+$Z*bwuX$hMj3TEHk{jVTX072Y2W-2-XFunks#r0(_-e z**Z3`kzzKWId=F4&|n6~pBbG5GhS?hcO*w&Lew8h(I$a!ygDVyXEq+}S zMbV-q04a#wFa{{~EaO5bQ$t!s6(U05?bgIp#f&J#FoB;Pwuk^tOM7gHLJ&}Fh&3@u zSpZhI%19%xSO|GIFh{^swS6YV0oEk)-j+%j8(4uc0LFp3gfF-V*~J0bon~nTA%lsQ zDHwx1Xa)^CGNz2ti)~|#jZalpM6mdT2skA z+gpl-IECf)X_pqCtuv`Ypyk!Tho*xa&Y(4UtcoHY2YmlVE)?V0ADf z0Fp#wYD*4Es0OHpJxZ!J+AqC=qU>gCI0tc1D8qGOk>=JI#!nL|T*K8!T~t!|OUqEO zgNqQKP+AkHA^=G+g;SupvaO6>ylIIT2S>mI|6hw>n3Wkg`QGgmLlaOR6UbQ)9fwH( zgB+M<8)#S*wus11+HIy?Fpz|5V28{mhmWS8!v$5v6;*U0)fSE`jS8?5#Ni(mRFfN@ zHGykMm;_T`#&viKfiO$GGPELQwlxvv-=1Ix!B7T!FyoE_53qqi5=G|D)&nVfDW z0#l-`Mk&B zZ81^8`B74R5SX6cLPCW!)YO8PV^r8Ofh&NY1c?LK1p*te0SCtj+2uzYsOD%fQHfRq zQ&^300EdGbz!p|x#0AH&=HGBm3KJJ5|8*?l6i>^LwE{=DpfGqQfthFIGzDg$Ieqz_ z?-68)xXu%7v^=_j(KOvgJ{T>av}ME*W=L`rZStX=X4&=2YW~tV0RmIJavT{4aWIh0 zj!5D_Q_nVK&=y|N&d9yXMac=U08D56&M|LV zq+>!UG*Vn)hi!1+^)5NMcBUs47?ZL`ldJWxKsQ15*l1V-QEFAu#e)qJZYp-LLdO9Q zzyTlA5uj`cWS868~|v{}9c%w;x>ldh@=1q$6dJXHiq{I2a})G0pZB9De!cwL3Z z0VY?}#+l|O(&qKG@pak4F z3`$EsglMn^OeY6&5QlI02Ee0OM0#5L6m?WqxQ`&2rVFsLkL|P%gfP&EHEDaOnuN)u z_c{E^b?`!qkgDPXP4IHC|6mXDL&pOV1_I|!`j@L__G$FRm)3xKQWP>Xk=eO;$9 zNgON&3;2IBY~)@8Isk|(Ie^$8h{MJ~96fs2=s^GoL;_K!M1cr#j2ctb#(5hj?&CLc z=g5h}N8lvNlqy%UZ0YhP%$O?w(fo(cRjpjRdi4r6Y*;a4$dV;nX0%x|na-9vJM}~+ z)Tl76Nv&%2>Q$R3=MiwnmB74p@W8J7%8tOgb=Xdsl2j-T9Rh5?u#rpPMi(|W@B}L4 zXws!<1jg}0#}1x4|9I@;#KEKJq$5Rq@vPC(MGFBRCr$X!;==@v6%Gj$Oo`A20f(p= z0wHiEO6rF|#^kw$&Eq+9=D?97Adcj>u~>%_Cr(ovtem?D@C-)a=gDFOj1`mq(>c#w zxoW}cRs1`6sWtI&oj2AiSFvDEo`sjz9X4jB3WdoNM-2hGUH0E#qnB?XlK>XYIQ6XK z4grX{#|9pF5a0-ek~O%-exBiA0*0OW5E>2y5wJpQq}ecHY78CJLu*%Dk(xzP(DsWp zxa}s+NxzBn%~#>+xMNNK%u|bU&HWVAP(>Y;R7^}clS~skf=88;Q99`(Klt#&k30Ny zl_gsItfM7b{|I^s-zk^mkrEgLfUpKibFmReAp}f9Kq0q`WWoOCz(V5mhNP^&PiIbbKtOEj$=)kXXcmYNnq?pmw#{y zSV$qf+?nT|^K{YRgOLT&AZ1z@dSQk>X!hX>jG_jS4Uht{(1a95de-kD&fd6+bOB4T&vDOM}TzHSvO3rw|3Vy z-zSa73^LJFlk&-h6DU|Tw~ceMI|nZAGR!h}aRnG4Xkl{~)Z&5dBw&DX$vSu96U`NG zjH?Lh4ZeZ&Wm;%~A!k1HU@yBOw#%r9@hUV$8n9j6Vr?;o^xN6G)#zJDIrhD)!Z_(9 zBv23=IWdwjDaqvWEoq$n#{W!{2OV_~kjEe=of+r&>7z7BA(C8jOEl4Jvk!vN6f%Vd z|2@n+Gi*!0*;gT=&;c-*0vr5@#uV1zpg)1@4QwcyXPjmN3lTtSi$aY`+I24iq#-mW zfYeDgl?6$}C`P^s7242dup5aZ9o)m%+gR0{SH;S1v}%>!a`i%l)npk22;Z3oK|VUs zDGzx-ADyt}zW2rNK=p`UAV9DJ98eAfn9Cf4!qAr~h?`AG4yF)H66PaMSB;i-D`qlWn;dPR~L z#gb&Pry%kq+p8q1eAJa{+(HsUB!Cd((h@qv!9H;aLmc|T#FIrM5I~qBYSuM{|ID2- z4pFdy2*TD40%)OV4tk7(2%t1AL_v+2NeE0p=$an1i!_aL(IA*{vca4qg){od3co>s zZ~O|AQHtSmW;nOH&FVTh+|J&-nMs<=Ls->lOZkilK2Z{&lsl^@9pZvYI2Ga&mLPyU zN*0F>*b4{hve_)(goii)AqOjSc@A@A#Ew+4rC<+}mz3#D|Dh~+c*z>P@Z|X5^23wF zp)M#~rI`@ut82r`B=bw-$fA_RpfM8T(p|AsUxu)x!* z_B7GpEDBPK>xa6Qg*^NsbR7|gAXAtU;+5(~$^+_mz!^6h(oI%(%Nsg%h}iIq$4gyW zM>S^Q$upfPE{xe38|YA%(c(v1RPARpbkR8*XaF?oQkUoKV}l&nV65uu^rQUZ^8gasb%h=wU9@kyqhL=E>$2^%t(ADe-}2VlTY9Kg^64|u=?S~UR> zU;~C%N?~gTJ;~}Eghr;>6+=+@ zq&o37(ShE?{sTF16DO=1YeREpC$cjJPp`hhUh@bGzViLyXH$Y7{K!RGo=X!#|7#yG z;J^X}F0ck*3x;?#ctbvTgD(kzv@{YkxdlZ@GuNmP71@;l1#Z`DnR|#|pkp20@WwgP z;X)OggJ9zDhD%S(LK*+qDvAV>O&bZ`=|$3dOzEk43!PY$u=kMga1WkbCJD~g6RHGQ z^3tGnk`+y1C9hloUOfQUDS*MD9=H$=TqjII2(xIsTvyaWR5dqKDh*d)W=c%(0k92% z7tRM*-$Gw@JPxF(S6`X);i-ZQ%Rwl3qR={dk!&=t0Mx*Q0Rly1>q;{~=cStu=U>ID14u{~y zMxuKXa74S=&ORGU{LD>oLxrY{eApv(D#@i>%%>dB%0E=XmGzE=Snc!%Gmd(QP(Jn3 z`5CnnxX8sQh@cG4YD8gzTUKM{rIa6~*GXhULWDIlry;wbP`VU%9k-(sGz}XoDHQki@3J z_d`0c`&nbS!Wdd(geVw6GZsNey`M&43)EH$1;$XU|1ksRHw1h`v5o@J8Fg?eP+bbR z^8nVhK8Rgl15Yj{N!WEG_OeraupdGDVdp**?tUDnjIQ2GuGb`!>%QaEUN1_7!=Cld zV;tCs#xG1^3Ie!g5QRv$`R=2JEP&zgjZaByeDF#)ui)_cE^t{P%rtxXAP&c^py(Tt z1~yz_1sEUz1SlYaG1MUw)i5Lh5L(*6n5gvGHmOAI4HnwDQ1Kn9|sl2YnC+vB4Cq00dwF1YCd&@SiD6|HC<~13HXEZ}djmOd;CbNJ>zg*%8bF zUf@a`TJJ3yBmtjGC`rjh8W$!E2PR04klT5rgs{{@Ih@4ER6|NQUsK!y0c3((9K!gt z+H8f;8=1hCp#*1W9@89CFRel7p~#zn!3t17{uzN841z06gGfA^be%*xkVH!KnKxL2 zHh9AUqJ-Ji2-=n68T!ow%1{Kl9R+%u1*YQerQCW5*;tH4xw%JLY>7M6!#I>fH>3<5 zVgoTcUo@P;Es&sF7*VUOz(Byu3<{zRz!iqL4w$))XE+^YIK*#FfqkXHBOak74niwT zL$hUrNa&fKaiVVYS+s$oH+(}(VWI6o|654NqAJ#1O?j81NlzG#7o;K5dEuibt;b3r zU-Eqgu^@=BJl`$kK|?W$tC@fYWR;u6POaS<53T{MMFAMV1%80d z!!5%!WJ5Njm^r8eib>Zde#1GSgD9c{N5tVY#p67#1hj1vwaFsewW5x_9c0NJKB9yo z8RW_($W|oCr6J@(3JaK^#37Z~-VsaT5F>nHWY2*JM~2Xq#f}ahQ>|&ih4era*cZEv z0oX{wE;NHRxMYeEfJ_oVp1Dv=eFHkkm^YNe;@nw0Vqq522-Rld1dff^K>U`DLHfo)!h7Bm$d6hb!?i1S%PGnB$5 z@S(HV97|wCv2}wlMrRy)!;PR~P<{ggE~R%e)&zFjcq%1#jz@Z48j9hlR3ZkCie_la zq>dt=I=GOK5@*^hj*MNJXDaFJSlA5Wn`YcV(HshuzJZluX(5zDKy_&{m_jBLf)RaM z;i<$Jq^cS`Tsin+H=LzRXUAfDH(|Hv3*0RfBw9gJ9S-i9cmqHk!YzjEQ6ZrhH*9V|+goU)TjOeLTi zE3zu!r(V-9mxJp!Ll`A%&tGcqQ zMbH*)ZOTc+>xx`K7Njb^7Q!zCBdp$;Jksik-s57{U3Wpxu8P--2ILM=Ca$LFj$Q@E z9xB!5B?G#UI&cGuoy9olLo|c}=T(p~O^`5INN#~Egr&hC$QRV~wh4 zO)QeG`XjGKrms$Buy$ul?5IpC-%4cdv^wswiY7~}Lrli0*5+l`rq*Nl+k@F=xDtXQ z*d~>Bkm=<>9?8ImhybLlfD$aqNTG&{&_NzZf+^gB-z}$n90K?pf;5Gq6(XSDrf35W zEO-`dQpO_S(nR7OZ)hg(Nl@;yUhP>tuu7=ov`*~eRBtGNYv*z6HFm~hcrPKOfkUhS z>jDA!u7C)jgc3l?h$z9PwD0?x0xz7zhSqQRNP;tP1e?ax*l8z<7OhR}Bhvb=()KEh z3h#^>|1hyi#l@o7Ifq5=a3F#T*`IWQ+pq5t~Oh<3h#NrcEYWaugR%S4Ttl(i)g?|F1QY zG|H~Q64-zwZ0S5NjxBR_;cOGZ5CB}C4cdufJl8cgXv4@Xg3eYw|`jvr6vcKK4|n&0VHg6uSdD{6>mJn><8AWQ4OYJqDGAb11(K9ms<> zFrWc)^(YdOO6jmDe#0j|n>||tReYj1RD(0TciH)Ez#4U67jgg}^1&Xl!m>nO4(hZf z_th461Yh;CO}0C1V!%}QI9#`Pe>Ip9Ks=E2ZPGzKU;{g}V@p-AFF%`JsuW59?R&d7 zRm6xk#4}!d!-`*F?cFZlULY~|u5TZ6O9-EE=czCoH#TFfRa~ z7oN2L@0)0rMrYS+4Gt2cbeC!cpC09 z2l$&ec0psaOwc(>)OoY@X|s`c#*T_k#*ePsdPNbyJG?`oFTo_d|7xBIlJN+8JqB&D zuXnSvwxx&SN!+xhtIamq^RXlL5BK(CieZjFT2nXe;rg#kD(hK1JWSl>kW>6rFIze^ zc(=zjIlyY`m5i+uz>O@tHnq38 zVmEf>GVkMNd*pUIxLdT*TU=MjWIJ-a$GgLIhy0T30%l`44om?xa0HEbxJzXmNx<0c z@-<8N`${zXUduee--N+0^^2CFQ!jJRV|!x^~?rp(^W~YocRE z@U%{0#^XKSlY{g1z00^CZ=sGO zKf{KPL-reB?++H_$9(0d1kJPdvtNF+6ZQZfHm({lK&T@?fE@vK1q%usxR7B(hXm4{ zOGn_GLv!iO4K!EIoI7jPe&qo|AcqYCKvv)|vC_mEMK1}x(c(i_J#6ICVa!O5=R*SG zfQnoAjp#R@47DY7X!M)5rrM51l{%Gb)uaEg{=@gG|5mPDy?O;3HmsO2WXX~(Q`@W= zRcCLVoqA%GZrz5~?B><$KImrfyHh!4#PHMfy~I|Ai+<%zQ5BoG@Watzc! zd8H!)irx&=60O@fa^}=!%Q%pnRG|%xQdPRtDFLX4ap(4lRU53Xy9j^{Mquo~Vg!s8 zJ3e+e*j>45!Rn=(di8->{|Y8(&~VO)3>Omsx09SZcQzyYAQ^?gWg9CjtXQ#fg`-f4 z2x!sY4V!6*;|buYwTFUwjew$Jiz=zx7-H(c*{qAOuB^0jTQ4|0%B;QwjkirGZeA&xVm!@Bji20>Mv< z{BTI4o^rEx53V8^$=~lq<8jUY?6?Ixpe;QpJH@ zR7aqKy347?0Z+5>9C5zU(Ud&8SfK+SdceUw8>FDjNDuH((g!VYSmQ}|yr~gD0evcP zsG_D?@Jgn#jMGaB=c(nwurf3YEwz?m%PqL%GPNobZOxSc6$ev?MPKpEEyfv7J7+wg z+!^PRF>JsAheL5700%G%Az%!Y2=Ks!DWWw&y(JuZa-46>`G(2>HR|+A*|;2POI=On z%{Q;c9IiM5j!SO2Vb*L@I(Tixu2+KY{}fhF8f7%c*u=hBXBE;qCCgV8UvB_S7rqae;Y;CsP7Eh?TXKxTxq@cokpauJu8D_-| zv)MwgFw}WNSY`c?*13Qmi0J13|F)CG#&N?a>WVZv?KGe)KSx=Ko%O*4HYSOtzi4i` zqKmOP4x4}_$fl+=a*RrhoN>gFQYdhKT6x`uoZ=0;^{!>Mf!^Q1G%w%??q2yy6LSs+ zoqrt=fqofSu=L^`rd1>(3ELhxEQgMVVG9T1V?)=@7Yg;Uk9{k_K@a3-8geCu0B)0# zc=$)V{(XulxXWGMcvm-~{EmVX%1Wwm)~e%mXjX3t8uXr5MBMxX9s`5X(y-@2`(5N@ z-xxhO;)TZpK58N^@C%%D~gUJ{YXv*i5@E>CP; z6l-czcpN1sOrwkS|Cr`9KL!dMUc^QruHdmakP;ge(da&+L5NgpAq`PTLmY7sih2kz zi!v&Q7f;5&J}%^s1$2lY@8%S_wrs9JrAt2-s7Qc1u#pjTlU-AUm7zN1VA!jQ1}nlr zYyJc%OclpCo;rmtWGx!w7#BCJF%GQ0VXIx8!V=^tR-#00G3Ox1S@8!BdCG0AF5TTO z^JYM~HpMoiqAg&hf+|%RGiMq~X3v@!7v}vGw-cI&PmpTZ!(wrAD(fG)RO_&4#DNWX z=-N2eF%WK$0~^u610gCE8ZE%j2Gl5rH(p~75C-VH*tF3=Xh+Wm1rl!qY|4G1vZY`a zmmzqq*WdzL|2PN|^soG-N>&ED5Xdq2utmMzVtT?@p^z(3BIE^it!vqJWHcM;z=k}& z8(#guPYTpvXN?l$J(2}QwQI%Yfo}KOAHKANJ&<31ouHlX{0}56SWgdTvYsXvH9rCc%Ia0|B&=&u zw`rNn-EhPkFfHbgEny1ti)Y-{Rx)yoXQfODitXww;{(MukVzS$xd6Rmtl(pKUpc04 zb?P%A|1a-=K#lcv=7B*vDVZvfqX-9=+`qUT2pp;5-xsYsh{|Sz8wALxJLEA5{Dk=p z-vYxr%Q27~uG))nvk}{bk_{ZGyJaTxN$x_0-(>%KS9s2=z4j{50kf%~^lr!z83H*> zDmp6O8OS*hz8IsjIBrB~wz@6Li%C$qvIO7;hp`=sN)UqMGLfSiz|B8U6Geb-BX$17 z^R{d>bsz%FaW7#!=hBY=^+23{o4mdJ z|E<>0hOcd74P(>WzyI8GUkUu@pVwO}0XO)A5syX1*Kizv`iau7Nk6Bf-A3G)24t&# z_5Oe(qmPY7*FVE?u~)eq?L|BNm|iYzkmK$H;&4;6>2kW$`0fM%>Vm8xY@74An6~ci z&U7B!W)gSs3&D!eiA|S3MJ*eSciVYWe%=^)Q63kD1Zlc~j`5_ij_+{9H58%MC9D7! zrF2X=l9j2(3lJT(e+)Vd#7CoOv+1*csonAaZfhji-+k9}fceak3at2e%gd+A<0%w8<%)?kMsr|Dp)@1})ZZ%Exjl_?9U|h>ZA(?>dk#v1-mIjIJVH z1K}Qn0Ltz$x^;Tpux1fGEay5UJouR9X}Ev~?|yAg{~BWGme3)t z$88)hF(6|haN!xKVGX?z90%eW9>Ec00T-x&A>`p1+Tj|yP8J>^4a#5;fZ-4z0T;&b z8C2mGx?vsbp&QEK8ODGPaN!WDf%5=G?r!53>+UwBF)5Cb5ew1p01)u_Z2$>yk_hbp zAyEO%0Uf+2;Z)-s%Hb9W02fr!3kg6RRgxOo0T(LuLe2ntzfS9@L~sxaUy+?_izsQnsL{hs0gF+ z2#s$LAtL#vA|o76+{mFCBmokJk^q3=8K6NH2!Ir10TSf$5oEzG{~O^EAORTc(H;o^ z5-On*t^pVhGYtsf4#waSq@Xbe;tZtV4DtXH#^5otfB?>b2B;tvv;qG9Fa-}JqXG&4 zxWw0p2+;fw-~Mi(GE(pc55YR}5hD>26KxwIj2hn39)V#lp@9J8aUgyZFogjT{xUEL z03U18FbNFC6z)L{a|j4P3WnfB|4Cv9&fpO0!7-+R2Fsx~ ztnD=WMm3eI|0?qT_zE@=XFmasP7d!jBe6yjOC!`v76%j(Ab}K^R2CYc5foG&@8C%h zp-KU>F(JVaIP*g901zHQLmM+YsXz%nG(^Wh43r={hX4i$fC{Jp2F^ed>VX`r!92Ao zMw`ww^=3Vt$33x98}^Phk7&1i@5gd2xO_@K$@2INZy|_|DwHcgSJEy?K@%DQ5C{NM zA7Mca;SS~i5j-_a2jUK>;0*4d3{I5_#NZB;;113}491iIlwb)+R0&XY2$X;T#6U%7 z!34uCHhvZEfU!p3jsI{oO!CY%FOo<5?Fb7HNH0kv|31>mBnR0R14(NVFDvy@0l`Zj z0ZK3RQ|F)z7UB+ERSZm3L(4!_S@l$5)lJ_AR|f)Db5%{xpce$P9CG9_cu@u&vX}U@ zPyf^`cW>5uuRgu9)lWwQx;;O5h4K*B0)U=Ge;UDtI9sDM_ffB=TzX0sCxU68iQVKlRWXzngx@8Yer z@>mtf5c3TY1yDBS^B)kcA&OKgmTW*2uJkNc7-WGIIMxt4_DMIj5kj^M5Mfg2jYYuL1bO`WnH*b=Ku=Y6lS;d404rL zl^}O@_7EZv9NdlS!=Gqr`sbzISv0H8n&T6Z!})mCYB2zGa6&p;nbkBE6O z+>Eym4Y)?%Q^u^fV7204Z!KYiEP})GaTgUI3R!Z}vLc+ztaS1>uYmxFfolOF5+u}1 zJ+=`TG<4AzQyZaWJ#}_xmu*Y+4#a?5vvVPqfB^7!2y$Q@V`k-2@HTp9Xb+enme*L7 zI9Z`r-<)`Q7j@Pwn4zLV;Y@2l|8#jRSR*nXZmwA17`jg` zt`lPt7`S+IzZjz%L8BQoK@GuDIhvcbG-Ts;cH5L8RCJ|X8bt9{3C4gLh+=?$F>ih` zkR2j{0ahD)+K?LF~V*lo~SGgJXdJG{KW2;Ymq) z4jw^5wRu5P84&;>R8@9Nb9h(D^bBAihZzEYl^_eMfoN=ccXHa7l@}@6iXws9BAd8r zp*V^K?KY9xC!Col3R^WcBBFm2s-YnkMxhZ#p|PPfbv;!RCR-E0`LZ*+ZQC|hUx04g z6tqc{MBACPvtW>SQO4dHpMP1OWlXRU_mBblQ1KHQ{ray9j|r(lVOnEAxM4iJVISp^ zqGdr7h`X`5IkWt9ZTR90UA23~*wNPqxByADo^V*eFz zh1Q54LTH6~n4k9${{zs^YFj>inu>K>sZFDU)a!D)VVZ^8E{Xe#pVSai7gH^@vZu5R z0HI3LfOX42!FLx|C!Dh_yq!q^24H{&>R<}}_2u|jyam}r_<17}cNrB~pdX^=CfH&B zfgKo(NXfyxAEGsIM7`)xDf1f|u9*~Kp|KHRAx>CA8zB}rbcF!{Ra1CesQ_=sbWLq} z$#eAulz;*#90p|I5IEvwR*EWjXEblc_Ud{XbCijVnYRC$@X~<}m9VsO+b0BMMwHGZ zmGU>aIL_f*&O0_i8T&~Wds7DjRl7BH*L8PuRnTdm!j*u@l>h@sV9BW<6ZBQJKrLqW zbf-VuJ(<_Y|87pp^P0;Yf}q*kZw{L8D%ep`OxvTvYA5HKDi+m+VIbbz5Ii?^1$=b5 z`J)?i$Pc`2G2jfK;0#D02gCpdFhB`%KnWUvmSMmH480cUC%t;N8U_-l5=T zsUQblfCOY92ZjIx{{7!yfCgxxHRj4e7=+-*duW*5%>5aWe=ph*wXdyx)9a1F!sFni z!rRfMe}4B z2D+g&|ExSBC7mkluAhH8X=OXp9p1fXty&dMEsee>6iz^b0&6u_7~EW{H#wuT8ZQ~U zt0y~j&Av-tdERff?Qfn0vik#=oXI^P2hM7r-45CBSt$-4ftlU!{h2-!S?Sl?k-;1u zm@Dc-{miTO&92^q2V%IjmK3rYqbIvkCp)B9xJyTV?{cymR6zk^ z4j@2t5Y1^+M$DZdRE8*_0%HgP0xu#?^Z?_9k$T_=XtSp6<2P^LCQj7m5hY5KEL#$= z{|2+9%b7H5+PsM~r^|ml|IMpaE7z`Gy@CxJR?HZ(WXYB}ZPqNOv#HLKHPMMRD@|)! zyL$bK^&dWy=LncHM;0eJv=hlq6i2QeHC)#SKqDrMfUsD54aG7vQ4=XmjSM|9q{+}7 z0tL(5DMR3n5-0@DD3N$ZA^{~Ylw^cK#~n(U9!HX9`EjLAYwEzde*KlFHdsM-5g;0j zz|o1t2pB8Y`)F>WyK>cn)eE-r8hBt=41^!$qvj?nzgZ++inE( z_FHhn&}k=f1W!DXb1h0o-HR(4I&HPzlv58bxQs`Pq_KQ!61j}KF!FqTTh$1RIlb2X0`%gO0 zvP6!cYJnpTIOcp@&ojUP!!5U(2qVm3q@1kD%80pKSbAk>^4J}75RecR?Giv46_owg zS!V?Ntc|{>eTmw?vTF0(*0mbECT?!R+3=ifMvSNB#`ffOcBY3d|29n=lWjKI?EtfF zd73~Y%S3a3d)T+60Afhqf%RR+83gbr{6u1mia2LyR8uBMj+?ZSi7GXC^sGjUP9m-r z3WXum8v-lrhs0im{Y&MKjt{lcn$?wdtY=dacsO?JsClHw5=-rdAZFOO?g3d4KsaW2 zb08Ieps)lnEMW*s_zy*3Ace(orEwEE%}31lrR#0QYv(%?=4_)mg)vM{!ugzWB-X(^ z^<*B^sg_m@RkpIh?^@IVhAil37gEFxf3g6~xcUb(!u$;YeL#Z05rAM!89XJ1144m6 z4P3$to(O|6Tni+PBZ)VNhQaA+uq8-KVfLJ(EAH{id%xdg^8abgizxaBJ50B} znTX>XbF3rS#KE0xphSMckVV_JmA||Q#t^)FL?K~@G9?k@Zv#x?@Ss42B}9^eLs)?l z=&-nxj3hyi+agY^Q8_kF=pVB=SP0*=usAskVkZopC?!N8udQV}Ex}_Q=}5=69I87r z>_!a<;Kyy{5HEEj1z?bL$UDTMAc~yBV;qx214?q5lynFkt`(9hwhDR(%F3)j`O4)m z4JcxKnuf|awKGeJMcx=gOWMmGqElwWK(2HI80*&;13sNX!4*~KP zK*lUm42z8nhCbkmh>Um_|oG>d}yPRR2Zq6elUw1j06nP?eq| zPAf|aOP-7cj<>X>Jy#{I?Zi+V*RqjaxCKyw=8ph_$<+QD;X5KKlxA}{ouWD3DV>`zROBNPNj`VEFw7`*$ zv(@MsvVh&T{3r|eI7$}AWSM$;H<%(4Q6P_5!tfvig}@>T>pd#Sf}C9n6x%FV02oqzGp`9 zO{G|B9IrS_m%5u=M@H$$hBvOo*NM<>FON!3?KEXB#Kc0W1b~m<{`TDAxr+ceYv2&7 zn}cp#%O;zI-oM-l+LwgVy;>>lDLF^NhnbMI942i~%n_aV+O;Lwh(HoV|0aadMJ&a;{` zYT%=qO{_Me_F9>=^T=yW&JngWqZu6pXt7(eQC}L#gBrrXkzIHkP#(T5G3hzz4|%5~ z#yl2q3g!=Fn&<%nz(5Tz(QHjj(@hJ4WYQPZ2LGGK+~Jd&Ql+RI;VMUrLQH4cC+LV4 zOZ=qfG_zT!D9(nQ)y61CJu1)n+cRQ-gw!JqWRXXl2xOkBz-d+lG+inOO-cf3(_9g| zCh=-WI4EjmmDRjtRI8V30wVUlHQHVI2T601j!P539W5#y$na%n-^Pu{{3R?P#+50T z6?h^8VJhH4H9Sc|lmOMWK^^K*4sd)!;DoZHNotD|18tbwHSsW~LHy~}hMM8c;YreP zl8%hC86EB*N2$$`*K?eEes;O@-1Z{e-N74XjZiKj2V%sgCh-i8B-H|!00T=N;#%M! z_-t*8Z0-pC(FPL7!YOVNT!m4~=SAmQzyI9UFmD^_70(Jg*5S2uxFh50;6`5EQIBrm zqRx8pBaeBEm%;Q-J?}OIe1L?mrY7Xx3@IGHm4I_4aKas?_V}s4LXLS*UD~D0+`~~D zVpfuP@VAnwPS^o?th;0K=}<@4zaI9r!H&j_hy}Zak)Gbwt%wC~`9S>k>*prKu?#kx zgI{1(*si+8Yz|bQ+NT}CGrlF`q;^`ntWIr(^ z{dG`?02qdZBuM2c;TKsZv0zF_5mL}`d>13)_BC0telD1Fnr3+N2XQTBe~V{@SLhQl z7a|YiLwr{FS~(#w?l*1|p$_xFd=Qd0c2+z0^H;=RW1)C! zMT8iLfDnw4U<$NEh7&jp#{U^FkO%ePd~Q*Q>_>&i_kQtrTAs#*p!0>i$0z$2c{4b9 z%-0$jHxD4AHo`?%Z%8`|m_If~TuK5MgwY6dz>W7d5rq?3{6S=#F(8x|g&%l`Il(c! zIC#BRPEBWR>f}}}IE=<;i4(y=0(=SuZxF*d<}>!6a8XNLU9iRy4} zwIf_VC<_A_HzxxrNY!!)c?S#mYmT)t}l$=@N$GktAq>?^tcX z2Z_RGIg?0<>}V26v;PiexDNZ+kI+{RI3f${kr#Zy7v@1!gmh3#LP*;Y>k5jVtNH>sJ8APc7V9)QC#i}56pWu2yJnvwMoRFYYI z#}?`Mm$CVqf?0Tk`HS>7l89N7in&5H!GAK?m1byp0O$_avRlICBYAOhgW;Ue*(7to zGTcZr4yi!LlmDGV<{uGO4(i}>4f>iOs219Ri64n&jp%gOMibe_PV~r|1@n|MafuUQ zhLuO0EYk>gfI9+O29rf(%{382 z07(x436lqM=-{R;CN1airsx2nO{pE@Nt-fdR^4-(kEntn!h%=Xp*!KEEs+kGIHCm5 zd|=m4vXCVFbP65Y;ilj@DCeM+ zi#V7^I{$wnsd$Omq3zk2k;jt#sEq1Bfce!330Oa~qeChBoaymymwI4Nl0*muI7G%^ z4gsp7Y7XkKaczcXZAPa|nTWMon@l&AxQT7!vzvp;d$Y+B?68c&sSf%`oWfZ{Wr;tK z0#uGN7<@4<2GKirNFPnrtdbNZH^XoJ;WKX08o78KOermJ(W)?YNyje4hze8?&z(3`gGx{n_?!hNk_34*&0_Tq9!q=lUGB)Fc?GyH{_8XTsjyA z@&6uoU}D5u@vA38vReCcUdvG7XK0!6V0S46Ak(+isyZ^lwwLR+aLcWFYN7F1 zl^ZIkcAIn`imn$qp~Km?<2IJv;$I@$d4wexhsAP1wipJ=V9j*~odE?7K?P&559NRk zZk@B^4?f$Auliwz8I1R5w-hp)Nu#&%nT#AurHcZ3Irdw$^CQ(5yNJ}2egkBs zX%e{m0)*obWuOKCw{>0>Lx)t-37R$7n2>&6= zsD_luW5IM6ap-ycc{kgr7!0)-Mx<|MswGr12n1NOrq+~fdqv7N67?&wa{I998L|Is zs})O}7b~2TcZ2gec?cY`+d>TZ(_i0`H~TXOfN?y@3SCjs5L)6N6$NnUU}l4|KGy-l z-oUCOT%oq9zeAg$;%c=2TY?^0x`&#H8B3fM3wF4WqR>zm139dFp?0%-dW<2yq4Hdg z)h?boj{Q^)TL%vBtG3=C4*fy^#*4zrdw9SIvCiAaVR(_6m=1fZu>~A}V0VCap)K1& z3JPe(e0EUS_&1T^OqJ2b`7tjH(I33PzAj`}mg}~7R1)*+4MfVDQ5m#RjQ^oC0cQSd z#aG-CTq&YUyR_@@iM^#G-y#@$;ai7=2$A47jQ|IR;3Ut*vcY4T$1{%SC%NaK4c788 zk24cYJj*1^yn*b6gRFb+sgm*8w=5i=YEx%-p@4ORmNiy4Zr8O3am>cUicj*d(bOLb zO0(yH$23ySHi62jyvp60zm6!yuuR2MH_IjX9D%8{W|*iYif%aKDAc%sZWtI{TSQOt zpWqi6zD5Of=^1BGpYCu)Je%4uxG4SM(*;Fb>FDq?=ZlG0oV!T`)~*sKGhelJ~AR z)L*|<3BXW|>Txb#%R4}JJkA{|Ncgx8p$cJ#P1tR0@;krT1pgA_kkE`++g}Zd^BB6~ zopkuX&^P^?UHORyNUzCxKSY&dkr8C^!PLdmTz^#}fKVSmHU^D5+8}OS)cxN@(`*xg4KTst z&-=u!Y}>&Hx?(Nkyyua4D&cA~+`i>seKdzSEFXWf7>kh^?Lu?y@~Ndw1KltY|7{J- zGYu*}4b^bx6EO|3o#omL-Z7r#i%H(*4csgFiPGqK=24kz2^jMMWN2b)3QWM{*2(E8D+36IWagh@ z2+sXjnDK}F_6X%5VJwm7cRmfyKmf&F4G+HCeh%YxtLlP|4@n!&jM`WCLn(T(EkCq7 zhExVi@+o)l(Mhz_L)H*VPzS)CSW(eYXywXQuG^}DX-OtYKvI^+W4(;o} zl`46tVE3-1Ado=~H;q6HqmX0fvLyAFmUiZietik{Y zXSpMFQ#X-N2C2`lN3v2>FcG`33%@WFzhDfI@9_p+>O#-Is9yBM z-6~7A*q1 zn0>=Xz~e`dAw`ZPS<>W5lqprN{P#~AtXjEt5$M$`7=dBKJQgEhtQgOnH*4<7RSQf)|n!N_%VTG;Sm!~~%Jn^!C5uA0Ge7Bhw{S+ZrBn>9=6%(Js(O%ji` zHrn)Q)F`9Qb62(AGhmNoL6U`t0HjA>b3a;p=Z+x)cLcU25#vN4I?Wn5IpfItlRf3IBAM&|XB1BGumBJyWPsTi2_HEB0(zcW`@@)riz1 z0yLR}07HB`}swQPE>gYmREM6rWkVG({MwP7Q^S2$0D1ovdc1CX0y&d zJLL%uQ!1@R7-RG(Ds|i`=b1rfBLEiy9>I;2v3hLejz`kCgNide%Hf5%U{C@Eg+3T0 z6X=*>raE(E)bc&NOr^p&NxZI4ZucvG?D-W zsaSG|90;JoE*L}@{;_s#5{B*M4Lvm3^UC*L+LZnM1$0$7e5tsG<@di z<{4ZD>2VNXRK+dMN7OLn4F89W81$nLJc6NwGsaMZ8e^)f#+-RZHP)#vy^AcoG0QXa zJ2crlc3P$4bLZ4K;Upl(K_D^YPFc@bND3-KB2e*fTWIX>&Sxuhz}v)lV?(? zrkr`?E%@Y+lKpa7F~=j*yfceR6J?rHj>;Z*%Aw|&yl}wL8g1Mu7U7x+_7FswMAVl=Oi$GGBu+ycb>OONKDeb)(;=rEk%0B67iv7BhL*Xh znTCLJ(wVyKs#TteW&iQWD>Eo)WtNt2v!hxqopRU)Dehq6rp7{=2%wvpX&P5&Z^W&R zZ_=zQ-J0J_Q}k@bu{&42r5O_@z@ComDR~~*L5fOszWoN1S(lw9d?>?l@7w0vU&qIF z0%oVWBa?-v*Ivg$&v)MvOaIq1u?2R1dxNl9sIn)*Yh6tgB3f!^Q-Z-w0f^i zcLsIs*SG)vlfehCWx-)Ce`fpzP=Jt0^pKYxMkGuitT*#`JP2y#e&VG7*Ko&`Mf zMNoqq%ntMnNWHEl&?KCqUjsRKLKJStC0_d5{wnA@@?j8wDWqWyQ_?=^y)T4c8{q;y zWkMSUQHUg&Aph_Bcfs@oZ-`42VgYk_z#Yo3gphI|6Rmi{BJ$6Ize{2UulU6cdhm%L z>=zG961E>w5sYgL;|i7KLh<=ehKFNg9qorh$cT=N)>Gi=0tQ4o2J((>Tp1jDH@=v8 zQILxio)hypJ+75eehgG2BQ4oTLbi~CaYSOYF8RsaLFHa!j3gg>=*QH7Qk9A8P9ZmW zNI7P%evqJM#OOimcT$)VQJ;o9qC*uKQ=AGElB8?qDW8c@oCcMlH67_X zgPPNcs+6Y}9SItNdQ_MWRiwo%DO4kR%YiPHrA-CcQ?1%jsS*>LUybVLl#0Nuio~fe zC96rpdd)eu)vZY-rB&ERDKXg<^H5Xq{vlHwxIR&h(W;r7B_}s?%?p z(x9kht7G}9*pP}fu9m%QOGa7Il@`{koMmTU`*~B+np3cm?dfEHTG`Z=7PG^ID@kK} zPyfiuQ?f0^o?-KPTYQ?yY8}{zb&q8KPpUX`t_mBHEv4lO4xAvR!w@P zt}lgK)aiEjm$|iQT0@f2(Tca0-)*dVk$K##V%M4CWUhPPJKN}fb-NT^uYnti;Qewqkp3;NXkja2 zMoM_ID2}jMuZv-NYIsZBWicC7Y}*>An698r?tHa7W7YD57#MJXFKo#VK>%O@lrR{Q z0U!V-Kk&wXt?obc0f+~LfF8zzij$Wt(|)x2wjnAjWB-8x z58#3=_pyg3N12FX)-#zs%-9L9t|e=3^P3+9m41+b0bd>!RPLN-F!R~ZXagOOa={K(lYqLFn|Sli3n|>>lWs9{-330|IaYc?aMK(7^Wuwy+OPkl+CdZ=RJ6L4pFrU=IiQvb}r0?|u_|%u%@p z6(YcLQOxM$1BgZ=FhK7#zgn|67PP#*{b{Jk;}H^<02yYU^CMgX-$(ezK4Jlbd>7&E zTaU)Yhi;2@h#}u@;5Ag%%m6og9ONP=IX8x#@|CyzGne1RnqjnE!b=m>+%w_#z*Q zHo!J$ZgZUb0}BA4!1aY+fEPT!`Q*2qKHva=kGMY7Gd1lKKH|d%siTCbP=*EwfbbK5 zHt4sH695}{z69VkR$?LOIWJAm|CKL%*O_q)6ltUvw3 zzvpv4{!l?n}TQ$|g2L=p)C4@i;e6k8WfC~&e&!Q;gx-{3yG$7lw zc|*J90{{s)ggXGf3v@C7D1ai!gkwlF0C0d+5C#$m0Oe~q2LB*LV0Z!HTL3qRg&mZ+ zdP~6X<3l~T23qq$Q&TcVc!n{EK$rsnnTx{s3j&*~09Am4>ni{<00t6Zx`nGZX}G=x z2m`NUIt7#l80V=T9SNXBJsMvx;y=ZnT^ zOdPa88`FhP2NMDp{=11Nw9 zJUllmJWl+$0N4V>__zaT0Wos}lj}DpL`e7x!a~D`bOeACK)`Y2+yZ(a%BJkZ6O=V;q$ya-M#M3^mkhx?#D_$ywZkibva~W;^fa2pf(B4N zrldQ45JHFyN)}WAeH6mPW6CXP38<9HYNN{Ic{%p0ysVVJt?bILS{J2)UqvxG9V zoXMJmvbMy8w~R|nkipFC$GYqr7qc{#3@|%%tTgXoLlQ-v#L9fII z%rs0~1WaY5Of4i%k(^7K13vMbzVhUT^Hjz5ltNcbJoS9fC+p8an?L#-JxaLGQ}O%< zGTp)G+y*plQ_^Hp^-RwA%+!#qyK<{j1bx|` zsI-(6(v>9AU>#0Q>qU<^1U;}&;Qu4W>pQ^&oDq+d05_lpV9>kqWY{XbQu)i$P2|!} zlz=dJ1ZSiKGvok0sD?-&%f}o#E-p5oi#yz%TK-Y z$ORw+O<=+FvrzBcRnMu{%qq}rBh;A1`I#}_)g5ER}H+zdo|D>T{DkB-df8CD4<>x6<1%|1L0dj;w#un9kolnc~IkoBL!@$J6XUxeKS$TxH5)V7weV1ON8hAUI#=Lrk@MfdH5QD)!pc+=qL60G87O9?r~~tXxEFSgqVt zF9u_vMVm4{tep3cQub?X%{3OJ{^J z8P<`hr67!=YoZ-%*&tpv${MhCnpeQmwn3dHbME1`>eb-2XI^TyZ7SG(_U7RN=PE;J zvSKiHds*UruK-SHy`?Qd4Y7u<=!c$X;C0zwo#-61B7WBBktS*D@=cHyVwNn@lNO|s zX6Tn*B8c`?X8#7(7!lT)mgkGkuZ7;}IzqPpQnvGAHhc!^FN$fTUL%2CX@YL%oL=f~ z?r9E;HL0fQ-lXScCg_%S=BvIUrT%Ir66z9jFoPCr8VYN)mgkdHW}*fO^g8Rc#v+9V zF`1t02&(CUF6!ZxYrNJVwf^fAB5N}nQjr#HKcngo>p;Y&>$ZL|r{?Q|KI+D9BgMu> ze64K2&gze@XUXpBsMc&03T)BNHjlor$cAf|qU_Rs=DKF=)^_d3R%y>x?RzHT*mkwc zF5te+?PKF?)UNH1SS!}{?LpdYM(%Cn<{rY1=l}z47}4qFMxWQV>ghh8rWS6GAa2~| zrbIhmG5>`R*40D}@wXPhZhRny7nsR&@&_SkgXgJky)5sRK*SOZ@1*F)^X3wA+wb8- z8}(*y>@d9d2BzABZ0#n1xGwH<>IWCl19A8Ub1*rU2!{)-(l;z`B=hf2=~orV#iv-a z9WVzBED0+U133^jOaS4NP_qK(Rr$_r`tH{UkMIen<{iE84WIEH@9_Hm@TZuy5ifC+ zI`I_mP<=pggBi3J=d#mIHuJLW$?op*c5tbM4{1mMEmQ{b=8_(VvwJ`QTg&f!5CZsZ zarZv**1-f0CnEQTZx}~zzU}9HqVX$d3D(K-{^oH(X!BbmfG{8P8)}I%KXWikbA1#7 zHUF;{CWmt;pKYyv>)|$PD(8}Z$bdfk2YC=?OP)C-bAvlrRZIX=6J*^45QY~hfSfG2 zA&}y%Q?rYk0HU=x4442GXE=nNJd?$Q4M4IY2s)n|W1e$$+fjxPAcggIgLa?-3aEet z-Sk>_JD=Quw^N1-q;*@L)_5R*Th&f{71(Wa7WS+R7HLe27Hr%{#^D3kN`bM zv<6@RAMCfs6h3%eb_{@b+i~wOC<9lIx}YOOL2nV#p7BeMvb5uLD)t8#Fa;{m&XySU zo%MBK&rDW#^+AjES&wxPR?u9Bxn1vdLHKnN@VQ}&xTNE^#V~ec7XW2vc4xn3hX1Dr z|D!we)^v~Wx0a~6ZckWnf4z(^_YgdH14#FEe@9RyWO$c%dS8TobNYJMcYg0se+T$* z7kH%8I2qCF)SmRdW^gufaQIct3Rs0_fC8`;gb8p1M>)AsFaQFWIb|5SRUih{iwBs; z21KafQE&impoUDKhdV2RV`v9O?+6#S-VY$SnB)Q^ll5hY1Qy-gPQZpp zP+mAV0e5%?PcCyl*c-##f!K$+5U>Vf$N)!|HwowjYM29Q{d-iX<9ytbga7Am8t>T9 zM|-t*aS7l9o)5FQr-zM{e9BMH*|&v%`1`;g{K7x{#AkBGcYHX2e1H%%p~$jG1Ge!S zB|#6hJqEV@i}+7jfgSjy6{v75AC@iu_Q9#J>KT+1_>?7}_b;CdO8WK`Io3{y9DEV+ z#Y13g-oJPP++ahbv**vCLnY2^LoeT#PS_$j*aq^1yJvb<3gN?3V!o_A^92cEjaY^@ z1SI+kBB2wtIS28La)3(SoD&4@;?1jfFWCH|XNt5&XEy?O;3HmsO2WXX~(i`=Z4 z-@MK)J3IBHGUv{o`C9udTJ*nuB@L9Dz@BU6~c$K7xQZkpU{fl~Qx zrrv64C3sdu1fZnSbrEUD9ULb_v=0H`4L6{I@hP~RM9)PxU3E`pw;h8vF(?s}wr%(i z0q3RX;cE8b7tTXmGKn3O_vKgRcT@QpVMGAVh7W-RxP+ihgZ~QAAXkmLi5rEZ*cK6n z8q(BZYaojFqo;u3^Gb>=D#n;&kV!^aXP7yYOcS2MT3KkZ{#cp|ZfIjoHWJQO8comX z#p`Xh>BAd_%?*c7KSN01hDsEP@PP<0(07(J3Ai)IA7?BAB0>! zA_HQAo}}T@B%Dd%qy(+B)P|Q6e#f3Q9J&1}px{|u$#IrEkeqRW0F?X#4Gb#iF)&TE zfwaR%g6QX(FBdj?R$gMqXJd=M3_u$qWsVJjl$;xI7ENTbU)_LAd{J)S&bkhkWV z4U-c_v?0H0q6?<2<3buyvdcF6Yyr_ui=IT+X1i^-QU4h&Zj=!nnBbD=vb2by?f$Hi zn>6Vo#ufx%0-e24E1BNDN&n+7-N`;GM??oFyl}%0Uxih{aT9zom2T>bR?;7bEbRv+ zZ@sEC!?*kh3`Gnr?sY9@Kkvx@%N(E{@-u^hmLcTxh2qI=*LX98>!Ke#~Q zfG_9{NSl4psK8AmM|cVB(U&S6@VT~ic5yJK!9-UY@I^3nTFDS$! zzQ%xxxHi=z%L^eaHL-(xKnI&$XT>wX#%CR?8*ld6QaZ~2oG3*sYtf4c5ThB@Xc#oJ z8ISzunVnH$QDtM49ENril-LeJL2C)}*cLG7;Qvl{?=n<#_5?iP5f43CQ`gq^Lp0_I z!Fj@A4!Q()k`1&cf+JKAV78a0(H}mZ3upjh7kX9$M?U$(1*T|8PXfu8?C|N4 zEjW;V{1ZOC)v=Lhu}PE&Mlo;+3oaAlm|3#p0uBjbZD|}$$^^(Fm%$8yGh-P8akfh! z1&xhyv*bU*aTg0l$~G%>qzs1~>WvgCb0WEE&2` z5JoD72_<9!dT<9WI^og308nKr`u}AC4=6Jm5mQGEd~7{cDG{lX6rqli2ufADLWZ4H zsobHdO>qhbt@iIrKou%cBY0Z4EQA_m$j#T98n$#sH6&9#T-tJQxU8aLr?E|@SHUV) zvTi4-X?>K~*t!q5j;kqhr7ITfYLCx$7ajKSD_{k?DBToxB!{I(Vi((3&TjK2mXj#v zD7v{+eGW5ags-gl8?K^;M;>>;Kn9B7ByiD&E)A#vJ?bbsdf)>ZFL;0o#)pqv4My}HND8vvl z5Cd+gWFMp;!2>uTHvg6|fGeZy%M2K^1TIjs#(XRUdAK(k8(6gQRd5C75zg}+xx%f% zu!cG8VGxTrZvQB8RIz~KY2sraKc>K$<2J!Y0=L6}HXB?DFuuL*IMQGGagbp!A7781s7> zjly{^9rdW2!YsKZ`K2|h{I-@zlf4qTi8S8MtB=yXZXkI(-FSI->%^UH&h&E5EvxLE zhlwLS`?uTl{*=H6p8ppfhMP8=A))EY9V{R@{4AZJx4{?AaBxd}-p#p7r!xLY1yvl* z5anX#DvDo>US_}d&dat}?(&zzJm&Y#_hk27XUpO_vp17D&wK9kpXUrz^EFI;rwzu7 zjxnQ~iFwO`?)0Za9q2S4ATaGLOw9g!=TYA}*SntWXAdUYC8svhD?`RtG2QEGUpw2) zICVM)^Rf!m`DWX0_q*f$fs~6V(WU)Gi;B_gN|$-l^KSUVxBcxlk9*FuUU$SpKJr`_ zI?3&O^uYV&?1Yy*=R5y&#S`%6{lF&R=tO9N$xH(D65z zvlp8-@tuNj!ZeAon#0X-B80~|mA96@`y z!5mY@YiqAz;nIabLq7UQXvNiqh(GPX-IVgW0P zWHgebC}5;DVq=7DBM10oKYe33j$=faBRax{Ifu7q8DHa zCL<<<0l11?abB1#HA!~z*)!~ZqF zfJdaoTC~Mm#Km0Hg*{D0RYc5!G(|T+1w>H94$#9rWWe}p8N82q$rRM9Apn2NEW8+J&S% zr&SQnbXMm?R77@eXHR5+Yr)W zrhG_oDS}Q>lS%^tYy*@AYK`Vem0W3-ZmCRyi84sXms&@dhEscp zDsLH~ng-WhG|8Ld10JR7#n5SkzQ>&wq0t!1^PmSZE{RTdhiW~qo(?QhGhE&X$*3w*UL*s}5$wbBWJV*wN%m;wXQpI3+ zP>r^5i?@Kux(eF4M$RfVNqcYsvIGpB*s8}M2yA2k$wbQ*7*9f&+5a&RYAij&7$^Y- zd|>`Cj>yC-(zViZn#Uy9f@@hGDxNZBNw)pU98z=xgqQ(g_Vu@q{V-u0RFlC_N6&{m#IMHUV2I$_0m=3JFGL-M|Zd?@XfBC_GeIG(~Q#>G&A_Xig<&V4O zRj=xi3kfP8(H}jKFzbQ`iJhr$P6)v);Kz21E#XrANg^vP)mfyHx(G|03X$!`tawVQ z=uzqkS|p|#VFou?r%uR{3N68gaNNA@4)2WffG;;068{T-(+i(3hUDx)WGy~{1g}mA zn{rJ@=&;GqFZ!8j3FmL~*r40;2?6L}spgWWF)oyRF9xd;OK|}n)88dx(dvmExI$s( z>6egJ@c#(_8B7ByWPphYS7_-KU&S!ijDlh@Rwuk)X}~}XWJ5O`f>a1X1#Ck-U>88n zma^2AQ9aLIaYHH>Y-`j}SdCR#m5EPE0|JNwKM(_k`jsrx^3)!qh+S7o5JRUaiQ*!1 zhP*=%Y-U;*7GkZkd%3c9hyq+~*EaBwbzv6KRk0nC9v<4?ZG3Vl zkFv7-RbZ9H*a$NmHjmreNS+@)Q1O6vpM@mTL$Y0tQS#bcz~*m0~$=*$%|8 z9BbNyx!LL7PdT1}h6Ng3M8E`ufeZAewG@DfSsFwb&qAVg5zFxCQizZdSp&jBN}MDK zOh61+PnN~?K(H4q=|dy1nMRz%UWdm!kihjwSYso?#~z!Rq1i_)TU+}>Vn^AObpj*W z+LNv99l?Pj@!AE%03bZ7{V6XAPG3JeZ~s*rL2F*4Gb~zF3M`!|Di_P1@ zwOp$NUE4KYOB$t}m|uqzU@J7%;%tPu+F=`NXKy8AJNETNw`3blWnVVNQiR@+7CW*S zXuGWgXaWpuq}i-Bv*8109|1q8kFJT0M7(wa$Ifiq!(un~V?S^c7M)v`ohI|2Tz(xT z2c3nNj=cqoxAn@vY4~4!VWt6s3~rpnX}C?CTVJH1yL}vr7YBy#lFV8$&7S8&7DtJc z4(gQ9Cn+3{kH)}JoL_)ovWj?AZk)XBI3;5oUliq*=HTwU6IeA`j%|^QCfg7Y>Ixl6Sx3(ps zzxiIoIk|3nshhft^YfVhwyCrFpO4(35AN%YYbUpQtrI$`Gp}!>psn+I>QNr7OE8dr zBCYp&vHLm|?|FIlxv?{Q=SBIa$NB_Qc(YSG;^8`-oA@`oQ}; z6g|B>)bcxo(rkNZk*x+#IDIE{{nrOU*pGeLpZ%0TXVKR?v1^AIEr441hSDBr zscyIc0SxHae17OF%Kzv;`-9)Rv|l;Qzr+HNL2`VE1o%RMm{r|=W#NED20%?9 z&EK5t=qpL-r~0xJ{AcZf@ymxa$OgTz*C<@9abQvqNWZjDKcuWlAYy;YH$I!&d$c<| z)5APIw1XG`#DYct_K9gDZ38|6|Mbav5rSSmA#(ot6Vir2#)TH!{KGd&0lt6zTu@qA z&mlyOD_OR5`4VPKnKNnDv>Ef?KW(sT<=RD{SFd0Mh7HSDjDWFXNQ({)x+_;LSiNpm zwR#n6R+>3Iwi6@pUp^P8_H5xQ&(MV<|L!61RqEy#s7>UOO|X| zJCBGVgBy9UIAWoEf^aLWFFIL=7zwt~2a6ESu_v4i$VlfIJ@BFHquoX-DIX2j31@(9 z;#q(a+ZfytG~;;kDY>DFIx4B9mXnUUsd!w{Gp_J?Fv1CQiid&(=wT~{W9o?{fsrzB z!yJ0zDgVF?aZ+6IqPn_#k&+lU3@4)-b7YcEzX-$U75;?s&oRg(qim+jFw?Bgj1(*s z(X0Mx=L`%AC}Nw1NK0t6SYCL5gHEo5<^=)dzy}&78j!#ZG&{manUL7z$I=HR(DX)8 z5}j2$oqqf&vE_(-?#Selw2szbMdON5M6yDG_siDin-EHPsP~%&rbmX zmH$c5%o44)t>G)l72d{JM&a`&z0T% zlR(Q1oEhqh`yqh?Q3CFo>{^clTDgXYhS=!p%AS?5c%TjM?Yg&eGF`-~ZWlnT1SNi7W&~{pE~;RM8}T&@cj0pT4lalh8JdexuzWS&=XBONL?M~wdlv) z7M=Ch5tKS*KKph#>p?$v9rxUaBR%L{iH@%AiFF^Icsucq-Rjvnk4ez21uq`@=*_;h zbhR5#eLBdc-=6!HEJt4RcVA}yvcL2`=Y4VC*;ZY6_2nlXcFbkpe0-Ripa1v# z`TZ}ReL`X!>VOA7r2P+pp+gt=SSG&OnXgXhOW*`6XRC#^?{vS5*ymD{I`F4c!<>2+~P{pj4$zHhH}Q;?at$lqF7%hyO%UQt~DH zyWK2#nXp4DE|GRLqbGY=%yU7KfmbW#5(ODYV?L8#X9MO6T`9y^LUWd)tf4Y-=@MOD z5}U?sWe}?v&S&WZIIO>-4522U*W-{<3;g zydxw1DNAi~QHhnz;VuihN_h^Hm<|0TI!BpL5;hT)68)z%1FFbZ5>%s^)M7d<>cvws zDU~5D=9 zam!j}vX!^uRqkS`Nlx0T7rQyZQ!=;9%#*$rzU$2_V>kQW{Eksg-xalr^x0IXqqk1Cjs1%q?+;15956 z+t;-uUUB*~e9al_Zn#`MY(H-dy&Cs;eI9l(AYt5HAh+(vM+RVaq55LAj(A5GRF;55rv&u_MKn3rnfK-bO8TBdA;yL@OW589)Gb#&z5+~@QDxzUrRT$?qWLM%5~ z(feE|Vmpm!Nbhv6V=nd651wPy}+00MM73-kXlHkc!g+Rx-<9$CnB zw+|421AJj}e`)n(U(IM)Z#z5e{(v*6O>R_6`rQ9;#s_}QZgz`c8Ub&ygwOnHDBm03 z`F?l5sRvkOt5}o&r~?2ZZtyz9J1czV^`f~9@rV=0;Oqzur!$U^2AtgB4FEY-=3U&C ze3Qo~4^6j0vGQZPH`z`4hoL)d@BYE*Q_M4&~*!UU;h43h{QgxkkR4 zd)=SC?*ae&>hs)s!gqV~hwuE5L4WhnTK4&X;eFaAullQ3fB0X(e8;;T`{UoF=3Vu^ z=t&#?s7GJu(?7rPtL}RCOa1*=iOH<3`Ck18C^5`xD zm+u9S%lU?<+Y*2q{KX?8(Ivo;5f{-y%rF32fGtR%4!I2hY=Ivz0RRpFL%v3Ij1Uih zPY+Wq0u^QOO6CtwfF4A`BlbZM5V6}}F$k#f+q$g)C_)-Garf9Q7k9B272rz3;Seb& z?w;^&lJ5sakpN&2{G`VhZGj+Up$gof9m@Z(6{*o0GvWXmU|HUQ1UQb{YJ@bt?NLC% z5bY51hS3HI%@}i#Vhqko{y`od;S2a69MsSZ6Y&+v@EYMTFX)ilx{V+Oq1yz23~*ry z$}w`BknMR`UQ_(|FX80Aq6$ z>F*tjXA+6?9-{InGa(I1UCZZ2_<58)C3$|E;8 z&gdNA63(Cm{GcNAvqdfB4-r%vB7jDTlTmPV8x4+3%@j?o^hfa_NQbmY`4kh7^fQ$K z0GQNCD*_9$Qy%6Z7ukXv%f%(N4!;JI7=ozUBZ{6%+K47C~e+88AKb z&>`7#2_IEh_%lpbqWua$1;_vu@IfE=GB{n-8{eW%g_QvW;8jUrIRSuLtrc70z*hg# z-GY@KcC|s3!Bt1%Fopm1E49;6DB^3((_T6e9TA82V$ucMkrXjicR&&!VATvIKvp~^ zCkT~P_wn02r4|D~0Y(E8+N~p$^C}zG8I_?%AW>R%l^1zcNs*E~0{~2wfmpE>3rMtI zvvl^hbPofL3C)Gw8k8KDv)d{l6`gT5@N-QY5k4W&-7Xd!17KsHlihsQVF>~bLN*d5 z@?Ci`00#9dQ56GJwm+4jR~z6*>6Kq`mIt*DAAE2_+0h+`FnZc;E!?o(E`UD6;tW1h zR3p|X4N^uifC0quIvYS->6IVDHXrJC8?JL*%}^Y{(PXuAUcZI`gq0crUwOULRLC?Nv#^wj=t1Z{HL9{?(T-5H{7eU}F$^4!{Apfgbij6Y8-Z_8>dM zp#g9dVpk*x{4q@@Gi?1q3@G3=7KI9qWgGdid%yQed6R0hHT3l6{lnvob_00X|( zP@j}^$q)cqz;fa8W-X6qAF@la^bGBRA|Ig&_`pIK6B7#H2=F0j&B98_*GkbcME~I( zU_gE4^hU3wPP@&5F<67)ARDLUe)D&Kp9BUXU{DKnS`IZ?zBX+4Re4p=Z0l|vE44!} zReEc)-BkZ#Lc?KC@xdHK;%`kO4XZN^dGY|bl3tG!U|e)?SHp>+I8Yf=KH}kjO~NBE z;a<1%Hs!-R!IOY17ep@iSuqAxsxQ4@M#gbeWfLu_sZpElHpOm=YhH5-K-xmTUQz!xR7< z;3wyS8pPn+8~_#mp&mxzYvE9sOJEU797v z7M%H!C8nBRd3vjb8uLn*0Cf5qGolxtdLuPLj)TQtS5SyW`hl9R1Bvkd5?JsSm|4}i z_yDj6Lvf@Ju%zwUD$uizm365rPw+l>2R~X;L;95Gy6gzM6=ha|l{yL2+4Asu)!zR) zhyi=Yj+nA#!myJXu``>gJ=a+~yKFD8FhE<*N^!JdLKro-ArqVR((n2zkd%kTc?Fh3 zQ`v|=bhb$wsb?0m*G{opZ?#L!vt647fg5^Nd9aHc1T8zSlN<2Rum5%%Lx<0li7T*Y zQ?#vH8W+%Zv*MNCe8aMYWuLydA)PHyZai5p*yZ4JGx{W zzj+o}_glS{+rK4{v74~9Kk>Dncf5tLx)1ydvDKuMrulT*dbf%i-@I%A5iZ zz$Vji!lk>yY0^I4ytunf2D*G$I9#(`QwAtd2NZ7mD4fJsdCZqH0=|Lbj(oe592viX z0vh`OE&v?_UB*W|w#6L3yG;c6ZP9bQ(bK$Era=UlyxU5^&f)xQpZwB>+^&P|2eKd! z?7@)uJhj^nAL`%>e!$tD?FYVK5cFKWt1ZZjEXog^A+0>rk^ObP9MgsT*qI&27kt^J zy*`wVzGu?D>)hJ!gXOy2=YAW^p3!cHN!*{^*x~)(?b6oSyWo+r-N(JvqdnoF(CX;&;s2V@GhN>&etP;H z%`x7!MO?1K{Ngzt;t#&TMZS4GV!*}w-9z5weTn3Eyybm6&Kds6sIabMe)wMA(Q*Em z0{-KnoZxx>m~>vA$_{W!~e9z2;qg>LZ>@t6tSp z9^VI^<-J}!UVave9_O)LCM}imZl3Iyeik$y&(8ks0oLcSp5(M%>5U`q(f$1BzV1ue zBW{J}(M<1!p6}(pr2s$hhkfCpKJTS|?{%W@sXd5-L3J3v@xA~2?o}S-OMLUozVZ{p z{2&JNT}bmoe`K75@FAb+4&Uhi!5Tuk^pE89sbdy?LiKxo(B1v>nIz$M-^-_A^o2t8 zl*0Bi9~OikV<4>fe_rhA#M7C7^_}7Q8H+KVqbQ=|aU@3AF`h8effiUj>+gfjTOt>L zg8El~=z0QkOs6@rANiXC^LHUh(qa7xWA@2E@|gr6b`cnuZlJ+~2oow?$grWqhY%x5 zoJg^v#fum-YTP*R-@kOQYUSFst5>jK1cnVGV9Z#tVk}*z9Cor7$y~KyIpfH)r_Y~2 zg9;r=^q|M1y+n>Q8BAp{WXYl~o7$`yL}yr?C2LYNVlV%Lvy62eOSY`pvuJ&KEH+DI zNv1Q4wR{=#rOKK%an71GFRkCdfCCHWc=R8+kRwZ;bP81}RjUuPIy?1bFhW=bEoEOV{l2xoF>|oemT$RyEzbuXo|h`D)tq^XStLE&Q+8b<)6x33pt)>eZ~2 zYu8ezzQ6zf6nUghEw6-x9ZK4H?6qx9!bB&wVnS#sv!eYYvJhy%PXxo(-(|LCb?N* z>I`EeVmO-g;c$;dBw2p9mB*iwTy|+zb}1(4Lf5`p|0wEjG+9tfZpK zm`^Horj9%==-o`NfCt_%d}tQWcvvLpn&2lovsL)SdIybbr3SA0tp*=Dz=&mx1fBo z?LgU9Yi+dno;U5Z*4|4%w%T^LJ=qVR(zYk3TZn~ zx1|sq=yijpL>EHI5P)uj4vM$xobHwaue=M@Yw^sR^?NNs0CVz*zyp6;BP<5OM<|FQ z+IK9LXC>37L$DYm@vAgP4WPF3a-53CKOao4eCDdjV^j>XMpLQ~<}_q0bh_&D%P>pr z_H8-C%yY*+hpV-e2^YIC!(6@X_umuAOn|)eZrpRhc8@0Xhr*eBF4%LadsCcer~T^M zh8xs2;G7ec^FV}GEim1Pi@W$Rg%Vq|L{}!OtXBg$+WG9$aZLBGHJUEa)^auOIHLrB zeX`hisT>>7ue9qf)U-GM*W4X@S~~y8-_hA2~0pK(W`>H-PA{^ zsa(OMYY_0kEA5?1Zw4u(oUKIKs;lpk5B~o@fnND=m%H?xX=shn+laJ>tj*L4fDlBB z{|s_9^FgX$1vH?p;?lK}9Rzs7bBW}PCz~N9FMs;8AO+tQ!4^7%d=tD{15GoC2#WUXVOsFCh zI!$6m45LCU_$>V?5q%oe)szh7!^q&rb!LH|80R<^It`>1`CH;EAO8wqjIdwBn(qa-6%Lp_9vd4k+5r+imue}CFyxQ=8dTrf6`; z$;9aJnT1MV(SqoZi>t76f#9#wv;N#{>=oncCfU9gm3^fZXHHr~x>*qHw#-tp2GfVAQ){qPG|;~G_~gLR{Fp<}KC7Za4HDXu{z3p}H7*C71` zaDe5T5Qa=suAv4}S;yqK8l z#5Q)GidWo{;!c!UZiTQ$W;&h3CK-Gu*71%HBGk1Obs^a$>P6*PNcU~0!dK?pikZ`7 zClk4lFGlV~;`tQqz2!pH~ncz*IFT= zjtR>S(dHbqn30_kIy?K?6KOWJE28XW zW4nd!UG}QEJ!#I^`P&d|wwQ+!W`t}T-82jLL4M6{)!OFV3K=)Nm85QW>wC)h_V>P{ zCGUL8St!BAO~424w|94?-3$jDy~|?li0j(ngMqlkEj95vmwV$N54p$*JL8d?{NyH2 zILcSf@^4qXHs zgh>C!8Jb`~2%w&pp;P_u7Q6e^xBh_x8~_29kTElo9)fBxV-Tw-bhYREg-r0h-dJhI z9s*!^1_Yo8)42Kq^iZLJ96b^T*gHqUoQW)4o*_O5yzHYTnPyyF0B)$p)eGQ;xQn3y z1&F~ZoPl~3C?Nt9=>0$rKmg(&zyZy01~7)u0JldU7*qg$3@j00W!C`!5wN^w(jJ7* zF9R4wFa|U*VED410vJ-jzymm-2Wspg>ssFdF&Kb&xd&he(wBX~$9)PR8NKiU0N{GS zW^c3CfcR2-x2JprzFbC{c z043lC`-gbcfCVeqf))sauy%JJ5CJp*0Cli{6Xt+qcq zh=+P+_YCE?0|ViK)es3wm<)SB03q-Tq<{eia1BAgd(7YpgC!Z6&;U8uh$8TMKXG@O zpo9US0=ef5>$d=mfEBpF3C=(a7yyIO00#}Qcg-+`mf(LoV2FsQhzW6bMsNU!kPJou z0Ef^MepiOPs3d2Ie*iE5mf#G3*nR}SiKqvCi07fu=nb!=!a0YAm07VdrBp?8M5Pi=u1|^_`Fvtw|hl;954FvFi z_80)zsEx6hU1q=lbr5~sD1I9ed%d`jFLH*s=Z2g}j7xY36nO}%kc`c+j7w(_d$@$U z00jXM0~MJG&cFyuub`H=unPUSk$|RbvzQ0XCmH{@7>0pSdw>uF ztH296ObMmvFa#oH%#@c$fC4bbCpI4?qK^mtC2FgR3WXT6vlP za)!KD4Wj@6oM?v!fP)yYjvCki2M~b?XS54^RQ?R{%9|fw;$-2hetIaCHYD0POdFj9CE0`2fcGmphRP z5%2-l#GV9j2vM1k42YWe=^qZ+dslag)9?&sfB_f40c7BO5}*Y0cb3vO1nQZLSOE-5 z&;VRle}?b^)hBkxxf9ULf;m0cL0p@|hR8KMo8pGK-4f%aqP=91Zkk)f$wcDF$0QCUHB zbVdN00l5>s@Bn!*4Im0%VFqNS7o^nyXarDtc7_#YY8A9`9Lkmn7vP*hIuKd#0Yq9# zN1CS%DH%?hZ&P*=GTA;+wehL!)&TCLbta0wx;(=>+G+O6p3 zUUH`Jpdt4)RR4d?=8akeZU+p|U+ zLA$XGL8~zDwXVx`v`{Nag<%bh7EL+pl&2cCT3bYg!4COkwN1;O&bqZ^>p~+-TuXag zv{AHW+qQ1A8%^=GY6}^}>b7?KHgKCaYP+ggd$<37>oEL3)hogd zF~Q@qK7$*5&5*$=EOYE}2oV4PB47rR@EVd(1rZPcBCv3_QNu|R!XqprIi$HgoWenT zbL^1?MjW5)Aqf!>0W)kAN5K!V@B$BDmV+V186m*7U=ROb58U9uX+gy~Ou{9Mz@+=b z{JX_5cN7HheOaIog6s?>5CL{p7|L(}z&W0aAQ*0Z5$5X$+&}>1`xkgzz%l|Qzp=ce zE3<%Xb3bc?66_Jp=Y?kM5&lpI+^`RV5CAP8$wxuJlw7@{@DBuV4Ax5zyPOEQOuYDD z54=phq9Dii;0e@A55&w3b8N>GvC03Z+{p(#xmAI|e>}=7hsp$C1_uDeg51sDoDhXU z0AzUrwCoYKToL9=0Nd~n+(5kpG0ecM&d99Ho4 zo{Tqqe9fY)&4G-{H1Gj14qbEHOaoXt&4C1l2vHdP(3%P0 z%YiY;>|DJBz|sVu(gYw1!+h1x+y}lu0P3*Pn5@eJ?bKSG(iV}^hP>0&Y{Ajj&_T^| z+&qhUyfFX(3afn3(!hU|(8m9@9MaXh4fbFT)oTmSY}0+f4fqhx`g{zY(AXmU&I5tf zW*yZ8UBw5@)1ds*K>XGrC&-cz0A-xkx=a9H_#7E6Bht_S3$O?n{TEP8*?oZ0E}hRb zeboez(thB}F8$8gpwh}b)dpeGlT8t5P0nh)$JX4|uFKgVC&=*do?Bwvx(o^lc-N`T z01rR`5wHNZ@YH?n*VMZW!(7WMEec_+&WQlb$h^)~ZPRCc!}#{)st*89xw zUDs*2k-&kFdYld6H@KoWl`Ko-6tWA+-!}_B@T0f z+zU@Z#vP zm<|CKo z0Pq1&jOQLv&;eY%mmKA3{^xCO--14H+DF2p5o{p> z%-*8y=Q>W`JTB_(S{RLP>Nkw)ss8AzKH{P7>eU*&$586$;n>$3>kzRUp5D_V?&i9V zuVPIA_@K+p&g}or9_`RR?ayBA(r)e4e(l6X3{CF(zVF8k@2rmO0e|pl-tP;o+~kVz z^v>_%uG#DE@azupw+`q4Pw~1wva}HK>aOt)&%$Q1<_mA-AYbuE@eL#Y={s)lC*SVu zVGN>z#qpl;FMs7M3?+&|C|T_CH=pe6u?#EE!}Ni|^8WLJ{_@6d&T4w^LcEYIq#p7n2@vB_XCX0r33+`c9+_P<*p(_jr}Y7Nt19u;B@ zb}#p@051O=LcwxjwG8d^Z2!Cak_k3LF95SMR^v0eL-M1m_D3K1pIi7ibNGmVI9WsW zySwmh4fv2BzHuWjb0a+kq&Vv9?~af4o&UEvLouOaI94P2iG%q}-@Tz6_NlMIJToxT z11epA#hN?1488WZf4TxRLzRR+^;G))9{giF{HDt@;{!u7L`EM&J)|MJJ>0qt@BEi5 zLo7r9)DJ3|lt%aj{EX|n+<&^^e@1&$CiHYViYrRz|M=3A#7v%~x&VO; zS+Zoy3@&SykbpCW&Q3ic5K-bpiWMzh#F$azMvfglegqj(sGE^ zy?zB7R&2;tWy?;yiSre#owEq|{25eeucAnY6;sNzsltT~AC3haSny!Ng$*A@oH%gK zoD$ixCBRd1T)9K(+Qlny;NGWD8%|XmTJ&hrrA?nkow}oBw{U*~4NA1A(YwZ!3e@XV zV${E?eg6g?T=;O}sad~;n@f2_xpV2(J(?MJL4qZBvG+{PW%TSc&rJA0K1a1jUei(vo5OaO2jco9e3oh zB^3XeXByh>l5WO`oa!vT9+zaYNhg{7@tXbP11hECz;wbLxuqNILy*bJ@@3ZPp*DM2gAGwvhyGWJxtS2MHgkXQIMi+ zW=^F(K}bepI_BFZM8Fk(+}amZhE7!H#XUzs-0O`;I;SG`p39~1G|r#+0L0ix_dqr zZN3-p`$wLrPggp1jo>s~C<~{~{A$^k&o4?d4u~KNNqT|jo6ki6AOcA$ z`jMYlUU)zPJ;3_iq`+;l=O25xd0Z>hRX-6Q{-8cyMvuQ`KK})Oghzw}j1Wge{y~8F z{i7Ej0KoPp@*`y&AOjaTfB+WpmGY4UY<}Pd0lC&bth{e{g)>~#>b5Y=>Cc2|)tkg*WAm(UQSAQAD2i z4Z=mF4geq>MSkQDEEJ#(b5vhR0+&Z762KFw1Y+0vKo5Kvk(GbU1|I*>v{+t|BaTdC z&bU}PN#5^XmqaFTQb+?4d~kqfJOn;wcp`p`0ssjhqE{+tN2L*fmIO#d9|#}{C63dI zeeeYV)GPF-5~X+!vFlw`BISbOR&+Go1+_0MuccDUt>Q5I_m? z_0WP~+lD>Np)^}qv6p?|20r#k$YXfIqp8bdM93-6kO2R)jlzuNye1h&f41~AnN;Bc zdN;Xah~K)T_SBq)iPPOn}lL0tekG10Fzu2rR%B+H~kcm9`BfZg8j?q+=qE_{5-s zvzJ1x=Ogc#(h;8Rm|sO~wo>?k2!s-=1UN)NB@!h}5C8xNH~;|L3RgOwByiqLWjKeb zzKlXqw4$iu`XchprCAZM@JrDv2#YwbRx+`-1q|&X&{$Fa=#w-E%{E!m&A(dZs=bY$ zY_(e0l(7`I&lQYr0bmAcdX5GFputQNBixk`*R}t~4IXUqq1=4BE@fSXZhFU(!fzn( zAKwUF9n6}&u?9D;Edg6`t8(0fj(4{DZ0=#zJK(PT2)Lk#K<*-7ghQy~O*sP|J;h62 z5z>}o=p}H5w_;%2@isM4if|t!N7mT>*SxUPaEh_Q;p{pKMI-i2;QETy5--=nB($xH zb?g$Df}*~@;jU`<+g}QM@x;#EagvwxBlZDj$V4`=+0L6|C0BVwF$?Tz;I_5oMtP+I zwsM$B6tfJP<``!_Mn*pZry>{g!c-P>oI&(c6n5Fh07kK#^}IJWd%3p!+;gCX*5^AP z8O(x4bjt``UMW)&(20ih#o&a`3ga2ck;ebDq)}SvMq@bBpKjQs{YFHCq+Fhw;^q0ZqYFd-aR%WE)w^>c)4#{bZ|RxPmRkQ}(=@%WQ!+oJ+QvhALXI7IT;E#%C+F!!-_~ zX+&fi)*!&gr_u3`Tf^kja7Hgw!3tIwoQv@;#?Bz_O&0q?Z76cBq`M)n%CX%-{E`Fv+j3_ zQ$~a%1moNfKl$w>Uh%3}IV;NYcgH_RrHMlmeJUh^`PFNF>x^-Iux8b}V~cJNefS z4a=Lqd9|>GC&}5_@Da!KY4r;G=T}WybkdgBgkSpO?^gBEKYzlQr4PkvtKELH&RW@+gnyfW6sR754C%Ljl1U^g*t`z#0sS zhzJlIgccnv6hoN^+WWyK{EDcckn|Xk3t>U>Tf)%G3XlURC*(rBN|6<5kr%NIV<!n(4he*r~^p=hte<|sszfWWXW{f$*w$=D3Jsp$|BOW*AHr!X4^Du7B_z@DZPe>MS3ks3&SNu6QyplppgD zzi)%ez|0i>L7+NlNPVLL7HZ4}dY}lJAkIo9RZr;7q!l;1E(K~# z#q>=ttf3pqp&fEfo5Z3Y3Zfw@qCZNaCHkR2k|HYlu98Yi!NLbh8%kl*&2h^lGx{;) ziii(*fRoB3IFh5|giSumqdgKQ0`MasQXlCwDtj;_L{g*^VM?pIPLAj&0Hs?gW+$p73bQWHi2T@;uS zC@ms@U68DUs-|m#r$5O{{^X-?3a4=@r;AFbbt0lnVkCIduaB_JO#{g{GfEQOlVxfq z6a~-zN~ndB%-3urh>|FZvZ!;)sEyjCj{+%x8Yz?1h#<|XAqArmHPSvIP?_o^$O3=` zg_E7?sg%qLpG?jsQlFyYD5O#i7da9)nCwQVNFU<%r9jss@(lTwgXIfG7Bmj6| zEVOC>wPLHyDzF%p(Fm=pyt=EH(JLpK&x-gX+MLs{`B27io#VUMUBkV%$BvZ zkcC<1DM=^mPwdp$WdqEfl^raLF^?4+mIYaDHQF)aSH!3pn^D=D9NNDu(x*+nMO*(m z?$lZz=nliA-tk};8sgK+{?V*3%&(5*2vS{4n8%@1K|)B z;hsRah=kpzEJzgQHPkDGh{!$FW4_I6Jr@?rca%MR6iFJcwdIot?ejgGXuT0`z;gjW z<87(7^0Qww z4l^~Thzkrt|L|Up*~VQtV>#xv{UCt;u)!OQy*;kQh4kZ7>kkwhLhp6JDuG=-UdBW& zw&s`!M|KfCHbrTXKx?GrVhfNB48tj86=kf%9{gli3lND&kBGpGD%tevlZn{nXpTS*3FjFe=SVwei%4gQFy@UwhC6ow*{s@GIXfa#pilFF_pyrFl zG$d*0k+5iwwla{0iH;^|jT`BdzBiO+X@Ohmmlijdmg$xj=b8>Un#SpwzUiH2H=Xur zY>VljCN`iZ>S7z}qpmfhR%%{L>ZWcrriN-)d+MnMHL130Q>*{#tj08~=ITt_>aR{T zuNG@a3+u8DG_po(K|AZUmNT_>Ydvf0xDGS8rfV^q>$_GmyVmO}%j>?@F}?=u9sBFS z)-b^~>=Y~P#2zrjW^4jm?8k;K$Cm8$itNh1Ey~7h+`8<|{wmG(Y{cs9&~7QvCT*7* z?bA*s(^l<%O6}IRB-V!QOM30uJ|Wq*Z856t+|Cz5v0gbdZF7 zCW!%GaCgz~5-@}Ni}3!QaIKk#c@O~xaD@a3S zKmvx4^OvFX7u>Bo&vQNBbCKzDRGx@G$MdKdbR#cx0Yh{aNCHM^u0_X~LO+E+Xo3#7 z0E*Zx5O@MF_yaU(0!AqGQaANeNA*-!^;Ku}R(JpPSBLdjm-Shv^;);}TgUZW*Y#cJ z^D*LPDt1uq~Z{z(Ff z00A`ELiqfLCpd+DSNMfz_=b1*hlluxmv~lZ1ZVfcf3N}#h=>vRgD5!>QE!5f7x|GV z`I0yJlSlcKSNWA^`IdM2mxuY7m-(5e`I@)+o5%T_*ZH02`JVUrp9lJ&7y6+m`jfW< zkN*cR=zz280D|}UC$IoY$C%yf053p#s6SvU3rGUG`Vn7X0;vy~3uppgFn1K$1|rz4 zhyZ~kAo~*;ha`x4um_nAVEeK^dkskYh@g8b@Pa2Gg1DD^kkNo7`1B+o0=)n8iO>KI z0Q|rg84F0PICJXU$+M?ViyvVA97?pP(W6L{DqYIdE6hbD_<@$?YW>IF2IZ){W$aM*t2Ud z$kklX?cl?U{|En}j))86)2m;fJLm=L?BmOyuedt>`}p(g-_O6l|Nj66DByqu7HHss z2qviDf($n3;DZoGDB*+@R%qdc7-p#9h8%Y2;fElGDB_4DmT2OMD5j|5iY&J1;)^iG zDC3MY)@b96IOeG1jy(40NG7S|l1w(~0+o_zM{=bwNED(Iku z7Ha6Bh$gD&qKr1`=%bKED(R$@R%+>`m}aW!rkr-_>8GHED(a}DmTKy$sHUpws;su^ zszObm|M%*wG&PaLt+?i@>#n@^>g%t-1}p5a#1?DpvB)N??6S-@>+G}8Ml0>K)K+Wl zwb*8>ZLfbw)PxskfGcjeu$U7zAJCM^v-Ku~O;nKP+*(UJyV}L{HGFu|}3`?6FGIG~`9MjC^eJNFdBW zfE^{D?6OB45D@aqG&khTLLD?w^Ue&NqEOB}2dz*92mvkh(F6IMkkLpZt#i>&H!ZZ% zP)qIf(otKzGt~z%t@X}X7v%NVFLQ0s*krpLHbH1J)J)o_nl1L)1-%Wot8@$0w%rOX z|8qB~awLLH$Y}0q<%bjC zj5VU4$~oeBe=|?&sgiDxGq1xQ_s=?KZaO};t1A0I%kbX&?N|#GdOE}VUVP}&{F6NK zP$Odu0q{H@JJ!<^;Jo#l<1SG1+@C7@FsO%L>gqWULx48soBBKZ?T?DS&cVPRKjp=* zf4`{n2SY&l`vaBidI7)yc4~gT_@4mV#O!MJ-6^LP*U|hBT~UqhJU%9O|%AH=N-Q|4m2$ z*$Huol44;Fk%%ZBcC87un8gyAc&PbBab{H9VG*%*#VkI`inG9?7Zv5ivq3S8V{8-{ z#hAuJm2qxeY@?#qD91#-QE;_@1sm%~C@bobk8a!|)bhATKnBW>g*2p}?&vru{!x*A z#2q0aIVeVUZjzJ~6e2SjC`+;qlbrn1CPTSMK|T?bq~y~mQ@JNk<_wjr%o81BsV7&S zF_E;K(=2m&CtJpmmv`DFFzMt=G!D~FV06azjwz>NE@PR@jFSXwkqc->(@nONAuP^u zO*sW|7QwJ)H`SEQD8hmq<9yRMVLH1X9$bQi-P%%mqaC8Pups)v8#MU;;x5QGHL?#4rTz!Q= zT0sC-7(^9mRjT*c%G9_1v>>jS>p<+9S10{0AwHGoK;$Zrz!tViEHsE)n<@~XK9)+P zQy)RbIuNZ=b|99$ER*I}49yaNuLkj~Xr)9tW0ZCv{v!ZqnYvmj|4Gob1&Qr!Ys*@! zg)V!hJuPpm6xrz&g8;!5Zb5cCTq7O!w*)wDa$!3Y<~G+t^V1yxpgWN1f+V)rRnhOX z8#)4H@3;c-Zg__j*@Kifdjdi4MAW<96&=;xMNk~!!{_nfgN7MAxD5^ofh5=jcMI+W zf(1>`AR#z|JHaiuySu~Su7lg)5S%c}e`~jPw`$+2z3#aWed+2$_viWkT~ z#=#Q}?G)Ayve#kJPpFNOH+@u6!(oJ|w7s2J1eG@$o9lo*tZw!g>TS;0HCdNNbrP_g z+53^0moJ%mNv~D?utiP%ydvqhnE|0#ejCIpvYjr6&=YclXo{D`eLVW-;yxpUG^xr| zXSW6E>-3SS^xJ)y^cUi3Vvz_o0gVqC(9JBbXp7wEEpF3+L?P52i%LdSgLkqi91LdB z(Z7+de%-Dz)%5)iYx#i9Pfl-2JJOililxw+1;T(U8NA-+k-Al9!-JKgp47mZn zV?MQrC*LxwLAlJ2ox^O}!1sH8!e6hyfCY6&gb^`C zJya_*Y=i$AEA(d#0bdvvmsLPO|9f>b)XyV&r zVgzK7x^#Ec(w!K2r~WH<^a$6~svFAiKrHrpLL=!EetW{dT92Rj#{eB7Q}z-V5;YS@ zaAPZxW0RTe%eJ1|+dLY0G*rE}uB+}t7AVPJNN&s_^H0-=KEI+FW>|Le@3OxRh zU?1H_5G8iQafKk4O#UX$<)i6kpy=IBzS8F*T>iNX6%hp1MbLXb78k$K1pfEd4}G+eFEp zxnwz~RYcZg*fS4}xb}o4xD8}+ZY?`9Q&*wi5-;4bd@{D#SPQPyejKGVfxY%AB$No@Ba27|SeyytzMpV|50#e{GO? zFJC!6u;?o#ctd$D(KozF#Js}9l~&yx&-1)CfBR^WIqk7X5X!O~7Ff?|gC^a4o1%SL z2Yfdhd}s%?(D?n5#C>yJJ!!NFYeao~cyT#c3GYcAiZJ~n$n@Xtv$pR6QIdedw=9qa zF~#?uyH`fotRhmZgfdC2x1Gui?@VCz#w}9*PMiKj8m^R9MpU=HsBg3wbl4zTKQ#S8 zl@kHp-O3zLF9e!hU4hpwhLg?Uj~col$L_%6y+E)SuJZy*rH}6qSx-+Sxpy6lAsy7? zjtTm$IuJ}A%p!)H2zbs(1Q+n*xiqYFI?p<#Nm&<6`Bb5}?^Ae4nHJWn>ffm|wGmQ6tiSYF6l zxd8QG4XUI&QY#DYl?cK;##Q8vW=Zb#S5wn&QgEkwj zcN@bn84G$73+;|WBt^f~4X11h9hL?06tT*OM6=w*u@%LL%f()Nc+Z|4=dPB(8Z*lpt4>NG|~T9Kzx-6mPcp z+xaW_t1ieD8z}3}8t9(zT{qDQJHeL%^dmXm<7-k}NYcBZq~8=Ee|MG`fw1(U=n!3S zm@YK?4jA>7HE$@=D^|*7|E6n%}Jy8jf)IPGh@goSWNw~ zl}>4rJ|2=@jUBmskh%>>1EXX(cV{f=CQB?P_{D+7k#Ki{hVoZS7+I>_;RUZWRCJa*6IyHBO|013Qe6(7zmbNqw< zJ%=tOXHp1xXmiml0!Yyk$Dm(O9%Yh%1!d;N*q1Fh_V=efRtw69)cM^5^-;?QKevXLAz< zWwY)@JyNgvK(3Bmu)3I`5htdBx1>pivIV3UUylnc@B|m(vew#EeTD(`6_Vl zwb6-n?jLqK<8)miDjyDOt{#djVO2GVCQ01p#)nS365*NUcCN=Rp0rN(k>-BA?!i~B ztW;eW%kLOWy8eZ9P3yMiBH9n-dM$05zJ@j7KQ;@Owgh|j;Er?)QkCB<_q;CcnhWi1 zEbbNZ%s6TR8gYWpmRYuhx}?LaUW7AShMRxED=?=5I~YWNG!UeFGjNF0&t_1~NY!iS z#q2U7`lb{}b<{!QRTPh;?Dk&iW%2C07p%HdXbnsQiXUZpwYGe>1qOR{g|t@q+A@Ri zd~8+-Jsz3;(^$UR0$WoFe|u#oxAy%iEsQp(Uux+!H5m36>Ivm+`~Gg2RIE#od{{Gf z7^`;Z)MMD0680{|Fdd=Hg|%^v=O~QZERElk zCTDUE9UP5tl-9ivVMc9ZD+?z~7w*Nkt2gn=U$q5dmNAnUvXpST4JrZ`sd53&_fRCH zh!~>$38SViL`Rkp(x)>sD*1JeSn%7m6LB#M82Yn})*c^CygjZqcy5Ip_j5$_3#GeD za5YGcP7cjYqqWZXt;|dnPvDJAsHXL)8H^$xN0?)#Op9l;94EC!W_j`J;?ri-ZN^xr z<{pK|o>tO6QZtK1uyM8N=x_~NDa9D1_uRJjd~O@}Rhtt2H_4hluUb8ym@|iYK2=FI zz3(+6Z#9c`KHo>RQ2aRagLD>XGM8Yzs3+X^&9E(vdLn~5(I0=*h-;L)b7VcaDk{bEnra>3|m5m%36+4u}zPQ`I=Rr+|a z;c7_P3J^ItpRlu_g1RWmv_!bE%>Hv_#c=!!_2h!xim}KjzrlQj;@l$#Tv;5RfC>lI zEfKFS1-P$987$~71z$yNUq`1I z(y(hFJeeP(UO6&cI}ur5v|GSDflE#2)=Kj1NWjt_ME!+iN5b7W-!ItOZa;CCO}$g zs8`L>5OH{*6dHJWWML`yKuzQ#74Q(X>tO5nz!4Pz-`v;9sA;EJEbz{2%~Fwr{UK70p0S-Mq)f|7`|&&yS^^NL8Oi<{Z9I zIYMPCb=Am=nTXx6$O8g;gu?0b zY3BM5v%*Zos>o@zle=uvZ+XMAHTIc9S%V<7FDp?AulEf{#KkamQ)Zn+uvto?gkjw zX47u+FW-~@yq5tz91h(vh2L#)Un*s8(wtoX$T&Medk$?~E_2+}jRmwJJMKCh`sN)T zP9L{xsh*$}h}@TV{mcKRpN=~np1f5Kbt{&RI%ZE!k7|+E?vTE>$fp-#qU)l582B`j zNva#-LHN{=0P_DSKUvQ-%M4mPfPis6ka_a>|F zDQAfMd_sTayulVoodTe~wWd+aS4xKjzT)5NkEE4|IkBK$k}bxLEd?v;OKl?=&pX2pZ1k+CI;*@ z83QQ|?i^=oD^6w+@7=86@yW)|y^eFe!2y5eF&d*sgS|8dg_~F8oF<1`s~uQIUUhC+ zIcTa4SJM8kCGhc=A{%pv z|6;e|%dDu1?+Z(ei%)tmR!Nb$pvJtw^shdZqC9;&`EdocHM)=T7x7E6%U{aLYS zyhDeM!WH5OObuT79YMv)mVC*0UWK)!TtdDpEn5|ZH*!qXbd0}>zbo>~ZGog%I!f)8 z1l|x$6_AfVVh;)2@Rg1tTZsuJAXw>P!}K3pd6A0ZWL&UuYR&MtPjWpiZ>2M)()iR1 zsFcp-#P~7l_y-8>U4q~(+QMWBh0ULPQpD)biY!FC(67o=)2C+aYuC(Q*i6%)&GuNNIGRcAk`pmmE6s@ zY5}{d)pScPi_=$2W^gq!-q5^O{~5NLSRhaIPg~Dhcc<=iASFVJ&V*;w%|zoRTyxX( zkmQG!7Z=>EV-)PHK5rvX&;L37RpPmRdN;GXX_Sr7`M4R-zG*iA)8f5pDn;Ip?R1I0 z+ig$EM~eO54>r#rIc#jkjGUh|)I8rEvV?5qwk-dFNcrUbqrkO}rgkr{%ZP8kV16~p zwz$MkcR=|eLEBCsy+6bz!6{bHUZ4-6ed_n!UdghfW_x+9YnHF&Bs&oF_>CDiYWX|c zK|l6cAyJ~R+~Aw>G~Jfe`{v7m5=>nSn*mb8;a-m5l=GRsmB%Yiwt;;oJ#&_{ze%(a zznxa>>eBlAfAM^BYtSA|`9Y^$@$;N9VSeQ{x9XbWp6u%Y>yNC9dhgza7M{85&aQ@Y z&kKDs$irObN7|nU6_B>mYx-U|!#Pi8rsUW`>F30I@5fC@oxhi; zWw7W|Iy1S;>g2ZUyn-co!P)@JE+}he%e1;k8P2+73sIwuk{uejM{B)V*E3Jk zZRlNw$}UqROfn3djomDlQWX0EX|lto|3@KjNF_Bbmsv6;B+kTpntR17>;0%)(bYeP z^ldTA$E;iB>VQl7tzNK5*br@rfNI9@Oo6&LcFbqf+%%Nc{5KB=y>NOy;oIK9x76&! zUzPug&f?ASzl~K2ljh495;m84%c~kJ`j-KV)I11l2_ei?3A6 zE%Midenx4T&)S?}u`xVC8qS6+1bMlz)=PeoWh(&=jXBj=9ULmsr)u}bYLzo{&RSY7 z>G~9F>4)3NS}|W}&nM2+39yw~Ip}N6S-aL$Dn)#`YFrLpNGwa#l(n}!)E|EJyOAu$ zEXC(jD8;9!NZN$urcL* z$+{HOkx?8zzBJ!fl~D2a0H%dhBWb@IEhj^IMx(NjbO3;-61NY_*W3$i_gsfQW%?$# zHoe6J)2z~bigTD`S6!H0l>y0N3m~^fIv`{Y+OlMtNpnlb<_!(6k>5(mSfG1tKNye- zGa<`NjLn*^@(n@Sno!cB58}&JtGjcm(-NT%h#8%KE;!m`;n}Y2U3FAQHHhYTi%$AU z**Zx|&)m;hm%et8_p`~QEO3xQKwH2Bjbgr zdVGFk91y^9_KPj_F;Anv15d56N>PvTu5=sKzXoPrGk!0;Ax7_QHoi7i>(ZH90ECZ( zqx2>OCL!sy&8jeB*AWj=T9+YvbY5ItvSjL-*VX)Prg#BKORrp?f{L&^ReY%&n7O`( zh(Z{m^}M#G15aKlJ7ypMB6cUIntZ1yE$-7^>B%vu6Cx@N3b&g^2d_P0m%gkH^X>Gb z;#MBW!N{>k`qn;tJhO1EKOLoXUVmV*HSfPDKD25&9!4J zW_Uf)^(Xz%`i+7crtZmeI`^Tws*DqL1eFx_?T3o^a(HOgN*nA)Uz&>2v1vT|?&(RW zHu_sa^$t@Cw(+KRGTwvFm48;o1B2ffi0%LDW#VCaBiI@z=2I0VaTd<~2*|ppMCmm|B!1J$*(4-#^^-IvSxt? zr2qjKw13}p6s?YfRJ_2h5UN`u`q-b^rtujH!s`Moiv+E||0e;UJt{tu z=q5fAnigtxc`LY2DSn^Y>boFnPb3g9*iQHe5&pyaQc>LZOl(O3;&)q@ohnKe(1m9Z zgnUN~y=N31f>jcmExt>Ns++x5 z#4hz6=gLRlN(ujciPN4cdSXD6P^*7*y#WWiXsN_G<8K3}Zo4hv86qeJCzPJ4)Att2 zzyy_}?2spx;&y&3<6QPc`Oz`C_pO;26;tEyRN?o0fG^>VvQ!NL`+cIV5~wdapwaKp z4jX($>%WFSFY&an8uUpK12QN9U*g;UnO0g-0ie@;0nt*7rP9US{jy9@6UjE`N9n*H zQvNzJPQB$oMZh;nXaEOPgb(T_S;)x=@FRvY5D(fZHkzhEAEsq+Hf7S*g@Z(8k;$=r zBBg_Es@TD$GN@-V(Rc&zIX~FyL%qTWwJ*hAjL7)5HbilRL>~2f*kb-Fm5WIm8bTWk z79Fx2k%*;&rstLh_I|AG`H;m4`1TwCsf-CKm`$ zf__jAARnCykY4Hy*{K~lmRH_IZy@v(+E*O*OHxKqHI-~C2hfe2286DokMALr&Rbva zt%O|mD*xq^xp0PFI;&LY%7;ad-{Eyn_m2M?f!=SABl$-A&?ivp$o?@+py78KN=#t% zab1~BU`6nt1WsU^lVjyg;4;6s?VULNJC6TP8UIRk_h^F9T$zx0vX^R-NJ)`Mb246W zk`zN3E%4K$*CfSH6^gn~e@iE+`lP6KKAo&g(q2JGNz@c;Cm38*7^J2Og{GMQsnWWr z-P=yFa(!aWQ~QZG#ZmW`d3K5cHpPAXnj1qM_hIVIKb|-I>No7uNInrBK6CX`z3KNq zx!)(KZ-!0_ws8sesn0i0i~QphxtbnDOp8i!iZai1y_%62<&e;vX_lXnHs_EIoT>4c zkvnFWtD7k;o>6pRSKOJ&U7At3VpAc}Ovjm3OJGxznoSg()g)olbeWB`p4CZb)ybRH ztDDmQSE@TZ>vk`%f2`@TIs5rvzVWE0DMqP@l$I5*7PjFWQfTInJz$grjbAMbYfB#!bmj2W*0(+ z7b2xJA~6c0 zW-`-elISJlEalYEk)okf6UGvfTXqb+d1It!cT<{8ii4^8CSa=PE@f z^U4^-N{<~ykEX$Z+)95Pd4Hfmhx^KqCizgEL0!?x$Sm2&jzQVt%D58QILT@b_UaS~ z*_4!Fs=(@OI_a#7VeHq{1#{AcJj0OW)#WRa<=Iugp;aUtgC-Hf=;Cg51D~XdA5P8= z-yS92Ha7z5!S~FG_Y#coLg5H%Vnm-2YBT&eo#^-q{&)aC(4+wVA~=q#Y1RvOa>njtVD4m0s@=SjPw=#BlxG z_H`Zbju4RlxjK0rckCtZ+GKt%VI@|Jd{Pum@ z?oHcDa{`jr+X4sMs*qG`W^U%2Hr!Z*mJS~J84-y%I>+6MtvdJa|_x#inesf-}9(9bDi5G(y}llz<~RwAMZ99A4;ZrYD{GB_v)8WIn2A`YAF4_o>V(~S<>GH{!(ZP2F=JH5BMz_xiOhduBwJzCEq z3895~)UPGipLkRawH@-d)oefNA3IWq+xDH^ki2 z6upnzB9G-W>{`q1_a%C+2_Ed@H4CPeWtL&&+_N)>w6m4J|i{}h8?+BB{ zr%CY)(D4lqO9A6!Ji*Nw-r6@^-CZDADAsAn3kPG8@gyRdbK-(C92p++sPn9(^R(!* zLf7+M;5lV53>4f#i*-TM0U&KKW{fhY#JY%8J_3(#e-FI4ecoq5nt>Y}`$R9;;O9|r zN7fU^h^GrM!DT3o6Ox(R2@1JnRK5%}a$>M|^7p=^i@fy7aH1{0^lrbT9&`E$ccMDE z^sr9?yv6~HKfg&abD%x|Rsin0FWASKuW?)g%bL%}88yilmmGjEi)!46?I9WnPI8vl zw&oGVKxKWeA&ZJj=ku%{e~{yPbz*6VqNxppoBi;!t0d4!LZPVg^Sj0JB6QJjz*AT! z^wPNsI79XQ;b-A?Z5e!R_51${KX1N~-F#=caeRN{By;1eedA(zLc0R+;o1c3(-ZwYT`|&t zo&VEnvV*Kt^Hu-+534Dg4(S_yK9OGWY&A8DU7oF^)pW5Ttlmzy#_ri_Dm0X@COB-s z|GU-faJocbC^l9;Tg{(1G--`hTLaOI`Uad$)jO~xY98<8|86xW?RTk~YY(OipBZ_2 zbKT*5rA)a6b<2Muo%q(Hqb&`m%gVoSX_X(H&NiQ|=6gKnrpvwONN1*UTl3X_tmfKS zTg%Pqv(~ENJ=~l$DJdx_J8&y3 zEGsitEInN;S41pWk1jDkFG6cCyx}l1HZU_mF=bUTn$I#aGc!I^Gf;UpV1PF|J~@21 zI*@fcGd4Tl{ya1^K4+9Z)b2n~RY5~VLOnb~Oi4saPegc}M@L6Vjju`0=SrX>Mt7U~1j#YpvaGif3_bOmWD# zaz!z7H6C*~C3B&$bew{8rk{3pczBeadat;9RzrJzhkQUPe2IsBmXm@~ID&$Lgi$nv zZC#0beTrj2i@ey3e20vxt&H2_j=!~!j*O6ugpiSykg~6ljgOL}k(0Esl$@EAp`w;x zK$fVbmbSE)y0)3Bs+qmInrBd&c~qU@+Mchhp25DK!@!}Ki=usRqL-JVqMo9ss-m;9 zqR7OfzPzN1W2BaLrOU^rySb*!$*9iCsdrAPcw?#J->T8gth~Fd)6cD>qpiZguAhsp zudc4v(yxhTuZ(lA#l*0&w6NIJvB<`;&damP$G41Wx3;^wyS}@YdAqi^znE>mnu5Q= z#=qayz^0bLtEIuMyusJa!>*sh%gn~4g~+Fk$-cYE(9X)Omdno1%klHfv9!&uuggwy&wCvvF?bovH*t72F?eFgG z@Y=WV@bmG^mGR=}@#g6B`Tz9g>h$I6^ycdI;Jfwn_xASq_wL*I`1bnx{QKv>`|a=j z?8^V@!vFvOA^!_bMO0HmK~P09E-(WD0000X`2+ zp`gKo2oow?$grWqhY%x5oJg^v#fum-YTU@NqsNaRLy8fFh*r_Y~2gVuyOK&a89NRujE%CxD|r%(#S!>)y?~ zx7OMcIt$XINe}_fj(sa%&b+yEm%twrAWj^4^5@vIYv0a2Vf4q;HF^K;ZgY+K^XSi$ zU(ddM((jIk7eD^^QwCLl`2NnRBk2^RRrO6BoHt66Cnz$$7gcRBY zUtjdm*I7e8gy%;V8Z@+k8D3bx(gPq=0);^fU=bd1hM1@j2#0i_Q3ABwA_NZ|B;ZIg zPV{h8gbfi{;gLut$yQhj-8LRWE?~j}3NHX5NER|Gbl(_Rp7cNqW=OIJ7zd4D0t;tm z5D1nEO)v$QH`2lgffC?2CsHrwNt}{=_UY$D7dqJ&hcOoPz!8N`0E8Wy0Fp&PBbHbY z25Sr=$sZ(`s3@a>jA76PNDNYmA3T`&r5Tz862=DzDH;X@d#wMc&;?iE!4L*+1To_& z@$i!j5hgN#1vv54b4dpeFrm*s`TSFlvj0T8&kz?3TWljT3QKG~*v_~~uhYotKr>~W zVh=u~*eJmU=v+aIJ=-v%u0snd1mU0f=BsaTOnUd2e8yQfD3q6E=|jO2_&^B{CIB&q z7ZTi;p9C1p5e5n(OmVCPE>O|~3RbQ-!4a7704gQAg0SWYVA`5c2M|2GkPC;|Xw zK#o~}5Hx`fT!RlD9bn*yD+F$!k2zU9;6gKD(>sH`M(+RX_umo*O4M}J2|VyZ2b+q4 z9iGN4s{}s87^#Uqj}J&3@tYD%FaZgl%i$l2lq8r?=Bvj6@j@e#*(wtV zKA->wVRVpoe*Q6m4zPB9pg^5+qJ53*6VPr!+F)p^e3ug#k)Q<|OuPIwq*!3$2L5dE zzy|rqp?&fQK2-0E65I>;`s{NjI8os}IdgcGuq}ZDnpC2HC7K+&<Nx;JZ7d5jDU2p`&=GSV1f~#U<4+@z#af0h9a23Q`!@O5w0MJ)Fr?U zSttP+y{NN-uq71@YYqd8th;PJ>Y>N!(d5F z@*p7$Aj3MCL5ZqK;Q`~?ngvjaH|q7Hi2FFA-uULkTH10pP!vdp40pJj6-)vQFhd?p z1OYp|P68va(f=T~vCxrjWQ7`$rzTJX68I-(Ubs&o3vvNO9^`_@Gzc~svL9IyYY4F*A|T!F)Rq6wglyo11*t$i7@0OwsBQzVlt2ae!HD4{ z%N-vqQW6oe#9LbQqM^y9po+qf$jK-Jf`C}UJca_0PIC%*_~PR7U;+^wK@=YtnH5ct z15H>C0&F-04@Lk2NdT*-VHkl3ggLK+WDWvmFocaT5D0T7ghs4N2r94Pvlci)Xk^$& z7+&B5a45|Jc2aLKsheK0vCXzA6d|y+CEVO0Fi=K zaD2xdl-!hsv$1;WkEWoD@6^55@C=L2rLCG48*}wYLF!auAm7? zguzOA045zlL5W8dBm;3MZ5T*rL7D#r({lH#V4L35$3&v27`^&cBDJNDel!Az#T#pK zfjKOF;NuceCGI&s;mt%c0UY@-hZShik1dSL51Vi1F(?U% zWvqPXD;LK~k(d2iNN*F#5EXW5zy3K$fDbY+4ea+K81Msm88TplJYc{oEQt0ZJYgA4 zKp_v7K!g>NCENMOhc+7Fv>FVN^q82wC{D3kG)lPYY)CB_aKi9D^4|k<#D^PBWc7wn zR2BR9$B1!k{9cTf95=;>-u#~RfXw7355~U430QsbHDRgR*vVMVa#>i+nJHdJ%U}+( zn1$k4Cyn{cXihUtfP>$Sf=2(%aE`N_6_ekCG-jiGiOih$%;!Es1UM4V3!Zfu7(W~O z(1r#yq8H8RM!%%aj*hgXC#~p7Tl&&-t~91M&FL^}y3?QzwU0k7>Qb8;qoh8ys#ndv zRI~cku=bOyV@>N?2M*G<&b6*5H0xda`q#h?wy=lYkwF{#*vL+{vX{;5W;^@Y(2lmW zr%ml@Tl?DB&bGF<&FyY```gnd_C*Fjj&hs(+~`iXy4TI_cDwuC@Q$~<=S}Z=+xy=5 z&bPkz&F_Bu``-W$xWETa@PZrs+`Sfea1PG!hCBS>5RbUTCri>rLtEU&rFch2*k`~2rXm-x+v zo%4QwV-}zo2RX(u3sZo?6vI&ZIHD1XP?#bbQs;&#svdQvTYMbc2*ohIK@M)TqUx>S zhSI6Ab+MCO>!1k5)4751se^(VN{>d{XF+yyoIUPhU%J-0-uH`-V;G>I`{DO~c)81c z?$mHRC|XhYirXFU~;mpkP%{*6|cg6yHUI^D@pdbHF18;FlPD2m~A z%WIwQWj}i;TH$r7%O3aR(0$rH9(md?{^`Xix6>aV3ckl)?zBJn(1TrceUF3fmEeNX zi;=(L+#T%Q7exOks8M!P$fpv~ulUhH0S8c!-4y)OUnp2l9YKI%7UUR)AmnrRiU9CqUgNfaI`$)Dd-BcYd5_d#0cXX)p}rKno ze%}Xw{G$n`pnM3ZgyR5%Q6__y*9xGp2B%jH0+fV!7Yfu72yqY!+yDwes0l+zaMK5D z)t7HfR|%j{2H?Phl^}qvAPBP%4y_OeST_rm@Po4;2(3_df)Iih7k2ySe&aA4+z^I> zaD8z=d(r=3fB7c})KCr{7Y+Hd1_JnqIyel(QEq`?faa!uyvGfJD1s9=4#g0NTo4U@ z=Y;xa3Y1uG8OVFH5C`h;anwK@miaTfOY(H zgK&U#ffx?okU!jzi%aNnVOI(EM-9=yhTouda6oo(XNtL~3EyyxWO#Aas0nazdMr2# z;GlmxXl~!|fYgAEJE(EErf?3ChWPe`*QkBu(1*>(f@24cOGg~{7zkd+g{)|M-vA1R zNF1#|f|>w!tq=}N$3F#$hr>_|>1cR~7zYB#4gAw|d$@F%7?OAwd~ooP$!K|CM-FQs zft>$%f9BVK!+3hZ7YM~L4AD?|6bE+SV3Og$g;7U=YY=?RsC+3n40u>>sR)Xv_zlZA zlx(7s0qzi3`0o{#So2o=ZA(zet|fV znt*p>83!C$Zd53MV+nP`fO*o_kN(II0I6@(kOruy3Fm>1r1*p7rU`dveqPrKl^}ua zh?(hFi{=przfgOJR|)gyfr0=BaUh5o#|^=_gP{Ni<{^pYwg!R_2Y`@*_qd3HNrts& zd2Nt<6L}tj5SZlPbP{-vaIl)BNt8qwn+6#Ttq=%s5P5z1o3k(sYY>=~8H<+JhgkoY zKeJE_vzd|h$dIX+n{c2BSXXxxnU6xqiEF@n+~|dLczA2jca`aJdkKG6SDj4>dYHL( z`xgi8nSKR#hQHRB`9=){NgM*GhanhR<|S;F;_g zj2tPTu_XxGNPfxbf?c_K-)VT_Ie}G(o5<*OJeZ$hcak9rpNTh*m0%wE!+WL>g5gks z(ZHSj8HoPrlcqqMf!Lk0IC+#9rqtMt;5l#%>T3>)Z;$y6;1CF4Nr=_xejWdrkx1%w z$QY5z2%02lfcl4jfS7_(XQ-YCmi`x;6d92osB|Mq4#-Fitq_x8Ig=Qd2Fb^UuZN`N z2BM(&giH4gm`Hj^33h514zmCU;82U&u&QsGq?CB6b(f`Bxq}oqe@hn%ez>WSI(uYF zeu?;msTg(PFb?2Qkebj6(U7D{CyIYsaeMiM)W{8e7?^SwkxwX-$!BnZx@&`aZxE_< z!;yT$z&}g}2T{kFu=t8)*P75NaUM#KSXrldmx|*jby*q>yZD+&X?~+9hO8HxW2p(@ z@QPqZupZ~CrYZ_8hn%;7J9vjms*KXPhjy2d;qZ>(kg?ZSfjj?59FiKI%{UIk zL3_10iNi>Sf%%BA_zmJGrC&#p*9eYU=aA%pnp@b6ikOa=8Lt@kpOx^3MC)~^*>@oN zgGPIIAckHzlMm>br>VE5fSk%mn&Ci*aWEX@ z2M%-Cwi1YnH8{FuSd`+)bZZ+3LwcrbFuG*en}^%Frx>43skfNghjFV5>G+8F$)Xmw zmlXK8f?ICl=XIs$g@K@>i8&CC76?0Ww)Iweaa!e5#00(uP5JI2{ zLXgK1H+2N8bDGS>Eq8qiT65$Z#G=f;9vpM9cgirwbgKMvtc-DjEIy$_2H67vbwCZ= zKn$)mG9md3yr3)hQ4G}Z46X$McOcBdz$WeU$dD|_2vNyJAQ5`(Z;RT>EytIph{Y_3 zw4!W&+>FJuV1qn&cvHD^*XOT2hpQ_~ackTOvYY{%ATLbd3OvvvypSL%&<#Y8FpQi$ zyubq*-~;+B0rMOL3@`<7b7e;Yxq4E684<~oECJ9AC=pl9JcrHW46WI`#oj#5;rz{8 zJPbfN&eVX;7k!hkte8Hc4eGlMf3-9OG662o4MG2qEPquervL^RunJ&+EJMQx1WnH- zqR@ZB%oPFA5>Nz|OcB#;beOr(GKbOT%z{uIb6*P5GRJgJY0h1lvshfx>r8X%oYK_0 z5DCx;crYbZcF<7Q3N&p2KHv>mz|TZ5E?Ak4-!Lb}TnyR33ZBA5Mt#(@sR_}{)PQ{0 zh>gXGz1Vyl%Ns^DufQW4APq<4BW0P)6w?W^qhZ=J0{C&2Wv{U=5yf0aO1V%-xV$6jKPrzzxLQM8>TW$lcuR-QMo~ z-Y8dT`_(0(6gCr(0?BYg=X6ols}t)D-vA!q0zTkET-F-F0FQ87n!^O7KwGT#-vl1v z5ju;UgllZa(L9&fIW*YI0uZ zdcNn3ednf@=X@UMg1*OoUTT0o=!pNG=s^c=jNa&u{^;7K=#oC^sSW9te(9K==}toF zoZjhvtm&X0>Y@(mo?hyvPR662>Z-o#)pqKx{^~r(>asrTv_5FCe(SjYa<#teyx!|D zk?X)7>>}su#9r*HF6_vj>=kG1%--ynuI$hr?F@Gt4e$fje(gU%0nWbd+x&dr|K?AOqPh0PMc+{C?>0{_n0X?~g(6 zFmVId-T?ic@ZApZ4BznQCh(3y@B={s9KZoTfbc}&0NB0)(1rmpU<3ND@FM?gFI4g- zfAT1w@+!abEZ_1j|MD;&^D_TG^E6-cHh=RtpYuAu^E}`4G`|iG-)|7_7ZN`Jdm#`P zfAj+Z0ym)V4L}1F;AMRA06M_&oG02}}VKL7)YKL8By_*?H~4?qD1Km!IK08;Purtc(qpZco5`mEpjuK)V5 zAN#V;`g`B^`u6vHLGKm6`y3DeNuTjIAP^6L`2hg+0>A(;U;qc;0HR+2RWASw00R^t z02Cnl2Ouy4AOHoR^`!rQ`rf}$vmgHAKmO!j{^o!F=wJ4=Z~OR$`$SRg17PkCQ0@cp z`vV~W8DQ;}Z}lOs0005Qh5!yL1`Kd;Lx6)70tgJyu_D03gB1(FFyN44MUEXkegqj( zO?v>E_0#Izb{ucCz%;wXR&?plEZwhvPdJ3e9*!Tmt?X@C$s7>q_aY#a>^VB;B6ws zhU1Mlh$zS}q5vww@ygiX3o=bL*L3VAeUOARPC4hK6DB2}#4}GlF^m$Vv(f{MM2{5f zC`TG38sPtdz0fq1O-CPvbfuW|dGjBA*7>HLO6%mbQ%^tD?9M$$CAHMB`UHtURaYgj zr3G}riv}V``<6 zP&5%_mU}b_g<_6prn%;y2o5=CoxvK}WS@V=Hl}fgnWdFksL{uiaW=KNX{VpYd1tAo zwyOVUps&VyO`P-@_Gz!bo;hl&$0nN`tFuNsZ3QPKCz@!uvFD{>WCpu#yU`W9Y`t|p zTW!Dpj!fuK1PP>)S>nFDMp>y7_3_0_jGwRPCPf1Ln-{MpAJR#@4m{C(JgBMooJAHSYzx~Zmo_|?BZ zfBg5?zkmM!_g?@3I6wgwkbnm?U;+`iKm|6Cfe&RoqHzZ&BfH*`V z7SV`DL}C(^xI`v4(TPulVict~MJiTNiWk%%kvQlgu%m+e@J2nX zxJEX%(T#6}V;to;$2MkgNL%b;9uYQ{0npHoe*|P81vyAU7BY}F9HS7|2R}MC(vgpZ zWF#dy$tU7tLU`0;Cd>Lw3@Wp9EznMF~hm60shsupcE=xk^^H(v`0qq9q6BF-_JI zPv#kAE_Jy}UiQ+DrSwNPSOE)G5Tg=lV8tlZ2uo%*)0xkN<`V759ZcHNnxy~1r7yL) zO>TBml)z*K90I|LN}z%ZtdPVqqq$CYw$q)Fl%;m6c};rqjFaBPXFm10&lCdF8|U*z zI>Z5odZ_cA2SsQ?723ppAK-#k=wcOM_yBhGyG!kMce|(1!x>KDUnLg95dz*v zKf-!LrkNJ8;zcca_dDZI_Lso1Ek+D~xUfEd%*&7QRJk3}1UCBy?Jw1|O% zC4`9NhC6ItqfC0u} z=)eOOKn6z0ScGh@?weIj$T;s=vU-TYO#0jpS2x20GoghClK5Q*A6mRj#sdpbpl0hb zU<#%fMk_3B0WfFb%kXtTGno60aeRXjA4oy9zrhVTt;ZhpeeS)6y<&6ALIymd5&6A9rbz&N@{>~d!R^P8JrDyAECKm%{Csst zpZvG4fBikTYu#U;eU@&Yg&D|%Vu*%cAO#)}x$;B67XrKUD=S)KzxQ(^pV|(5prauJ zI47bz;@Us{lPLdtp#x075d1m@9KINkqiK4OwTmN1&^0LfxudW<4jdZ~6u}wEG{|>C$Qx2fbjimda!6^iMTwk9)u~8}d_#=P z$adPu@#)BP`N$gzNhRvTn<2@Pe4LX+$t_gLm0YKm?8l60$aLJqk;KX463CqdN1sGF z6J$M`DMlok$t0>tc09`QN=l`y#GYizXF^DbJQrD%N}@zYqWH(FBo&?1${OU#u56}y ztV)hy$Re7`Br;00%p2ZdQCrof(LLR=5$UKdH^8E0uThH2LJ&|h_B~N80i!%SHw-H zk-`e1Oe4xp?cAH(>Q3(jPZpbiDe!{fJON7JJr+Vw_aq_dy0|~efIxtP6oP;_2!ueG z&t^1%HPC>{vrjs-O^z%b2<*=y`cD8Yo7}R35g^bKT7W7@P!lr)D1ZPr00KdHt_wf{ zL6C$$fI0}c0!^R=2+sDwiZ0tjG()vN?Vs696@f;fnSKlr#O zivvvngcW0eE2soX;86eE1A;x6(gs}(2yg-haDxurMHUc*HCO`?9nq)K#Oll#vD{7U zG))!-PKqKSO_+fgjnFBW(D)1j24I8d!o4HN0}}YM9yJ5Sg8(xig$`JNE2z-~a8e04 zfkVL59C$7X9idJIQ&W8|28aU>gH-9t0}Pvk1U*$AcmP#}t`VYBJ4HnksD$qPf-mUB zK7}exJe_|SOn*pGB3e;Hr5e>-)Drqm8J&Vk;MPF!FbJ4~3zdKzEwOQRgCKZNs%y?A z9g8N#fd#lx=#sQbtZISTkQZ0hyy5KfH#0mRPeGXc+Fdt0Gv%l zVjWPjXxQF**i4Mr!2BBIyI3H~SdHZyj%Cyl@>zSW)0%aIAy8W)0Demak0GAcacpwcznk3n4%!rave*}iRCvhYzs zs01Oku1*yMKQMwrfG#4{gg~$YKlp&*1p+hB1m$g4DTTZ^bptCfg8=?GC+uy(TSJ`c%Le21eWD+Rg}s9M0hg;ZGl4A|MXpcTvqG zI8OiL4ACMsVvLen6KYZeoB+A~RVyyUArc}iW+yO=kS_M(8;%f9l;80o<1!9NL>(d6 zROA_8<2Jsi97R|Wg3sWQ1m#^~I}W}vDkyOfhkD3kJ$5E11l^?Y<3HA62ocLc_J=|? zB5KWNGy&YHx_?rEVo13ueZtYCptk`wi-??mA%VhWA0EN*E_^P-?N3;TS0E#X0Ly zyy~=ml9Xm^-U6nI#-VW%hcrzGO=bs!vgR%>%eyA8%m~N3Iz4> z1A!i&9MYl2rlZG(?3<8mo2=}D5yT?E>~q#^R`3UJ2!!$32X?+8c6f%}Chh+@GVRl@ ziPW~r)js61Xd%~r?KcA^NpJ&n3WRCc2Q}qru&V3cj-uZNZd^NNyYy+|c9zyoZspGH zYN!Te@Th%Yg|N16NW$*y4jmSZN$$34?=J2>Vj<)X@0CuZX|9KUNQdfP?=@=g2Z@-d zX5V$75aI3#ai;IC0PpKY7Fn=d7hAXP;j1La1qyv2RHE(zae1=1WBmqN>GD}W^5RTqXBo2 zkjCKz_ka(t%n;Y{QQ`3(7wj8?W>$Cxad-wd=!Q;4^3s-Z2w`$So{;}s18^0U@+r@1 z+}`}H#qVze%Fb7lj?J<84O0V%t*NGf|^{L?W)vy91umUScf+V2zwz73ymn2lr4qu-T z_zv{>9(I&2^h8zUFG%C!Z1$LXc4%jBUUv{19_9|;ZV<=zZ2#+RPh>@w_tm^8D0Ndm zsA5_E(DCi9QYUvRl6LIKhgN_EQwRl9DENcthF@oODKcqyFOC0VCksU_+kKiZ5x#&N zxP(jIOFY%nr1JNFm!g1gR$$KwVTX9tfOiq1cx-iK$BoiTSm6OK(i6tLE3kq%poFH2 z`9t8n3>}0(P&^4>&L0hgFn~G{x`2Y>^F?Vm8@bGI#>y(Fe zH(#MyUyYXc*q3ite1}pkHQ3sX122%Soga%6Kzm>9SGK=U2dIQGn1Bl~gL)02gQGMp z<@lXcScXN`st@_Ae?#ArLUqAQ*gCHUD}7C-Jpn~NeYll?)wci#nEooBfDr%!?5EGm9U;x_cie9z z-7g)JPnJyo{j7?06cYQ1?^w$>dkJ^|=(+$PkYdsY2niBKXqa#WjKBp|ya;Rv!cd2X z5-d14^udAz7kMm9K!Kvf2#z~Q2!!BmYxgeRyn6TY?d$h1;J6Y8)GX3c(+vNYGc>h4afpY642MWCFf&F11T$VH zOkpVS1SXOo@AsjY$^X~2YH}K#Rc%Ch8{5bOD z%9l5tnlCN%=+dWC?^@P)b*pOwbMNl`JNWS8$GaU|_;BKui(5_rVn-7t)R?bmk~HQ4 zXM_UV+o|LSC1z#_B1sq*N%opX7^!r@6_r%-m~atFI3a}i!HkNB8)GZN0VVJX#&R@IqJA09E`Q}z=R5}B&3B!8hIp=Ghyf< zlTAALQ(}pxd~qd~Si(UiiTI#+mWuyu#W*IJWtwT`j5F1^k`-1M(WaYlzS(Ay zb=r9+o_PwiB$R#n`6r-1P2~@lBLg=s0Rwc08vrZH*yDX+cy`YTJJmO3o4#Tqqe zh^|VvDyxbr8(luIItnJFV%~Z!w%K}@D@?lr`z^TP+9|BD<(gaSpi@2z9kZ;i3mrbv zVppxU_1b%{UEIFpt+@UA`>$^UoI5bVqYmm1s_%x?ZlVg0)sMW7Ru?J06k`2}JMzh^RQv7Ck9;}Uyi2c@>Ay1-{P5W)UVKBxcdtD7ku&c+`R78f`}ETL zZv9f&YyW=K+zaIW{NdX__xS(i>rX87(Wftc_1LEr_rb4#Gn1bL=$F9zG0O<-zlau!jF4 zp$T)yBotCmg+;qy3y<=`7!r|IGo+CXlPE46>ad9#LZLtS;lsYMFo;GOViB{5s|(Rk zO-lSCu$bt?F`6m{MVVsQmL`-~rOjzte4`P$h{QR9v5uY+BN_8}7AX3IZ)kiMKZ?Re z!?_WThTP(wx|k_DGLlnx)FULDW5%NN@Q;fE;!g&NxMQ7O+9(z80R49YzR zn9qH3G%w_IW=oJ@gp^po2wV(wJm4FFO>duJrG$|9s zTt&0fkBfFQmTjD6N0Z8zkNT4(BEW)9Zeq}voWKdbNht~@-~yG-)KeOmf=wl20kInMl-lkrP38HOzoCAfxr?V=yW9tMQ0N(kb{>tp#uvzf(>wT zSR16|0XeY21T4USDv+S8UA5C#!-`X~z67FZCF4(p*;aTF6{AKaBvR|D+PU0yUoCm+ z2pR$eASh@AQ*DeAEMNf;G=T&c@PSoNz)%ZdfC>?5tb!mwS<7CwPMNj92sqo>&r(-V zlU&MLM=O@MA|K+v?&kzYsKOK& zI)M~8VFCX-sH_XpNd?_X-~lO>>3%!)T$y+_z&RW+fen1%(mlnyLIm%FIT~TAy%r0x zoycriY}0YNCw>fS zf|=suD+1Zl;goaZULsPJm^3CrQV@`2lI(`O8rJ*X zuY3PVKnY4vfjXO%1E@!|BUvwHlDxzJpgnFeBjeQPPM0)>!>JPC=73SCm;U3h&y`#;^;}m zeMxYlw7YNokaSZz9+Z!R+>j!#AIV`o0BP(4M zPc#9Nkq)2#q~}!sc?5%Q6nvzaW%z(ORP^8hu*-YfONV3ALHKk=5`Y)(KzBV*UXO0D z!W&n|I{D7vfxUa+3>_MJ(q2w>r<@`Ne2+mt?yk{FpS|9^jID-}*h{jQ=L$Ss$GwqU( z@c>-G3v}#*6|C@wHv%D!dWb{$q9@-eJP?npEI|*0r#9EQKdj0{C5p?dgFPULh7{p%!i-7jB^=bRigC;TL|P6_Q~Xo*^23;Swyt0^&mhYTE<$ zAlm&Pkp-a;Hqr&Uf-GpDH+aJ{RKv-^!qh>b?VuhGF2VKXQ9m?;0%k=Dz9Ag)pd8kc z@74zx!!&pUx+NYVI!`}%02^LKStKGP@gG*ig9qr~4o=7JO`^@w;hfo_CDu_M z3Lhl|0{neq`7oj=qMipd!=m-WRPdkSSzqqCqWQt%96ny;tzRwf5&>W!F1mpvAj9(Y zoKWmTJ+Q+Q^5V~&!Wo=_BbeeTE#f#jf*Z<`Br@Y6Rbn(k(=@t*E8PDAC9r}xRDv6@ z0wA&j)ro^7u!1CjW6%thIsVeMLE?|ap*zM9JjSCg&7&*40wqX7+|>gt1VS2c!3g>U zJ3PZEAOk^;WcesvLuSuILgXkBfGE)8EBFB_Or6!$!#Dba)hQ%N=H$mKBPmRzA5cO%)TB->rT*|_Pp(c%3Z)$pfGbcVD#(I3u!Bu@15PrfR|1VwKBeMB zrBs5^El%Vfq66~<0!@CUTT)I~iY4NZWmzhbL{g+5_(4EQLK+05R=y=*ZcSXuCEL(t zUD^;;V&F@b!BM6HE2M$^DP>?z=D-XlVaCr-ep^r;ru_upM85w*DNuqR;G;OeL03{H zYL1F!UgpynW<%&3tMwaa;*U$df^AX)C8R<%m}YABW@VZkYmQ}RLR=7ro47p{llg|8 z{n>1e&}ZJlE8M~@@PZ^z0&n^zb{5NO2Im0{=g2|cN(`Gsl$31RLGrcIIcPY$tbGjB9e5N@&1sb&828fd*i}6!ZY1S>jjy*Je5B z@=PRsMkg<5pk(SNhCT^@_9wo$CKBk?O0ZW;OjZVfSZ~Ew4gkRs#MB9xnqa+@60B59 z5f+R|1O^xyMz|Me&e(%4C{@bmeAZ_-_+y3^X`gH;huZ&(VZxSdnb$xJs7fS2h;@LA z8QKK+K&rJUN9@@L{8n(qlve4NN=!gQ+*flxS9C$>O8lsNN@#_urIE&IlOSo5YKvih zsDhvu7O0m5i0Dd87G#tLWnHNgU|2@*S$$!IV(Gx3(bsFRgmN*~nSzynEvP^c0Fc^d zJqqcC%BiO!2c6cb40$KcfoHxsX@z0Ih20rTB*1b38l#R_Vtqz#71v5N1OvQOI#ods zK!C1dfvX9`Zz(}@HUy8t1m$IFbWSJyo#v-L>*BSes1DDP8ks;?K$2m^r?CW}3dCfQ zfI-aasqtB=UBCyJSfH}l16%+|5QMiL0eTjz1a$vee99+%O6Qw)DzxtFH!K&s}2|eVd2D;2J5(DgkWJoh{>o+!Bl0bgs-w1S8=S8 zd?t|MYcg~L@s+I8_N%m}tm?R?1c;|Z6q`#Z*R9@!(fW$}O(fFdtA#!-*?yP5N-fk- zt#OJQxw*t-J!;o}i)Yqmkn#dFbVJ;gt==viaH6e$lB(00s!G77Iw91n#;x4aqvFoz zrt$*Q>g(Q?Y^eIJ%>eEP6^W%H?xxHw(nkO1EARp@Bm+96L*$k&q4X`~c97+&pXP!O z5Z{od+VHY|? zF}QeU5vTC`(x={aooA4OVF(@a)+et4elcXu1@?B{1Dvyw|F7C(Dr!hGIxx}dN0`0?Y-LVkVdmKLm@R+ zvtV3v`eri?PjMi-r1KK;?4tkiIG6Gon=^SJvpVO=0#h+01FwB%<2(muIoor2p!2=4 z$($rX667-{`)@0MGQJu^I0rOf4m3eSm_bkAN@xN{ck~y`07!>)daMKn%mHh(1PBbs zNi#H!iE+}p^1a@|*iy7xS~Ni$W=H3=PIL5#T0x_(!~&Q>77Xl5@P~f@h|wm25Uhj= ztUv_0eWNUy|t@CE~%1WkYdCGda-uz(=M z%{KS4=Q{JUI<5y>wN7HSIcv3I_{g7vwF~^g6$nBgpoRlD{yvHe=qP-sQlvfRoC-Hg9l%$hhGaVU`xVKV*pqu09c>e zWK4iq>&8kPmuy$IWn=aTy!DvLr)L{+Omnu<1~X~rwP~OBwq!IAZuC~GL|6YcO1OX_ zV1z<=fnXEJ&wqLN5y^_ekn>HK#U9OgC#+ zHzVvvXGp+qmqr{Afr9wJ7+8P{bhJe{#8_u;U0ya@2X`yGZ*jZ#bWVdI%l9-dH*JPDFUpvbS;1XA+MxhGQLl z8?$|L^=hYtNmT!LYP0ejEWq6#0I*$Abi_N*s7j7x*dzvL}DGdVj9)iu0FKSD2qN zPUFaq40?}r3WKjNAVcy?8+UE;0x)nQpZ7Uh{W&HZW}D1OoLoAbur!|VFWp-4$A)c% zU$~_ExQ1^yVBj;S^U@=GvyI>QOqcreQMw|l`l=tvK1X;1H*+XMv#ihhsiQhSq3=Pf z@~zA5oxeJ03n{7pdb-j29^3k{TS#1ga-M_wH%o&*M!K{A6120iv`;&Qcr0{swz)ra zkczFZcYDfxdl!fQySPi!qIWjEU;DjMC{=SiyPuM~J2AY+yW|-0E1&`;xB@D~?y^It zG2E>=$n(B0TEGABzXLpQP~LD;f*O258F0ZDv_S)tvC>Y1ImGwFLwv9c`%-oIEK+-s*eFlTpOO@Wiux#~Sx793{O!vKquZ(cimtF8q-4 zc+bPr&x>%-3;k{kK$f?6Ue2{`mVwQ8Ht@#ubS6VJBxTf_{Mn*Bi&VYU*96vgwk(L| zQ4+k|m%_P+{Ws_Q$n*T!=g`zsFx$7il4v<+Qvx95CF0w>x#N9%w|DS{GvD`p1pU1M z2fpBs+1=m&0xE+pMF0L-1;6zU zKk=^w*5fNGw87jPKFl|M*3-W6bNTLiJW+kYPiI431 zj$BKZFJZ=%Ig@5hn>TUh)VY&qPoF=5#*{Er!T_R2ks>XUlO`%4WI+bcwt5>mR z)w-4cYuBs;2J+$s$d5o?X49bJC5tU8N`3%^JxkD-v~KJ~=GD8GZ(qNE0SEq@l5pXb zK@lfbyqIxg$B$heHG1`_)yiHmXV$!#b7#+;2Z+tZixMD8b7lEyBTMW+*J5GAHU_O4 zyM2Oj=hnTOcW>Vv3kN@W7;j*5+zylF1iafStamzte zav={GU_?tVLJD~S1{rC*spq!(KnzjD5lOTNKjHNIPsJ5kY%!+*EgLYw8ELH1Myf*p z8|@Yb*J4Gj*nlx+LJM;-#+cE7AqJXm%=xgyC!vf|O5IKjE=3oy%u-AKV5|x{slL2X z%rVKNZUFR95wAhG!hNFfO#2AOKwn^Mm``Ro&;Dh*?4%Rvb(bg?d(xj zYO)vwj7o$lMCjF5EJTosfhtI_fYM`;oz7B%{GcQkR94GHmb79s@=YO0rO+6I?sAgV zZ@~?>H&;`FmDY37-4fBM!i1{bp^z{FDGQ8%G^zxu_`ukyFld6P43yZns|im3m;wtb zNJv)Us#<74V4_U07pgNO94jtpTTAOTUW`me88|r>6`5zOVaGjjQBGOqyvRk#+;m}% z*}rwcRFo+Zu+ShW4SZ_}k0QS};3o^pe+Q{8mv1JKs&QUSsSUqB-In~3tkxyRkx=ngn@8_Za4~lPhZAJ>` z5r_f;h>j0BchjfRQ4UKw=PsLg$%_dPapdwQ-CX%cw@Tl$Svbf{S@7xm)x=7{Z=RuPJ^@ zO1lELFC#QcUPB=qRPc2_3Hg0X-|JI^UD zfK40|gnxr0QLZ->3uw=g z66jvAzE_1CE`V4UaGbH$7f2=&5{btYq760BNFka}h$FZ_2`ZVi5hxLp3M1Mkb<{~u zRxE+!Ovc$hX3AyQr9-N0!wMG;fHM6F90-kre4!n?f;yWU-u~LMbsulf?-kLsTy{#!I0g zMw8)`uYG0NJY(5Y!OHBXP6;Z!T6mOtO{H~?1nI-F*2}3p04Y2~>t$`V*5AqLLJQ=q zEV$@JQby2YVr-8){hC_UN<^oD9qen*Ral~!5U9H1m9KimE6nb;iI&PD@Yc$}1(Fk6 zp&jk0&dJW>t(LjXwGUun8{Krtb||zptW+39RxIR^x7|%vXwuWMQk^tOoTbZlTlL7%y2xGR{?vp&Y;)momrK+Ey-*;aMbk z3>wM^GBLb7WW6#N$z@KSl9`<5{Wuv{QFgP94GbqPX6RaTZ7GIwabhp;IT;Jw&=}1e zXbqxS&4o4&o4xdAMJsFo2R^5dF^Epo zqES7m14gilUzDpSh3v@%UOLE`wo^6GaWYW8@}7q+uGK;p7%j`|^bUFt9KD@Ba!d z0P%|eL2#sqY_wv-_E4^ZDzM%Eu223@ECQ!N8Y<8Ntt|s-@FO;m1Jld{D=Y-9PWk-q zi@qpBDyZFJ@cn4b%cQ{tr$GXrp&FX-0&6e|J>mv&aLII#2YJv3r7dmPE&3)f1{V(S zT5t)Muo|4O2~kA~)o>%IunJ%73b7Cbm2VlOt%`;W|DM7AQjiXfWd2%E37gOfyP@~i zun_-5_}q}<;4lsca1LutRPGS^><{`D@cjDl=#uaS$t<7tPQU zhp`>~p$(5wx{~oIn6V!1aT%k+1@Zu-e&`AefhjIv3i6<;%)kq}u``lD2$;eI)?kXV ztnW^6)&g%FRZtqRZx_o@9WTiD+))_eF&<%S9*IH|q!B7+Kog*13YM`FC;NbO()O4V6QlA4*Xb4y6Drj)5UqhDv(gZ^k}G@4D!I3b)4{(4fd_WNX00mmW45~3NH)Agqas-YNM-cNNGcWfnlE|R) zFx~AIeeoBY&>BkSO)ElPQ|>d#0cg z4&ehbKoBJ159rbhAb<`up%TzE2%6$0vvC`h6yO%fl!g#7tJF%95EEHY2_^6sh4Zwu zbPZj!OGPL~FHQud9y^Jas?rf41d!Mg|qM$wFVp2 zQ9(#K;b0A9byjH=4rUZ99^eQNG&-*!NjJ4jIyLM-bpm%UF;8wpukT6`)f!ZFRWq@A_1VsqViWT{ ztyThkkqiToUA-Y`m6kti)@ehMUQZ27qqf+lRw=XAM0?ZSk`4w>bqR|WQNy+#`eAJU z$(Cu&R>GimZEI`}PgE)`cK7^Frs(z;O%+k^LT~w&`#|<@?F(&(O>G6YWGhzb40kGP z6XndYWq-4A5tSYK;b0@T-X@oFV(G4g?Nj%yJ8m* zBtaUO7Z6Z^ETGrjOfG2gF9}`tdc7eV9CLe}u6w;#OTagLcWZnpp&C-54HBpgSfN<+ zFlhg<1@HD3yCL-KH(|>*fB(~R`wesX7s`(J8OYas{XiVkpn$zF{$w|3EwY6F=~jO0 z_km#!eKm>8lDR0Zz^PmVA%HX~zJ?S#1Lh`5NGjDj)l+mLv0*NMo80i}|c6_NoJ z7KnXQ&RBz16Ro#k>%n)sc!eVvj2~5uan6Fvn7;@YjgyyxkbxD1;n@_Kij9>FQB;eu z;g0jzi}#q1U)7JPj*J5tcHK=F(w7n_ffb+^5LiJPhA=Qq&Q!D549l>AS9FqxxRNbd zS}|Gf0C|(e*WDxmE_RuKk%53OmKa32fK#~_tvDTf@hVMbZ(%u>{dSiBCwM4s#a=HL zmpQrJOrzGIg%t=G`<8);O`|tQcL|ZXm0!7;vF(eYd2edD@ow3gGtAw>S>%YJG(Pw} z&leD&!367*l}Xo_FGx|*S#@Q3of)>BQ;(YAnYWa02qgi1HBA@*;R{${kz>*CoOGS) zwkm(Ic)Gz|2l~_0*`SZ(h!;9)8an!j;h|As`HX=T{Gg&Qug_R6ql?ErHSz^72&6+g z&_TuqRl4>hU@mS8opfrGfJk?g0j+A*fJT2~P(7{Cqxh`<{4x~sop0joh0 zBw-nHVVB9mSw4lP85suM4^gFBjOY!nx%aB)npTw49^n96RWTKz0s{IPGZNslx1xN+ zb5j|TNuy!_DliZz;TfPMO}2U2*sJ!omZLY8U>TUFC7Z?My0Q;NtH1zRjly@0Iwz=cHI}*vYXs>r;@6(n4wfDSJE4|fQdVpI1g*!mW z^j43eI}d>bF5n0@vwYq%ATzZ_NninN+;n`P5jvp8QDLu7;1GD=bl#Ey%s|08^9aUZ zG^N`-jj{;#Wx{c!v7bQ_+MqN{BNyOeJalYrMD=BH8*gRS#N(Q>Q+#+=`~x+4Bwaiy z$}}mM8ww1`0Y2d@-M~(DC{2mNEiJ&a{Tu`II!Ezb0tg)f<{;5`@-0X6!GSWuj{?i5 zsIjYI8B)PVBmqJIVXZi8+RpCw5>uxg`+^2B&DET@*}Toa_sw^(UB;BoVVo!keJC6t zO)+2)>QNNL2MFRI5CkDKF<{1x#j}ZE0lbqLi#)IY{oDnZF-;i&4)!|IzqS0!StAEAOIQ8KP~6qKO`2!Sos(kLk)qD^=(95*O>g}u zTmZ=>07wVD&xZm70wLL}z|n^S*@r>{BoqY_1_bDo*XPt%4-&PB0^=nE4Bd@^#$El^ zEksY)Xfv`K2sUiFxZPLm-Qj&FAXPx$Te%&eC(8#x2Yd`3zy}Z%Ekm>BY5WQ#fe0+X z1u!9DG9V8$Ai4R)!QqmBi@Flu&-S(^NkB~Kl7!3+mE=>DNoG*x<*U_OKE_}@sOv`#q;nAew2|OzQojA*ei&XVq&$#|8U~@S-T02& zJL~W(D6dHOpMlZ5z5eT;Iqb7q-s60X#gyCa+FGqZ3u>Jy@D(cf$11Mfng`ztZ_%@a zi?a&CN#EE77uc9J_JSZk@|%n0DgP$wIu(8#;UlF``6<5-LgBX=tXsQ&4Li2%*|c5Hy^Xsr;N84?`~D5Qp7C_?&P#7?(9R`o zwfgSM-?byj>Jh*j3CwN52D2oSO*Y}G6ED1uD{eAErkgG_>mJo^HSLxsPCcvD3v$RJ zpLMTX`SOc$%Dwp;YqrJ>0CUVT&rEa8Hs6eM&N}bRbI(5i40O;!|4b7!Hsw^Eb(aZc z=8h0{mM%>*T|DQ;-E=&1)>?0!Rmt_Cs&d$3|Hbl01cOA}&}y&EcH3^h4R_oy)8t)_ z3i7ygP)@)3jL}jnV=;J5UDyjeQJkpaVS@p&%tsewx7%)MygD z`>ksl7IN2y0tmtou4jOcGoT6iMLv!sFjfz2p$lI)H26vIbZr7k1!sbk3`!3?nA+b5 zMF_+owuXe@JE0L#h``HP&M+`+q7$DuGw8fYJJER`bkaqdA$^B~f-789*x^4RigAo# zA>t9wh_V#_qD_fgf}$JW_&_a22Pi|CC7YQPvqL5gyWW;~=RRi#FYoK2OCe55O1 z8MjCVix@ts6b_ey$?9z~PS$&uYDNjnK$h~9#6)C8Dz`{BzAR@I@PIR)SpfmSa+=hf zn-u9d$y~UJ7{a*ZCDFx4)b((GSNml!&nd)V7IU2}N@Yb>`Oa3>tN|Wq00Zo4&w5sK zpRUv*QE-wAVQ4dX!szBDF{r;yRw!fE;FLJbiOz_EP@U|oXulGfOhqp9W)iDM9zs86>?QHu)oKJJtN zJdG;PnbC6r0T4g{3h+?_=+mTB)tmr4fB}`ltN|g&KvQv+Jen@7Qn}D(1%4 zW>ZX6s5A|QiZ#s9)4B?GYj@47as5)%qbm1i4Di5XpIU(pICTJ0Wk73XmH-cMKmeHk z4Z&kYDow%~K(gB{Kx4zJg8+E;u>){GVM*&o(>g!^1_;4?Nt!cTYBF@Q1x7Mtnjq=n zGF{_DSB9Vhui+|~xP&a?aUZ;wJ+tiLqbG_?zDtJBH z0H+?dv!4w>Xax&jDAH4;tZgb(ab`=pJ%f&z(Fw39`QLLH4P8+)3LlYTn$sj}!Au@m zaUl$4iAcDw=d)P^w)@cyI8(X<2mpr-8v)LGdAk8PtN|AAOlLaQybWmTiUsi5_Kx(r zF@|9PV@+-}y`CK9NKfvV6ZRed=4?jK z2Q*axB5tZw+f3&HBw({$?r2j4&&FuVRovi2e4i*SHJ)R z*Z>FA8v#(S*u2|iY^Z7d!gt?nbwuILO(^5E>_iN3;+!Kg)}=qExUL$oTeiVR)$C_0 zd?3-zXd7=vPg}?AswuwNo5!r;RC7849vHWt)1C2*dmsWYmN&d%xjKF~$aKmRm);^K zM_qZQdIBE}YU-h^gX(ue;dBfs`GWN%vtG!7*_qnry{;H(P?9TPPr`_ubW3_=4 z+a~{L)k#}7tYl4SnP^Q8F)3ivESBQ6RD#p6fT5IvLr5|!$bvt&S8+#W|007;s5#CvNj8{r7~~x~ zGH5_?6b861L;@Xx;|&Z*gke~Owo^z)cw8>1LNMrrYp6Ciq$9b2dP9PP=Qn7;fQ9XM zF(Al83MCEI@P$GshJol65NL)Kg@$WXfo!OVL$evW5{EaKR_8ZCcUUpHWn={S9nZ%O zg2;(vL5PL8PKU@!YPg7{xHFSs3_7w5hL>^%wJ>8>EO}`E6MHC5n%IfA2o|3Rio_&} z@kENI2#hvkd|j6yZ!#I(R5#+n9X*sf9E23;LV`?j9=J%2T%n7*XiB|^RKFOE-54`I zW<|~;8SfJ{!)I^9ViV$#Lmj9o(^!q~coo-(jfSL+%5^aJh>tHZ0RB}$w8RRz@Q-RG zN9HI_Q6nBo0b3ihiSWpf@;HxYRF9RGj}u9e$Hb4x!wP_q1{>K1l8_5AhYX`PJ*Y!5 zu9HLQq7+a8N(}jq4*8Hq6p>3@ptKvL;&RSA~|bCt;e2!PNNb~qDV`IU7@41hyqOVM~5lSc?y8h@CUxVV;Vc|epV zllRw{aS54=G63;I43gjnln{^^xsyYJ6Tm`_7BXPX2TpQAWQKW|otT)4sXJ~Nbdl+r zwKAEN!V0JW2!2qTb;%0Cu$P{RL(rgm%~zHPc^*t@p759dn(7HT%-Npt$)GecnVrFt+{6l$kd>*> zbwjc%sn~_#nUvotpkP#>fq0+@DmDuGo(+nk20|uTxeOVZmAF|)0=1Pqv4fx)9wsR# zc{HFQDuyFUqAFve*_Wb2Iv@#yD}WFP$>5U`k_%~o3UovatWceja2XnSlz9Z0f>|*& zv7#1wCM_$GQ?N&;t8f5hlQU7)WL-~vui5%kIf7T^F}Z~|38tRH~^O`riQ+XN!ZuP31aQ}C}D zVFFBxw3K6823skpfSq{hZ_&a33$GvxvS1y&ayTLm~f0fPGx6JQZHo3t+R zv`s4klN+^Ci&k<WP^=p;xb#W{5@5F) zP!Sk#w<7@oRbT^=E4ffR0#I8L^SK?wz@n_s29!XZZSavd!5PW0wWR9`x4;V<#7BK3 zw&Gc~uq%6LJG+6hwxj6(5loA{5^x1+S_Kx-0#hKo@hSlmFaj6g1K$h1xZ1omD*+&I zw2(`9o{p-Jw2fzW0B?Ih>9$^FyToEEL1yc~W6#={=!2o`nvpT>78gRiC zA-KSL!O~k1TMV;y`>P@R0TXZmB`^XbaKH?31cU3s;VZ*4j5Kwb3zo?WQQDRKR!5Fl zqt0kFP4QL&=eh)%#LKqCOl&1iyp0~A0g@`WZz}04LA|FW{~|fCVWW#(xX5 z8Q{GeOa&S+0WR?WvOJ3gO7H;`Y{wdb$~|z$h_T5wf;5!i2h|A(&RUzSU@=s*NeReB z?pMTOi^Pq*bB_$k?Nqyodb&!|l zyV2a1(=4lIYRwu!u5|0P>6#M28nqUHw7}{BO(3uLET6pwWTA6!RZ5I-a)8TsF$0$| z{L9b{t#;51(J3Oy0CTGvvC{%Fyq8PT$SN*a%bEVAdbm;)7XxgU5jdeCm`1Fvj7-x^ zX45x)QH<&Tp0-QT8L_C1`lykL$pI45BOTOVG63X)dUaEU$pb6{DT2`8a`+n>V7tgu z?a=6H)hCqGC4r{&j0M>293EZP^4T;}ST}=488|41zy{ZFy-;&~qtB3bc5QG~o!9xX z*MqIuf)O#|$ion#M`j{;j!i6k*g>vioRocVmVMboCDGzq&1AaSWjz49@-+0BAQxFQ zH)tk}jX$M5P8vfRsjb?M{Lrl(Ihj4%nmqv2fhGbiTiHQNc6f(n0yu&Pg2~J-V%rqN zUEGew+Q|JI%DvpQO&yd}MUEo!`4b-v52iwf)>OM4yp_;O+O=%KQ}4Se_0J z-p3u`K>FGo%iq*Y;kjJfY4U#-WJSd%M_H(cMUrSv!4#4m;v#w`Y%Am{uH}*2+z;a`!S>~cHckW<(`2r?1&8KT?c-|>t8GreDL&D0p0v$v zeBr`xhh{;n_tK>4d}Pi=qv7X&u3%~|=y)N?h|c1j87{xJa;SrT(vyds^n6fW=9Z5C zmYDuin!f21(dnM#-gYvrg-BzB(?x9k|{g zL!oc0$k-6;>y>`3tUl$#E=+-L?5ITOPi*MP9@IC&H0R3`1PLJ$L#M!Y+E5O*m424l zuI*%0?A`7V$S&^3N+z5E>OQi3LE_s@Z9SBp%-BBfbye^7P7&We$>E;wEDlo>bnaMp zfTj-D1CBAP%jZ25?+2ge+^+Bf%<%U3@a0YJ=E5VavxVQW)W7YF1fRcw)A1hfQwbmP zyfg9=S@N}x(xwL_1z6*ZornGj*>fFueF$9gPV+!y^EZDG_`dT*YQ7RATe=?qo<{N3 z?Be9q{uw}VmP=prAn){%ob!(v^+v88V@E$Zj`eNd>qg(}Rk#da5B3q&^kYvDP=EF) zn$Md7<}?MKB4~gG2KRAKY;<4u=XCdY9~fny)q9Wj$^(o2NfV=?L4h;)Jj6lCeCm9} z9n%0@iC^Vn&-f~$_mCf-XU*kAL4aI8+VPDkoj=#h82X|gOpDL>;o8^q4A`jeoM&y0 ztp6F*F7%ln-;o|p4I&M-Z~LQ!`*qO|BUQ@iJG6k>=I6^ z2o%iU{DSoSV;`$X5WE>-0Y=dK9wD>6y2i}0xJ^I;gqs7rivQLR5GIiSXpm6Q;6a1~ zpe3aOX&Em{%h*xlM32&zB~!M{+&+IPRjy>&(&bB-F=fuAS<~iC zoH=#wN@8Fk;Ija{Nfz<1~z?Lz))ZS=vdUv1QMuUEB6;+__^P<=s2!SKz^g z4<}yS_;IFTA**%FoGcO`XMto!%V3I)35+L{V0^;Jkt#eSWH^HVrArUSE}Rrxpo516 z2V726siNd8h7uW?ba;?ry#xpLqijF|3p7wN%UZAi2p|~qYk-CtGA0>hA`%UxjGnOw zHH)5^#v*AvRIM7+N<@t_WuSpgHx^rT@kJP8lyRosYAotEpVjI8;@lK_M8rmTVl3;bj62piz5V!a8pM92a%o#YFICNv0hF$WKH z^G!G>OOUYz8+_0hV3N_3B+)J;t(p!$Tt>qTG1O=p5>-R3Gm}u7@li-4m2^@`ZM5_s z9DUSuQ%*bO6v*b5Lkl_wjlcp6BaozyEHf5 z*$B~(d?xoaY0gdeU8Iv%dTHF^t#Mv_qn3K=P4yj8)U%9~Hc7^ol>^Yx!^gz#(&MObG@*YXF>a_H!biMe=ze z48?_J8E8-|H>5@NM3-sKJNNu^nVj}bDXL31{q*6i_RC+iSeF1`3CL7+t_M!&;sv~X zz#?R`3!+lI461M#gkUFdAw2l7P~wBcZbi5W5*9dOzX|HXA#m)oXBP3qEu?7M#uplG zHOehlv_xqdN@jD=+jswccSYw7YSinu|9-Fx5-Uiovus5Q9Ra5sCL0H#tLn zuX2W}Mhy>@KFu8semmr04|OBIqtFk3LnI>q)l}E7{+$I0GdqY84%Qe4WzZn98;gSi zv^T#*QHmwxVizCOJr|0Eg)eNJMn*$98rIN;8c`qneE3E<#xW>Bybxgr4 z9a;J)f+>hVkb?|l_FCmdL?+Trh*P2B&bTGx*>ttu= zmWdQQLQ|ge+=?{M(#NuBQ=j|f=QeZy22OB_aU!DW$hd%J&h*(#o$X|3L)rCCp)`}8 z6QyWV?CD2p_EV!9E#UwF`qA$VbQuLTR5{n!Gi4xSp$=tfOJxL6OvSUJGo9&0^T|Js z=2WNI^Jr*3>dlb)k~APmR7Z+BjfMCO9h`J&Q=Qs0h)R^EQ+1|I5rWUF?o_K=eU?X; zL5zR?^m}6**HKmTjK(eXscU6xZD1-@s>T(MRy_z->&jKU=G8!6B?ef-8c>mvm8d2% zjkkoB4yLwMv5T$ATV*O&$mWr*2C*w;^?F&%KE|tw`)Mu(8q|^wZ5r%IEMrZ3+Lbsq zva2QKWYdz-%*Ix>Uj1r?z-e3m&+2TTj|pjNoUO>I;k%Ua~t&$SY$>}zHF zT3Y zG`hhK@aA{1;w5i?OJ`o#PE)-DuI6SPqQdr8_pf8DWKtpL+l*A>zWSx`TKVf=4C4#H zI1LqnJ*-HtI%H3J+SY>0co7Ge(YMtQ2YxI3;+9@G!!!2OhC6)Y59hd}q8YKC`YMdj z{Pn?>Bcpa9+>LJ31I9~ksEld+WFOghUjf!}m089;hvXs`$Vf&mPyq;32*X0+quhxp zl479&t)LM$hl`o~W@I}58On3ck(5=5V=L#mWi>-EASkg6RsbRmf(C>z_Ix6c~(~AUaL;So9RJ1|MtY`x(w1uJ+`ApG0LvwF2$6_}} zTGnrzbfs^3{J;uggqjtiQFf|-$!hHmnWWN?c0Ed4ZG127+S%rJ zRkkf)Jbzo@lYx-3zw8G<_(2l6P&h9_qii&)8pW+HDN(1LZ;XF#-~Hw|RQ`R>ZVz1K zXeqcdh>;6cphD&UtPln&kZs#|r)JUfm3c-`Qc+hVe@S=yEECbL}u-oABWXPA@4gQ(s zo~~W0+ZX3qr+d4$&h>tKJ*z_(d*8#dr*XHzPqXx=UwVI*F+i;lWMtzLJ?Q|j(` z&$iz0%6Eepp#_=;!sc7hdCs@MK*j^%nPRmpH-!!LTXLS?8$2oEWi0SOYB3Ge}jxd0cK0{Rdk zh4FzF01qUP67K*HE##XfybiQzj~bw$^-?xy5Vi>MLox%biGv)d3l|AvhaB8NMs%wl z^g-nR1H$uS9U?Rw3DCSJ2qYo|8y#2=?OPKISb$k!j8?G#2nat5Sd$L8fDe=a2Ixd9 zkrKwJ9a_o2(lZ|F@d5NWfi!8JgD68(92V?Tzw0xMUi5+1n<6w>fD`OPMAI_$X*b-H z!5K_3N%|9SAgxB+M%oBKNQ5#;46wF<4kU>c6Cj99Bnvb#2!c4p!Z5}1lO+j&KzK|U z07(-x!NA^;fGI>pvLHVxc|{&5L+n5sTxo(|L`DUY7z-FhzHtu;5EEWJ3wLBj@zW~^ zK{Qs#1<%L@N&vH55I1W4rO8>g$C(@tsfKLahkN5jmu##@{6?+|MR|269xFA=87=_7;Tx=yz)Pd{a8!josU4$M6P)LIa z6A`kQ-XX}c(MpAMfG8vZtPDjy%&d}A2HsN!k0dug6NZw|7X16Ak3gZcJIO&ol$Cr* zz?`FZf-9M{HJUWQAp`;q(10qq47U)NB|!@`aX<;UfQ|{j?2AWZDF_Ih$6!$zys@SU zpa2Zq80?!HhQYqDK|hO}01EOLHMEakY(<6f4+Th|3E+|kfX(`NAciRcEWv<}X{B1c zEC_Lk_M-%7NQF}KvQpy=pu)?7(o1EKB$V{adRWB3+)kSC#=^|7!<@3Szyb&V0fH+S zoCSfE>VQQEaE~xq9wuayLo&+RTpsU34`e|PCbR+RNt3c67KHE^lDR;!6cfNmlQ0pU z2OyqdgqXg`l4Oh>vam=F#7J~XPEc!u6d{IVW0b{fyXhQ>w+oRP@`jw_P86k)nEXzT z3(q?ni%m37OT5H%BnuBz!~bAHH58D0+?&p<%pAaiDS#fvXwNHzfFQ*JF1Sd}qyhqh zo;E}evpf|i#O(gbcO+%sNgL`bdORxb_1Cj`S?)%*#Hp#%Cze6U|dsRf#?2)A90CT_cOo zGXm(7zCoQ)onpm_DaHpn0yfM;yYQVj%rmlMI+RN}Nst9(pai;%PJ<$;RJF!%G0~QM zRltl@Sw${d<+e@rFbn_>5R#Gys#FZ@i*cQTDF~rP)iG{Lp}qpEy&TEVLRDwG);rbK zSLN1jU9E2gxJ^Sr%7mg`i&uH2E<&7El5#ffveRtkS8nvze}$}o1v%HtSbstxc?Bzw z;s{?#v(=b7Yn%pbl~`A$*ouv-ixoPJbvzhYBj{9S~mOtYE_g)QI%cUscPA; zd)dCDEt$Q`X%&}&Vl|Y~6Q4=4h~3#8>{*|!DO3_dj73bL9XO)xERmwwc{?;{*b|93 zS*YdN6`k5S1KPdITCv-#1)JGZB_A;=GZ7mpvMt-~lv=c%D7C#jwhcRvdWML|nFWhG za@j2h6WeLPxu?b3;?vu`ou|HCzP~NH0niZ92r`BJ(9}5GMdLBM)mg`NNyv>{Xqwyt ztlW{iT+9V4zmnD%+OB~jvV};7NHST`9o=m#UDK_qs>NED6;IV&T@87Luj;GK6|9dq z+=4PPkhojjo!GPOT?|9rARJziV_oB&*W``Udp(rW2&V}DgWl+EMBb&|*7{xHUEdeQ z-fz?1+TyH*?cDPTsS<(M@+IBrMPKr=-t%%_0%P6G1tV(InbUx_pyCkx&EMVKU;a%l z{{>*J#axcX8IJ`l1NI06-iQsUx5j1QojczLE}{sQ;P;gXqoq32=q0$LA;zU)MigNj#vdKt;Z3`x#QEXB65`G! z;;9?rBSvB!RN^HrFZFHTKLuVV_AqM!jds(M6f)HyK3aG);FKg^Esi`c?qbww;xR_n zhad@ML*utqTnFalpY`Ng1?5^6Wq5rBVR&SR zxS4MXD;IV(@MUF0$>LXryiJzns8M8GCgW02Ayf8cZ?cex7^^?7UyM*mmON%Tj%8)8 zC}wWHq#Wl{Y37|8089Xehv*FCT(H5d<^+bYjc{ddHe_%9X1=9ma24aVh0zumB!C{I z1T<%z8h}fX1zyNw^#Ll;P@H#zFk+_Xdd|9J#^-to=OGjU?pr{D=BNR{gHj;nQbyz4 z;@CKa=!iDvi4Ng=wrF|6XeL>U8bt`{iNaz3^@{~S0(+d$2m+yHS%9AQ$Bssb0>Fbm zcm)?6v51)0_%d1+LTU3U=G9L?dhCvw#AnUx? zi=j>kFldBQ0J6kW=oK!BX{0%ZEsc4T>JO&sC9~?QKBa96Ilr~#=F@7j;OOq-lGq6o z5cEC?!ag8{mE{RjEJ#Lz6qyCMjs^JAP{hNvHV6XP0yz)`mP@=`a6xu9No)05VkR=Z z=4+O=;lG~cz_!}p1!IGV>8(DC2i2G=ISgFvfcIn`g``I*QpM_-g7ruNV@d7^CFlW= z0xxI;RJeu2+vRo>#4EmQRvs#gn`+qqo;AOo?H}1$|?wgVfjK0)R^H6nFn$}1DybZFwoB?fGF64F*s_a12%Cp@YSv_gX&s| zcJK#p=?IrBeHK{U-e(v!?6E-an8{6tk)HMFZ3jsJ%&g2~DZi(r8@m~giOEzDU`>eZ zZwX*=DF6eIzJ+Zu@B`;=5KY>}1#caPGYIc-4MNvEMSubRl}QBnC~-nR|h}H=JG^uaXA2ZK!?9*1XnOLc4In|^UxyR zF7fGHjPQnS#&l}a^i8+4AHQ0{2J(Z@ZBOUw3RQqa<;I=YR(2fyVZ1s`W+xgInN*TzW6%Rd5>85PCCFbw73< zZ+9kM_S+6+h0kqr9%p*5_b34HEtqas5c3{x;kb2eqw0_xANX}Ic!T%i+UDoM4)TZR zs?b*SEdcXU&}G6cT*Ez|HDWO}|Mz$bWA~8v3XDGa6x;$u2XFy@W>m)bhc=M|P86Eo zYnwkUoX>f#*m<5W=q>R1h@b9K$b}1j+jr~t4>9-GUiy!hZKp3KsE>M%3T-LSb}$zO zb(UY)1+AY65e-gvu-C7qAN#B*d$ZRldslfmzy#DTyHs1)3(+M)F{-(%dnUVkyw4Ye z4_cFF_PyV#zHf0aNM{#R=s^^BAwGOIt9ivAF2-+se|!9Xj{Jn5e4S!(ZEx|m&INVW zd^qKNs`LD>5B&-oebS!_y;psGVts9&dR({evYSS6XI`U16suEu+_x~0-~DAa{gPjJ zA@}{&Z+&eCX<0z+NSD^A|0Ws@R-^uFe!G8u=+`6Zr`%?jeCt1Zs&9P(e}!f1ep8is znXjRnD|YhFFWohAsVpW?c*wXH0jc&Poqw)di2=V zuFK|)EqgZY+O}`w|0ZaVuSUR#e*+ILd^mBBiW!>(gJa$9(TNiM)B#$rt` zaw#BYFbahrk7JTqrkUIMNFIS(jUSIqRa0PMWK(yN+3DYin*AtgyqT)oG~5BAYBoqvC057*R?g#~-q^fl8sW z8d|GOE=G1MJ7V%0uDIiN=j&L&5}U5NZyKwtyYIrA5CNomA;z;?MEl1pX{4fMq1g%u zad_&PMHoVaAqFO?3|ca+1!jxWWh@^Yv(!_Q zJ2O}h|Kpss)_LjNv)5n8H^3NDm?VW1P(Yz49+cc-S%6G8J)Ji#Q(d>+x>`+E&0F)` zcU@hB9k}3!4S)x}Wp4q+lwp`COsoL{d~#0Mbwl^vmt#(5-dfeK5NK@+*;lg}M>=DYK*;N}wU4Z85c18%zU$6GYOC32MH@sx_g z?nf?eFC&cgw07B2?J!RBbT^gj-n;nYul9TA!=sP0&YG|bWNg1pZDi^4b1S*grjqPzsd)sr`fXrbz@Ud(r3Y}txpp|kbnke;K2{h z{~!sP00{=>00sJ?P)YW~0~odthAixlME+w%Or*hzHnd86hSH&noOYS-Jy3!|9HLby zc)=rTZG9V*$P7mKkOfo$3=(006?kBQ1yErKQ=9@LQs~8um|zVW(8vl~C^Q&Kfkc03 z0}Yo^yOt^^E$oL25MQKk~2!o%*K#5$ugj+7DqXM_bLmv)kZhf33Et6tI zLE=(fIKm--CW`exwELnMk1QCzC)J?mO1m1tmcBHsMC#{1A0kjG7IT>r=&3>>*-%Il6Mm=& z0#C77)Gw|9sRsFi6_S@KlRVFPS@;uGt7_ODzNmJ1oM#Zd>R8NyRjiVo|5!{PGSjj? zQJBO$#0Rq3)1UrS0uw+13Q8~ni$e6SUmW8@SSyh`0akxdNP@;15xbYPlQJ?f=}q1^ zAkQ@RvBITVWG8D}_K;N~Bj~F`&Pq%}kf5#>Xe%?#kcA8I;2}L#Eq9lCxF`fxf6Dn^ zGIpy#-I^?JK;i0e+grEd9yh+ZNv=fnnh+>@!2)UZEH*<6f=Wmt3qrMPcMH4_l9U9h zQ)=Cm#GnRQIA=1ggihqT2Ms)auO9VuFNYBZU-^QVQ+ysR$?ApGDM|AoLl7(+Qe1Skb2 zp2R=JWx+9+FoqjN+MH|2@oAIWDm#X3=7bgb$ZDQT2IB|6894dLQJ%AK2Ji|@h-gHO zzyvXH+setnDpu?~DIRH5<})KYTxni2quo+GIzyU11IUFappYMp;Q^$`h~ojZHyPZj zvePLW&^$Gq=u_uv(T!epu|^!}S9@pxvJk~zkpqT2h~ia#?(}53%)~kB20Iw)T zt*2dWRQg$@CC#24FT7suggf6l8aKHaZEkb}JTP{qb$;~o|Co_UxT^Es7PlAe4dD+fE@Rg?6w$G7Zf zKl=dkfCn#tK@N&vowQyN$4}Ee>xdzGL1re+RsA1wVMF6F?N004215h6%^N z1oA)Mwx|h>$Dw3C_F>N(AUSVjjQi&E8XtfwM1jBk|KUjvr&s6PcU<>(BCsdfkv;Rf zB6i!88~3_5`v7iHio*jRUnRN)mgZh^V6(F&Iaz-5%U_kvLqDw2pMKM;?+Ud8Klqs7 ze(EECd{zE@K;$<+|Dl5Z^t;shv0u$q-z%t{mF0;j$U^t2o~PAOrx}+1p##44Uj;73 z{{f&r3E%)`9LK>QV{C!&-2zLvM6darVTGAZs2l}aU<(=r24)~GZ6F5%9MqM<0Yb?Z z;DIR65FGgn3c``ysUXX+pbNGj48~v}%^(e)8}At)^GsW&4T>9~*9mG*GGO7a4dMP7 zp$j5m5+2bKG9j};-4kY?W5k_gK!;&v;RE*2{|a_t7k(iaT96oy;j7t?@8JiOpdm4+ zA?=7%GC)HcrW_Z>VFk`19iopN-XWyP81rObN?6NG{0B``6&4C2r!A087~&!FUm_|Z z@wggBlEmDX>C$eVHZF zTT2MyDoq9n00sm?7CZn;?vx7zm<3?KLiqq5WYYqOr$cl=LA(`=9Thiq zX7b1ayMdq_h=L#b3;wZEMPj6<9VIWg=2FHc-#I01+Kg>tmP0Ur89>2IW+xOh6kSPx zLTQl;L=;Lgl0tDPi7L`RVt^lTs1N)jcTNF{s%Sy5s0R2z9w>llO~Ej^KwC-xeytUJ z_GlGsr*;{FTD@3)j({LcRIv&tbwx%*j1A{i`*F7kNMvR2k z6jnMEeqq2Ncx8{q0TCpp|6Pe_L7Zut$|n&h(lCvuAE=cN$iXz_D4gP{jLN|S9D$qV zmvYu=V*+Vp#ufX_l|iti6-8M$B>@~P>0Y2hrA=En#)Ju8RhV7jAhHq`vSu=DDeQG= zN6sdgzLc2a=02LxiY-89wr7bh)JZaDhq{3l;VPZpYKnH21nB8B^(vYIDzNUULV##< zGG`9h6=t502AIGIm{vAb=V8)Weu~qj>Rd{cLYIA7fx?k279~0!r65*AIxyp@9^IF! zsxC#PMNDN?I>c2{C~x{Ea7JeykR@=Y*a=+12(W+#96=snmvb6tTOR6oqG>h-YeFP! zS`}-1@<0bbfEuJH|4|((2pj?r{AMK#sX>5cSN0{8xfVvKTa__J% zrsz&RK(JDi9cV&%@>ez;>IDG7AV`83biv2+l$(vg$OatLp)5*Zf-FFugmtP#8ec|Y zT^71yy3%aR+3d{w+hB<|uCE=D+R6JkOm=-dv%?aG=Q zI?~;hqNBof!_4k2*!8X74$`|;M7+WQC#ml0t^pkEtTrCcI6>h_Fha=9-7IP%1deN` zwV~&NZs@LS|EiL%AW7pi_KETK2{rZ!?0Qj%=-d{l#4UUp?t<$owNpA4B?P+2=jtu+ z=G*8FFQ)V&^A^|u#2=nC!Y%N^^s?-E$zr%l9`|~$=n8N6hN0<7M5U51cM(ARmC7Y> zAMSpus2LVU9;i!T&-R+?{2Cqj+HaeT@BXUR&yWfjd_pd;X53Qbw<4$plax-dL-#T; zu{rPq7fA$5a8V7w9wvwIVKA07V1XL&c?oc@eX#tFu*;RO2@eShr|>qdaQ3YVFtlb3 z8y_4|FM`(aZnfhw=CBUeFAooo5C1SG6%POK&y$W!5yvgv8L;tTWR?P&8?_@5J~7`$ zu@r+2{}os97l}<8MadYXQ=fG)2g4Buv+t+DgksT%2%qtp?XVhCmgsPV{t!GW{G!lyIvo0D}?7@F~}ED(g@sw{o!sGA!rJCOd>7&vFWp1gS`iEZhQ$ z^fE1$U@uSeUVAagR)&ob-B4yvyhcd^T=qa9B#^%5fZc%3%8 z7dLlvddRXk8;|({&13Mws7CU)?lMMVqD5vTHpjC(w--Iv^L%|XL*VlS^G^t3LMgDO z{}>N6CbDzfUNk0->V=_0HX}5uDfB|$#yvkY>C8(St;#F(GYnsJMJMAOLmo#bgM!v{ zYhKu}g*2j#G)dQnNuRXfj7q6wnk* z^;k;+B}l@^Bm*&s@g4K>iY)b6W1>`Vsj0;^AipwQuZvyZHP4vut%-sk_yI7mLaP8m z8ihhw?Tw+b9c2IyFxMmf*+tlAou|*5CbY)w~9o@BmdbT)^t?&Gk6D?cwaSgmp4D1 zcR{4LdgC=Q@&YQn_j^kMF!(`Wm!pnht}lzKcWXE=NP{~BxPW&Rc^9}`#I}OJ3+jX% zQnCUnus8rU@`@135m$49YB*~W_lJYHN{RT0!vzX!fDqVa67-UZSB&zov@rk!FysON z%>;c{cq|(B^dcn@d$%#%cva_kAMH4gJKBLuK&9@$7uaJO*rY{}=y<-W|HB@Eonl17 z68T0jIwED2dpB`Yb(SYIfpa-b5prKtgcXP+{B+VroH-Ri zDt8`1+GfO?9tH%&f!He3;MJr<;ABPE84&{mD!_psJUA;z0~^tVQWJw8?s6T0D^!ik{E2IwHoAILedEY=Xmacoz`eyo@oc9*p5an|GHu8P3|YCSG0Rm zg4N|honQCJ$;1t{K^wHJ7q250Qn`13d!UQ^fVXzJPqvpYsiIS(5d?xDByT0S`$D9s zZvsJEo;h`afQo)++L9*}OaOXTsDNbvnsz70x&UWczzOu`dFs@xO27#S0yZsxg<1r& zPCUhTW|A860OT?jQUV~*w{r+X8+-wFI|r;FP;Q+(M{9U8czC$C{8y1X%=fU&C;G1Q zdPPWVecGoR&{Z=9lbv?`Fx6;t=4jVvf#Kumv9?v$OJ<#F{uLy_A#^}7r72d<=jTZ( zrQX~v93?Vze6{okG29RUndX$H@geg)bBn<+UtXQcS4!OW% z!9|`cQXh;kfda))rg6q_1^O3oV8Me47dCu2uwTLh|1ODgCF_=~T*Pvrl7uQ3GGxm* zcjhb&w6tZ=NJCS0`ZTg+#(3T0mCRkeeGX~YwtX9SZr!_i_xAl8c<_W3i5I`v5_xjv z%b7QK{#@fsnTCJ<4WvoXOd*;o1wL_zhlC7=NF*hq!Mud;Bs2igwAKxe4iMDfz~Cm! z0|;@5sC{Wxy+eX{00c^KkYIr;0)avT4C3(P0}F7<0FEpWyAZ<+HQbQHzXlisk3U4A z#1Bexp#%^=+CYUBXgu>QG|@^U4K){)Nrso!py`I4*@XKM$RLFrlE@;B{H?eoCpr$g zCY^i|$|#|f?y%~BGGV7Rc9_A6opuOp0wC?a>Agcv4 z$aKPj2Qsju5=qABYOMd1>Iw)mDgh5B7Ysyj3Pylp;EkR{`Jlr}Exi=e!wB#K3^_tn z0~J(OWJOd~lyULJWk6GPMjB5gZ5d@)dyPjQkGvJvTy@=*S0a`C^&cjs9G2K(jV+c+ z>IB=aCryMH>;a%8oGIFurhTAVn5NZm+rYBT6x?vd&6EHzqSykAFvR$Tk~Vk&CYLl! zg*8=GM=MR5R-wrk$JgBHvDbhF9+=>QZv|FKVUJyy;f5V9DcQoT{A-3vcB@T*O{m zG_uitxin=ydj_Zg&F7idUVo%`;+FPQb#^Ol|b^85Z*gzqLk0P!! z-vlXmEc69NeeToX201900T^#&_7kBL&gCggh@up--~}=j5S_FtgRO1@`M!PC05sq%wbW9*BDS|lJ1 zt2f93cEcR|uvHlu*~spl5shq=A#J?O-j+xXX)IQ`yJvQPKc%b75i8qEu+`<^r zpy3RuN4;qf@PO6mMy(d<|43WjQf`ohhy*FwOJ8!tl0d=aCW%?ha1B5X!>c2@f`>;b zig09dK%Oj&QA5yS4wj%>B@S`fO>gQ z;DttrA{U==9_Rq5N?F3~n*kN*uENR7avpSz=d4LO+1XHs3WflAoag=I2~8<%!JhcE zSJ|)$J!w?K8vq^XNJ;uYT^bajDXpMFZSqi;zSJ-SnCLP?m`8Mhp;I9$g)l;cDt@-o zpWH0zP>G79la5oRNu?l5XTngJKDD7To$39icvC!t2ooveg{lVlyjS+~qam$jQOR0X zhZGd4N>!`um|hIm*0mnid~JQn zTjSbTInGt06OHEn_-8q2JWrc}6pbo13rMjN7PO)DhhY(0TG%O80*rO+YLPeqFnEEk zP-JE!CdUfPkswW?+9a^2@nIgC=ap8A9zdNyJod=%8;Q9eG9{$?yFv;Ib>kN z+uv@Ex4Z$ipc|EyUIWLmT%HQvY)f$q`5Lf#2CN~zVpcEDA>&ma6{>$Zj7tCu7{pRK z?}16&+SgvS|BA`G1y>8%&(UOfM#-QVYEE-64YQ+k9oDfeKOABoH)+Hr7IJg#sy|Vn z;*YEPDXU`jsu`+tU2G(X8rdCkEgl*y=hXls&c*-)(|Niy3JreAhh+qmLQ1`mAAcDX!pb8}DR1`NFA4K55+ zp$8KHLA*~sa=~d}3MQY439?>w+A;eUzyJo!dBN%^69X9l2ZpmLtuLH!&fs6nvCrua z&Z7n$?;jaD5`X{{AgCNC@NNMXrqBcu{O1T*fVzYnz=a%4!3kwK!W62YgsOwU@@?{g z{}U8ob=+}X>nQJfUFPU_t?pt|kuf@WAi$vaSk3&-|F}6k1#@}<?bE+)3-p~|xd0Jp&qPOje-H8gnjfqydrIkRfMMHyrXIk1Hb4ZQa%_-9AzhLsBwI zQtZ}l(HJcyO(WKf0mfpc;1CXGVsaa4QYsa~CU4TTB2prakk|y!C#^0dUqKkeE-b}P zDQhgzloBa#1TDEME#YoopmHkXk~XT+DiI4SyAm&BA{mja3;S{mzpyL^F2iD^Ez#yF z6G<);lOgD`F4yWV^U^23QrgHe&SFh7Zsasf5MSI9HMlAv5mPZk|MMUivoV$GF(DHl zfig$NZYbAo^XC1X-Fb8~ed38hMN6$#9CP*XUiV;|K|{ajO3RL$8+ z0}PSjAZzRz;ZiqqlQexZrA~7=HBcW#;T4z>_13Nn0W;17voqqaMsAbmrZY6F(>f0d zJG0aC^f86r3E-Y2V<`R6FJn_V zoh?;pBpJ|dGw|ioWHLbIl06C3oD39j5L7q^!1Rb=+WZkb#ZVei<25%U5lVq1C-WcKPR`VF3{eBa(2zw}|06~PPR4N4MiUc9b99n+ zlt+_3XHHbhpREKO-bHP5{ zPs&imQdNz{NMp_{bs2(nM{0CdyRlYnb*Vr!eRB0983Pp@v{bv08Z{%@%90E@V<5lq zETarcmz5!#)mak@S}{mkQ?up5E=AXG{r(c|wiVVs|1s^LjUZV=#$uIRIh9b)6(`fx zh}QKO2Y|Xrk}NYZURP2xIFZ%r)kQRe7>e}eo>Uw(v|mlpTmhD?1on~$_9yA|AC2=t zp%Eaz6fJ4&SYPc+%MKc1RaP_B_BNJdPs?M431qEs07^1PB10B__VgZB+Jbc&RkC4M zb?huwQ)Si-X|`t1=Vs9NgA&TXM7UK17y*Y;oA)-j`Yq2M+? zc@_`=0T_e?NgsCRk|BYX|F&@Jk8Kf`Xx^4_S8HeCg%<#U5>V~s z(g4Zy)HI~+R3B|y*G?Fa;bm(tbnS0+NmpndQwZPy4upUbPdBboH%Cwe6`+kZ+J%~A z_e)Qe;8txi-$fYYENy!?27ecL9VQ}2zzQrz6F>nwzQ6>K7j@^BZrf#RRm2ZaK^7o3 z#@H_@Qajbh(%Ay!U$GG$LH0T3(%fej-GGKMBhfP?`!xx z2wQ7}42f0NZp1aEEk{mIgEv@oJJ^HE{|SUOArPzp4t7Eitl&Ck_%GsuBVQsx3}G-Z zAP}-Z13+LwTDUMY0Apx^1q1<;6qp(1w|!Zc6+E$XQq4%Ej5YcVgL9;ajd*5}IEg>> zP~Ye;y2NQTKoA6hD+rm2abgNC#y}w83i>1ufC3p8MCH zcMYHOnTUkBsQ`n3`PmA*qmp0Z0+2uu0znY|!~rOQ2PPl~F2MrABn|{Y4J-r#*x+Kw zqCw&y3$9qAVZxgiBbAeZYeU5kSOFMB1r@w>;Er^2^Vd5Um=`{GrG1$<)5@hKY3N4a zV%9*e*WigYVFv#BCBz~RI$#1mxu~Z=5(ME7Peu^nya7da4cMnt@^f z?6_Uh;0xLS5OU!QBw=C4|5S5ZwjWUsrAH@b(>lvqx~-igBGUjnSO6xzKob%fC<=N6 zSYQFnz(V%qPe34=ttCKsKu;DWpp8P4AN!b$VJyck7wGg|-uT+mQjP()j*XKfJ(qV) zo27p_wbdvh$rwAVzzWuYJJR>vrr=OMAOi$J68_*#yZ{2|fIAK)2*PAcfcoSdU=VuZ zrj7ejCVMjTmo;v|wqkBJ^^FYYY`R4drTG=R7Y?@~PB!RfiQX`I7s{Knhpj|)sNmZoVe zg#oSrP68neq?*X7rYwn}oJA!lL4{SPcWk@RIAQ!XW*o~kP0P1DIZU@w&^*n>WwNWy z7~1@tH(H&e984+oHKx1Htvt`Od#(ArN@3cq0^Ps{T`80Nfwwlv{XiB*T2=WJM;x7G zNjuVYZO!_-pgJl#>xHF&w*b?n{Y9b#NPwdS3*#5vsAPxVr@ zhf(lYC$``0JiuEcGT1%fcWmGZ9@q?Cxe$Jn6d1;dG#ZywbI-B_%i7;>;TB%uHLg43 z`)lJlJ|yHF7yj8wJ}HfM=A3up)sN+6;T4oe8VEP$1zy`}o^Nda<~1ni7d}5R zGrE!1HKWZqiN5GV9p;mMxMrT|%gy7TK6&YL+Vhjv#CqrP6++|8-;2I~y58$W3+%%l zn8u#$p-PRDwglPFFF!HWu6m^5EWlfy7XW_lzkccMzI5?E?_p~1!4o`XtuJeA(bB#Q zp}Sx3|M=PyU-2iM@wwgcAK#@$oGg>J?YA}Hv=z=|%*mWg)fCe6!>jH?pB$W?^mWJd zxo}%cIzNwg)_&DhC{$nZeD>*n@ogV`M?d$YDzsPA_i_36k9Ja3lKAgj8;}3<&!{(* z-@KT=`Pa$x$&${Ye}_}!MdSFhD;Dv!f3Rvl^l?A@4admQUIf|xNYPOE&3#o%8X%HQ zL$`0Azk>)9DqP60p~Hs|BTAe|v7*I`7&B_z$gyL`e;`AO97!@{yK>}Gs$9vkrOTHv zW6GRKv!>0PICJXU$+M+|pArTD9ZHmFk)lYGDqYI7sne%Wqe`7hwW`&tSc4iPmaJ)4YHNEVweY_4Hdr(La9*|N4? zX}_&)HW{?M28YX^PrttX`v;Bd|A*YU|Nj662q1KqWoO`l2qvf?QUkQ-9Wt&dgW6nh zm3JO&XUVl*cwIr$jX8VaC*p`CmS|!_`x)1tfGoD?;)^vA2$ggT)@b96I8rr0|7veR zc%Fo*$rcw{=e6aQdK#K#mWKqL=;V`7Mp;;j#jObAl~`u^pNvn@nB$jVhH2vfyZ~d@ zgKpVnmXLH6iQ!r4f#p>)(&QGRlz8T;=bj8vnHWl11}f;Fj&bRfmqS@$gcVjKk%UwX zxIsi_kxnY+rI>m~W@ye~YlgMqT}Dx#l(}?0T6an;)kV~MjKiZLLgrtRe8g1jC_;|5BAwItR2- zfj|il8su;Y4i89cac3G-QtePI2;y&LWu2LpSj6~P|S*Dp^eLFbe zu=$91x$LoL%xz5WR{8np&wRN^I&<#+`#pKC6zHNWWi-NbKp}wz4t&x92*{Iw3-aK! zpG?37hOj`fg76azNNNFQ(4X0Y0D=591%V%Uo(8RQwa8K(qN#@|k!ye{Deo3L<=!oS821>vIhxot=dc~_eNMKoU z2t?W}fC1Dwtyn}@02`S2yGR+)1-(ilP#ngC0&eh(QHkDD1|SPrkT7s2OQDgv*S$5# zMl3w)iZlj7!yEeXk0#0?NZRMaLK>1XxQfoM>X(%JB_&}J|HwcR0jNBnxIlOk0H6Yk z*uX$!u?i9d3KoB&0TZAA1tj>%15WTPG`dnMaDxgP+ej8*ATEW_DBQP7mJGm&=Ph^y zhF7FfCvnsxa)3;xGUX#k{_OCO(1a!sks?IaHLOB3Qr$m`G&?@}L1o7Nu)UGvh1wxtRgH;ua(1MJ}`_$GG&em$6Bcc<6$f zG7%#!(Ac9fmr2oz0;idTjHX68H_f6{vys;%3M7%3K?qzy6Osr+N#h_0KP15njj%x1 z29Z)uK7d)y;(^(kXGtb%VhsA^r%-j&#()l#phYrI|9L>=8V<>3EV&qHTK3qDiCXlk zSOpA5pSjVly04=~+0_?-)hI?S$_iQtsZuNu)S#-;t!teMT!%{6>b*i10KMKWe-xqf zIF&Yjqf0Oar_xWf)yBRLtV0PJS%o!Ov(B}ynBAv8i(1RTJybm}yNkNg zl_X(8G@{j*Au<)q+Sc|-kQsHXYzYNfpnx#5xXrC-UUApF3Ur{VS(lpBlp4{t$5dxw z4K%7@%+}vPEnC0$W@k+v6ypV*~gKBYu|EVO?!bMKd;G=Wh%ixN_SH2M5S0W39 z-wNMXzx#FLZ_D(?hUTJ##C>64;o_A9v$w%0zUqS`%;Fc(*S;2xF&Z)4Mp_CKz}Mq2 z48zmV2|-jg?lJK(Q%q!vuDHcW_SuAWCF3U76~o=NF@VQ+e-pR5i=Bbf#x5xlMQa);tNcsCR8K|0v%g!vOXzqgU;>A$289X_-bh@=WVy%gffd zj`mG-&FgAk3d(n7^9Z*+LjS6|(OLenR}`uYWwYbb&Q7&Hn08##)ms> z$E6PVE_Gry!bct@g){u*IpFI4t8;G{MhJ@}5J<c@1czH~wKr2b{PeyG_Uq?(?b-2k1d>_t1;Z_3IqHy8!hqFO)8G$E|0$ z95z**cy9H!;|J?mkM`EPPWRA!|NWm~&o`|;}fSG3YV2ix6F`1!y+?zf(M-51Z9 zyXQUTi4O3iUxsrycWCWYFTCcp3R|usKIn>Pyy&s=b$mmf>=|dZFh{F;?71iKgWtUD z>DBqq+dlN8&;3;BU2(8`mlu)$?RZzup7E9#^RrL>lxlB##N%G~(664Udmm-~vVw%N zh^qEQeC1%eUM=R%yz<9S9OgGa>(7tA`nBV7>i3o`{3T0NFpMfoUjrpT>rW zXjiX*fPSC`ZJ-8aKnbt#hQKyjHlD1-7ik6l%d_PBE2 zIF3~42cpo3tcVJBSdI!Pgz|R_EEs{FVIkRqo+W9?P?9o(l4YopD=AHf=#t%tk2A@S6$gutmsgDgk|d;u za0D)9L6j$Hl;nq$Nohze$&}VM0LfTrYJdqdS#OwFV|mwK{DzJi7dUD{a7F2rGYFPp z=|f{lmb~Va|5mtwYguY->0Mk%kV>a<1C<&O(hcd*m3WzadbyW8)R%rKY7;q=;y8}0 z=m-6{j4+pU1X++TBn*tXi@g|`RyUcH`B+PNnQWMuo#~m4h>?cYMj3W-KRI52bDE9` znX0*Ytl63z^_sAmgJwCCRHz2x7>TP`2K`uLxjB$ZH$tg_8W|@{sF|9(w)lpo#4qc;yIp9R-VjR3jf)IpZSk& zIe+E|lGwv)N-~tcX`lUvpZTdT`^ld&Hj$edf{myK8tR$7=ayp!Q0CZl;u9Vv$%_su zii8-U|I{_16l!6~DUNZdp^%uCpQ((P_;|nPdRK-TTzR5jnW8E>TP)h5_+^&K2&0<` zqsm!mHriWxm!shIU%XJF-07p~2BAS(D?>`8+GV6x_@$Z&rph^(%b0hsAd(u#VYi`w zRcfWphNW3LU&hI$x+j}odZc2yr0hAB^!Gvp2@I&|rjiMyaq3lDN~gJ%oOg<+$vCDR z`cHU=cm0K(e_E3JC#Zx6r-dpghl;3~^`eW)sAO7d8Fr@S*kAsoeU3w=m@0dkx~UT8 zaGv^sNSdUsAPRS?jC<;j4VZnu6;+mpe66Z^uL`SJ(y6mbS4N7bq5z|$0H%-1mc3ew z|1_sZY&xvON~p%Fl(L$v(Ki6iimT093eSqGq|m5(N`0orS+aOz!*s3KnyK2ltztQ+ z-dc>Jkgw({j?YT2xjL$a=8uS$f;ZM4!Wyq_SFH5vBFK8Lnx(J#>I&!TuejQ%=t_O} z=A^&}mj(;32)ml|y0AUeuny}=;0m$%Dvr%su9|tV_4aEj_?ERuJ_q};KBurEDl9jrwwKsdqJJW%*~KHd}Q#o3jIAuRP0gK8v#9daI&(v;iAs zF*}PHr*k#?v?CX_QtKZ(TeZ(~vOue^DvPK8I+$u!m@yZ&W#S$lOSWYjvS(YI|J|y# zb$7K8Tdv6{u5sJ71KVc6T1!c&w>@XJefu0#3%K$IuAk7h5c{@Si>?%@jKAiOM#XR1 znYWIsZjc+f%8|B|dohBGxkUTALtC+9s;d?^kfJLP1y{OzdxoBex~}=Rt6OvffC)eQ zvu>NY;@YfV8nB}pnlmd*$|iEU>ujg%yON>0!5b|Ekgu44vP6r#-@BQ!+oZI}g3z10 z(`&kXYrPUWx!JpG18}*d(7nftylyMLj=E-;7?KEyzUfQ7>&w3Qg1zs1ZJ6*0^m_ zT$%2>!GadQ#CyQRtH8tyvE56*<65yLtZj*A4ALNLDokq^+`@}Nz%cwJGW@|ee7Q9& z!XW&;|N62QR~9Ke#Ish!Mx1LeoW%V(!=DhwKs&=fo5N!~!V+t^`TI}kXvO}E#e1g3 zTdWgE?8P@i03OW5PF%)gEDBD%z^|*Mw@G$DEW~ge#{oRYoZ7{99Ah$k373Gt^D7G7 zi@=mD$ux|)DI2jc8fE4i#1wqUL?*|IJZ+5J$Q2g9ksQe$OvCfr%C3wFI;_d!yI%!) zXQ3?0C`QVq93!WU$`|$tt1Qf|?7@Ca!w7r{v5d7!>X9TwC(WhH|9ZyDy*v|j49p4g z$i#dIVSK=^9LWc)%#e%;%KWqP+rYL=7SNo_((GQ;T+K0Y&DeY(1Hj6|jLfV|%;8MV zkc`Rm8^zK2%+e6e?ObH<49_ni&-0uesvO3KAk1Lw&|wVD!ragK?8g{QzvC#upxmYf zy-Wv<&#83_F0Gv0SSRt*`h@`Smi0{%z4jnKV~;8{V~iT&Wki>|!j4$9pP|L5)87oNogUf|2I;T(PyS$)n$ zt7c^34kPXk{w=*0p5hL+;w+vVF8<z{ni7T;_)reAF6;164zKR&_^|3v4(OS# zvzt!p6PFOD-VM(%3drybdSU0?*$=H=>$5KF|10tA(hlv@4(qM{WuacmqYms2mk{jW zEP=oZl@JJi@C?fCmCTM0(WJ4xXcr?lN@yUL(fRHS+T ze-HR)(%pATTk>S=_diH)TQ8RNw(hyE?=yUFz<^q`lQ>kEjk1ZUYuT*_`=3P5hG?4A z*CY-qJl%utN++H_(KeMqgUaB#U)nvquvD^vmg2<8JoRf z;(N~Pzn@@o@|Pa8kBd$rOP&u1QN?YMKv)#q#5m)oOm#MKjD&sw(KHJsn*>g3 z-55uuqU8w&~A%!OgTu`5XS z(IL|Tb~)ze0+nIXZWxXU#Gs0TbQQ&$AwtKn`Z@(}i_^<)i0lG2324Fco5}2&{W>LK zu>7PPJAk@jrjtutCc}b(*DBHTE_gxVkWQ{lQSL_~X1M_6Vz`os@TYyT4u-QqT}=rV zvq(1Vx|lQ>oBB{i(E>+>ZY-z$nu<{7p)=ipy?k0I8uU3&#~LPngFd{!mXW}zOrcpl7sVf(Ai>bN!RO=Gq%d{ z@nCuQ@euZ@#icfFUO%XC@1DUODyG!Hn>6%^)91CY#rWaFjK_rfK%m9s#$YYjt-=}5rw>jdr%tfGxx6OtgOS=b7;js0nR@s2pGMCr-*(~3MV5KD0#$2bK z&e7c6G`pA!|2VgLNGC5Z_AsreAkwR&In{RF)O^8bAKyj80IIz# zLNv&m$ilO01YacEH3RSn>|0xtAIfB}&GpJ~_h45XdT8-}{OV(EUb*UzEZ(!)5(Fj-q%Od z+_@_g!(gqu`NzhmTnO8r5TdR)bCa^ZcVc8V-hYMR9xVOd_7+U5RBIsYS&gzimLRcO zM8j9B14x+`p~*{VLCjTw=j|IeYXzdHO*70$Fh2cd;fV7m^ z$RRD17;`hSAB2dgj!#0!1j-g1FGkonUk1oE9OWfpzC6&C3_R$^(KVdrVh{4*q3O&< z;WAL*8ksVJ_Z%^RdH-_L`*< zf)_wTKlRa<8>XJsD_mu?z<5;pzbV1lE)Q+v9`Zsf_&d+8A$3sHKS<1-= z+eY+%TNS>t?5A$sg|X(-rx_+gYc3AtvSSp`nmPUUorF1J8RS(Qk;lrkA3)@9JW;go zHOYIxMWMT|1`)Z&mN)uMnSNLKEv0= zjuagVHvZC`1INp-*5J+1kgWXaJ}rGxPV1A*lcAuSzhIA)_3kYehAmTqHbS&kG%FTF zu~uDXi?Ccr$sJix;x`%N2QJ;kV04~nm}+p?!&8+0M6o6dWlwUkgsQN3|4#7TaVia? zM&Ui~R9!{$M|m1Xx%N522BJ=7UEX5#Pc3NfthwdxtRIyMYP9N$IGU}hF;y;=yZYB{ zSX>{~CXZhvjF1S!jGDfqq}|&X+R%*igu+)L*;SB}B59M$y4LaJ{$qPa1kB)ap#*kH zky-1mXfMb+c=KZleMw*Iu1s!ZcRshVQ=JNmrbKC9tf03<+UT|pG>5h|{soj!NXrQaf*Ywmh#XZ4lqTX&7#tCqt7sSUA5aOU8l7D1G% zeg5A+bwzFu6ju$F;V?pMpXtM6`5DRy`aFf^cR066g`?OYPE7Rmq@fIO_k2Yn&u(l6 z{AP`U7vCfJeKo|BoE38|$Bn-{T5eGpY#SZ1FcZqdk;Q4NQhIOr1%bQk2g_s+95{{>z(!Qywe^oE*kc+es5HwfP1_RM=*;P z<7}q4{A0NSr&gj^A_W6$ns2o0-2vJATDkMH%m?gQ7Q4@Ww!Mx>>c=Sos`_)O9JWcexMQ)XgU-vtof2Ow`VpwY$bLqI&mwD<#u|_5R@2CC6(O% zUi-PHyJEK9EZ`QEQBIbjI0k3id5w!$6WFHTahM-*aelRN!oMqa)+}=0W?mBm6~fG| z)!1E<(EH&1dH`q(N5vrRvA6Rqj9g3tcM`-tJ}nFkD3s1yQl zqToz`G;?heJ@_T}9^km<`;YF5E$nxPPCk_@1J|OOHK(E*oNrhCzf^(xpeahP)l#>I z-G(+DZ8RI$;mie@d4&DvXTFuok13-wpD>l!Lzr^2rGhpA{~$E$oouRGCy2k|N^Q@( z?Vn!NarB!?@rSK$#LMxZAGeC0*G$)Hw|^siK?SUW@LL=iNn!nuQ@i}ONlPqafKO7M zv$@LjM5h1W#zV}Y&jsOAmW4XB(4v1o0mLlIHlUlP*VHXj{`v=xdJT@@c}hp4*dtEJ z$Ftyg`(1xyuF6wCVL*wbU=~K$Uw2{JUhhuqfW`vV_Gtv3?6$Vbad6e1v;e~Iu1ChI z;{;*kpzwra;Hz4=k5QS7>-+f`>-E{J}Dd<4^l2B`RI5hH5 z&eiYf$LwqQ3D>U-y8I*2C%?Hu1?d@XxU4EwqC27d0DhOiXiJ?T`5K5|6c=od4lsoewpan!Fok=T!0|N<*xLTpM`k#)CelNa zOxqEdwn6?aAmtn^>YyWaRVI@73&-(Sko}5CiU_jEuOOd+fuPladZ>|hjFE!xeJN_+ zhm^(Lti(fX`OIyl9m++dMnxRqgTHNy`hAHp7U1`_#WQ>>i?t;$5rt4$(8T&CRpilLO(B|wKRD!H9m6ZvfQ-|qCkF^baMi*x?;RBc zvFCZ?VZoGDIsbKdE0Ytes0AT-|C+GHnvhZ-?t{rf34qp3lp|$|nC##jd7~dKNH)4E zufj7gW7xO4OO!eCu3ur#?OAG4&vy`ltoQc_It4><^@)+!H`_%A<#T z!5~(-cOP=*nN5@KtUYOXbDk|~QLY)AY1bCY=>beGWT6eJ5Y5gKOTWD7ng1#=&+$Wz!)*SW-8`p)n)eLh5dK_!nKI=5^z=?gh2@+# zooXWX44p#ebirIRI~{f2cv`7iF4G+4dl}UrAuFQDT7ar(2+4P5^$L7{F(1l&I+0jy z^&2sTFO3ar>@?`eIkJuF2&r@OmC*_Z3y6H+#c71ag0}hNUke3+3m{RAP7IW5q z4E>>&&Repkj*L|sXVJ3?+rC+Aja*0;t zBudo56845x@cD|T?n<`x$`G%nbJ9w}c|6pQRZ}DF^FNy8A*-xcT7?j+tmxWnI|9Cw zTA9T1S?8V!ee^D`P`XSVnt#gK9=$DW_BC zv`&xu>w5qD+9K@AtE0Z}tnLa9;Rp_y-&^@No(c7x4%XU{Wh)8Kpq<%mEx}6r^-?hjW*f1>-#^J zDID#Ht)kA|nhir{R4f*_J-yX=c!ND`4B~DDS|XOc<`tuF24URxTaw=PykO3zRDhOCbkPb+;RJTJ^wV9)Y;PJ%V8Tj7Py8p>x~mhZtRt-BAtO`G>PYI8e_=Wbq7e z-TYiF$`UOL#?g#HC+3akzXiVzic!Rt!H4Azrfk&gkGSA%#k?aUPrk|UruW~{Pwg% zx;(-?+Q1t|x9bGhTZSk@z$(LsXj=l|{p?SL5E;Nrn1AiviGN{pIZ?Ug6oth*Y{S`q z0j_Lb&l$#hS)bvN+~D{{vI2fn82^!N1z3N9vlhAi<`Cgc=ZJ+y?UQ1g_X#nb-ykR7 z@+vtMjokXx0FC^el<7hRk8Y=h;;=OrgaLP)Le4OX&Jt4jsI+wsCBfQHp&BJHPKFo< z4=u9XU9Z^jYacz+l2V7ax68VjxP>ruM} z;I?&RtQo-tj@8Oub_7r>3%Q*uxVa3Y*hyRvI>6e`0Mxc!J;4C4?niG3x0)G%*Ua7P zft$tOPYaj(zZngmU+ytzai(rAqQVbj?lE#Z?mpZRPJX|69FU@>-PdL`-iqKtl*azh zdUzc_os67&#XW_y?&x1U9=JUJ{flA;3o9z}%O%G{|Brim_oK_ogKG`2G~2T@*0aP7 z`~uFoV?p)YAok3=jD$vmwPu4wV-0m60|I|~URb?UxV<)Ord1v}N75cv175{2?rmo2 z)aYJ=e|oLkd$r)bory$5U%V_fzP5L-6kh$h+zvI0yXn^K@)mjSrG1O9KIz|DAJBYD z(nnNVksG~CA%E}~7mhem_F`Ll8#s8ILPM}Kv!5kRCvO6}(!I+$0KI;>PaC|q+4(Gk z!xoc$?-Lyu%m+6@gh4{V%F=-?ugLm> z5%Ip-9$%3UgkjRER#;wB3`G&3p*|g5Q;x(@NX0W*-B67sGHBM>p4?DRq-Podnd5lI3{Q&1?&aI*d;&+k{rQW_)?}W=e3jkngV9{6+wH|U;feK1 z9}tTbRfX$ff9x-ZgMsSf}bNkMU4Ob7YoK#PISB?nVl}jA%x2BJxuG zi8wEyjF-6>*@;!)E7gpXkmWTC=VtJrPTs$IqWY_^gH4@e6v3O5q@9+!muyhhN+V_% z^GuVmndCu}Nl-LOljYuqPn+$t#$}NiK;dqY72?`joE^?EhMyD2=Sh+qD*Hm27pdcE zr4udGb`mJB_)?mdWX#7<`qOHRzN}LMk)a$dLWiNEw#(VNtkE9Kqg>v~^;}lb`SP;< zrwLJRv#gDvous;(p`59*pHEJ+c39R+ziwQI(58Oc`c(N-bdfVBWu6E1i>8p9i?TfB@=Obc=X4fl$fLS+~0U28gnqNS#7a+%E z+J~h3i9Mm&Any$j$Ld@UB3g8?k0wii*vD`JiR!v3|Gs^VqaR;C8eW5uZOtZs?ctvXA@<|C53NS?M*1$e(rwfG)rZH(A8>KJ9o%lm zv->B%@M$-I>w;%BPOj@&D^XMN=pezohN zx=i~D5?!|Unrb-{@|G=mtlGcq>1GnRo=h-(yOyS@78sr2_j{L`k;>k?Td?WS7J6|n znC9E7pcHyI`B(7%z~Fk>*`MxsJ1P9)4IM#~6BX3;^4>q-`>K2=^n_r`k;^tHJoHD{ zdb^tF!)yBjYNQSlbH)HpoQxBOT*zmDoCy6bM;3xk6j`Mv@U(4PxJ4x##T7l6mfl{|+VPOa z1{-=&PN?8%m?Y=!tbpe_WeIXgxOT8H?gHCS{wd2?OYtCrKzUMfB$+HR3MW-K+d;t= z3`B+CJe+zxA%YEof^iNPzC0uji|+bV_12cxUkHV^|9M(^#Rz%c!%Mztnh9UTJTKsJEAv**bTx@7j5$dQ+xp zegDJ957t%{hJR)Es0Sp$kkOl?j5B3 zolz$yDcQYP!cJMeeQhGHsjpfNc}rtGZ3(i_o8bYV56>$gl#dxj>Y1Bgm;XR$}qvl+h_Ua zN@H)>VbX)>F#^wb9lK#dc!RG!tdO-6m9Va~cAP(`q-go4+M9$*u9dN2r!YC3=v&qK0{*b~GE+HkvI-Z6l*RlZCwO`_&LQ-7qL=1qRZheItS2-9R+h}-h1GFTa5lC;lt5YpQ zUc=WuUT>>pXdb7MF_-apui$;U>Ws0y$s7Ilw}XJl)^-b)v$TKSc05weQ!10US-R&( z2dHhuX8EzM{&$3XB}dxWiq7r$@BgHW@60c*qcwt|uyei~KTlBHmjWwK*Sq-gxy4Wv zERDY`gh?{Ou&dO{Wq@wJwl~hQ93!c+d;MCW(H^pQi$D`h_ z3w^XYBc4vTf~6}7_uuLCP_2>kcJx)S}D`-zdgE_w!zotV`%O^;$rAr_jH^$u~_%je!Ay=_ph^B zQqi${9>Gwx8y|K*!FFTdS{;Zyt+!DX{`G#r259aTQfbQ#o|$Qdt1T!#Iq2jyXr~qP zC}=wPKzc}QqKvI0pPbwKEqJHDw(^*C6{>V4(4fIAs5Zl#;GB{{Ll^R4w*yVoh!DVy z;bZUi4mpYKh@{X;kh^0))_vv!`OXHtQ7k*ThR*;GXog}C3o$Pn<01zqEga3B0G(_3 zfO8HNC5Uff>%EW^Kbbr^gS@EF$WcE|2w9A%CJyj}oY*>&SdxI`M&m7{k0<3jnY#f$ z4Al0h5g8%{E?D-nHi*31N1Xi_{UfE1%{dbq(wGW$i%KJpngU6BMU4H|F88Of6TXid z+XyO@0zSr=-e6aniI~ZE5M#_Z6L~iuXV(^mm{ll(m4<+=Dwz#6KL&I{)hf z1@lrKz0(kjqzSPiC8srE&m=jQH9W^hDu+GpH(!(FV9M-hO0%SVUIPTarBpr^T>d7L z_>R4d!9BxC%FmaHU=zO3X_Aoa49^4p10<2VNqkg4TT~z!L?3~;DcJxP=Uon$ zJ*6wmeo;yePMin^ovE_(4jw!tf|kFGtG`Shgf!+Ld`f{_7nS@_fqW5_UdnfZxPAGy zPi8>$J2-9UQ_tyWX_^yeDwk%8IU~X7Eh=+ZAS*P)y-%esNsZV5%UJ=5ByJ{u(IcxW zC0w>DJd~uICBs+(i8x@28PzYF_8rr(5cXT4$pn<{P@&@YLGFeCxRC=non8$^Db1Kw z&AcC42BS<`W{ST1y5jvhLxs9raCF;+#zG#A`pZI zNue%$pDII<*@rlpJdK8;nOYB|Y*~?vb)VG-oVj(XxdDzv5j-bMAi7qMjpTuL{0=u2 zjR6adNdctocWN6JSSOldnW?_ig~OzcBDDjWP(4G;H*-C>BQ<3)2gM@Cxey$lBY74J z)uIYWkC(&F=lvIzVioOTU%EIPOIiab3pMfnA7$q5Q-f|rM{;RLDwctMQ$4nGhe}Y? zjy)E>-z=Qgj#Ry&E&C3A$0U4Tj`V0GzF|{)(1`M4TRKP|CA3;7(bM^|(b@7@szyOO z)*T>giJpb@u_E`IL_z!XnGzKA;;mZ}{1PSHE{VA*iQPOVpc`}_DulxhAQ%XsvV=PG z5k=7wA(%l3Z2 zjzXcnGPBbS1%OAFuCq6mfPmtR*gvk#?SZ6Iqv1`N+3$vgR30L#siPY4V~5^f&`#J@ z$mA`3Cw#dyz=(kIpg}N{n1_gh_ok7Uwbz1xI)f0gEmbB9bo|~D4UbNz5Kux!s)f#T zHXgtl2&MJpnnV4mUO^{fi#Nx;m zK9N66im^mylQH#Y%)^Ov9*!ldl=|XnAiu{4k?gk;kvwU?E|?xHleyxr(O(4-8yO~Z zH0rC6R~Jf`swrYw?h%_nQgLdNWgExXdxMR?gug1taaC=E!CszjjQ=DgYlIACjP`CNfg=|PUcJ0$_*yl8qbz%OvbYm+MCY*wfsKYocv$JAE{zT z%k|#Ke-VGJw?{Mia@qfr_^Y?sn(AzSyxi`O^!#aX;vR4_h=494+~J>eKY(zI>FnF$ z4Uq&djj5QE)GK=r>9*zf0;%cG0uMPt;sBEpuk8DJ@0#-+@nLD}JNnl!vQWrFE^>(T z2J+o7I%jv?AhJA>K2%Q7Og}2i1zQxbtsw{s>FkOd5s9v0J_||^QxgL5O#~bL|7?dS zlm)~W2_Ia%S0**-%f+7w2o#p7mbk~lv?Ajng>8YkAq1f z3t)q$`+*}3kxT`Z`0(TW<IHK5$nIjU<-xHsufx*M#I`C}vkQa6r;|NpvMh#@^7+#uVa z|8s?XE=js3=l^qs{TEb<4mSFPO7n+f{|hRuqZ;%hP$(0B+M9?awasdHyDk_@VEL*) zSD{)sK0)efaEknraIBom8cidCm{>YjB#|i_EqiadSg4t+vk{S0zEow{9pUq-UR$oy zp3UMU)UeZSGTAxJ7*D)VYIi^TT3CoC5}>xLn7Kx2^~1179irl^ciASrbesUJ5RrS5 zNl!5oQk8e=?cqeGsNYglYQxc7u0o`wAYbFzQjPYwkb;+s-D1nn>8443H_*Sf0PqEI z#?!B@c0!~s?{fm?qiHLNY}C#+=d*?CpUe5htdN1nbmw4hP5=ZAT}~kE%P?6WlH|&E0IDqfPB5B=WtQ+q z%~5P9-q_=o?>l@V8iY?Kynh5!+VO4#Yh4MNn-9KvEYC?r^IVL;!|`4$gCiXkkU#kg zrW>*k{C>PV*U5eY1TrsGBF=2fj8HtC%t5lA>xo&sD&ljYm^8sY3dA%71QO$CQ|VaB zuOWKcI3q>gBT+Mg{UMRW0u=;C&=-%RoM2p8l}uNHGz-zvs5umVh?d?M|4%ug9Pm5ys^XJXdnj!F*Sy|cGXqjlz6Zu(HM?6ey4&tm1 zV@5M>fm!tkjjoDtHw-#_4LkzlVa_0f&3PmC`&SVN$R5Z_QKKv!=BEARbabJg$Ga8i z3^Y{K6?5D+nW^1blsa#CWlm~R>o$g-w9AbUyt!g1_e0A*&${= z155zQ-OP79g}YhNYzz@PN$%*oc||D{&RNovj^880FHh5*FL#Ta5eC2w%`Q2`i9d^) z#mw;$0S!)%TGWTUyyc zV$HEH$|hFblUn;vdn+uxyrO?>vL25J!6JMR#fA;fC!G&_&*dR!v-QUnK^+5V2}%o9 z9i?bOp694uify1S=<4h9)}I^v=R?9Bu~&G7&NZMKsRqy6ny!!Mn;#!Bo!v!f!d{PC zl%SWejNeML_Xj2sZ$|?hTOZvs#no+(w+XL8N7rsYO+F4iH%}J0F&H704c7Lyo|cM% z8^q|kJO4J{iJnBvGOIm3&q$%^wR%m@FCAeL;oW#vdJPsY9SIUgge0~4rgjtqakY)0 zbuF18<5_~y{}>5LOZS6v(L#{a$&tWbDHZT*te=z-8CrN)DN-M67)1s-mcmmXWrTgW zTE_wcN^|&(ig>8oxN(Kh6A#@XTeudsNvo(;Fp-V*a)Kek0+xRY)Mm6%c%Xr70ba3QPl*4IfENh z^HCxp%*3R={2f%8Vfnl|Wxt91OQC031{^1cLhlh08yUKt5J!g!TX-7S(Q)1lRfp1a z%G5Azc^*gAWSmtqj)VPHl_{pwS?7aqdj(^inIn;e>*g6#0LrOUmA}Q<#Z>L9y+(7Y znL=xMJ5$eb;(hgUSzZ>m1s-4}mJR!e3y{U%EbW#=c+@`=*vAR`t=u{gJ!uF##ozIC z6#EyCb1LPJ#Zw0q8{em+34Jhmg^!e_7}yK2-!p`WFbZw0u?w#&EJfuIRa3swXJ^a< z6uMIs)1gZ9t#qyAx=R$YUn~n^Ux8xeY3N=AjZ!>5>k|2^^TjuGW!WaDqEOb0DW|_p z_=v4}+Se#^49@cf1nIRk0~e-^)Ket%%VlbGcp74UR59e9?QM>shwr*jnJefbH&~-r zeI!vGy)ulz>CX4Z?7O;d5DJE9{}>Jo{+z-x(PMtan7+QZ-e%gC<8A-bGgTp;aBgEJ z)BZ=RO0_=UtWqMTw0QeGp_+q)IJa2EQ?`=)^`PasDqrXKxS8$_q!1N}=(G{yrwod4)E>s+@ z|3(Qs)mpY+y3*#%v>3JtxZ|_z`Dm>1Q2^!P5Uz||aLxqkgN!&{SEt}O^$Ii0lI_2f zEyIa4HAkP@+x+{t{?XJ@-Py7EN0;nSlDG{v52$v>=yGn*+%jKz;o+6Oc@pC`RpMi+ z2ZV9`Vyooi94zFIH?WN-)KYtuaR~V5>A>?Z%!u@Z2hFx%3A=j@cm-4hTCD&25aOv2 z?&9dfgxRA4Pm?CARo(ol@9?1Wdw$elxlL{FIIM`|oeGe;&Afd(6gnd94zh2_!pd~U z66SkwuDTn>?>vzJ@GW%m-<4j1PBb)i=jw$z%6?~>X@OrRnphrWJv%{UNc?L`k`ImL z(I@s6?Nxgubxrd==D)gMhZd^bB33&ugM|3E!Gn*zq@c^tEv-!~zo%9X62maRw|-pK zXAhjNn}Y~}LrBTz$%M0;tm@naF~7>G1Ox3HVV~-6)t*kNUH2Wbf*_N@msQ5I`zBx# z==aaEwc9Ms7O-HEE9;x=bJx?G&bONb@wWrC)2FFn^{afa_psWQ>P&Z69!&K+FK74b z&oLqJljh+2b?nLO;a2?9yi&zYeYV`OUw6V1E0{&0`$IWG_#;3Z{4RF#@i9#Gg6A(V z=La)F1>x>5^wl5H5)*F4Unt%my#*7QCxGJGAGZY)GbMmZC4lG{v{J%jIvw;FU7;H~L^F`j@>2$=kf{XSY zrKJP-TY~X4gB0n+Wxs^*!H1}aDNDJBV1xwe9II-tgt!}r7_R)1!OJvPl7o@`N&^06|Saw-un_6VRUr=pY@YZyDwh7G?`B z3DX$~b2|>Rf)CfE3wM?dH@6H|4-0pq3v(U`S2+&1EeZFv1p0rm&PfR`e+nx}iJ(}H zD5Q%_e~PGqC$)5sq?L*+<_T#lk!yX5gfNTU;^HD;eDjCwS}Q7fDtKZ5Z&oLM`PX!TDBPC`AHC!td- zl#4f5&_h=^HCO;Y5hEmF)II+4DL&jXaXKvVz9cbrBysUL@fAKPi7pAbGy%YyC|(+& zlbWP2qs=LkxXtrN9Y0Q^HCzin#zOm#)d|qXD*5M0oF0BM9`0YwFY&xQ@m}G7$ySpo zo|8k4|EiSypz zoJt3uR!E;FES*MenN}X2CQy<_F_Kn$lEw?4PD+>FER)V1KYmRT@iS?8r$L?c;{$62=s+4ywXA5z)RR@t~=*{{Xf;L&WXz5o@_3_G8Y8QB`(W_8Oc34%B4iigQUwll**&G&ier7 z?G@*-j^(``WfQa$4bVJN@@V5 zNerbUQl*s^rRfo+gTjCQp%j=zo>$Q*S z#gG~tX&W@88f0u5?12qx#SKd14K_y&%7DfnjE$C3jS3cxx{-}$#f`GVjYemU#(*X% z+9nIRrk@r~;=m@m@+Q6ECefp&-$>2cw9P_N%^o(*8o*}$;%2|`X0@Yc9zaV7V~es> z3#Ua(WMqp%am&}?mOp1LvVc}r+Sb2vtx^`POu*L6@>cQTR{EpXe55u}+BPbwwo;om zAz&M6aa;9x8~;%oKA^pkv7JY%9owS4J+hs%xE*b{z4xsBE1;u^rej#HgVmy=Hnd~1 zyn|`DqvEh*9;uU_wzE{SbJeDk3fP%k)VT#7?<76ybcX9XVC=$|>T)peI*aVWF77fN z>bgGbLIZSb({w+|bvK!JONDm7mv`3=b=%OSM8fw}(DcMh_WZHzDGlvmJM6X@>PbHA zp@i#=r|ET;>?JbqMT_cnDC)%>>cs)|n!@#=)ASL__i3B=A%ymkSM*5@^+6r>(IWSY z(Dc7b^fTM`bA|Tb74~yX^s^oIU&0RXG7V5l4jh{e2uBSN6%FhT4oH9oaN!33Q4h+= z52Bk7E`$uKR16{v4Ne>kY9bFo(F_es4C&hry@m|+6b_k84BZ_JwZaZtF%4fz4A+?r zJ46j17YOCiaB>ke z=>$8)Mm_aUe#*vdiY{bor(()IivSpL&{5V)}D1NF!t#t8n^uVp{278WDEp znQ2B+Vg|}=1{^gbR5R_HLy^NYOdLjRpOU zC7+GCNmU3Dh)E2zjn(&!&5MoAtI5rq_sv`4tvh10 zh8g5%ACyV9&CU0%58`bIp=~IGZ5ZF}Jz{|R46NP^Eb_%Riap#`^forjcKY@6(joCK9m_7@g4h9@8)AD`eGc!@A6<_O-Jfj_4+9B_9mzzM z@(f}x2)KtxzK_+rhiJ4P^!2YZa38*RU;G&f3G%>|7a2+Pz?J?0A>csY>HwklK)>_= z0C^~SasUuLOkqAuCPpdSSV~g<4hi{x0z6_!ug|or<$tDK`-CYD*i3;>yQV?6(S|~H z@h}KZ|Cd7TztgUQ(F8nj>Hk)!)xn7LWOw`ghY|$-CgtaW=-6n`uGVR`<^evak2#ml z7b$0drd=*&i)C8X){|o{<;(v`yHMm@L0aituRHzR%0>U0*&vZ2MDYB1Wa5Kgpp}`U zx!Wwy)+&(Pf9-SvVF}^Td1_6DA_>JL1+>}CMpGzlx;j?tj)qftBR}-;oh|0`RT5A; zUz|^tD}JP#e*&Kyeh|O*rhMADFSpu*wz6bWSoj@45O|*kwf~c8xO3B@00y> zdpuwMbGobZ_3G|pD_H1jqyW8(_pLrPtq6R`lp4zJB>zE%h*YS1`x+IUj z|2U)s9)n&Dg^XaL%<&G8ER>GI6&trWf%^mkLQ0S*=2M}@HUw`J9$d?T$hhflk{k73 zY1i)T3Xw7~l-2$pX%hNv`y5nyrS#VXA59>S2bnBAi8r^-^hZR{SLWQI@~&sztgVoV8_QP@VR1ZWKxCaatr7 zgH>#79{x!|N|zRWR;ugiab|`m14Cg>jK@hy377j=89R&PW@%{*_!JVNeo6MMvgt75 ztg7uHq8!i#H-1*rhbsrFI_@Q~DSm%j#`TyvrD3*zemfc|_h2b!4r)J`S9N{D9yJi2 zgiAedI(~UPk38@#wr#tjd9Ct>2!7q|xSl!@72dr8vIbc|^h~;f4w6uZJ0+8NQCz=2hqq zvI~dTx-G(uu}{wIWc-fDZ(h%w5hER~>t!j27ID|7fUc53T{zzB;8B6YrictbPu-pxvC49ZtZqQrykU z2n*h$#QQ#jCYC>Tu4eH_`JTkZS_J-+%75B=Qdr3!=RCTFUPUB*-Y?HZAQ4=ZgNW}8 z0dVWRY$Cy3$3C%sA2L~frg$Q_6?={wDx@&Y1MMH^hS*FZqWlX@Z0GI{K|1kUH5rdHUbN&Q2Hy;utfKjz(^VT}w2?7&p1IM}fxuHD6|u*CL}Jashti zYiV_KnlpOe?vwP|09!(?_g?=mweMJ{Uz6CQzR*d8jNH*vO4hb?#|{#Qf+zdz8RfdzgNk2i#8I`#!U=lPUI?A2=SG5DJSVPQ$S zfVFCv>sTHmvz6Rdf{OcSgBQ~Vy_W%jvg_PkzWC&_(qxHRZqsm4mNhkY(5ZS9g>4!_ zLK&x{&LSwxHtHFXMlJSKxy0N$chJ#Xb1|r<&|0qCfXYUj)SR%E<~qT~Wtvp3mALV+ zF)nGMOctL(Gbjy5&5~MP46Gv^BQ#evbIoYd`Fll%TBA0kuo%4FrZ94WQJr9BW5~a% zSr)3npOmDIfBRS4lfcR3#+I4K%X4H7JcgSoOhrkkG_Db;-^8YHP>JwcS7O?DuA#4S z%I3D^-+lybV>IF}ebCwZ8B?=)Z6ynT?s&m*n*yV^o?hfaji`*8Ox!XLq zYA2?7yIzkC^>^H_3li1N3pR9@zOG#>6_;G;g%v1!m2K*q6c-#sl#9nkjW8ltD4l5RYHQC?#h_k*l6c35(g$Trd!ux zmk1{vGXWE_*M@2WMWRp%(sHp3JX@CCK^PKbJ0oOiQ?~<`E=CAVu^RFL=Vq}6p}{D zyI;)m)dUMAp^X(X3a6UCN2j(6IJ_bjSO@eZd5iX4bbysw#><7Iljvya&$euuwFwgw zl6Y>p#OG$5jE3@6u1hzza&k7hszEr&Rox23W(lwEq(aRXC6yNysqGtya@-v#!xtV$ zpp6HT^p4x}3&&e8xu=iEnFr=8uh;hNFdeqOd&#R1-HJ_Apk@zcR!yjyKp~*p9gJOd z?IqT+H~zviqQZIo$6s<3hQ9)_9$hLY}PC;>@9VCaUSTUtT{q(pLt?(Q7AJEcQHL8YZrN*YBenV08z z-?RSbUF)3B`^(;6?zPwNzOL&xmi9Gty|Um*;A5v;q9mf;tFo@=aLViXoTuyWs*b9k zhRS*~EiXCCJ^%gGM&AatKlKrP-+OE~B(dD0@TV>D@YogCcX6lQsA`t}(79jo$$tI2 ztJdB#@V?}xAoulP@cvnZGU@6Qx6&S=^+ccG`xScro8Cv-rMD)uhk17LB{J%=0GDK2 ze&cc?wa>p?CeV(x!H*-P?~2$@>X=seb}88LqWy2uvFAqN@-OK>-ROUddi(+Rr0Bn$ z{L&|hi2(~d{lD{rA03=t})cJ}c1Oycg3(Vu$*OZ0sO=f8&zhHFa1k`kg&41u$#Go6W$8bx$OA_&Fx z@Trj59l@oz2r^%LYAWV`9Y}XkBm-1~naVpyEaU`<U16UIxFN&R;b%z*jvXCPsdPi zm2i9L`}ffBcTtgn*S^6AA^x3Vq2s>c;}J;lka+X~G8P({2#Su*@=cD4OzlKws~|HE zqH@Qh(;a>DsX`*7B8tR)OF+?uRLGifL{(*U-9bzR$hRR2S#}W7viPoTJh~|h(W`>! z9*^k<#eJxJH+X>PP>CGzeK&RxGfai}mKFTvAa)uWKcVt&))DckGV(jsyCqQEykqcI zWzYsFen%x??Z9XEAb3S3LM0=_;`E#?=c&>pLq5? zAp)9YublJ)l(amSG*gjucaTIBon(WXyzP+WqLOUsko*st{B1vZWGop1PGPP}Hs?(F z9+~Xnm|_x{LZq59gqu1~nfTjSN8~1$7wzQz^d|Y4N#OJ6-jY=T++0y-2NA;WQ*Fc3 zIt-Hqh|1=w$VMDy7josW;^qjBXBR}~SW#t#sD}E!Pio+b zHdxAuDa)}^$zgKHp>)iB6_rb0k^5piH|QXj1~;#YD=$nXkJ2H}q%v>fFpp#`uLhhy zt(uR|ng1#(FUBz+6qz4XnV&J9kB#2X4+9kxLkob)1u>2Vl~D!g@`8-%BIGswS zBTC4tO87pNOzxG?f=ea1N&}QiIh{&PsB^Q#b6ZuzbgB`0w@59u7!5pc6Pk=#)m-_* zC>1{kwdE8unha}h2ixjY`>qV5m@+s`nWb8c!}2R9ytLP9UhjT-dRLddudeW{e&yel z9%!2NraH#$XIW^BXY_KG1$sH>ty*+AUToyg%Gl+s;2b;T=d#S0s$A1o@!VO7)zSIO zHraj|=@Qu`cs6A*6%{$zRi@T8%NcbNImynkgXpcxxXt}yYW1zUa@sU+w%T&uFYEhl-WYe>r9kE$(Ba}q8~GgbPt6`(orVz`hsk4p zgI9YeQ2?GW!#B%;*v3#M+(5-d)I< zYs95q&qIsi>o(-4%@;)bql8urL|)g6t)QNp8A$r)OGy^V9O=uhS1mvAEN*>@KwSFR}I4v8P2jtduw%!C(8M z-bj|Zy@tPCL3!qudLL=OlPr4AQ|@15f*qTN%M-g16Bp{A8h#XuJkkoxEeeh;kB`+# zyemppuSk_Ni#AJ*t%=W$)l4@l&cv@QywfP^E-smSE$ObQ?p4?5_b(a5uNhuZA6Y3Go2dB|t3DxF`sJwh8@~Es)ieCc3!a8w-6~hH zRexg}Zq!xo@Vo!r*`imJF^;RST?W9%O1Qk;_>%)f1WJ(FYD^7#l03yH1U2Nfjg-8K z)RQ%|1Woik3XI3K%(YEh_z53gH*(UM^6@tBtc34QB(Q2Iuxr%u^EQjD%8S+3J&$Xa zd?7D&|5E0-SvF2iezIOsqeZz#U9cxy$R(c3C4onyQ@yrDlR#FLx50>@)nrn}EUv-A zrPXp$+WNS`-euHDL&`y;@%7}WTaT-DZG`P)>)T_AH?@u4ylwAzCEgP>`Ny>d(z$sO zL?VydLN(mN(JqmZ8tu`O?y(x-@wM%Vs~X9?5$UTM*~hM#1c|w;ZhDgm`7UXN_rj?z z9VM%2WpNrc$8Qa4J1bl|>ga@mU7DIEJ6dX=wH*(2)S7%Cc-B4H)O+07fA3yZ8#%1; zVdP$5kYIRXa`GF2=a@!%k;d@+ePYAphfgoert@@X^D@`s-hMYv+L(;mqT^q2>DuMZ z-7!zv&wKhMuKCcV>n!f+#c}hmbmJQd2#yu6c)(L{In=*VBmVn%uL4&$C|*C zcy9K8Gc#!B8Q!{prMNV~d~V5jZYe2lnLur^e*v=eI`Z+e$`%2NfqJTZ0qXf&+VNaE zQe1j`Tn1}Uqc2dCK&aUps0BUL5(2gE<+QEiw71}N(ByPF;ne)+|5_9J=8L~u9rSI! zzbAyt`;Gs*H(c+f{K<+beuNMqr4~aVixC!!QBsSMb&JuxiwSFsNnaLI{w=1`FQo-8 zWza8OHxlknE#*TfQS{5M$wcnw*yVxCl@MGWzU5~Z_?7>b%m3lmO0Bfkk^bFWD(Izf zX`*v04gQsrs@qC`Ir!etEcYb>R)s_!{BD$%^N+{H`sG za)X|EZidbhMp?Z^-w&Wags^l^FInOme!hDNFkFk*-)NvGHyiZpvpd-egY5p6lmPTUZ~A}z1P)*nfEpto_A9OLr%N8S1oNQe-M;@#OVCt3SCRVq z+kcqam71u_QndPPwZ%j`R6fmQ=X1Wgk*iF-@2~%46#2h7Xc@)J~NxUmx#4R#%C;s-iT*lszHq z70pm}4W84_98?6F+qt%3!TELahyo^Kv86OSjpTNetzB9Nq3TgVi?r!+b2(vXbsniA z_J9@epHNL5wn4T_`=pOqRZGMrHwoYxFs1c;H?I2xR^D85(lzhLbA}rJk@K^&W2uGE z{hUFPKl$GSk9$E&o-e;qM_bF8*gp>8#MbKzay zk6N}bXKouzGv^P2`0yWJ!+MUavLD_(+uu57#wx{;gP+5Ype_78mH~d88A9Or6dA7L zm<(D49!A$w<`x`0m(EJbVWqoen}>24o~`}H<3aSmk+;jD!ZRJ*wnvT*g#g?yp zaA||1y2YP{4P9LI9(CjvM}CGE`9j{fqi#&%L(+oC<= zVYxpBDfzrjr?-m}IHFB#^Def}Uz>bO;cmI`OL6@5+h`UC(rUh&;Vd$-{1x5Ru3H(t zm!g0zQBYOYmKkV+@|a*l)}}gX*^cvqrUYjGkk?q_?_Q;< z_E(S1s8P|5!8#9c_6Q(ng=3RtIyIVBS2$+6?fze#$}Pa*M6O7Ui{xELs{GmRGfog0 zu>wX3V1NK5fWT=P=x}I$13SvnWX_R$(uM!BUV|hi{_3qtE!~Xx_3N0oS--l~APvgN zbBbXqHdt6qu#goJJfQDEe#2c|ScQ`!>z7QG5r87sg>nyT8w=SoJzGpi*Bm)xG~%1E z3X+Hb?@L`~Z{xBcr-5%NBT*IAGQBle8-#a~A^&8nbS5~ROrCPs7tI8s80W8#vlsgC zJy7atN+$X(3rPWgj9w4!i3U=Px9+(r-$OC1`~@)<8x^3=%5`kf%VANc(^<#?#0}jR~V57nE{heo^rbz@&uO@ga z2}4#`Zwz<8vTcsZCuodbeU_QCShm`ygAjJNrGSbLl=KTCV6Aeb7!w{&@)BP?2oD)H zuyPvETblnqy9K`Y(RHQuSoPVa0+dk6VnmIHC}UbdC>jwo5~hbs(tq2_aylH9X}8|8 z2Eg$wuM547&m)O4=O!}5hd=4N$JTId(|k9{kT|^{w+X0bMi+MGzp=O!MZVbmTzoxf zMtG6R-`>eg`FGMO=z=|!d{_K?=huH9J=N%GRG%|SSjWEfEab^38&8M%`40)>1v@&jaO|uE}WEKP{G0dleae!|&GrB=%dhzs>Zmt9tp) zPU!5=UAb%Z`Pa+7nw!(@FWu|k`@Du5z$ZGEFLpkh`gTi7wF)?Zmc-27*H}uoXB*wD zF0H@+7EFKc-4-dO^3=CaUerxfMrx1N1{;S6fFY*>%^?IxFDMtBGFyy^O}Hd7x3PXw z`YDi`>Our#c=%OT-b<`y7sFz_xlYUs;}I73-qiNZ?V!d%2cwZ{?1Kd=Z&^rB9VyQ^ zdheMGRfb@ff>Ia8McG1Bqp%3UP)uJ}dO#8mWiY5q%z$AzH*+3`5HQ324UkBeKReOj5yvg8^_%Ndbc8BrHbMlN&1x%X3Ko z08kMCC{+N^;~x9GDEh~9q`S`Jb#gB*U?~ES6$QXV1?L!r z75~-Cfd?qdMFP&P_umoS!1*gx*jhHlP)sq%>vt{6QE~$oQ%^vhnK(H~O8?mrY{bMx z$OEmVF_FLp4o0jvJJl?GS`by)U`Qwh^Jb_D=v=@0eP(% zhu{J%ns5r&}v{sb9h(3e&5-0D*Ny}elCuo(UrhZ@RC=3a$u z1AzC3T`OnG5{oOA#nD-1KOWl}ZwVa%EJEl?+Tn>H0C;e$Edk;p7_gI5Y#B36b;`{3 zQD9)?rw)8?!ug)*(|fp+W7L?7-UrhY>`0?%Hr*u`^AB;Qf7A7#sne8Rt@G*HP8mw1 z8KciK?3R38!6A;H;&H}|+vYR8xztRk{d~nT)4pduTxBAv(o2*x!w!QY-e-l6#KqoZ z#DlXEO)`={v7L$eq+@4i9%imYd8CLkB`6)a9L(Xfk(M#2= z#;Qy{k=$yh9DT0bDwE8%Pexk1Ihi-Pu2(vphq+zUxrXz(uBv(TsS$uAA{t`VsO+$? zu{^kUP>5V!+jlFxMocJ_dkS4eB*~;C9G#8jo>Hpa7q7A{O67xeFi{cW z3gj%tcX9_9G8LeyeNV*zKb^Aq6%)6_g1mg$mT!m-D@1&MtA_a$Zer))Tc-lxw!M#B z$E@qXNhJqfJ9R^pnNw@!gu#1`odW|--x4J&)s0ax3VcXEauSo0V4G423Xq6L+#+_W zgVdbsKg4jbWvS3`6F*20r(#xN!lUEuYP-UL);ZE0+#pmtCizF#QMS4g?s6(&wp)Ru z{>Tbqv|{2P@t~W|#By;$^mUwX8d2q3O88B7mtWBbN=W}yA-hC5T_+9*&|vo&;_eQR z!~(?9Q2n`Wu=UeSK>&JKu_Ucn3#^0ZgE#-Xz^Ec=EO6qmL0(Zkx zy1U3ldhXKu{2SA^{XhD5Z*d5W`=^h*PG=PA`C8L6^E$6eqE|ga;0A0>I@<$(4!=eV z_^;nRU2|xupNO%=CI=7`9?+y5>*Z@b>&xd>$No?c0}OJUeOWL4U)bXBr|_B#tc&B4Sw6owlt!7+~6JzQ2;LY&zt%E zKH|9o46O?&-`_n2y9}8ca5Akz820-U-VGHsF>S+!DIA6)B8Q6whegbXe$fv16%W6- zcb8s$2g@6heDRUr{G+;IzufAu!hOHe@u0TLh)&#yUcg6#+K)y7!zOee&3H#GG)67+ zhOOh=)y>~&u8#VpV2LzxxHo=4cV!aFR`CUx4YlRl$9*)K$?u2+GQVQ8Y;4v;r{MTF zvmLU6%vf{q2glAQR5#qMiv$SjnKn1>xS*m1pB)YSpwne2-=&Tgsz zY`R1&*-!h_x^u*M(kp~@DEv!B%aGX={8&rMS2o5UMtxYubV-91w;SY#Ljr=iW1`A4QSIL)ez*sN8bcJeCutAMS(E_o@UL~O8hEHKi$(czlv{Ks z;opiROM$TT9@(2K?m8T0xyEKFYT=Cl zro3(eHjMDOCQ$wv-Y5%*k%<^WRuEVXsNp1rqQ14!!Ex#w`d&;~8;Y+I*Km-%s6!Dh zT#DQ_%7>eh5J`=8*2{#FQ{xTC+&AWI?v1mVYHV0P zm~pL$c5jH}P&fyiAWIlvMCyx6{0thgLgD3)%Y^muex;3X^`q1Z`n)*Asv@Kj^Y?z$-%Kn-63(m?pewR1 zgT5IKk^HCec&OkTv@RWGEJ2)k3wS^8G%nlnP0Kg0f1#^Bwb&jNdpw*8@nDb>fN}%= zg#3)NS4}gkE8CkSEV;(^BX>XuVzIa@5t+0}nO>V(N5qC$Gxna?Nh0E=m+2aPjR}bw zSV+{W+}eFJX=A~TTMC&LAV9hUd=}SRrS>l3w_POh8Gy0ZQq%lW1glK5wyb+-803qv zotzbh6RNREl<-p+fM6Cz=}nYwA*hPN$jD!lL3@8PdIG5rapk4nC4JCz%J89m=!oCh zGL`PcL;?O8er1#1qYK=YT}cv>04|?zx9Sn9T4CTe&J_f)L5W+?Okm8$C1j(pdOQ%X zk-bNpxccF%B{3*6s87dfz3iTQIzZ+Gj#c+ddo(w9^!Ouj{4wu2{+$gT+xg*`E*ijS>NfOuO}Mjqcemf z-xBhS3C}XU&kU5$0Ewsjxo1}lXZM6-4_fC*_U9U7=U~Q;Lxzjn({rcQ6U_b#Z1nkQ z{RM>al78ci(em;M|K%^OOUjL|k6#Bf(S)N+{g<3X7t>EKA{8$b9WFxT#$G==)Jh@x z8&DI|L0srHP~NO;`b6e-3hx6MH2BzCs9}V=fh~-#Ez+DV+MGS^e%puzWG;dKbls1D z8EcL@t6*EisN~aoJBuv=w$&sxIo(6W;)9n`7C{rrTENdn1upvLzl>!9)pBvF?|q@0 zpB0N=8a3mD(DxHIVLEM$#j{B6HnO+C#WgnUb9=7_?-x3I*t=IJuI$+g`CEo3KR=aO zIG0jDJAa*;T$m5|QdjzgV&IDsO9|nt5?tu-(AWMYOy7QM1a0?(j6jdiT(IB0_J?qM z$XtUO{#Ff^hfu!4EADi{2uvXb;T@q%@=1bR0enar3JBevb=XcAIRD1pbP}Qc%cziz zQgV9VaattMrRV{hVQrh4?**&H3}BGulS~NqN;pfSt3`M49C2mQpVzoC0oeHU>cetd zy*`*Kt)DZ`)y1LO;mbHc8XXl?Wc?|G z|CtL7Zz9yNc}yy=i4ep*JZS1Of>P`K1X0OAd>n>VP7}?dm^D_%7}~*r3ba(bL^|qM z@G1;BCtcbu;E?EE>c89Q~ey2er@qTQnu zCkCA{k^$cmH1o$8L%5wm#6Jpse5k%uSi;N*2xG&pw>ic2Su0s9LAS+nOPf}M?Jyr2 zMdaGd`J0M|S6Ht3CY`rqX&#`NvF8U5H>Zq0e}C9#&>Q8tEUfEFAsig3sT3eTgqXQ-_E{u`x*s$dXQwC8LCKV4DEu(VHrAAg@{r6QRzLwa6Ph z({nX41H0)EO4}c*Xmv#}O(b)5(rmIaqcTBCS4bnbN@3%({M|~XU^0NfsAsPg>9pe_O7Co6)GHX6f{Q4#!FEUU<6W7051 zN~2{AB?U?guPpfJG-sisdz}7f)e;^M8Q{2SUstzkNwm;MgDWZ5@N|h}&O-yqqpS-a z^3c)mZ6%ugH%ca#>lgw40igS3-m*pPiG7JMAyfNX;f4b$B_FAL&IECe$UZQNg)6k9 zlGV0x*wc+o-hSHyP<>0!(;RD`k)}1n z=Eun3afH=qOGR|uS>HX5Pg^4fnU!_gh_m-~6At=QB=#^4l~tHbpy@CWe@@&vgP!p5 zIz!@y*QADxSKJEsuS@L0t`o-fs|ZZvsJ&NtjFB%qK7|gM&i8c|^z@I}ysn5M6rv>1 znO4pu@tW0Ewy6A!`I7wCT==ccp7$v`^7uO+i_~8#xBcF~<3`^lG#&!}axsbsN2P`@ zMC}{B+s}@a_8Ua($Ifq+jrDQv7k=yWKdc(l_B|Whc=`9@+h6+oIx|Jf+e=S3nV`9O zO3R>w4bHWof9ysZxBsHYHVz)dV8PR6>LyR9?m$^xfEZUKQJ6N`99MQqI7 zD!Tz`1>&y@zGb2`GjT$8lpV{{cos=V$)0TE z0(9Y$dRG+*=S{d63ijb7={s&9=;E*(5{T^xAf}*JREJyP0q=oH?u{WpUaRmJ>vDDq zv82E;1n$VCmUcAkgeH05uBMgXjt3?9hF$X9K zt-^guC(0NhTy(OXY-hq?UK{3E69Ur-PysP=E0x(@7>OV;E+P%QS7ukSBEfGhr^hm0 zmr0q*3XZdaU{i;yO#AXG(GO&wagUq-vt@Y<9vf~P4D5S&0sH|dh#_ncEm7NF69L<}zIG=3y|qXGorw50@?cYx?EK85&_ znmZ77bW)dAYlo1r1F=4fK6ckp2l)|zh8B(u$ z{IC%rT$S2d?>|?ch>(|m#Kd!M-KNYhE;Zt-7y_MIWq8o5u#=(TUr0!(-~`n?q1epk z!sV?EU(G`!z;auI&h`*xS&Y!nZ)erRpBPR_!0dCccmv~vdX_|r9l+0aFY5);PeUl* z?-?|3uJKq(^nrXzm*;O#0!SS)4RK+3H)F*Xpz(T41#2u`gb6`MN&`b;rZ1#>$k$;3 zOR@Wi<1JBXOzsB*0(}^f11Bo9^paHJG#W{2bepOkHT6Dx0-^aJYut^f2y&bxoWicf zsPu#16}XTg1@3q!UH|3OV$rtF&PPShDO2)R3!z9Hz)f->Ipx6zL6i3VQ@?k7hr=r! znDY=GXxwb%lzF-*d?VzGdpA=R5m|iz}91BT}OgKpVnlu}m`#qgt;^px7=bMx=oA*ET6AIJo=?Hnz z%oBA_gO9nwUcfy*OuT$}mX*a&ZX?0bqs#HCT8P0q7wbMx%@GW73=J@W9LfVAzV*Sz z6+X`K=~K8x&76A1(aZAfhMi1%(Gfy3xFv$v&WqG~l~f1koN!-RxN(Bcr!-AwP|#-* z&FhMNtC%}EX&4Xr-41H6y9;Gpi6@**f~d60g=*M}#mztyE}=;h{BcuJ-Y`NtTP{5& zjIN*22~-t{&4?XR?&4?MrOiN6P`Dca3muICn54zVIv*fmV<+exwFWnntTt@&fU(vBpIt*ci422zhh}_oyhDRQp53>wdxdLYVk24ia8! z)NM=R)llPp$0HniLg1gox4ew8GQ_vG&8Oj?0%LB79r$?c&oj*m!UA>7iuOSVuyU+%!=IQGu4w)rSq-;cB=p<;R9E;uD~V)vV}ub_GgJ zAWcNt`^gct3oxuikO8Avdr+`$xryOU5M(~uqpt8l;8|=EIlFChv?a*dh_56*s^F$hs$be_n0aOox-0-f!n(9 z_~3-tQ_N)WySNevxy}c`XACJFd`+GCPw*DcX-aIoSGC>LWjf?nQ?q(g#9wr%uBL8p zA|AVOEFv?kiZg5mGwk*=9NsgW;WNE7XKMv)>WYz=<7X-3T+ ziYp!dt2lCzX|w>%EFGVo=!vdyo6bP29)F;oV4dDG13hAUN#XP0 z;K{7q2^iXd~XT?Kc?@9V}Bl&nD0xDkH-qLaB=f{h&*0;cB~&JxizY99Nwf;m2&gmXY_XD zl>EBZL!b!5&t`|*poV+g+B8xzO!LaMqMEV@n_klhl@0n&Yu8Z`{^GIqC(&@naBBUf z?Ot;<^vPHjMvnk-t0G{~S}yb+ml=jUo`@vLYE|oQ%{+_-<%V}ln34);&Jhqz5wu(L zv{M=i4bm644J5c9Zr0jcUPG`MVq+aEV@6w6n?}s<+dyxqFt8otvou4{BG-rkS?j`a zp7`d8x1L%@d|P8=KoSmsvf8&RI}AEO@i0B{2uy%86NV6lrApm2pA>z|O`&L+j^hZm z(mJDZ(6polnd$d>iuPQ( zyPy;M*IIizFZXEkU&(xV#o6@=r*04BJ0H+F_dvyd4Rg@QPhRDUJefvxda!t$ykNlS zjq~a@JtZ1afwjLi=$eT%HgH(UPDWg1cojL#8Vgb?O0l}rAJNfsGTJGv|@U8r63rg8++u<6vexKhq z-uazy{JZFAs)!2Ew8-st`BBFK$o;|TVr&rkgG6+y=gSH)jXL= zq@x^ydQ%Nz>_pII+~6G(UpE;S9zz)?ONJvCU%z@Q@^a9ZyzqGSU;a`~uL(>JAv~2$ zbRANHsEPtnYurU82_%_1MUJ`R@?;@nriR=%<+^CXft47ps#%}A7Q>7|fJ!920C;Z( z49ME2O)+&wnZg@QYj=l#|B}~LHEGk>y3N<``9sq#XolWpCuw5`wPUkE?{LL^6{eZu zZ4NZ8KRV|}TmptZHQYC5eZ?ZWO$D|VJ zJ|z6XIa6#qB7caS#3ue)X7o)YdEC+Z2kD4O)qvCmUrBB|p?|Y^ox+l2ghCV+*dk}1orv3n8 zV;>xTJ5EYKNk`m7sC-nx+K%_Vfn@GbLIP_ zLspRXHSWFhc!^AB;LtKfW>@*;YC>$jk2O{Ocj||58l2J}&TB@$sFI4JMglJQo`i(^ zx_o}gHTlsci*hNr$gL#`%KPPIsMGpXOdZG?{mf8 zWt`dN7uur=nG5?R3$*=;EZ^l(zFIQ4PBpR?_+y{mp;zg!7uK*J{j^V)Z*;wCueKfJ zucCNOg$*IjKGpHtLmpVxX~sH6TJtU3)Odez3qJzL5h&g~SRwI*{R1gt{0|Utx8vjbP$F~GQ=cBWXUkAly&DWw2*{?3Kjwi;Owc4 z%SmUUiu7m%wv7>Z*j^21M5&&gen-<~mzm_YZ7tROJ2p942s(+m-uy$)m+GyWN9~Vq z_RtZ-Gf1<1Nz-Z9WHu?1JR=@|f*TbKj13^U9+8tqqF>eO{KrZV4B+^$m0%=_M$F^* z;KKH!PYVp0Gp}XUWP&GOm+Giy^&u0enf_!kp_C&9wY)oPr^hLhMCbsC9gSb1%(?{#BG6(!rxEZmfo@$hu;h67pW3#S%wa zrf3*MUGQ)AJz5DO784%xPr)NSUu(gzR~q5M+Tr$Z47|6trg9CCE|~`2A%vDv&=y z3~Aa&)#2*;mkvheIR65RPGYELUEEh?G?1G$(~pC5s)iI8|so-mp@f)-3X@IUzw z;zD*LAPjVTt}cu=G)AoIQ@t8z&IwPS#e?Q(j9NrLMOI8n6a|b=OOz)GPlyQx>F+#R z&O2zVr$N>jKqyvmqUQY)M-n+Ufj||oQ@PP2~=wNa*hK6Fi?MpUud_5OhIU=sw)J_Wir% zA@0@O^*!rHpZe{%sC{SPkp=nopk2 zCoVnC%K%UX1(Ic{4vEnID%un&fGC_Bye3a-4v zx9?adn)t_`{BXI}p5L%6c0nF1!AwCZQAaC;B+ukWFS$eZyV--M$DRe zqONRClF3<hqi`20NgIkooNnC2J=|BKHMklOR&11P1dR_$%7wBqu|>l&>H>Se>23 z6_nGo>6O5l_9Gj}hX#${J`^GLP1Kk|RJLTeleu;TVnmBzF#KJS?&`H+PLe?;fLv2e z#%igi#8xy>tS{%zTB3=X-YVtqA`JB_?eIPlkQf$o1c{w9Jq^_$CAT0${bmK`RjSq4 zX?r$f#J*%ok-GiU)a>Be3&w(rCsR4TEg4{r_qQtYq?d02hIPccv!U`duN;Xq1CZFb z=U^i|Vi06eF>>qm%C+%3QYNMIK$M&|2YE3=PJFpn=rdZC6bCvi$02FMdu1MwKmi(N z_mkG>J`(vyC~q}@D4wL-?6IWUz4`{5?2kaQ{G$&+DX>|QKZXQ)zDPn+KJ!#)3c(il zyUY5qzrF(?1h#t;%Ldp|LE*!Hm=uA;Iman)y3JK#;-q>%hU0nW@vdGMIl8<@` zTY8kFb_{<~^&CIf8?GJ=wVC80eIwp*F3s05;!^7|i4tF`w5+{nGk_wMjwwzmvCjH# z7r)ayC98BZP3cShgsXKS)_T7p)zVF`s>2=&Z{0r)bYD(J->UyqALE{@-9|gtz%^@3 z6F)C-F6cjQd6Jj~ni_Sk8_3J=a$DYYHTM*>RU^q=tRHR{Vp$C9%RX9~6?QR4+_N&$ z_{PJIlRrc*wKdV`96x{rs-_L~nS~l-qNr_!4!Ic% zey~pTCPuJFK3TOM^I4C~3Mm+UPLdCsev~nF4E<~_4AR_Rn2LA@Kqub;J*_+tnD303 zhRd?vdM`Z5-=q{ux`?Y^drXqh4V1PPDEcjiHI5ZJh9IhYL5`9pI@i8D{b(%OW1cyTkY zUu}^j@ur_xw&@HsU?&SLeT=@nP5TgGzALK4bgNP<_*%CjwYG#j?$RTukjF;Q?IUdY z;Eiy*Jmjg4Jmn(-&LJk;49fl++R)wyl?i(^M`ry07&F z^${}*2@7K8CIzv8R}=+`uu1VqnP8yfFo9Yr$wgwx`AWeBwCKQ}$-_ntn$`C|e6o5i zTs`mfR)2mK?d~U7Iw194yGQ^T3*NqQ52dI(?-TQ1rJoCY-Mta*kR>~^;wzf%%rfXv2hi9V&LtNI&Oo?^6-f%8> z4ffHo*!h^ZiiLQWcf8orHi`;n9C8WOct=L4DC=5(Jw|w5KAOfOIne!()F5g1(}fZQ zB_sy9#@S%0k3CMlhZfeFrR+<|NS4M(EvqW#TCl<|q3DbU2=H1QY}ntu<5vF{2p$|) ze0gVaok9d$^`KY(_5x^=h`^)+{gz|A9dmkWF{Tq}d?S7@Gk)QRMOSv+pH9DiCgs<9 zXTr={dEfT5fmpWjfiZW3P6QCwQ1_SovNKNN=hD2Cta}ORTtXhXFma5VezC6G1N&+8 zgQs8c$9}*c|DWVGy_k)glr)_-(xpfox&e^>P|e!Q`!B2C4h?(53nuv(*W5QO-dnDy zSeeT*ts0|Dbx_NWo5t2w*c_HUZ#S2GQAS8B+!&_r6Mt<8%N| zBJ9KEYGoVm<(bqX3>S$Bdp8DMY-r2PJ6%?jMv6?uKk^d{Jjxg_97p7nrZKWecur$^ zq(9O-Tk^$_V*DtE{@Dhokg{W!bQ7?04sFwNYtgwf@IPR3;eF)#ip2&Qc-osq(_SpB z#w_Bu{Xm9MsVJ{*EXx`+%&N3~9T>`$ghsGS#7Mr((ab9vYAWN*E7|;D_OWDPFjwT* zR0*+D)!J0e$m1Z~QB%`Y`8lUNW2gR-iSn5_eb=h`kh$9EhD;!)4n>&`1G_FqneJ0| zJ<&2fX?A_3GJP#}1H&?dm+XcdWzR!QL>A29+v^$F zFjSzy=WAk?`?kQ>2j@3IdJ{d|xzi2U|4M zUfEyzlA{Wi8{i7IK`t@VrU>VzbhRu+eUEr`@W+K7TNyaTzG_+u?-`okl{fetxSW#kar&kTl@o2 z!N@|`u*4~R5#D(xJ!BDVBzX@QT|qgjmR#ybBmh-KF5KRQp`K_&wLj{ zj`Q^ZrvU)VKaeE>LRVl*Xl}1>TlpT0tRE6o@wgJpU4T!54t_jPeM6hd|001sMC#{> zv|NAwPyz}}i_$#Ywsj>$;n;(@@@_+i`?hO!uuLGXTX^yVs5u)Mmcs&MV(qtBpd z>b#f*foN+G699()6OwPx3n+m`oTD@?+qa@{dmF2M3s#+-ldw zg2D$gKTB2iOJ^bH%3|l!THMOY=ewX<1q!$`Cc`;4JGT~Lt;z>y?4Wbp6DQJ-`!j~M z)8@`fcIW%bGRUDlsKd z$mj!Js!BheN#g?GtGQw5KpVqX-zxQPHF)|UCFIvjJk`Cx?@3`lA-^e#GL?Alglq58 z$lvg)xE4WT&$hh8jVSO-@b`!I>@J{hH7R*jn5lYfP=d0jf;Rpa~C$(@RTr?@{$dHKi8jKSfkD^#!6t zTm-W8mPKN`>j^|X>J#bd1>PoWf}DC~av^)N;Gg#K>>-DdRTsP}R9FL4@P1rdO*s$C z5BLBtz?!sHKX5^A&4Pe#YcPfX6!1z$-qBU8p2jz-?!h_)g>0L=Nbv&m0$q6%0PeA0 zwEMYnTog9z6LI~M8vT!b2)E6yG#v~-(1Fe@T6UBM6v0r<&p)X%K-|wGFA9z#fpK9` za8{^L!~{tl)KK!9Kc{Xs-vR_uD=Xp@YsHf^m719cr*uq#YdGH@Jd*WEL}-Hifto*{%&*8MoV=tvN=gBCL#}pV{Km^g2yl5 z^qy9Foi;xFPK|PH^0Snq*0S?7W4ImDG)Ue6$jkwg9Kg2pF$_g7y%WyAmBQ<9%>NTx zEmrt~yCPrZix%On86BjGR)aw@spoJ%7^MJ~L~u>2B`Zi2$njdxsrp8)t(eWWp8|VZ zZm%ER9|O_JK(`av+b;fWh-G&{)-s*Uy9;IH0v`&d6{!f6NY6Rc73@n|n&%y!&p=oJ z6i7vj#MpBi6RtfzB)@!0I%7ic6m$=Qq0>Te-e^lG9k+*=U{+mi67m>V)^GOIDz?{7 zDg9CyJwIirQ>TFQD1$DDz3vFE&;9s+og{JVd;gG9EU-yte%Dm;&V}Aqy3j7z+F^Ik zf$^>5yB()&CcBGsM;>8&G$*S%;WtaIhUcxS2d&+aUQ+Lw1;$$~cbToWVy!KStf$pH zU*OujAIP*rzM){?s*&~Jne-lx-D-psz76I0G+^h6jJO3X6FrL3Ov_)t_;UPKplgsR z_crtJmIDhmT6tRld`5j}KbVDr-3K`r5WYPw-*P7}N#k2J%^LD-{Ea%mi%NVj45cJc z@>VDmFGAL{O-c>fHAD1+1EeSeWZu?7drI$eYvH@uLKg!Af!`H#`UJ1rnn+xd;XuIP z6x!hgKYMuFZ32q3fzOyZ9TsnM5c54i@CRKy>=*h9DSk%aJ}ZzM-|UDN?+@>fUfx~$ z73@)_*elKn1~?w2gJw&x}VRV_wGneJTFrs*vmbLAE_K zMN0<#S~{ga>Urq4H-YBniW)9UBaMvdzX>&n{JgoYnxwxMLSrRoeYTguV>!gjWm zceL6SX1|7cgci3feJts~_*NCfJfepWgB~=X)>mX7bjYYB$^2}X;BAk+{=&VP#EmMS z)Bq0+wVEZFOpqzQ&Xqy#_zX=@=m&6)@*sl(4^DHcP;`vtI5_^mszZx^{4fz!bQTRk zF&OI*aA<2qaU(+Rl#1e}ft3;3_oe&&A!*y{Ky|zf51PmLp*w4OhSN+xu1ls#NyUaH>O;VyZ#Ce`|_ul-&gR6 zctB)=ph`Gl9tzb;IZ8{#^C>SjOeAnVcJj8{MNvh9v#T!idq3VrKNJmHmb^;E8uw}1 zfp<%P;N?=WF<9s&=);^U&wOy-M}H|-s+DB9(Ue!p;*u&;OM=sDp-a>Fr^~lVnj%kB zDn*3}3=phO8FcmPaEv)!rtJ~jBoC=~{@uQnZS-h2D<|;o(8!`Aqf6d51mDJ=#rDbmY zW-w(z{3F}k)lML;8ThX(efcgJg}QrEw1@ z#d56uY8hKg=IhS1nuRczoh~P1lCw9R-d6{y_QX|swYy)>tMoNF4Zmr)(CEDsakgxW zmS72cnR#u|W%$MGnZk?HdNdW9?D3MK@DL~}Tc-EzH~T?%AR4Kuq#wWCT(jF@nEz^> zO>Yj)Daq8*ucMve#Pt1K@=N``++@XYqwftKr?ctZo1A|hJx^jg!1bUPCBKz%DF*0t6^`t3Sz-;Kf>>;ZgTB7(kqEl2G-Vqn5-KLVB4q|Ha+`4wLg z*>56xn2vJbN(>(1=rF>B_S7KDvS>s&_9w>QU8S<*Ymy|Z<-)1JFHnPdZh7OAX1=>e zb)#z2wOw!s5~tD+%505J&1CYQ^zY@-LX{XN30r~*lv|R>pve|8tI)vaE{}S(C$GzS z1tB!9aT<^s{b0nGw{L~3?wuORqytT;G=b0rG#UG;q+<8cBjWE2}E;!>bZ1vC)%%?UaSGG{$# z(n%Hy1>!*VF&HA@PLB(_&O1vl*$fDN~FrIV6GE_o?o^~9p^VQa)Pvzu=u~Fr-8Chmt5c+&4 zq5J)OLtKNlU2Rw^-FKceXV!cst$RhpLBeBl zt6DDA)fR>vbU(;B=L#^moC_nqS`}ePYv)~@WT=0n1qj&5Bli^|_1~jX;npJw1`q^U za9YdOlJgY;VYUh{kmpQC8{wQ#0&B(+* z5%_$z3aSc9|0X18Q+ldPk#-cw+2ru!P~*(qf~zvW*42!*IZhq$zf>+Lq07 zxj&Dj~&bRd8qKK=4`UgaH$He5C5Caera9jkMU;nQ6`;#sp(Z-oWM4+;$V+;gG?7{ z0gPJ7g`L8O=5<&&6~<`zum8ka8=92Y}e`Z6P>jg zgPTuQPV$aAbQ}68-~^azujLG(#CE&(FKgZDd2t^5T=T{fLwqslxhlvTV$=xY;tD1h zp1;~tK?5hXCK$kWmQZtwK8i z6O?i`fg}Wavri)5Pjq-PB5D5=D{2oKhDRursar%%fAj8^Pa8|jy$&URvP>l}rAgs< z8#>{J;Z0iFFj_gvFEDCc^AW93L$sy3$LPCmm^9Q5Konn@;e1hO2YN z`z_RhyV?m-Kptz12&Pv9hweE6%2&qIf_y1^s#x`1721)HzZ>Jlq)-+2SE;q#KkJbz zR-^1s&z?s}{d}DWItpUNgn1;4Zk%$2esK9^i;{Kt3Rla~XyGwKo{A&(>bZxd)j`{| z_!GfyoB^L}nUMnR9wbzxjZES(VAXVQEu~N&Z02f-gHDH5ccrQyAlE-o zXlo|+Q9V}u6C<3c;C}Eh$T?jsKMU|61HrL(OthvpN1mGxLE$v7bJHH*Wh&5LY;+7n zS||u1N_wzC(lqlWmF%*EJ0Ea0ng4Bky<>&)&6GW615@3H&yV$a->@!6$EEf5h1av8 zL;m)H;_^)cPsx$`^W)~9ztBI#XlE9h{+M{OlnZT6c&oj*9>}!6NA>^ee%yI`n0?gv zR_voq&8L7Q>|&v5cs4?UtzAZUGxUQ|^(OF)r(zzz_P^_Xb?ZiKZyWk2cn4dmXZr6s z9{Um$et*FOfE~?2IWGhMB0D~(T+oc|Ja5N~`Gg&bT5@(+P;@{kUS1+Wd6DH^k(KL_ z)i05?c~Rh!^<=e0D8;=f7MCd2^(dd{7aT89-FVSFUD15&(E={f0(sFx@zFhdiHxA6 zZ+qFXdH-St9lgY&9x$i7kNxcudvFGO z9v^48p0X&AB8vxi#}#+K9{13dERGk?6N>lXiuYWP2fV^V;lsyp!^dgBC%D2V<|81u zI{X;-w3z_UK%&oaMVR$DOJ*oLdZ`5yUR2Dy5NUWJfAvCPD=ZNUWVm z{H>Qd%c8xFp5h}tNuC{vvo`4`XHvw{pdTN`CvCFeL{iU0viMoDKtA$tZSq7n^1>_f z(phqNMFS#uj37Vqsg{8d4j^g zcB3-YqP*gxy>p{|XrO(*q6PBPp}5mw%ndf<3H(T;2kkcYFg(sdsfM+whTIrN6B&wU z7?>LwgsvGx_!-5n8729d_y`#X>8Y0rsWkYRwcVLtH!|yAQ)2V8Si7^>HL^Hev$*lI zdb+dvHnO(3!13K_DBsc&YVnd0a-uV`^)s-MCh*P$vgJ3j6}z(+&aqYLu-6cBl=8DT zC2@SYX0NzrTP~y?Y2=)|=A2F991f&dY2@0t=Gx)s-goCdZsb0@=DyKpL}0f|5dsvn6Q3jM+^0xt*OzZ`9P zIeGJPR^a6gly;>_a^psFCs}eikb}Qi>i9;g{e|?Ifb_$A>1SPOV6!yRtu#tA1mhNh zCJ23KqN(T?eo+se7m#80kYV$n2f`$IUeWS<$nrJI3K7Yk^2y12$SF6=so%=+X$#7~ z_K>qH!Ju7$THMN83o6)oC^$7MxZNsvE3nSD0|k{r5p1DbIci3_|42w<10?}) zfh>q1w0}61$UV;guN+D;dIich``MQN;!q-z`N6sf(rIJqcsj4;SWYA%{baW2`;+gZ zO=X&yNb?v^%FX{u5$h8i!GX>FD@AOKutySb8m6-`)>5^c3%G8fyl<&qZL*bJQyXus zF)ne~LZpa^*nICmq==#Tde?1r^JKe;uchx-1QJ%yfF|ybD+9|Qwf1^Cu4BU>#%|8) z>y$9U_u{kUKy$a}I5xA6ki&8|ULU7U-))PHgrHv~5E4i*)(v$1 z0EEb<$G^!5%IQUExr?8q3w3NYcmYr^FfxH-d9EC@UDL=&BkgOk7h$Y}tousL)dX|6>Y@S+fh!mQMu0Ou z!$wN1^_L9?1Dhag0oBjLn;%x}mIyOhF|_-<2q%_z!s*Y^AfB`hfiOGg2y^68A7Pty zmxKkMSk|f;nar3;D~7pT+JLOw^6(S5aJXvtw|YfZgsX-Yb`Rw3ds{Q>t5TaYjpfV~m?mb^U(_H4n4BvF*9&d$zBl0D^9;E|lb{3aI+YUVJtN zR$|Dw4AB|0*4YElL2e^p0pV*1jbhIAxW|KPBZQ?B`)1N{G+Jw1^mk5EzeN7en;Em3 zuQ$VTtdgm-(Dmxuc?InO5Rm+})7_%MyPCVDH;N&}GLi_^tb$3p)2~(AvYKBqujvo*|9cCD#@!}^7{uJ~MC4QB|NjW#;{lZd@Bl=J>*E5b z1ClA#oA-)=aM-CLTnlADIMleD340K^aAW{_P=;2KLL3d6^H;E&)o>D>Y!>!IVo850 z!ljpE7zru;gaU7I=x<_;0*RoV$U*;|5lFYehYhRs0vJ>$Dk1QoW%-AW+f2i41tzc}<7vt9bcB)Dh%Sf^9&rZo(zKbf^yDk0&-9+x-|Uh^+2axXC! zp`X)$6M^+$kcLndfpfPku>@C(V2W>lWnH%+fm6AmU|R~$?e0$)n#C;T7p9&5@tw(KBc+H-CRtX zrPNa1MR$1a5So1YBaHR(p6R>7Od`K`RwaUM{a1zzB$~!TDmXJSNS##y+TP>g~rD)7auiIy`x1k+(du_O2cAWVT zP>*DlE3_3LKAW=^JdkU}cl&4#zSJV%iw9!~`EQFg9Gr}CE&>Re)C0~ zlS1bFCO}}u5k665q*rtIjs_S?9unja!T=gcAV2Zrhx*Qbp~Oi2ssGv+Ewhglwu6x} zE&1Cv=&b@><-)oPa%z|i9kO!9cfi`dFvJ3Ru&!{C|i^!Hh|C;|y2oaaJBL3OJKo-k&a;N!AMGUf{t}Hz_If0r^OYnU&pbvksE4dzBBql1Q3d8 zBmqP`7ptc&VE41SrvUNMMT!CSL!kgV^TP~9`4>^%AokBjVgARKm|%ZuCbm*GNt7NS zlYc`tBo1mlC<@Xh8M6jJ;0TD`Bh>yhBgHOx2Ay~F#KAU2sEt{~DFOcwK?5ePhAPu( z88*)ReP!IdU@GF&vcm8hkuK_mfN*Sv{o!)@kv9I~%faXD7ab?9sxLZKJ2)<6mlqr^ zzTF>GAuPf_R8_b zn#2i67-7oo@mCReLHkb^URH~qV{A^1ml~{|o0!(c%DqE$;Yr>_mE;1blp2zjGI&?c zq;>6|F5G3z0e1>S>3@=(LZdiJ##<%0sJZ{4m+$T}mkTK9*~6rR_%aLZ^iiMwl@f9Rfu=5~w?oTe%nhldyr^ zzr;}s9S93Bq7SUnTD(E7Z%RbCEzoe0yTSEpMe*5 z0AX;*Wvv^gt`c^LbG5#JFnP~#=nu2WjQr7*UqO-{Z}9*#v?3Tip}J~C$RGD?sxfy` z!&F$#-g%16E?*Nw%enq|x6pL(Ll3HGHik#IIj}Fp@>>1D(3N=e4eKl90w6~QP9EA! z50%RHsDlU8{f)tfG(lnnbjAYDuN;X9m!c_({r0nHfWe%`EQY4*Tf&^nkd4Z;`63EW z&+p%l*+x(WvuW-sv|=k?m8mRNBG&5+CM)&6j4jrFxT``0I2sJAEHzSa{-<8n2Q>mH z0F?iwmk}s>ir#-v^ldf?h0suZQf|i)-XfWBG7clAjD})`Xqvj&6LfyY8k^;;+yV3k z#xeg>omvkcK1+cB`KUb!Px}Me5EhqT8CP}l?0Kwqz|xWW)rwD?22ZMj6)&cBCtuN6 zv0opf6Tz>KIa(`M*#QQrBsm##$I^rp*YeN$Yzv}Q2>-s7QuLuT7D*V8<~KCB%U`j9 z#KY(MRoN#@1`Y^a`&^8Fjt{r<2lb!Q$Y{7k=mCI0e3VB_>W!~Dksx(U)DcuVKL8Nx zk_P*`r(Rv_ryxiywH5trlH!{T9Z&+ z?50LVKUI~X%cnrEWBUCoU;9N-&>0$2ezSO?ZgoR~`3CSaw(hX|BxGP-`;5vf-r!*% z37u83qBKFb%1UR1&f{l;B6Wh+if$0*>|BOro-02FYuVHHwn}EI` z0-GuRSLM;S|EWBhsX*I|xl9Ol-K_on<+hB`WUWyDQT>m;n1?01y|&98#edg1%1Sw! z?C$PgbxtE=_2MkQT978;{L7rR5V>DrLn&c;D+U_u_J543vG;n>qYy}&{lHX6PdoD| zRVJ3hK2;A2Xg2uUWNtyW?nWzkG_EgH46Vui%eGPl0B)%WT)Tl>F}aii;P_OcTIN?k zWEr9(4v(AN76wc~HnN`f8vuypmYJQMuQ&@tf=RdMShzu_8CsJ!7GCrghN!A5d-be+ zH2?z%S&8t>)56F`zeW|5Q*a3gG>VP;=0*&2L(6lZ-Tt8SI?$-*ui9LJWDiic|D?zH zvsTre=lcujBq;|$S+)&YmShX2b`kX7W&OTP=!a}Epu_s_vJ!c|`iMdJnOIeXJXBkr z%InBN3h4n2=a*68;FGRmj*&72ZYH~ zVPbL?L6U=j35-Z|0EijG7tBSXdMyCG)jy{-ij9{RGX`K2Q`KKlFlo~OGFKy1_fRw9 zU1m;<<%JaJr8~?Swb*|LZXzLJqf1s)=w?iQv_psFKK@h9n7GHMeeves2?qkvsS)$4 z-}tg~jY7e*bmD%$76Pqk9--Mzl5SZ8qsi-E(E>_olzASkW}y!S!U<9!l>a!Wrl1-C z34rOp4=Ng-yF8i+nnNWE;~(53qdKHPVR$3PF}#{U>mds z?ZlBJTKJ4<7S)%e&Es`DQWsyg)UIGPDs&5guvR&uEjw@SFQ|B;#d;%#7u!qv zh~Y)jq61c~NDm8os%t((LU4Hkkhk5sBSi9{G?02&Xb^-2VB%M1UC*6Uv&T3n*@Wgm zykdjPG6=VXQmMRO`>@{qT8A=MLuU|U3|%vi1B@Oz#2BNI{Ed2HkDk;c(fsQ`VTO!d zwe}r_wBXG96}8Bt9j~k`W(;kRJ>kp+0;?hcFk$ z$XaH;+axluN|5_!zA>?HE|5v&u?|Vk`h2XU{_n|#Io10CAe=YoRg#6TN)_zU7g9DQ zWc`oi6#uv6%#_konP(>%3NsERXgwxn8l@QE1WaWyWdkVWgz&93*r0kY9#e$a~YsZ20$Gsbs_04K8?kR@XZ6IB4DO%v1H$KV6pe||PEJO4Rr+s&IkFG#$Z z6#1^zk0Y`b(}a7`&RX|$Jt1=ObTgyK^L)FYfAJE~VCnMw>$~^G^Y86&p02YsM7G5J zQCZ#J$Dd#Fzx}x!=7B%mZhidte1BL6|2w;-*$IEbDgdA<^?)cYVd!cFAkvW@RPoC| z{1j6$1|{foY%wu1r=p(VWhe%V3kh<00eYu?Otq`5*H*#M*^oulcN|NSM#>@HC6PGX zf&#++8DvbN-%vo@W%y+Aj5z$Z0T)nyNjnQ3!sHX}BpKs&d+|mQeB02)RIp9cXsk$} z0Y)d9zy%cF4`ZVz5R(9|VkV5yz~y9}p3S?opAB$Y|7yqBnmbT)KGB{H0RaISZ>df@ z(HxvvBX11pV$RS}fJ-|Hg5vz%v#`RmBycBTEu$Cd0^=E&N%>AY7{J9KM~89z-K9iPLK3lQkhMVU&srxVLt@EewcgxNHg<-yy(ykzU9(Pa78rb)*e-C>$f@RmyzmB8EC2)t&!po2Lt|&%RofyQEX!j{wf`yk{zvx3|M?3K zzzBfC|3u5cXH*e1zy-AbZ{0s>Zy*^={D@TF$UL6TZP}N@*2FTIE&RUkHe7RCIv)aj zBBJmppDj`vPkl4iTrpp!CH6T7lkanPl|hsJzvkR^=Dl4{zmuzH8f~X5g~o-Qi~%ae zh&lHcXXQ#Cf>!MDFNisJz*Qo(T3h{2f5d2Ow8NK%-C_3|6&sAU#{KbtX_n22%*(eU z1+vkoVwvm*^X0mAKPNui9M0BT26N}?w4AMfylL4qxo`Qo)x$WS7SSns@HTELXKmSK zOFy6=*BWf;Uo;z?--t;VBW5hy=T;4+G=@e^q(5-(Oqe(O^r)LX-}`3d`Y3cPsQ8iWl}EmtTb@p#i6E5~m_qTY zc!#HAZ?Bt5C}^K)J9kJe^(O>Ac2pUa`r)L8v9yxCMp}F{myu@VF+ILYChD|-{CJI~ z&fw9Asy=;Etg>N*p{iPQqLJO!e#TURqh;~OhZ>vJzZuiXt)32<&2d4L=M}qYr>xS) zA7F;X^J|aiomXS0hplN94u%bn=f+Omf9F=&Z9w`8pYzWtVp#hy(le*+aFB+$a|u)@ zm_D1QDNBlMQ-T+IHdPx@W#7vt{Z(wHiM3G>mM^-}8P>K~J$*<`Fh ziQkSFb^|gwjWdaaCW0%NAX;4s&3=Qd0Ahs%0x*8q^P`V8P;I z2@hERWa+hJny@v;L`yv~4?=!1R9Akp5%od~6-I z@SX=@0hUFWe%>$ZsHQXT12(BC-?zxkItEAjhPWg%WyQFKCsniu?H2Xpe~HgpRipgQ zLy0fNF5dk)C%){}{3>?!mB1YHZ5YQE@y+C?rnQnVz@vfKShmJPAVx_j%@r1c$La)C6F{sCV7s)O3%HsQ9y#?u}Cl`7YG88PW zdfY%jwZE~l`56hg5Vhkl5Sj6i^!P?W2;ufg?su)(EP$@2{*(IZXK=hD;H3T|Gm>9Sz~PQ6~66%OL?TEyHjzCA#k;!#pK>!9G9+P+1W~ z!iy~yY1NW;J))+McOWpyj+mS+HA#lTom-PLf`RmScz_c*$yaQL>C{Jg)hdZD`%Q^W zT56hgB36oS;w~LWd#{evK}zHv6XmlvzE*1}Z(`05D!7b_aU5zoNZFd!*ACx|0wuwx zeFcnRsA{=}k_pgaq0O|bz!S$$^CdXo)PAe%P=uK{y0A~$rcBh*HkVtvy1@IaqxPN< zBNspx%Dr3h~8l8$dQp+P$LG|CO+yCq+#PU1iFiLXaIjphT8( zOifWGtcaEEQ2u?WCRiA`15{Rfj9lL$;c;l>%=eWBJ|Yq(Tq4RcaXZ9TPy-=*C~Dupdnpmx{zOr zY#@yiUMz=T=#2!0>JMC^E)aEu^vf#$)c&2d(uYP44jYs)yLI!C4BX%2xLP+H++6vF zF?;1}nYEeqOcu=@W+QxusS(k7oi9joDKqe3TuOK~dW_M?KG;dAr_$DhnRpP_U1U)g zNDhzHUSdowjmZeldYE7(LC@03Ng$qnH4zZ0l@G|MFdq5!* z`3A9K73o}R0DTI20KRNIQFbTsHP)vQyI`Nkq7^DdumT^ZR`cWHAbv4jXUyg!H*=3< z%G-*TmA#`MM)PPTLdi}zrt?2CI&}8jy%kj*|9n^}U0)M|YBl6|$G595yrXrioTEW+IkR&%;ld78owBP7Hoj09ymI|9wcW9r z%jqqRztLnW?24hG8w_`;#902-M?R$+Zg#o}nD*BC9lQRq34NzmuPvR;)XfF%GC&KD zG3;>@b&Y~QHiMxL5V|-Qi_`2qX2rL`<)HV^zuqta2ix?2a8bwg3m?3c{>A!D?1y*X z_W=O@XTb(wzl;Z9!q1i@K`}eHj3hoGSJ61)?n6 z)TDnkk)<_^&+npxE5x3APv?Cb{w`UqKY5)QPq4*Na>hv^@#T%?Xr%6^Z&R8WaA`VP z|7@41KZ|&9xgj{mdGh-5NqEGs=wdXy1nK_z!&bEAy!5LrxL_F_4L69jPe$!w*-_P% zOP$elHpK4>AM+G1e$O2CNvqR>2)8*1@RmV4Gl^vlAL&X9-$f^I(>rj?ok_Xzy%aJS z_nre?2mcXbAmafWKn2kIaET@bi?;>8Bx8K%ivN;7m}JCXvIH%z08J?)M5Qf6%{D{> z!Jb_S(Gd-Ol@X#y#t>ErMkIe7O9ys}S7k#pP)mK3RbFt^jSB&b=k<<8~QG;C*NhTrr4oIrQC1 zM9O_w;(Y+jATmoy^W(j3a$6*VFq-RQmroY;*}zk_%`ej@s`~yzMa286m8ddD@ojTl)Ml6jAF@^aP4Gg&ty@%kICLK zFISTZy(3vN5@Zai#3K_Gd{e$8rmQk2tBIM_DY-+*Q&B}zxzSSfd{bS^qm0^9GeT3b z^;69wKfP6UwK7a|Y)!iSl48%6YJw4M_K;TOllC($&4bO=tK!r9%=8Zx+PPkdn#XA$ z$QT$YJZuEwHVN*Q$(+ zJ`*l!Zrs0{&!S%JkaPET9DL;4rJ&w7k_U#?&aUORv*%rOc#a#Te6K9oepKCFi|MZ5 zum6zZGgSBdp>9314k}*n&uV&%S$$)l=eOzz%r5V=&qa?e?#_~Yv0hHBiU88)6Bq|i zKE{);zoay-pr*`cG>&KXi{luN<8nyg!HVPa^Avz*#|ruRh)~9gQTj<##Yp;PNlU~W z2Gc2!sBT%=s*_BS?N!^rHz0swL#6f@2BY(ujLTNx*+uK)4IA0{TkB;z#wz2+mB)T%%f^wT z9DJwim1lp-cB32jDGLs(svfcnf2+1UQx?IoS^<0Q-V!|%>#Aca3I5SNKdX9BIXj1q zYnJUZa~JC={&f8^uy}K#P3G80@jasUSk3=;FZXE=?Wl)mr#1^`6Sz8&{mHv*u}}C< z-wX175xu_B@wbK|y)9Aw)!K15U%Rv0IlT4W305}=|E#`~C|6Z$RQE4OJbGz=E!V|v zdi_*RPZe?Hz-CVnufE1w`7tep{^@036Kme|*Tu9pOr&S$d}*m3 z_%hMj{?qDMVEiU=jK;0Br(sNus)s{hqVwmMZ)&6?{#parNwYD^Q%~cr;}b%d9l_Og z%T(m6Pm@sliL9H6CbB7w=&AAYDIMd&@}t#Wb)Wl+v$|4>aZ&GN-NzoGx#!Z#C-Uolf7rnM~BxNcK8ScGrAsdG7gA)7>81HO$pD zs@^?a)72oE5((~&G^vTlSsSFD?uSoGZ%xYIS*k>4Pn&|;ouJAoevRc z3~xzJt)5kpo==i%jJ)6;Ce|3GJs(Cfonv$!nVjtY{jH2XZj{w|lxu2~r*@QYYFyxa zl#M%&@?(cs+{ineJm$PPk@GQ0+%f6RJR#gkS?6(i+;PR)5h%^P>iM{O+_)z9m^Sxn ziNwHb+zEZ|aYOF4WT$3o+$r7jCJXNMs^|?XoTYc%&34WkO_K-XhD+;{Ysz_3F15{W zxRJ~1YbITj93Q8BH>do%rb6L))8T)o0&vsKYR4mKW`d<=5>024Kh9X2PDyNbq;Afn z|DDOqn~Cn4j&+Xwq;cR>yHfmjI(Kuns%xfZb1qwI$@BamUuw3HX7C)cjLZu#trWB+4)M7#^S8=;_1iv2j^9I*ZkG_ z((kEd6qn_TxMd8Ul)#uOVBIQ`%h=ty(Vug3VwW_m>2-pO)faVRljlce+#}Cji?lr4 z^e!jUaVIUcCzN>KsV~-8;nPbv7t3gPD?&UgBAP2=E-Mo8D;2dHSlBghOt0<5Z`oV> zv0ZMPGT$ki-9q90;zm}SY73&%w^gIPx);4F(^rxgNlagTcQXcTX>M+Ts;#UER(OXt z(zH{rEG@V0c;RvjdE zSNtwE;^S8nc{Zct*JCes8S$=ycs6)w?=xNYvg4Oh>o)W8Hga7y%QQF0r!AAGS4&;C ztKv5sTz)i7Z&qAve!<&n=h;f1{$6;oSB$sU!n4;oz4gsyt6Ot(v~Ft{Z+r4$Yg%)A zkY|5BetVW@8_8~96|Qn9Vt;s#(z}6orhTy!PF}mWbxrj1_p3E6-}s}|{8-|H8mha$ zF{aNI(7qfrotdFF-p z2&I9mX1i>16jP-er`b>}wdOPB!-@G=Dy<4h+x?Zz)Ca!*hq$*0iz9y2blZT&8+UgN zM1bJ#?(S|uLZczLLm)W8-92~+ZXvh@0zrbiJ3aj6o^$5hnZ;e)nN9C%RaIZt^ZVpg zXd>sou=$y#l;eN*w%&FwST0qP!r)4A_9MdJkN>^TmGcUl!+k%6Va3r(zc<2|&G4=2 z`ao8JbT$0;efG~BlRxCktR6=MRTe1YI=-_(LEU@AbC zvXC1i#M7>u{75ipU8g{(pOco=RA*mRrM&2vwUn|HUHb^Hap}JtTXORiAX<(bG9s#i zFAJ$GdpUhbS;=1g)?Dd*=@IeAzvzz0Gz>yXTpomZg|ZfsrN3YrPPO&IBy2hvLn}(C z;bz_QaCzYumk=e<-*}2>;?+nM##)QdH2UeAsp@mQdA+#juaweB3et@wT%|FT+uyJJ zGE6mr?`$gxpe?e+Mi(F37Lus9A$H$J`jGC;6lU{t%2n{pXjFM``PdgJSdp?9MNlqP zW-klf*S1bL8g$er2pjCQGFi1d2Oayrwf;WrO=i^n$Z7bycV9Vksej4&_u}AS9Qoq# z*J{IGKk~*h7k)+`{hlB9#G;s=wBj_Ho7Pj#nw?c~Hkz52h@;SGeK(kO)V+z6`S%xZ z_44E&7A*gXH7ZWw@eMrX&Yzno&c>tL_pwhSyT?_OBl{aapN0od;6)1s^>oVC!*`o^YG8dC!Gqw`k-aIhVHQ-PnQnCi4cZj zG8iYRu9K#g1vh2_$l)c1WQ?&Jl+$DCEv#Dxvz84eQ&f0PcJxRkpGe|Mnwe-a*WnGQ zGIADTp#wvE$+{=PP|5Y?_cnW}#2v#}E6i|=dL*bA)FR_i^IJTkYD>Gnx)%< z@^WZU_^Hmy%W;7kHBvx;kUQQrX`Pm2S4KkEjVb6LgZ|z85V3x}bedl}J_!m#QJpr^K)$cAEk`{RUDW*4+KDaX)&;A#)syB*4q3Iu`jDMottCpHw{(*LHu3m2c7ux;VtO@|5Q~t}W!ecWYZFx4U z;^`E!66{S*J(NG=fT{v*Y%BXS%{PHtdKBS9Wf_wZs`RfgkoMpNfMN*SJqm-D9K99IjL|4%7n(Xsho zrHt#}@8_Mam;YJH@ViDF{G5k5UFx_04(=@6pXhybR~<-y|L9+s!na>f5bM z&!gMz+;Gmjox)V-yWP@~>bt$l=A*m)+QI*bDr{HZA9Y+G-5+bmvr$Ytn{7`%X6rg z^6n&D6eAy|7%tmKyQmWG-W%?-YSzcde;TP8laF5_+s~?h8l@PW0&ird@a>X`++~qL zny$t7OCOAR85mCN9z2j1kPy3}7cRCK(a-N+PO*Us{m(v2Gzb7d0X+XeF#!zEupk%e zfM5{P|4-KY{|PBY0$>6X!FWjjd&R7y1)x5MyGCvQt(fF;_5ZyKl!WF9B=`1s$ughlhq2bMp+y~fD00!TO?TJIk&+p&=t(f?7Fme_YLV_gW zEz8pYbVofdAReM6xED!g6|CtDo1l%wd&3o|;@iD@E{8(Qhr1AT`9RZyf7qA|5=pHE zKsW-CY?O&P;@7b80g%CQCP6?3CwvL{_Wj$uP@O2NRg$x3m?Ik9$?euBY(cJWk|SMMrT~R* z7o&hWeF-)l8e}9S6m{ZEPE9$^Jpo6oYpd%Gefa_quphW6h9U#%UqQi|5XT2#!w~jB zaNH1zDCjGi^;UJq)!u$hmmBg@Ayw|qUXG|a{#@p{lngs0?t#upn*tWS1a`v0`5js4 zygCjO%S%?%Rnb2YEz$=90tw&a_kJR$B3}?ITyZ`PP_qHv(j(`Nc@pH~#dl-j<8`5y zrFJv^vp{j+B7>`W_emIkA1@8*Htq*;LkqWn7`j|Yr6df2#whmn2qSR6Xv+s(%FT9} z4BsLLPI6i&(-#~|uaktg7b$2J^DeQX8m%jHtBIdmIA`_b?z z=Aw;;v5$o+6Xc3hFWW4 z#!vZs>JxL}qmap;je|9reIPD76uKDs%YP{*TuB-YwMEL%0fnqD_!3JoX__!}*>cjt zuGK@8<+buCbv`*V>rtcVy0hu{-rFA$DzT!a5fVk!7ZX8rdJLU6Ax|MVw261pW93&{ z%H>_T%_m(YTe<&I%om@*hT>t9g<^(PiznVt9wxkN??fp}V_-la?cPKyuz*Fb>i0ZSs%Q651Fythz=3^ut;i-o}X-4p~!^T=jqu)@Goc zl7iq~^gk3+;vChUE!aCi@BLJuFJ_1Z(b|v{0w0$|7Z~6tF+<}htJO9Qg@lXK&*HTP z^@*DOH7@F1)5fPJ&R(4W29iafpQ!b|%s7p)4IwpW?`21V;4qkw$dU`xCqEAaY8CEu zL-o5t-WQ&J3cx6&MhPLNr~IkUmTmzni1>_>MN4^?EA!I%)sVdY+2^#(Xe_*--V`o% zYN-%8idS+Y>KSKArHVzYI>RH{ZD+}~F-7b)azFGJ&r(`Oi#R=pe;D7LrFLNybA`)| znvK383G@2bDjghEEPqOp9mto$b~AEiW7}Ygz;bG z!D34#ndB#<^e^&>f0jz~j!ee+ToeE{w-rR?r#@#~6f!H7$?J?vrL-St_eku5L!EnZo{`1&XG|GPcMvB-ySYqTFXZ zuqHr9F93vkqX9NLWJSeSofi7m1tGoJhE9zHwa|8fn7zb|e(7s;)CV9rXayi&l+Tm+ zdDPl502Mj7f^Y%|fVK=3keeu-Z}tK8?pEEO{j{I2Tc<$b+#wE0@NR*i{M8IoeH zHiVhFA@0%!W$G^IC!&q8>5~j0R*QV67A-jfjA`hvtJz`4aE@F6Qf^mglo5-Cr)mh; zfVyYx+Y6AjgGnts7b*^YrzfBgL`0v5Y>HG9qO_`lm#NdiTw05)-eGiEtXazp6UWWo z|3g8a+Wn*?j`KPbm;t$#jjP8RiQF{cvnh&0RX4h0R1j&V zr?E}(NuJt-mC&EB(6@72|m+Co~iSQUu#Q1<6XoGFUo6;tr{#dVDDNqy@^PL&)JmdYP>p-=C* zLg_FJlcT&x$FsXJ78ZeQfBD>>=mTM#4wj4>Eye67mk}t2YxFT)%ht+4b`Ak1srp@L zu3CWP#Gl(Zii9IK{&p#zC&Y9O$>W(=FHJUaumufET2g(r`B!`B%@2x ze+1M55b#$iUlex*sTPLlC%T?*sWy-Adlan&1bw!>Lh^jqOn4zb&?S z-gZtcp9TiAUkRr^7J>%PCX&AelMCn_P|gGXCV#L`weLbh5e@i8mF;EqhXg1_=!U~r zb&?zIfgIs^td|&HS}LJkW7fp~4-Pv$@mfcj$6=m#G~lN?Uq>L3A5P3FoJP~@lvR-% z@%xtlx(Tft4Z`E7d--6vEpLh}6>yQ1=79uEy}pk5-awqo&##8 zI5Tp+Ad>cACGd$!HZDxHU^I0BHG93i*1g#F;pg?OCNMMeF#FVR7}OtFzUfheXt51 z=(j>xYnxrV(4V~LdgFy5ZCt%+igx0GxB$JP2F8ZD)@-XMHtRJF=ol|JWiUcMSp~-< z`FSuo&uIJDc-RmydhSZO!;8E*--JGe*b!I*g`54~Ub%S6x_OowFfzL+5QW(wtdOd$ z-tF>ua5D!}2YNW(=(9u3D4YEq28@4|s1G*jQK*7nx#%=BJ5<2!Leq@oPPEVfR;J0W z#4UEL7a)ln^-zu>f&eHWMzvXQan@s&$$W4iK!h>8j@Z@^ecHAlZHi#_3Pn?m^5AjMy)whliA>qS z`IYR+jBL#w583fFE+B+w$yz%y`BMzRojvtxisIjJ>AmUqhNPI8fAy(mNQhwk*wr8F zG;hEmgN7HY}Wjt4Z5y$3yfRY%$>RV+#RpP(84;sUZSx>!jmDF`tlH z6gVzr#k^%?hU9{vkDws2dfJLpC@~T!3oFry*Gm0FBNj|53c4l?ik;P!V$wCd^==DI zM)pi1sWcG=+CAYBqy6mImHrq8Z$a z2~C!g34@V*G9l3p@J=&Me{1Tk9^;W@SY{|(EUqgDg4w-~5>LS-tnLXZ`9c7<{W`TLK zn*O(%HBuQOQck1ZY4Qx(W4js?t(vE4nVedg6q6bRH=2YKW(x>CXVo-KG*#c}UDH)N z$l|U>#!#kevBqyZoD~w|QI>3lOP|OHO{!*1dX($|n`|589FG)@b3L4uVdK@voUL1* z&EcFKyX?K)9N|yX5>r)@F1Bwgc4RHEUWD1Q#yRnqyw@*j zx-G`QDJion@mnnMzti+;gW00xf7CAx^C>+eDSamDB_bkAs5V1T0>S;eWqV?MXGk;^UO1>K}oK8xT^s~ z1D;50Ar3VPY5@LP(6MGMMtcn-5DYK{Af3RT3)1-105om@sQ_Sgrk2v6hQSp;lndq! zs3WPULp=qcq65g<>nQH)xPDYK(Ezw=K|q8m00RIJ5d~vX10cEeA}3%rY5+JM#A8s$ zbpn3F20#}DlLNqSq5;fHO56rtx9xGkssJn(un0PsNCEiT6aaAnNmSGm{Q&ap1I(gp z(b@sxz5ulNTJn7*ZG+}Deq4$6IuaMKvU)w~ms(&vh}RVGd|oH%&=~lm9vlE)2uvIPAjklppZR;H_2^tJr0ri{>~|cnRnjH^#T44&*#MZL;6mR9 zbZRgF0QURQ$`cJdy$8qcw?U`@BM$WqOPy#j6*S#-lKTzd?mDE^dc4s(4mKEX1(=BJ z`M{tBcF?uo-bIU1K}XY$Ow-Qk+YJ^2^N`V-MR#qy?s+*{&OKSrsQ^RI1(s-%->s#mxjFrXb*Zm40fiI1qlv? z7!HN`4Mk)QMIkzdVtx)*_T*zd_42-ukJOe`7f0B@ zQ3~=iM^uZdi3aq2#6fG9x#faCw2G>!kge7UjSpmhbdYOZuHwPY#7BQoRVik( zyHL})P!qdQ)iGz&k9#LiXb?tL6E8p~XU@#o`Z(rCE!nn2VJh3nAZzjq=8DC&u<`cXI0chYoM?RZVdo3_A1y*RfbZC4f$yX>Wq>}y{!8a)gc~=JGQo5wI(k7SJL?J zd#4rAl|{;@LF(%9NTGG|KpdD0QV>iwA2K4ntYQ zoXw1f!k#wd#ulY1x7~!diR1phd-|o|KS-@SMUS~jM&pi04YEEVlZ(Z{<3ch<006?& zDCQTF@k(2V^t16?DDd2^;4UP1JgT$*cDB-X_S0^z@*;d}H`r()gl38DaGRtK5L^vJ z=Rz_$nhf)Y7*hkEeuD2PprYLX>v$xhE^q?ntY+1&hS9G+<@rLa?Yz!|(UpUDLVLs6 zbKcH_2j9jcVi%+-Hk+!KKA(`|a&5>2?+_;d;i{TppvZ<|&|m)(RXCP03F<@<&gz@h}u!j^jJZg=^tc{C9tE zDNlEu_K?kpA%R2D7jj75r`=DpkzGjMv8Mq>r>x)42>nl)$B&&mV9p4fGm4r+v_CN3 z@5;_uP`i^8c?U0Rx4xqrC4%f7Tx#C`SWvY6w_;@y>-$hBJR)p_-mwPR%o=C!rb zwg0;-IMtz-$bb*x%1>qPW6q5x)y=T*jk)1X(TAIenwzBYvlx}VI7ENE%Hn5{xn#sG z!N=Ro>|5iG+k)j=bIiN)cgOi3w+p}b7r$66{f=5;azDCq_wMPrhI0Qw=)MVYpA&a4 zSADNDdhd65-<9*=%K0Fw{GjdFJ3;k0CGt39@;LYLaUth%@%!U%#Gl6%#N#T}(*icY z_tV|T$0y0L2lAr_4aW!H*oPe|1X1-9-q@4+!IKXr;_N$OT?qlGll!N=@4Oczy7oWY z`+aF@8`5zULfR0R`d``43`+mq-hbot`65YUTlv}E_u(hj+)>R3eWaAi(A-u3m%XpG zr&+EhK+vCYY1;G2=$k(hnf8HhJ>|P>sV~}x`b|#D)m4ic`@dUV4_EsCVefak)=VJ@ zCOPQ!gkn-jXL@ZIJlp#sVdNyvD_zOKSN)lK=hkDH2z}&O)l=K4BBh+M=Q^?dT$NG5 zESWysX7MY+>CeF7Zo@%Wuo?oH($H&RzCV`gc}||^-H+BRk#NeaUFR+L57>j*M)&a0 z;cp*NsGc^@_5zA>WOAN(F3)sp+{39%I?YZm5C5nrcYf2pe0)TFkN)-u`BEba#y&t3 zK_arO<$gqFM-xS5Dul5BX?~-LVe8LYgo`@kxM9x zP|?1>1FolQBvq}NZ25Ci+kJc)RR`*E_|?exBj#KX3T41*f(kpJ>z8?{H)C`>b&G|G znXD2u?pyY>)d;hw+t5^MO4EFNsj%D7B<#*n?In>aE$u>vnoX|_=Wj^!?e+-ANJGL& zt6$n4D3AMeK6N9DXyR)zvO@oLiU!UZW(E?0+lBtQ)dXlkmz5cG^w*{JmsB)77 z5UB8h9RNt-u1{A#UP?N>P}A&iZojtwsKs)=Nh!VlHMafRBPo-W7hCDabGR#CfN7?| zmM&eU1YG=HroflPG>Zh3U5#C!;?33cBrJN_ReY}3%y;Gy&t}!ZyYEUVzp5n|vz8iX zjStTo)YMxgTTM(7kFE{|+Tp|>-w^+a_j4%$qht(Ud~xkG`wI?;9u7<9X6C&K&TWV% zT||9Gt*S7x&wXRk%=S-Mo@?g5=0I{YU}CyrDCSpx&t8D|Xp2tQ|E z1F<3$9Mv9>ZR&m%$Q7baYeuN`zMrscB3k@zmND!))-eL42WaWS59^M# z|0`xp3?=4`<3}UmYT?3AyUBznZW?#h z8!36LL4`T_LRm`H&jTdYDt-#xe*gg4gx6ynx#x33_^6!HSUU+dk}CAxuZHF4aNnfT zXDJY$L?{5xK=X8gN0h-MXi*Ny*7lqJrmc!QL+U(@jaIK^@X|3X&50)wL&z+I*X3B0 ziOJf3XleV}fTd)IU>JnT5mw<-6+DD_d}ip>@q&NAzVI;gai zCl~n>6r1<&2f@uz1iZ{~;m>wqOD#&n%l<0+CPjf{O(MzqU6to49oR;%>rPX? zuCG9Nl zTIUre1!ug>Pmk|7z=Eb|gYr!9h+gU*w&dQIH>Fr(sv!u0HMG%Wr!@eY!L^^J3{9bfka8f3fXDR~*0=(t|>(Bcj%S}iYyL;neq~+nhx9lQ#oq#f1 z>NZ_0&!PN&YJ;!K49ohG-Jc-KKCoJ!I?nm)C@qCUU8E&)>(OsnN^i0_Aex9 zs0RvK_>oBO_)%sgkSu+uZX)*QLe{<4x8pvLm?Q5m{;^@JiV@U}qqRR%*415FKW&1k z9_Y1ia(aMWCobV3K5j6r^G5+GE|Z{W)N9UXB}I^wvC3Jx?Bum?v0xUfL*CZ zN~}^x6}&3G1#!4(r|06Uz(h&t=cglv*^oi_&p#fxN~5ds=qR2$ zlIsByLk0#IX<&*MrpWw^qv$4Xfde1o-eB|r(Qx?!se-ry$%)O~$gaJ32bJM;2_4Wj zh5KR8DYxdOwM~WLhUNZ9&u_eMru^sMjw4aw!b}lCNqOKvn%6Mgo?}O}PlGK3!N<-3 zNq=C92rlF|ECdzqXIa4XO>Q*%B);$|xLyfTYGq&&P$GBbS>k>8!RP+_vEa!To&Q>> zcOf23fS&jSjD^dCEF#y16qEa1fs6;)lwUz2G;f#B2uv3DqwwHW5EvdNGkZ zIoy5B!f5W+6;Irm2<=I_iIiLoDvkJt=~skx(bW#>z9Ivn9kj-KwNO$6aaur%I{@Se zu&IS$=UdQ|z~`ZJJWj)0bkU$60)ah=00e9(c%qkH6RnlXvMO6Pgh#aYOaY(aZ`Xx` z_>hly78r1Zo`?znig7h_Q3r}}^DD{ni`Mfc#CLzh;gM72?*xLxN+BYvKr`HK!Mxsi z<{rBD+?pE_9OgYDjm%fmZ)vE-80^TkUKYilNCHZw3|>d-VMu*DLtPk=Xv3@5FccTj zlnRMzq_6#|PbRG6_uUA;B4;S1^+#74p0td7zxf46C!VOn7ofGHv`q+WJ}B1Suh5}y zVE68o3;AmoG-wx-0+wR=)Ng|8DrV>@{&=5UPmlI9Nulpx)Tm5x!V-6q9Bt~i;wNbq6M1k?!3XFGtwuYztbiu^uO z7QdB_=56A+7&sOkqBMc0r;2Xq$0pbV%i=BW8MT10T z`9)|1sUw0#ZJT!l+GLUd$Sz=xTCkWXub)`2JS+qb2J}dP#JnV-Ucsde0T|4@q9;V0 z0s^QvK`;1JaH`O7In)qnZ`JUZVFZ|BpGd?`A%H%E>=AT+I32L;v?C-hB>Zild{;1i zs4xmlv(loOUPM2X{GS021;VH&z$bTrJha&8G8h{wjLG+T z9WU9|PGY=b_U1mov_H_3jtA?F#MY0xL-&03sxGkBxMo&7kJT*Lb@q3#7HblJOm~3l z2VvSSEpqy~=u@q?@d4}SiTYI{Lc=0`(J8D>Vi?3B3V5NmPGWvE5?Dxp7X%Pd7m#BW z7c^=5C{dg?WDMa3)VVU8byZTkiP1QI|NY*uGoBcT881u%0_?ia(*eL--QO9`fIdG1 zM1*iv8Gx`B$QGM0!=VmJ3*>WFXF@H&*GZUHd|~ZN00uM!?{yswJ8H2|U6fIJwAMt- z>;muRg3h<`VFA5dg*143z?ekZhK1hkmw6wG5JBp|*F9kLiN$^c$|R*Z!h=PuA%1?) ztQc*t$B$t2RUn27SY2<<07Gi61EWk!E2wf&mStY$LANnaQieQODQb!o(}4U!C$&h2 zt!Pp~P|r+4Jqt6kU8;c1gCDoWFxZ0*&;<}E4H5JTAmjqO7tn>!8@6y8;uS6SG3eZ6 zb{+gyMjT!kaoh~Vo1u1d2TtYjSavIud7gR`dM!uvG=O} z(CV2y65u?2U_(5bTwt0WwU~gk<4;5N=fzeb#eb0e$5-C3k_4JA6%L?kV*{F2S8Z1@ z{7az9RM4v$Ym8@+U0ft+i*0 zGF6j42H`sL8tT|x-3M(a&W02I1Af8na zXFxD_qIorjl^dy*`Vp=MyhKu%javco+~XCF+w95QynOy!O<^$L}f!VeVrM6Y0k#J`of*%k*Vu)#RgTKJnkBqzD zxONjeOJQaKtXv(M&+V{<;!4N$hN(#AE%VOB;EbcyFGtatb9S+?1W0L+SUnzwEhu3K zz%XR8CHMtp2uRk|TteTV$o%Hj`Ib$ST}j-Icbq*tazgf#U6M-xBeVx^)n06v5Sj*} z76noB1u^c}`}ozgOb7ifFAo!rO0ae)3)pKG=D@KFWa$C{T$+(40+oMjvF-qfx`M{5 zK_gF&tPAOQX+bQxpy_XPbbLXqr9qUkT@zVBjH-wEik27_+^I`oWz_>jj_JX`97|-_ zK^zPRxeGLv4Irv@q^%DW$=i!|1xyu-R9_|HQ6IW)9Bfqt^>+hujzpA3fHXq~-P(h} zN1RS$mEyToo++lQ19oeaNBz(Q2v_9uDAAU-(OC67V_K$wTM%8hxX^9f}JdLp-4A(z#Lr$fOGB za3(LS3w$b8;_0ju{{azL1>s`o!DjI)QzRaw*;J4Pr6M&$Sb(+1j!U!Qu~?uD*z005G|n(b*osI7V^;Lihj|XhaucAEO3&-kYE%;1cCLsiCS= z_HWMqQIqVUu(>o~xzPCp#bKF9(&iCR4A%6tH9o#9bn!5?ftoRSm|vF|8-gvdFE~uD zC{s&2+^>#ca68`FYyCZS!%IhllvEcd^OJ``Lm#N9{E%DkYsY=P7{;_`q~Li~>~wq2RLHo>2FMpb@=v+&Q7?i@;@4uavEw zE{-APbvxKOr5jQ*g2l7jozagZvFg-2?w&D>roFV8s|@WlV9p zr$bP)X=`I(cYr>8c1>BslfX^ZWNbWTNhfh}P%tY=x#DRXUM~*2mXB`yYRWbekpqV= z%>J5lrNig?>~MenD2dR+6PeO+e!UwJA4T+PCFF+t@6cx^jcUhVcmo3&yq`vQZ(r?9 zXo@nv~(>XeMy08V7 zIKh;J3#*+U`L7P1Ez;Gv)U&OV*cXW}-aQHWT%`9mTrd23{cv-(JzZm>(f+546mHzA zA8LcH0o?=;Fkf6f56HEA4Zsp?L=q#Q3hD24H%ovK8VJ5^o(8`?;R+1DiR7b=q(jyY zHT4Ocr;H{qx~7fc9^s?2=2&F@{Ne3h3*9H7F->!0X=qC#w1z>7*hx$YphX~iQakPJ z!3~?$vPVh~C7^HY&kZIi43)%2kTzbD$6IB1Tl=$=^ZknDnYv-jbRF59@NtT|&i>@R z*UFTWpGT5}EX87lpP+kCQizP}m`ngr19dR~a3_#Nb$K+?ijd5exl)2Z`rNUUeyp-( z@^#PfVJ&|-Cs%Imb#=>HSvMlDUP=lIlvI@kh%u^Ct4?YnYrdbb#iQ$k2kBs2#v6dt z?FbA}h)0pks%G_lIRFLqZF=R)KKD+R70&Hx6%84_oNZ@%Y(O=oTT75e$5W%0rqRL) z00097gt}bF7cqeOp~J^{#^wDf?q}xhT)n^S1UM~L(IU~gWjBl4xd$nx8>=Cr z0PbRU+GYmYkeSUQmtF)%`|tT);&4swpKlcW>P9K-4!FmK?qdK$3bWLpN!l6J8H1b9 z%RsXi0*@CS+}j5UKeY^mn8q|58qdd#d^z@eWHt2|s_FF?UeDN7d@HupXM?}_?b@66 zb;5_i8YQNG=CZs-&^Cek}vIp{`5Y#jEJ zA>JS9Uv~=bSO37cKbai&7rdOc`0_9_>X6fVPAu_4=qATNMELfjdCvLOF42lm-=A=V z@XF>7k?#@PKPrWv5IPMa)%Pd&h*yswX3-fKJzgeEmJzouT$22R=6iwu6Rm8+qf^C4 zM7!xAa%nf_N+>m)7{bJ87XGHs4AXZg1WdIALV<7EbnX0dfer>QLkxeA-ay4ny4D zh_5f!cOtDX^zh3{2f{YFu;RPcVR+qAFG$s5;4#y0zDfLT38E?AYZ4|+8I*ognoAVOwXW!N~K_4 zpKSdS%7noOL+J5`#F)}EaK5k@AK08+Z|q?yw_H4yg~53^@y&6fCiX+tJzUH)K(v7xQeiDg{0tkEP2} zldsoGx$&Pz?>sP>&-|i=w>9MQ2bKupWstH_a^b}X^is9~P-!{)QUKp}Bq1ZTL8OfZ ztlh=p5*evtJc)QhGa&krTY;GR3mQ@CXY0g*=#OVz_E5{%*7p5BNj$$aUdj z^jqm1LgoF%Z>NjGii>$a*smg+Pl*-c2Zqo5CEwH2{7Cz?0@<6z*L-9qj{hqYssgcQ zxL!Og2%%0r%)W{pX%CC``C1JE?q>~@&woksV6qX~H=4-M2KIbSL@%__A`{nX_aUfR z!`U}plB30|?WzIj1~a|}CKcL6G>G9intFew*?;h;^&zR!3;l%`gh7jSYuHQ9$giBN zA6Mt$!EQra@qCVB3(0_mSOIe*NggaEG=|vS-mD?D^KAn~hs6KD-GKBM`M4h^aP2kD zb!Ib~8nTf%lu>ZQ-!i-5hKBT!-|NXN!3mnH7CHP3UedC4<>3lM%qWK$7#iD|f;3LV z^IP^aQOSWA<6;#M%2lw0sufzy0;egRmVSBBHs_=UzYVNyf#0(^$He8+@ghJ8QS8Herg&xo}By;tpJ+uXI z!n!%Iav{?^jYb$2X;vg-N1?m|o5#;Y0M{Ek#yObzVsUjJqjq`Jv?DIgPf(#uDCPSh7=>v7$v^;*7uF4eT#1KdO_QTquENxs&J7C5?btOI8uXqV zLs~Ir-d2iLXQxT}5Ga_xvbgArk*mwNWzHu$F6?*DuJb%pTqS$ag`v8D`g~dk0ho%y zT#17Xu(Eb!@rbeIkR3x4jW!i(Vy48otr;2f6jegv^9d;4lmYGS-HP~UZzd4|)JNz5 zHpu#HGNVTA=>szlJrRK<01&8f8fZ!dob;4-rc~w$MADF%N|e6({^(?9{gKcbh5J~N z@QagELF*Fhz$e)2y0FMzF+7=k_bk`SBP7kWIbmQR6-eowFW3~qNDM^lqzy!(?uG$$ z-`8W4pFHw^SydB=Ag|)Eujb5LGm-q-n-xLD*SLoCitmoJGu}jZ(1)oZB?+BN3V3)w^EA0rhPGUp6)|cYB%ss3nqDDrf_CZcgW{xRzE&)lP#$ECIvN2GXu~W0W$sH4%@uBGGwpF`Q#DMl{jpOaaTQv42-74o#)gOtHhputA8m2WZ|GSROHn83}`# z7zYhr$|t-gD8BR>Kt1n;-Woxi3Ss>aVORn_Z5|O17IB!_WsI3`(i%G4j8uJ!w0`Zh z#jLE$3_WX&T%Hhf(F}7f&ueFma>R_Pe31&Yj*M#V1*1hjU88wYr(n%LY|UAe)tU0wcLLVEYV+?}*4b!jQAXBj z%h!E=tzWI0b7rWqpV1!O(9!`dxIV9QJ*jY$(ydOI_c2?ry|j1*|HJaUYcICJ-K2!B zzOe`0c(X+zXqI9gF9ws*6%(VSe{f3_AeY8J^(7EB z1!8JM?UIFWMf$=#U3`5)B11#+Y)n#}o@afKR9B3YIja@bSY;t?dYAaq?elah2>?ox zKtl3ajs(I71jF>@w1sZaElEnB8Zlf?6lmvD`_qdFkE~!FSlZn7RSHE_tz}f7X4C+` zms_HI`zyc3piPQHdW0xyjewHdiWwCK5<(dlBN?jd0$r?9 z+V!$KVh!8Y3=BFplW24$Sl2cAEwvcwZ0yGrgRC!BWe!ohC%>)a1r!zC(hqtYssv;X zy(#`C6fm5ZIU=>y&S2@Ro^N$lY~;W3Za~Qet>k1ioPb`(8nt`M)l!9!7@#XUqhvH| zWHjewG*8hzuQmk#wRL`2ERY>%J*VxpWu^VO1hE=UeX^lkyC{A3cQv+XjV61Yn-aiB z=^?rD*RH_egvtGJTO7$Y9dl2q8Hp$RDaobbeqkiogE$*@tQL% z!W=6yr4-jXJo>SZ^gtBHHfZY7B2)`Sa}Hs?x;5F(u{iE(h#pG>xVO^?m8FOC4b` zDYwfa9nbQY%2zkQ}SD=E!hp}ew~ zBpE%uIIXC-L0RfM<2{H~dGT3HA>95ezAA0f1aDO!MUwrl9z(hMXhF68cw>1*7@EVT z292V)>Skhuk%8dWUh@HKjZaL?lRaZ(Dus(UxPQtq-9C|ZzmLnI!ELlbyy8}tP0BS$ zG9mqo{eDxqeRJ`T=Aiv_SRdt1d?N!Q-hrY*{5L}z9KE@kt*x=5Jq1+^lI+k8MEyXr zO9ScLVkH>i-$xIaL{XdVre*hHJs>``>!mEu5oiBP93nvgDl=vuV5}H$ zuC#%(Z``mAE$*;0h%@~RH2cL`zivO0jn-SwjxTiZBZ`HWK%E>Kl$x&k>X3YFt8%1s z!sg7;uH|5yvy5qGdh%@IEA3$!YgO4n<@6cqjI`5IWCgd;VU=~&+z9GCyeiv|V?kXx zwKW0QuMYD_&{SjFWjOdXCA(N&^{Lir$)8zu*q$bm1Zt+Gy-U8bRaIWf_U9<_&r?;Y z>};Rg6v{7l`k85RN3AK=BRXz`^TyWjhWL?y{H&1Nl311zX&oBE^TJ>4?dxlp!o#T?U1lB`8hG}~~u{g{oruGNYCLI3EI6NAVJ-%~hunH$2 z4kXYO!!@kAG=I+TN{_xJi_ioy+z>-Cdc^VvPS%YPMaR^!_6`X|=PM2a!pGi{H5X&y za9hx|_VJ~4ja_z0f;h42!gko07$FAwoio#^JOFv2+wC#ikTPt+E$HM|pkz9`SS9CU zO8Dc-@rLE#TlEF+{{f3YbiWQ)2!|!60fhrRUP&jxoc$9k~CV*f`8YseZ1!50AVVDbb6T!AJ?!Wcx#bP0ka`~h{i zfRRxG5Fl6PO;;rdLLM|nz8F#<02_uir4ba;bs5?fXu;6>!DKSP6;y&GaGf?@KoO9@ z5sZP14i-svXHI2RRrOS;m2I{TaqAYb5rYi~1Hhs1#P$AG?Kx;pn96g0Kd_L6HK$7YG6zumIpWC$rzFVV389JkL!mKld0a`p;Kt9v+f4POe>>3CNf*??W z6Aa6np+L&f8z6h4+rb_2&F@f8Mo}B}XXGzWd_f!x3z=N=RPzd2=8GA;G=6r#1(;=B zgfu#PT?gbCu;p_WNdLfeL4k9X9R#q!Ap`-4*m7F)1Zh{n1FKyyQ=;3A-d>|@Un}%8 z1NPHKvP2&WuQ;)WmZTJ?@dI!+65z2GPxj-D^GE~0bXC`N;hGdnLKtYp5r}eRT7el@ z7YST;fjvh!?^`8&cN#0n9|N=sx*Z$<^<44wY(vIh+xBUIuoZ9tC|oc9a<362_V=<3 zU6A)r^i8g5HC|wV8i@0tiF1F;B0>MuL96$A2UKmlH!xubeP{24)A#kh%v8fuC?S^cHYHPxHXR%xQB-L3XQlfJ-CSnHj3{J@71Y{9}$h$ zxFg@VALY300{?5J3h|FuMu`hKZxMNsM=O&ubCUbDjWao4Ir)?S33>-H`b;@(2f3Bo zN9bnxT~KS3Z>E>0Q#X^p(@voqI*A~9q6hV#GkQij`lGwV4;HVa^97|>dO<8YrY9Ac zZ@Ou8dVa4utGBwVzdEePx~$JSt=BqQg}SJ#E`yhPLI^sl)0C6jy08yBu@}3sAN#Qb z0Iuh{Cy+X?AB3;}I?(Wir^nb{AUn2ayS8sTx5v7&FT1Ea`?D8BwEucvR6DwNySlGC zySKZrfB*Zk$M)uqJ42K^s-HW)Q@gwGJHPk4zdr}KoB4vzIlUuEKHU;DZLJHt1; z!xKBagZseGd%^d*rb~NDB>ZVD{KI#=$AA3U1$>-itCUxKqn|p)AGN|$JI8~(%fEcb zM|{AOJer&Q$**k6s|(BT{I$cp&;NYC%lynQbIsfQLLB^~Yy8TzJkU42(`UQTi@cwg zywO`c%ICc3@chQ>`_p&5*AIKt551!Ay3Hf~pew!bGX2-DJ=?Q-*o(cTQ+?G#`^95D zt!VwybA8+IJ>Ltk$m9Ch6a3tp{hgzIV5xoIC;r~Qz0}8j(a$~IU%lO5IZz;%5YzyA zCjVF3>%HQ4KHD>X^vOxlp91Bp;pXA6*h8vFCCASz*8&#B&bho9Ewt%>TW_ zi@xAjed&k!>8t1G?L?ai#R42xX8D06^92fgm^zOMJa(f_`c3x)6x zKL8XzP#9WTYrqMNFY{MeThxB^Pe1#^dJ+i1cA3B$oL$^jf2e2h=vTbpgOqn<(Rl1aEQ>C7uZjw5cYE`ROv1XMDmH%s3 zuV2B26+4z}S+i%+rd7L^ZCkfzJ86|Wmu?pXck$-cyO(cYzkdM-7Ce}6VZ(z z@Ir-}W;ixD5TRp&6Q+PM2!c|O41u6b;EV%+3rSflU^poO18C8HfiFV8ZkZrc{#u5rB_804!kkQreIO01dr(zy~~7B0&O^P;wy;3w&$P!3QCX(5B%e z7-1-z0N}*9rz|O&WV%95TIr>;JDOdUp^jSW@BB<+hO4ojK?49@h8f41 zV;*SdvB@smB>>LRW|oRUJ#J`DG&b7lteCFb?xaVa+bWZ%?%VIb=S?rk!I7MyYp>G- zJ8ZKVZ#-fEagoYy&%;Ab=>vWF@^k` zbB5v8;>$(NTyxRa{M`57*$ka`zD+OQcs^NQJ$adzml^iwq5p>v@^{|R=9gBywR~vZ zci$ZN@4?4Tc+rVJ-u&}rY+mu0*Z(^D_uLTCIRD2l1hEDDVvu{Rj!hit#supVHwMX(1UvN(1a@Fp^G?}!W4;ELn5@t zAx-AdX`VFZ=RnCZQIe9GfJ5cio+(RdPLrC|wB|L_LrdyuA{6q-Mj(D>iY{p4ipUuz zF-dkzb=obN>sn?r;dwd+Mw5xywC6qXnNNMDr-s{9#W}K330({W6l)kn{xF!7bE0!y z>vX8n+BvRw#*?Bbqi5=rxKE94l%pMO9z1FZi$KI-4Qo)vFV+x;Ngy<#3*F?=9vah+ zMbuRjt>{hPb&~h8F5JV*;k&WR_ViJikZ!kG=T-DNPx$PBS z2e0zn=*|~C)Xk`Uv72B0!jgUw)C4T#5&y-ZZo{yqC~q7Pd`0!HH@WV0u*l%MH~LOk zPqoGGeleWk8^jRSXh#FtLIcyfHOBn8na(E{o(#VHr2(!l$h9Azz|l z9bcDv)YHZ}-XX;x_tSda%EU^QnquUFsKrfY%Zpz%V=1Ta#?Y*?*krO}EpN8R)!Rlk ziVEhU203k%Lu`7L%vdL{xm6LSN{pu*=e$_By6!_`A#;4sJa0L_T^@xb0}*IJ6V}VE zK(bOJp=1@Gip`B~nwv)_XGw>L&i8F0*jTbSnCN-aeBNlL4~e=i|DqC_D5@KaDrB`C zI){l?^okuFYZXB{(v!BeV*G?8A^+5XaDyvkG(&P}_hp*cG0btWofnf!8wAvz-e(L8 z((Lqf`5dYU1zDw-?fMPFw1{?fieW8lam$I;Qi1cWXPoPMX&`G@O9JBRS>*EqZf@+1jtZEGPH_Q<1c@3ZB5GH0uI%Co%kmh00eFb|1P zia_%&OK}@(&~{j7sr*S#+AvEP$Z5*Uu^X4m7g zW@8OQ4sbuI7Y*VXr|xzKcZl+S^m1>a?|z>x%lf_Xg=jfw>E_K-uDk@ za`5$T_=?Z?C=2yMF1VIue0y8*oAuiwu5=Aw5paTd31PlQKp@m8MBkR}) z{nC&0*bj}o&i(MoOaH(md1j~P=tmm_;r;HDp$$GFkeh>()N(h;U2z#Yaenmf!B@alzNXEbd9smO` z00{g52Q&}^lWkGbPoto14Ed_+l83-zua<=D9q2*fvMp^|A%ETw4pWO<=8z5r5D!%` za4h3b4scfp?;)s;V3I`vIS&H{f%8t_5O{zh4uJ$1AP&mU384U5PEeo3kP`EY0PN=s zH8IG7tP|fZ9RJLW_ui1`oZ}QxvE1%(6)Q>)f5qt*0!Tn%aPq)NKn_1LKoA7s4}d@r zyg>Wx;9trQ03e|8Bq0nwpaYZfn!r#Qbqo`oQ4??P{zeRcGT{+M5gR*38(XXnpT-*r z5FG6#0V8h+@xzQBphz6UW&rXd?}-44ss%G~$fC&hK2aNL$2b&nA%Cwq9P%MkQ6i16 zYTE5;wx(--?aDy1C+$fj2@Dg>N)t~q8nex;)P^NTkrWjx8)NdpAhIrKvL*$m*9zxw zcCyZVax3!*C`r-<18gY4s;tb48Y|}!TJjoS5^0{&!J@J|rqb^!&FZ+aE`RdW1kwd* z3CN^z6aQ(4TM`m2m-6n|vMt-|ElVaYmu@bz(k>m7ng~G5nz1CIk$R*NCG)Rh&@wG= zkTB=MFx?9=wW1ppbCej<#vU^@WhpXG?JK*imM-%s!+{1pLFfe2CH?O+L36o8Gb>25 zGy~~0*+(^r6P6-VGV}5^K`g}9P$f+v5)|?_)iO6lWH*(oH?iV3fm4ix)5?nTJ5`A_ zM=~$zZ#m!Y9m1h@Y*RWrb1mt{I$H}n<7+!_tveG3Jn^%6jKvk>7*1gkqVqs|FF~!ML>=oyt3o?d^n@Vv zMU_-_V$?!Mk|asX_Cjnd&mkO|;S@F#I(@Y6MwD}e6t0N$&5l&gl5|PUG(BRpLdA?c z*RCMX6C6&VKtmKabMQUC6sp3s*2Xm5R&=M%^iNfX8PXIvp%lnuFx%7t9GZbZwUj*( zlsbd-PVuxs_f*Ep6i@+`83a{L$20c8vPQMd9-5&THlb0swEyBzK_&I6^3>caRmv_E zQ_oaW2lYu^Q%XHi=i)(8SHV`}TT78d zzZG1$QCumET+4MrcL80~buy(@N;Om--nAV_l}8QqSG)9H@s-w$R9{P}Py4N3nH3j$ z0btiuVA~YL)Io~k!5-S789Jd86n0+Qvs<6TVb`fxBUWNfhhHrgKW$+bjsacQlugmF zVDCX>n_(49_7P5&VIkFnSk{8FZmZXS5{g=VKL49eUOtz`;W!VQ9UUOaCD?Y>#Pd z$yT<^_H0!%6`=NJH#KK()%HFXtm+mVsNq9-RB!3DUZ=uukv3cdx2}>@*$CGz3BYjG zR#V&7Np%+CY?N+$b{)QfRD*V5MO0-m*OLHubAym+F>=}V>~noGbki0WoV8Y4lSXZn z9`4~D-j!DsL2vgKSQlz#XLpZk*LL%;Dm7u(tP)8Y^WJdGjCR)_QSY)WwjUS5ccE5l zn>1P}b2%djdG+BQ+F@%IVR;vpXuCCf`Dl8n7pK$>0NCv&-)(!lcYq0k3rDXX4S1C> zgm-aFd_z}sgW+a{Hz+X_9{!3R@O8hu?VLih_Yd;`QQ>h#ljY)AoD;_R9cDU((Ch!EU)BPM7zQubGRr z`G+dGn@v-2a7B~*5DFk*pJgKm07Pa0v7JN0K0*QzU0?&9=76Q=AR}bP2MV#B4e>|@XGyrw0sk&R1ES@dGo8 z@dBe)&TZ~CMbzt#5{18|IEeI?Coz#SQkOHF)3y;8_CL`=2e~0;XX4hLC)fN1>Iwm7cnxYt~t< zAzdjr6D!-gv(_HCAr?xOt2>*!6$@^>yN|{?y#Gmiy!}*#Yi0tfpco@B2T);#wa*MH zffr}`2r7XRh+qOL;$H%$H1fd4>0==JMHBp>9_4Yyd(p?8q&0}ZBe8D5J*uG%7pfzh z9iCJmiPE|sH*yOUi>=winF_>1JbXl(#EZzRUq*n@Xj+^cbv6=~5FE;}$+-hLYNOj$ z&#=F=9J9Hh7%YK)IU3AeDk;XC%tzeJjmRc%GAFecH8)GaXpE%bOIcwwh6gYva1N~8lo!E=L zbB-O^(MQ>rT^T36p=)6moK+X1UAkkB+N<4eHQbSb9c6_*%t`&*Jty47-F(Qs+{dul z9bDbleVHj3%i&?quN!iKR@*zA+wnc$^_`s#z2B1^cbgjEVO`+UR^2uAS*6_>6<&i! zHeosZ;qQH+mSW<;z2Yr?+%X=QHJ;fgT@nBR5F|koY9V+HUZT$d9-ujq;~|mXbsQr1 z*S9_8&uQgZp5np##mNhn#}=K}PkDe`;EP`Nyu=QK1an-~FFIZK1vA z1=9Mx{-U!Vn%@^*iy;yKLeZjuiyj#~7%8E`gq9jQd}xW2#EG0dS-glbqsEOKJ9_*G zGNj0nBukn+i87_ePMcb~eE$hErp%c%H+4aPGpEj-JbU{52{fqCp+t)sJ&H7`(xps? z0(c5Fs??_vrdo|!_3G8ETDyAv3O20Rv1H4dJ&QK&Ra@mN+`S`t|JFw=a!4zWkP|XWPGzKfnI{No)HbtDA5F4oF~u z2A)M60D#z{U>OcB$p2O_cR|P3InxC;SYg){wwOKKwerY!1%)>rL*+Ge9(wMr$YP5w zwg}&RGS*Zde>UETV~#o!^`BS)cJgIqFoG z-F5n0=V3FkSmK>zB$jw0iYdN`W}0fQNm7h6z6n!}JI+aGop!p`<865w$!DK_{s~or z0N8>`8HCzWh9nU_msf?;T{#$GTWY76hu{>mNtj|zR$?S&rl=yDqK--`i*CY+s+@PO z%4(~wBCzLIL2dUyt5>sHbuO;zuFLLJx&i|4l86HGWOJ%Wf-g4FNq1#+ zicQL;J>YPIsfZ(TN@jVWR-|pi4nNEhx82J4?YI_SjPX(B0=aR!9)FB0UB|&I$y|Ua z>Q*qjsKTK%38eh0^vFl;G@N?fQ&qFh_GXx*rEUHi-Iq#bg1HrfaCT$#^p!_9f;>d8H~X?5dG zdg*iWE&o-zscR)c>#o24I&ZK8FuQNDL*@G4x-Z8T0BtN;1^~eG9-(u1xm;n)SZ=7{ zs=2kEUyuE^p~E@4>E3^zAL>|j414*x?{C)A+ulsN2fhqyur*rI zga?IC0}qODgt8*vR&J-3*v(H?=5t@`u%a6jo<)W*Tp|6mLOff*>uOg6AU0k$z`?|$ zW(K^6z#gN(orx!5=6N8)CJ4nSW~71^j0pxeh{Y@#rGuIf0su5HgD{G521(FF3BTfi zOaF9$cC0(04Qr^r*_m&SFQlCu+c%XK#<6yGteqbDm`6a~5QoG`1wDXag?OwNe8{Sx~I&*j8|E}6hs(^F?0n4 zhFD{es)8Fly0DLP`&e;`pyQjEat58Y#pq zLGm7C!&E1t$3XHl@qwKzrvyIkydVJ$U|3g9Hy;{uM6*HI!wdgSI7|dUm)vW8%KqWHz z6cz}g2p#32B%s-rkg`P;B;Ce4$k8t$8t{?#$VX$q5sODC0-Tr=rvo)f)5yNfrl7>B zPA^-!4$dHtQ0eJa>z$ey-fk0&80U)3R4(xf=gf4TK z3+?S$9qLPmW>unW?W$ONxmLES%LH$Q3UlF#!&)HHP|l>9U8GbYz$VK&DgW)JVj0_5 z%RE-5k}WU9C|gC#Vs^btyNV_@n@Rw9)+(;7gncCuhSd51wW_VcQfoN@ALKy+LcQlu z$3Rhn?iQJ0O>0=y8psC2@sGG{s6bOk!*x9u03(=dSlA-nLPE$bQyb}o^tG3Fj3cnz z-KM3A@y$gnFr4Bw6J*VcRzyweiDH>n#?`XlHY^rg;G)g8s72M@{69whurGG@7oLr%=T3MB zyf4k1jmb;sWUI;0+e5UXpUo;pk59<01gZoq5L>B&;1EH$Kn^6)+Cz|_0~l}v5i|e+ zHQe?H_zX2O!(8D6r>oWPmWl?*f@^0=_bvRU+TxxwrE`R1#TYuwHuC}OX0!rKi~Y`@ z*B0naDtqFl+HriG4egB6N!qvr3z0($fCu=%5cNcY9VGDwCttt}lE{JtG9ZXbP{IiX zF71Wuof~8BcEVuBd07bXmRUP<*0-iwtxb$wffL-nRpU?E|`E0aW|EjRy76+IK?$qgS^UwtlnKMru41D;*rk!ZX4J zX0}K~U5V36SkzO*AB<7Q(*-YMsjJP{hgZDqL&NyFIUe%7w@(L?Fh()noAQ-!+T}Bkd}hX7 zaBT*CzN;3c(}yD**laN*^^p&L2T6HfDmYd z6r^>jVSziCBNzyF@yCHd*e(Tt3LaQuoo5Rnn1o4)bozpV0mxz~RYa`!g51}AopN}H zcY|9vML8%sJP3yT;e$7q8$xJ?L--1|V1#rbYe^_;MJIx|AR#9Bgi9xd*Ozc-lL>>D zHsO^JS;&Qf7%^T*BVlNW_%Vj4rgmq@hyzjpuaG!XQwyN?hPT#+vIaom0D$8lfJ|o? z91<|!unOPAeSS833z3CdD2O*Gf!#uZhX{+Nr2m0Hct(wAi+=Kmx9}v2Lp7n-c~#?! zaR@+9NPT_xiHR{pTtYUnAZ(>5dw!S+fVhg&*ovtVi`R%Jv#2N9c#GXQBm__jX}E@y zNHw?Cd7(#%a>yv!fP&+og7#o=$jCeh1Y@Llh2s&8H8_o2SdH9+jsJLo+*pg-=#2$= zAO%ngrJ#ny;fv;Yj=<=KT4Q?J0D$4}j@k5ypU4=EQ4FJKg{BCNsko1V*pJ=P# zIU}!#l3|#LW9X7o85RW)lM8u&lUS4JNdJjA`5ZereM`4I8X`7>p%3biKvpA7DG~tv_$%9eJl2oaeWM7J`Y6=jePnc^8N|aE-K> zGea0(GZ?Pd3W|UTlL?ZI<(6-GW0(nnnmHeJ*_r5vm$W#V%Lx{xDGK7qkb((}tQmT) zsS79Aj^d7rZ8p$&Nq(}@d)X%{3qeT>9nh0&Oc!GcGG2ri1Cz3F}BX`cGX zr4;9-ItZqnDW)q)resP0cwm|`8J$Pkkd_#FB*>)12%xpOk&@FaFvbZlN|_HgqcloR zgPM4S`h|zenTa}Ti~6ID`v0gLnwo05rjzP?B$y4uunlu+qRV4&c=~tiP>h$mglM%3afsZhHRk>ZYZe@d6Uj@sVMk3ednE$Q&?Tn3f=*#YsoXm zDyYdiXv<2M&FZWa8f3~zT*wuzXDA1aV4AmJgy)#8l*)g`GYln4r`u&jWLa1$#SN=) zHs|W6g*U3Cx;gBstPd&^#tE;A_N<@@Z)J9`LMR793aQEvbpHyB!pN<13Wb3&mez-G zD>|%wI-U@#u1QI;6>G6^lCdbcu@xF-ZTGQe7_yJ5hW$pX`ggK#C?VI$j^S_)0vLrU zN*D;oP2*4ur?3db%KwLd7O}{Bv*qQk5W2Gf*|YN+NUk*qmZO1Y$l zwh^XFImfoC_Ei=Jw`CEscyOlt>Sp+vpS{ov)Va5pI;WZ1f|COd-vBLz`>=qjr8b+m zIJUS})VTl1MP39(!AC~#XSO}+KDDJ}XKQ9-m0UK|x%T?GXE6X^fCr9>rlqTzyMVf? zs|!Cwk>OAd>aY$hdxy=VQsBU%oe+Z#Ym^Zyu@jrSikq`K+q>6zO7rSyQsGJ>TXRsHVuS8LW47YqQ65R-P>i#5r?py$CSn3#%*QY4(B-lf7Y z>Qay3zFrH%F+9JaM8mPD6;?0;_)EM^7E}vh2L0qzf&d6vkW@$zbALbr5Wof=R0$-< zK2Vkem7ocg0B&fdOre%bUv_FJ1Xj@t0I-(H&Gi*vaKZYjkkx8*)cJ;6ENiRl#jy#- z)*;4uR{svokiu44tdrrs?|UX|EWd56PH%jOlE)P}3>BbN6}1%v82|`8KyI0K0=0Aj zNFYl=jA2k_0Y=O|42(RZNJ8y>a%!nh1YGVI2@Y>2+xS#_*hQ(#|}AX}WqUl!m3mUaS|Kmw^% z6+>lnq?Q2yHO&}Z&DJc*bcDH-jLA=dW@;7|1TfB345{Uqw9co_sJsmo8NTpb7*tDG zz!q#OyuvZ6&-={J5#-M&CQywSwey!D#7#0AzNEz+DE7Fz%YUeE~TtdMTOZ=|ffsoTNb`wLC$j^aDQ@@&)1 zzzKwK&&0OVxZBg5^wYa6)OYD#@HK7nMbMCTWlzy*P|?t-wMNaXz|f4<7_HTi9AOI# zwDc<0Q*m!%Q2=HgvgOR7N&1U#4cGjq3rx$Cc8wuLRMWr)V=j8nep<`6d>(;aPJ?X| zg$;&8&0$vHQ`Qz0v4zYT5C~8ZV1_^f58whMH&l6W0X#rz(u@IqVBHbDxu1=5qkY!a zyVfdgiMNKjsr!(@pbNhsr{l0_Vvb40Ln-hD%(?FAStlOWoVy$}rO{e~Vq z*E|{1#%R9GVh;)zo(F2iwEWj_x!+OL-~YXX!cBQ&O2E)MWEyUU1pojS?5LtJC~OU) z+B@PWJ5sGpqTgVVDUKzr954!J-?aOTnJ@`y%+oavH#d&s75IC?I|IWvxr%C`K;Df) z?#YgNrVF_XBnzS`pKO|NPTu8h%1z$A1G|agE5dx?gMA+2K#H{Knu&~}IIRAa%3)=y@P3k}} zH6QQNlJkcP*gWqx84vUsC-h`U^k!)EA|D2S@CSv`7KUPuBAzuW1A4Va^-TMpPdYQl zsFB?CboRXJViNZCF70Ihw>^h=T?3u2C* zpEc*G_oC0b7U}mW_59nBK#0Kl>?;}8@51(;{pZE_+z${41pf{sSkT}>gb5WcWY`cP z0EiJKPJ}3d;zf)ZHE!hC(c?#uAw`ZPS<>W500X2**|H@IodcPJE-=g-n$pC>Q#OA+Kr>uiO^R>gb*Q8RCdwXvyUcC z%EUI)Tez7x&81t{E+(v9Iu=L^uCaPOEb?4malP6H2M49QrrcK+@ zrca|zy=v8J)^^~!ex*K^tRl2&*S3ut_wM()jKz;9U;p0xdGv}MUuNIFeP+|~<vxEwlYcqBsW_C!AU3IehbPLT#6#jIOLRD&K|0q!|I-P)R~St zvBoM3JG9hd%dPI-0uQ|OT6FP67-KYVJ@;y~@iF-D({V>0d-SnK0D}~ANFs|wZ2$lU z#6yfSnpDu1Hd4uHmj-`&P&i$F*+v_sl7p%psW5ErDypKJt`D%R8!hiX~ruMQ+1csR^_F)-h5-0d1k@()0D%_41$U{X7F$%2vKFB-p77g_ zwFWt4QtdHW-Ll(V744Q?W*aZKOE-O1x#zaA?z~%f9dEy5mwon#q5z<1!X4Hv@oR$u zF1VYBI_hesyd0NYug}%08O}j8`}4G2?QV3^Pq+U17gJZ=J=S0M{`-Gv7k_-9ZEp#W zB>qU+a6lBC^7o+Pm)u+A!u^;z%&*d%n^pQ)H%(Q&D|Bz79`+1)!18Esd+g~R_#F7a zKao#@6C@T6%oheLB&`hXYaj38_rV9*f>RV)iaN3uD*ScLD#~jIH=5_Xb_sBR(<>kX zZFs}NC~$$6X&?lB_(S_pP>4eu68`~aLmNl^p%e(3(#1gdL@dz{T$W4Wn3nfKc3nq? z@giUiari|r-fD+917aD?h#w-RQH}dq$x60>hcYK5~;SeG18I4L?9$18JS61Qklyn z2qrTL08VnUB$@lyQyIAUf${r~w8j6Bns&a_BCqe)O}CX^)9{D>P#X&Yxz$`;T7Co6sT zzF@=>Dc>4LQ@*sdb*@vFo0tSILv%>c&5(xn#Ai$2rq6vQ^Pe;QAtpB>P>OVtBMhC0 zG!^<&kLc7RQW%25z`#v!)-jy$!`767p^M>cv`h9wVMuM+&OQ_(Whz@$A{DvPm$_7| zZiDGDWlB@Df^VQ}y$Cg~*?g5Xl4nh8XIsbCyBZX)2X$*+ds^bd0k>^PaqoBxa&cB34l>S7t2%XTo&e_NGoN>%C|mSR@9sN3i2Jp0@-f;Ok9 zoi1DjO4`=0mb$8~D^KUz5!=C54yK%~EpBm)rQTL2$1p55I@KjV`j)X$0SD)JSzNCI z6RfaWZhx8ET<$?vzz~$~X+>+@f?_wpKJ_klf7;sYN+fUsk^@9c#xy4LzznL9?C$uw z(xmHYg$!gkWB&wje60;cABL0kE*V=pfhsnn2;wNK^Q!jvM*m8TWp{b?t1G70mmQ5K z00+3tlkS;~Z%j}&Gq}OpeRG^W9T6iIwI$<)$r5j3+rjR+7fr^7R7)7>`P!qt!@X|| zjeA@;CHK+0UQC!5IA%%*n@5)ZZfJ{Lk)D29*(RAYY;AMO3*RKYgI#Zvhex4~!rH1@ zne~-n7(hkqm&;z{b-5Aa>+Jzs*y|4Bffc-Lco*r2Ml%=Rjy(u+*_`vuFd`Mz@S@o>SlMvsm6tH zV9{!98*gi!2d)dVYT~Q^9q_?qG3vXndNR6R`WKIV^@n6ZzSg@D_m;fw(VOZ``NAl5 z$Nw4?J1>=XJXZ8DH;bYdJ?_&xzueH@6YHmcf9vP}KSrcI$PwrL?@PK6s+ze&3T`+D zbowKylL~eChEAxu$nqTb6SrJzKKZLT`lFls+rXaFKM&jw>`RhodlsVuKs%xmJh}-2 zq`PgX5WEW$42eN@LOUe4gQM)Bip_bq&&CF zlqsni;@E~3Jcn_32lJCbKw`j?2|cSzK1Iku(`&RIB)mv_4pI^+rOJFMLrhXlM3P@F-N=>#t9yEMT;R4lzhTt!ygwI6h@Se(XY0*Owe zMg6nIqdKy~X`;#N3184X;sCyHp`}mk!z|pN2PDM^ypB`My2^6KXLPh^tdSt3#&rvc zYs5zDYok${#CyvPa_ z)JJ}FL?~#9E!ak9=|*tUJznI+w)4f{yTV}vu405p(YwJfq)5uL$T-1BUjM5{N#n>^ z^vI7aLIMy;h~TDY$wfKb#Y;r5Or$#!0+pxGiiPw;m}DNAL`JT9$5doAdDO`k<4H60 z$)B{wpj5+0>u75^d5z|_dfxJTri zG38v&*h9jR+$Pq{jmXnQ-+)ZIi-zME2kgvFbhITs>`rzp#qfm3c>GNBEKc;K4E0=3 z0c%h9R6L^DrlXoqe2Y%EJO*F5!~EP&%GAw*;|2j8(9zpV@q9n{>&gTjE8|R02R%aw zjZm`_kfT~nXW>33L@(JSNc8eW{R9_q$j!Q>N(TIfV&H@UjX)DMP@H6u745GUWzZKD z!Wfm&pM$M!GNn(rvy*JQ9R){U2ve3kr}zRXks_CSFqv3bgd{D|nq*SXbkZkPt|*nH zDdoT_?Xi#8G%KB;0YFPIkk1>v&;U$H-+;^7B-6R%ntbp>u>a8q>1YK`7{%|rLEuEu z3LMTk6{|Ykl{?+QJk2%VgRp(Polz51E)78GlumHe zE%FP8cj!;wvQ=CK8(ABMPRIm^1lAK1Rth9fIW1OBT~IqsR_O~>H0v~J6;*!Cv1Yw7 zew`~G(=>stE(C+O2IJF-pix%cK4|GuJAzf8fWmC3J1EOdG`*#Ak&1Ab1y4}dyp+f{ zh0_$(RC&FpV}*(B(4&+#OO#lo^)upuIx|Jna>)vNV4h4E#gMgB ztbJFS+uC93TFm%bu-#K)^D$?AtFnz-oK;)P1z1y+S(rUofA!g)g|jx>2rM*&9oxG?FxJwsw_V;IQ!_VX z+s*~pf`wKEn_D0&-HBL~qjJt0T}zZa+F12lMgLt?b;^fa1+K1`gkNP>L*m_LRMFqX zq2L|f!z*5~HQ3P2-{c*w=Ve~xEv@NIRZcrqPqSW#(B6i#CLHQD%;UwZXa`V~9-oi2%q`pKWn0mGHx(8#X;syc z7ghE(X_Ei}VZqo9MV&ENN zWToPr3)PtI)4D=SqYOxIoK6kxVQ`G9VgK-yY-?jTHdn1U(mCc}k;U4q)ml9kVLtX+ zKdv}H4p>13Dn@BoEW;!QT7DgI=3 ztI|=1w*YWJkH1k{&Ac7CRMyZ$cHl5Az(!V7NS5VX4U=}TKbmt=b;wF z6EkW&{*I)!p@D8{GBaqX=B}aKMym#doM@c(Q3XsvWL3uLuYTrF;pwKOibiD)HKkf? zw!yV_KTL&Jq<-ssTI!OPYX_q1x=yVExZ!S65FP!AGVp6&w4+r^WWm0+i#=>|1(U3Z zR76N?$JS=ZepktMYsyw?lE&;w)9lTzE2(BxOaSfB9&IM<4cWYDW@xoWt`PFIrCA1( zT;k4jm2Cp;=i1gmUe0Z@vTWWiGvELI?Vln5)imZZ003K{LsjUY9hK?!7Hm9>-RB;a zS)PjY>rTYA+goH@D}f%GD?|fk~U}qNl*o!aPPKD?QMvL76i2U zM(mM6-&Qb2IwtDJCd8x0Z2&(V0UvPBDsTg@rUD@2bZ(r+(a}pZ+6c$#R?}&%k*|!+ zB@g-7{NCrH&hCE>=@Acb?>6xPL-7>L-yrC~@$mT(#s@Zah?qcL-7;wJPX% zERE&^>%L@EZ);Uw^)qjESMMxXkM$ucfH#lyhxO7n(CW4mid4AtX1>Gbws5j;EDQ;o z$GYaBu4C*TbFMseX1A3Re|CS0c4?;|0yw2OpNZY6biZypTc{X7XlMAgO{EZ*%New( zbD=v(cXbz$J0|mGfA>Upc6p~Mdarj0y7w}^>Ny|n^!9g3mxSb|aaX=g5c?x3)A6n- z_G4FEb~ki@miStU_lo~tri;h;@gV?Xeg(v#^r6To*ktCL#s*!WkgNgrgAb{ap@vpi z_;eri-F4z1&tsX78Je$oOS*ZS7axUg@%OQ5*!=e8E{<~o_TfwUb_j<*Ut zRF9RapZHF`Y^)a;t>1dtApk~zlnaBFQV4lUuZ3{*K7l9sa#RjJlrnYThGF0YbO&=& zCyR$~cOjp9d%60%Kcl?Yd%xj(IDBcWrgNx~a%Fx?4YhJlQLM!;>lK1+wr}|+e)&v| zdCb2P&ENd!YIM*4qylJ!(06h`Pz4t!{#|%zH2!u?OnhNvtglgfPMCz)cYBeoe3+;D z+s}MO*L`K`{NDfH8}j~yCr^+C0aLJlVC4tCrC|M}M^mVcg~OeGT%LWBt$pmzet6%0 z?yn@e7GQt?AaEeTf(8#FOsH@n!-ftYLX0SJBE^aa2{bBo%hoPi9CxWY#w^&elD>|e z^rg}lGiKPZW#bkuoH}*w?BT?DZzs=t`t;qi$E_8iLU-=)LCR-opQePKLPaE1(IiNd zu40{(No%IAUNdnDD|RedPiD`eO{;b-+qQ1s!i_6;F5S9z@8a#WX)oWte*XdvEO=8F z1cnbIPONw_(;Jc!;Z}m zqefdLOWOYe6pUF(-nUT(W7+a$Hf`a+*}R$4UQeDs^|fo}sgO~mO8GQ(3iYW}L{zP2 z)ynm&SF+~E;!Ur9J^S|V->cUp{4uqgV7A8cSEz106OBuZR_8V}*=|Y@L$hD(fPR;G~lRfQFGu=@~EyYxJ z+i~aJcj1Xg-dE=V37?Qd5?Q2?@5xu6l1uJ&AAnCn8Ksm{QhAwx2^CmiXBb|{;0lMb0Ala~(WWR;t8+Nr0XN_HheSrRxPsh*W4=7ScNs;ZW$vg#$N zWzt&fmJL40keaV>)ZvFAib&^h*<>>liYf-hCr;{sGYlbuURS7g+aX#NkBc%2mZN1+ z+O4X0uH=N0@NG5UM0YH2`qUERA2#aVGQU+?-UIJf>4ZdQ96An#72Hy;uYLOt8v?-ppp!!^)( z2)m8+BzKHp?50lG*ha+Qp}}}C=X*QJN1tLeiRze7bm+^G`b?KX6}m8uYGe-#V>q7~ z+AxlCL>CToxU|oVDsL4MoE}-TJS-hCkVb6a!Hxk9H%V?62n!Cxwy}+KyrUc`QcgUy z)@rM=O$2hp(ManurP| z`$pLn6>eHVhuK)<+B}RRD3}8lam=P;x=GuY9kUA$Q z>S#tuPm0oL5(1#p)DcU2WX;sgG^{SHsZ9y9)3c)WF+Bw;TiaTzYCukEQM(N=fyvRO zesq!{O=cy1628wJ#j1nCs#ZTKP&0n@WMW%9u*VmosvlyKygjK^x!cH2NDu^v8SQ%^E)j4*dkd-WN=SbPh0ynr4 z$!uIH*Dxpshb1K<<|FUvRHyRfv_EMFH-^E983`(-uw5ySyh>ZN-8Q^y%WXGz+uQTf z@VCNUFMAO}+~{GHvz|qXa*2aMJiRESKxxr_|I~^=VK-WwG$B{J+dkA?Q@jce7kO7{ zUi3n^cIs^}h1*L23Ch*DlDl5jJZewnICW3^^(%D75sOUpQ^1A_s3I0&V3%qu!3&-- zr5fzU2S+%^benL6dkowQOE7Yjd)S;P7us{wvwI>Qu|EI)@gk~Hr?sxlj#kMCN*B-8 zeFmj5mm_847~)vRVxAd~eQf5oUUse!)O0~W7X|`en!tJdV#zV$|^7 zE>|z^-9c}><3eZ4$6+?|qo=FnCtv!%R01sCR1aYsC3ihxF7sj2JShAK$1G%|>}bXt zQH$_-#$USSp`)GX)s`XB+fM67Gd=DSWr=)!6AVR0FSIX*q>4Jtlbpb)6<_>1*m-_s zvcvf7X0Sd)}D0F z+AXZbT@Pb>D^~W}_XO?ZhWqdTXY#xsziTL$61(4h7fv6hBaIL2^a^W72pYO#sMba3Od997?;xl2NXRZXm!*%WCN1DU-EH~U)e)A z6oV=7fvCM(2O8etp^&O&-w38%0LBgqI-!;59t%ognYf_&35PErS1;Vv-W^}^wS%&B z&`_Y*24+{`5!m5@;Mw?}5^@|9GRgk{K4Ijk;1tqfK~!N&&`ASoAr0bH4)Wa&O4Sb< zMf7oCN`+nPr6JI*8XFFs8zx#DLLy|4lMdBkB`U-V_68Qp;QA304(1>{?8IrwU+3@> z^bO(k>EGDR+v{Op5;9`PIij^i;w(0XEWyi{(2^y_-5oN6N}xmoGSUTcAv zw89;Tn$8j78KPmTsbSE)qO`>#+tK1RB8Dw4kK#axY}}D99uAu1qCiyIN&F&wRgEWp z;ur3Oh_&Ofb;2Md8#8K`Gw$CiULQbR$6;L^(bY zd=cL;z5^u1+}|kieOXbTR)nhRMH|MOiWuf^cNdo393a0QNCJ}Yz4-Ka89M59Pq*nsKV>Toc z4NLp|8c(j}J6L9?aSs3Kc@RI{0bNd9J%Z-g(c4lwN@?QTX|hsks^&@NO>7=b&IHZi z@X>1yk8RQ>SV9DDLd`=?Cd^5sJPg|f`U#-OBXSnvMlL6+v7$agr&B)VUqmH!Dq29s z3h?aA-?U`k%u;w#RKXpbeGQ{Tu4QmuOg@y}IBZN&(xY9nSW?nw;*sWk(wKBYC4WMq zfBuo-6weTOr=x|Xf!dO;p<_p(C3^m*dbVXduG=@ng8gNrXIg0bpdoXLrgH`fhqjuB zdJ~Aop*D62L$JyoB?!>?(N(G_!Oc}sr6W)Fr0WK#Ypke)}Hvf7$5 zRGXsUBnC~Kvfx~0Se5$WZvtnQvdFrP568@7g#zk)KEj~t<#QUUY3658;l9(?}kQ92`re(KLDB2$j)f+1>O?B}VXSE{ON+(pev zwA_NOr9{#yi?qWx;2DJ;XMFBxv-v8h2J2o9E0-1PO(AOmD(kW?UDUKHbLC(}GH9h* zW=~Y>_-LT6x)vu<-v_GVr=F=ahU;yOYm<~K0iG+mnw*e9%&S&iwBG5o{vA1FY8_y! zn06|e-sAt+@#|>%>%THqz?#*-eqX^FtiWm5FuABYQtG>ADLu&FI0&AeO{})&pT#ES zBV=s zJvi;UN$4c(?WS_-%r@J_2CmjdR?E-&FOBKsb$`+h9^9-aKoZ=_knOBk&xfI{V-LnU-C=PoVy?ib#|Ea@6= zshzH`+N}EKtOIM)>q_thQ*Q;+;kaEIyZYu@(t|vNaObMq=}E1`7B9sfue>QR3$v~Z zXA=y^u*gv`4X@V?V?!n6ua%-FJiNmW_i)qNSuqsC-WssX@~WpQFA^875+f87H?jY} zJuwsq*SJXn9B6{uYD2q5t1jTN+b$O&DbwWvk1@^eD>O^<8B=pY#d6PH z^DAYuHnSDEp{*aj>)e8`7QX{HP*tF)v!C9p-~QP%!*eU6%si)YJ>N4eKLr1j>htco zXo=J@4&Lea0(3cR@j2M&7#?)#HnS1GGb^vIJVP{1N%Ta^T0UR2Pj%k=;qH}wbU-uh zIfMf>bb=|DbX~S{3M=pmZ>=l0bVNh2J;QVy$@EN5RPlKtHmm`I<}~>7@HuEhs0nfL zvhzt--zqosN(*aJqmnf@b%Z@NRHxIc0vlCZ^;H9OL~4U|O)cLh^dQe?>W($@2B}%U z@Jpw)nDta+vbCArnJpQ!cikNVAJzb5B8Y7X`>!? zkG&|8dGZ$LG(GS`L}r62a10=OH7chv`a*MNlXY2hwi==JXZMzf>dyaYQy2kQaYSwd zYUeaR@Pk(C0wl;IY}@P35wh47HD+t}qvUpHyDwAocK-M_oXU&xxFm2V>3C-dTKaAX zn{!Sh*>VFig_`tiGjwg=Hn?8*Ded-l|7Poyv$iRO0))Hw34^sNL$ig?ck^nvZgY5t^Hz5Q_<(0+(I}>g2WWOGM7w%#V-xo; zfI=ngL4+f8W$$&4KSCs2I0MsnkNdc11G$ImHv#R8c8a)4B02w>I|NT_E`l@mTqBt& z2*Msrc}c%Fg`;kcYx$P9B5r$mWBquTr0V z!;A}p9oTu*<~4j@ESB%NU;p)=r_p^2dQK6#VPEHo;){s$cbcO{L>zeBjx)pd@H=e7 zFNDISV|tAwTll!cUJ{$T>jZkbo4S6hxxYJ1FNnWP?J#b9_+u zI>WJn{LmYF${YP; zA$^%RJkw8z)6Xru`fjCbLcd@Al%KNJXH^1UHpyT3*MmKvgZbDmd_|-Ez#%w5(<&DC z<3rvgYw)WAL56?+w(rR@4-6DY!Qd`SuN$D>C?pzu1pI<^#U@0|Wqp0|^#1co1Pig$o%jeD@HdJbCme(v$b_ z+&OHZ=GgIQXAj6dd+LIrF5rAwJMb@~))RH;*`R<(K+YgVmRJ8km%6>M0sW674?)CECWwQJe7b^8`>T)A`U z*0p;VZ(hB7`SzvTke~#>g9#Tld>CUnd+1i(yLtEa{Tq02PhHIxH*OrQU**f0H+TLV zdi1`91wXca9eZ}|+qoOAXi*}>hmp7G(o~6aNRcE*lQzuBf`#A~s z{~y2r1@w=&;|x5|tK_6xkiiBWd=NstsOyfx3N5@4Lkqu)Og!_5f{Ul5>iQu6uymPkMCKU8K>WoA!MLY3573G78zMAYKQ$|b9B&y9$J^l2?H{%?2 zPCQLL71dM`(DTn$U46C5hy*3{qI0qdMHBTt@=VJ;PCO}6m%wbzQrKX8q)bgWJqpxj zoqcvIQAdTdR8_6L7Fz~cg_YZGy}j-nSqBa89c^@}1j{UY{WVx&W2#il`|!(jS!SWV z7vFr9l6Kl2tIZbRfCa{D+iwj%7-5FOt!%uEun8sIT^n^2QZG}Rltp62JT_Tnm(>^K zkelVV-vj?080C~zE;!+qT|WQ#A&M>%Pn*m@;Y{LQN2>U=FW(LGQcUx+#N$mxJ{svX zNiO-glU05iYHL}3nd++loKj|pGAa+0&2aW?y`9~&7~_oVGgfHYh^{y3w%rbJX{L*F zI_kRZrgLhm_1-&4%P11>oN#_g6lX?t-eVA+S#tMhVuPM{=(Rt7oASzei(Brp=)Rlt z&K=B~@6bip?wyL>S=dnX=2%qSUK8)KXU5Gg_QkY+BD!e)EZ?2?t1!=etj#?io_OX! zA07GR74v)M)3<2_ltDUTz0t4{A3JtJ%7*vx*&@$Kh$m$Ufv)qn1I<=uau zq48$U=oe4~(Y1BgVbA}5*t2VgTy40U9Qg=1KJ%ULE5cjf0vYI*_Q4N=5fq60Y84J$ z3_?WItDepV;VTr8ZAIKe8vt)7K%*59g}gf;0?*Pw7rsy~50u~xm1miWSj14K$Y9}k z#lhEoaB*du-NtO^6WmFTMk!RH2V&M`PmbdW@dOeEqG*REZjv6nH*)DgjlreVGlTE_T%(Yk5*3 ztz;!L0RT#Y@Drf^Z00}nh|OyLiIfH{h&I2YjbCWyGvXZQAj)a670CphkW}Y7ff+t{ zesp9kG?_g~YBzi)M4tr-=rljs&y`NonFOWgL9Mw*g)Vev6yXOy2Kmr$cJMPCB-GYyPQo0-tA`qQN?UC2#o$|tZkgrPdU zh&G0y3tazEw1*>6r-IzZL!)=NFjADL79E!3(6~`GHr1nkRV|JH+b_Wq7Pb&F>^@Nn z*|W+ttctyDSY>M4lui~_h;^mRBvX;VZPpZTb=^=y8^m*Jtb3;YC2IM)TI&v|wbOcS zY`MFj*{W2yyv;3FAIsA53U{}_<=cjuC#ciatQELgE@+#p%SJZNe;So8Q>p9D>;CsY z*(FtX32dtGZnd`6DX2ip>)4hW6sGE3Z$-#89(oLFkeLZZBH+s0<{s6N@yhG>*x62d z0T}f0qWAJRg2&r93&D^Y)PS7 z$>~;ijojVvq`-U9^Om@T>V0pEZ-=<&Foh&4aUMZPVjHEK8He5x4u>Y!;IhMLlGCZ` z=`Nhv4tF@jn>KNqB7{pnM`-Io6m_Vcd&?eCmnmN{KU#!vo>z@r_&0US<&6J%oL&MnzqF6!6~{$5S~ zG>87O@BSw6F+T4XE+QM&Knn`5|HzLX>>&V)f$>(3{o0Sw7I5ww(A6H0ETk<02a5t@ zkTL#F73g6b1i=&#MG{UgGPGe7esI_B0R)>t1U=yZoi37~uJ+bV1)Kkk1%u}Wu}%i3 z5Rf866GA~AejyOVj_?ex2UP(SM&ZXO;vR%h07Wo0@G1#uZwc3^37ydT<}V6ujtc7# zP^_>9vq2EV?j6>^3&TSjv|(Xxh$8f%4BMd{SkLuJFah~%2~+S5-1QBKl4c1^XisA#D=T#}Xwj6K@eAB;!_K zK@~#b7v4b?enIg3kH_voACA!w1Mn0wK_=|O1eNU-V^J345CUs)-f$5c38K#etz3xF z&&se24bdLdp&4Rf6WET3*02rTP#PtX8e7mBuQ405k<)C&8-xE41nmJF?O`3jK?DgQ z9hXW8+Yl1l5yfT^EGjV`IcXm0vCp<45Ju7e=s^hWK_9^p5moUKY~m4~Q30dTAfXH) z6KEl+ts$|I(=zZLztI@?(IV{u9KPWcR-rZY%jF!g_6jm2uWKX|q$H>9Byo|`hG7j> zvbc=#6kqZl@&N<~kP~e3CI|8nCvPWt@*Sb@Cuc1vZ!sl{(kP3uCBtza+o2qY@EjGP z5v4LGsj@0pt0!A1D=lp+GqE23&?qTV2$As^!BGf9FcJ_!%_0vZ+w#BOQX%6KhUO9z z$xk9Dk}pkB95FHxi{Tt&WX;%c9T|`?Rm(6Dv(gmP4!{4>8}SkxPmwRn5GGS`6P{8a zF_R#5axEbHK&p-V-uuilQyZ$HmMLd9g{bo za|oN^6gnX`1XJ>0(K!1GIg``HmU9Z5(*vJVJP%O>FLO9wu{g7Hn6{HU3CufX5Ii|e zH^=id15i4F(>j+8Hr3OT*t0#k>pf#IK9P?;dGi?UvohVU%@~e9;mJS$6Se}B0tYk* z3-mPi5*3SKI`h-b(z8LA=s_VA1|{_VD3m2FlsD~jLtRlnHS5SyVZI>8cX4<}pHMPF2jVpK*ikw$&bMsNQVNB6M|^Wh%slQLHU8k;QUh!jMx zk`j*;K$EodmJ}?T^faNgDQD6WRE;4;DZbxG&6K#ws| zTk<6lF%f6-QP+}^D78|xlol^_Ml+TB4E0dMvNXps6`P?{(NlL&)qqqrAz9U`T=i8q z^-wL+7@1NZ|G_k)G!-dx6C%~b)Kph1$5MSYF@Y8TU{y@@F_O)SgG$R3)oou6C_H$z6$zb8qVh0st zu}xz)7Dc`F97`2sL)Jq_Hlz&pFi&=1Qr6y9mSsEjUAuy4Vpe9clV)1gX+1}2udOk0lV6)QM-fo~o8d==LTF`{YVXBrua-+880#wPoWipqHN7}Z$YJR z{WfXRQg`L<+lR&u3=a`&u5+*ULTS9AHYDWhQ) z7PoO7cXXqwbWgWhQ+LZ&*K&RAE??JO+W{$~v?b4h8fIY<7&mnF_I3dza&aO$+RW4C>mP96gYu-oA-H@CU>P5U#VBitk-w%F?)M9S^4t9OfD8UVR@U^ zdCM0#q8ELYNqu#UecLxTg?BfRv2(%kZ{Fd1?^k^BSARq0eEaue|5w5SIDqAofD8CJ z}O|hlAsUeHe9txU7blh{YpNC<7jl zxIlYM^%fY4b9jmgq=&5-Yq2pS+3 zewR0z_nDtz&6@uiWdgd20&Sq9rc%!vuN)VA)KUzL%_>Psgq|KRpQTo{?dZh!VrD^D;Ga7JQd3D1D0P~?85PFV1 z@WS4KoO{}*Ll>x1TBwIQg^OB(jvA@qng6r~&;Sqw>G>WevV+f|iN!aXO&Y7Sx_q{p ztMAFHBM7Vq+Lehprnw0oocg8{n*2JLn{2q8-&&gIRj!AFuIpM=?i#Q2x|`owrpel@ zpPHW4I>=@hvETptq!(MI9lJpw8-ffaBLp$fz%3kLdaPUdbOz3Hr5b+M@-V)4Hu(I6JrDT55Uw;Tn6pRk^#9rvL=; zyF1Rf0|B+y0KNBbvN7Tz@-eVqS6g>eKL0Psz}LES`=@zZyYt&Q_B(k9;Jd$D5>&y0 z_Any|oUdmZBM$nhUvj~F_N+PgBBz9dH+dqcgqxb!6rk9u5xc_W`ob~Xcr<);IK0C( zVGTYm4#NLV5R7mC4BWNF1G?KgN7>aPiPuwQdr$^tlRLP11V@B%e7@;hinANPeH?Fq zTy%sybyPvkK|vR^K^2PosGA%jo_xh)I94q(AIS8np|qB}?UmkZa3*1Sb6m&cn#aeS z%!|a#&-_ph#ZbV_(Z5@=<@_B|T*aZYX%R8c^}*M84e?|N5+ETH1bx)MT(_Y$qI*2i z>21;Xh5&-D(S`2kP+J!y9jxV?&Z9fJvlS7S@*X^$N~Q!Oz)jpBVbDw6)D0b|Reja> zqSZqO=(1rOuz~o5PZhqK#8bP`Tl*pCTnGtx%JKXjTwAYCJ2NK1*pHpi!92|Qnc15? zFP{IMZ*(EriBA<^K@$R@yp?;kx2EGNLA@>A9^#?SwKuTsf!rq>+`w1R8~)gnJ#P=) zpW!`_<=tusfEnl=8#qqd%VQ0&VGV>a(6#{#!)~Tyo8Sxn+c!3u?ScQ^xJm{m6daz^ zAAY_gUd$(+;tkB=t;QFa0UNO4>ot{1~-5B$6 zVW1@Di~Z_r-qg2#NV^`%Uyt(b?+_KZX$hWh5aL$o}Vjf!;$e8~APVQ5)_9O;q!d=~W&@ zFaO)~(RVW<*ouAhN#EwLyS`C6^;I7(Sl?v`01#Y%@qbCtE z2M?ZG$naddhwbdyix^R&#fum-YP`p><2{S-8ZOkf&6FlcC?`#Vq_SnpOp{<{nn{x; zr<*u)>eT6}r_Y~2g9;r=w5ZXeNRujE%CxD{PMe}iol3Q;)vH)HbwR+jtJnXpV8es;uHCzM^XlEpx3Ay7fCCF2Ot`S&!-(y!0mN8RDqFQu zwb6x5ph1L~6E=i6k>bS98bgb=*D+&Ah9QUP3I!<<%0eq$%9a_krp=wWcRJn8ySMM( zz=H>cnzgub<6Gk@U(UR_^XJf`!%a-Ry7lYWvuodeoj{y5Z5RW3ENKlh%9bx{UZ@Zv zMbDl=SDY`AK4^&?HHJJbFWhRBt+A9yY_h#nn{Bt{rXVN6HR#}j5Jor@amQ6?VN}UU zr{RVicIe@UZE+{!h$NP1;)&SB0mowErIHFDzMMxHK?Z52&_e3C$Jzfw^3~^Kk3CjY zACDk4M3O0>1n3%UED<g1@Sj-3U{2 zG-YmCW~%9?!fn~*r&N7e=BT8WYHD+ArmE_yte!cCo8N`Un4GE9;tP7|6}0C$efsGq zp@Du@D6oUd_h&l|W%J)4j|RvRlLe|oTcw#?iYceqW~(hxo`UMFsHcW2?zrS4(CWG9 zrmJpXufDos8?ugO$vtgwgbnB)ID?~wFMD4IOl?2=7R zN-4F~TFcY67-!6Bx88mVZn+?bEb@lymTdCLt+pG-tKgLZNExU|Vv8*?Qii8`?Dg8t zzlMee@URud(-}oMo|AAkCNb-B zmp!7&uc`oZ*5`MVP~%S=4N;9`R9bqfwr6Py3E*PG^2;FuJ3?@_s*~%8{d$}4*edH z5~-sN(wb;;_|mQoI3UzfH!kJl3{I~6@=sabG3J{`FFpUrpjU7G^>m3ox{8aHQOfBv z$5>ge3z0((-mm)_JK(k>>YnZOsiSNtl`v~R@BupQB&EjBg#1m+3!ngpdvJgQyu3Fa$izr)t9!>d#^Jggc@TDb+mZW<1ra|@L>=5%jqmOUJf@`t zTG5gT#r`+I8Vay-1uPEq63D|IdZ>Xw3?jN7D7uvyv2-n(SumJ^L4&*_9OFQr&eXAv zeyu}xvTL8g_!W*~FsyKfE1Vb|mOsVqk8u>M*yC)-M&;o!hgjL659dfnR0;8pcuW%! zitUMg~m>nKM%9QjB> zy$^SI8%8T8VYp`A?{|oc*y3b3H5!ugn%gqvDMQ7|ZhF%#unea-t0T)4BIXNHl5_*>E$4U|S}V)LMz(&jd$lFDy7^qb?1 zs6+)*PVE_ybS(qK_ujTdY;41w?+ix=KWN2Y<%pPDEMc#}q0CGa;tu|NrY4!j$$=7- zS_Va^POqfUg=*!YKn?0wCQ8(z=B1(*?PLEf<0VK{EO8s!2q_fh>95%3s9&zT=gxeU zPi`pF6feEQ9%4FE{5j2&6RYMLcgof(^>m>P6{=i$Xw_#zi8|?G7*GZ2NUw@MMcy`jcbjc5qv6|r-a)u^fGKgy zN=8W86pc7tZgcm$o6epxy3-A?$gKZMU{_){yXOQ?YBj0~O~B&2+n~lfJDAm9S{Qv> zq9# zM-`#CnS@0u((;z~!W7OYhZp*6jO1;#m)F^$Ju zsvDb`fjQnWkKvF`Sye(6u$TpShfHK}%yzaMq%b+6cs@jOxD}>o&6A-VWyemL%A2(B zfTpS9%zF9J+qww{i4tme_Zi$!nNg)R&N(vX4!ictiI`4H*4UV4>L z{T##_f*a*pkC@6O4qhiwoL2wLQVr6;77CaPNNGzO`$L&dw&ymTW;I{bk1JY3Ai68+ zVbF-qQav>yL%QMi0(a20F7#Lr-OMUacGot&?D2jb>~{BL*v>2VvFEKJW!sxbRiAWrnac*7LE-UT&e{(xMBls2Q$!N)=v(1e8-LJbMrd6pyaZ<9}REI$(!CF zch0>>ZnAvqyVEL*gBjd#1vU%f%@1}hjkKW+gS(R(IHv}C0Uc;rvjO7Zwl&55X>Kcb z^1fQmFS|Dm>5gyh;~|fFTu4s!bea6*C|{Ww*kFe5z(Eq-g~fS(K{vo^gBlTr?P4z( z+&9?a4M2~$&<8>8azp=Iiymd49+f9 zUa$N0u75jN(g_{SA_&fYcfc86=!t%!+H&vAmF!yJSclQ1cYWdbwikj z*s+2u*nYf+S4s$iT=#*F2Yhi*1tK_yQ&@#CSb$MBgSm!xN9To&_JCs8Rz27y6nKW_ zr-qJL9ecojZ0Lr~hE!81haWfx@yCQAIEB8)hj2G&SZIJ+NOUq)U)-m6I0%N;a)|tu zh+M;nve-(GSc`=biISLaZD?{xSW%wXiE@aEzo>+uc!i@VZl!37i54K0wThvngRY2( zWauTcSdIU3VT;%(7`T{=MtFp8xJSeIfl8=};rNNgNP2{ZjCBV_=7x&;bA!&fVyzgB z%ms@VvWV6wb=kO&f3c0+xQo2_jSp0g1o?}gSbemXjuV%Rc1K?>G>DAGck@V(n`Mue zlaCk~7W}x8d*P3{2#^8Ui=tDIBx#O(Xp9P}juqEJC#H%R=Xw)Kk2nl5=d21`Fda3wH5*d>+xl?9Vlaok^ zyeE`Hd5)qeagW824Ov#l6&olfHNF;xPpMN;IcZdRlUHe#S*ew}$bnermElN~C|P=j zMq>XCscUG-luE~zZFx{{IeT%5k#nh&T3DAp$${<{fO<)cd>MCjaFj@yeeH-iUC4li z2#tq%O^I2CiAP zDV+@&h&8onySbKziJ$qYpWMWsvgn`x382R*p#Nx|2Ku4TiJ)RBVhUQ8Gj&`ScT@ko z*_#qdniN{07J7ylnvKL2pd6Z<`6hZG>YOM^n^O;PEo!Qx<0mPkg6r+qd zqu6+xt9hf7NPs&EmT_m4e(6|RifDJ|ER(Q+#Pfx$$f8I(N=h1>OlpQsYKu@Br8l~P zPd0j0N~NNRr6a1PKx#iiT1NLJrt;{NWm-dLx}RwZglk%h|5T$JDy4QHiq*HCbb6(B z>YhKEr>PfEDSDlS8KHriq%S%ZHd&~kb*PW1r%-C6JPD_D8mX)*sqIOrcL~xU>Ke@$ zk2$5Q1H`K^`m5y^tZJBgw@Gnzxd%qMtmq1E&01)cDyz~ut!7!BnW|+*dY{_rMxV-1 z-dcg+nub+os^eO${upIgny$#2nWe|B&kC*4T3jr-dW+L~D>^0jYD3%#s{7iA{VIgi z%8mf5p{N>(;a0H6x~vGht_u6C(b}*8Wpqm!rV^WjxmvMTbFuoGv9-6c9P2Qq3Py^` zlL>pWLQAqiYisSwuq%71N=cu$+LWA1vyo%77=p7otFsvRC`vP|#A>6DRkU8~wePvG zE4#8nN>h0@~Yc*2)Q*SGHahrZilePcarjI!Y@2R(68@6K02S^*OeE_+Ed#x|IsZV>jh}*V` zySP!;xaikHkXyHkil?<{xqExG?z*{r&vLR`@Lz$~n#n(Mb? zTfY+=xJ-+^D)y%ttijsiza2bpA6#`x0taybJX~BNFX<^qMO5_Jj!kRy`_9Wr+jZK0UL>MR!YLUfO|i;9L@jIT*tYr%e%b0^Qx&@Os2tH zy2D(|gnZ1(HkiA1Vu|+5g@e1&yv{LP$9KHR){M=3oMqXoy};bfnIg#GEMVe1&Wdx$ zPBXyg47qj8&azC+xeU+NObE_mMuS_=g&WHFoX@1Z&okD~$>tJ@!$~xC&X0o7>dej{ zozM$y&4{3YUmCG!bi@AxITP*8`AWS+$<=JmWXO2>8^yq+vhi1>}dw85Ue4fre4Q zrL!57u{j1rA-O7+w|@%-xQk|(b$C&z>Rt2S0!A|UhFrTAUA;y?o(v0O1WQzx}b#muxPVI4~LJ3I$_XmwOv#zDiU4e-~5#F2h|e`$hr7?H!lO{;qcpg=8s( zRju@nuS8w0xVtza&rdpCE+$p2I$tj8e=Pe}0mEk|auCLY_1646*GdUg%e&TS<);qm zSHld~CpS0Xqt@H1);suH>9Qoa{*Cnh{UY&fAgOIYK~qEo49(oFsmm`+d2LZ3Zq71n zN=eo%c`Y$!ZQ}j=yTPxqC2!dHdm{j6Mr~8tCu-}sYU`X~Gs1pDT5qL?VY^s<`;kHQ z*+y?Ze~wdXQ66nc+-vtJY74Zv3Q@ftakI;DQ37O~g<~WE{o33?e%vX<*kuM=uF4vx z@7u+T-enWmXC+*kliwlww%7e{>n4kjB6(Oh2Vg)ds9rmQ^Hn*eRpewL4LX(m|X=_%09|sIQ*e-sK&U~omHjXy~DD3 zDD-$}NO*)&y-i!^YhAr=)qUi%b!7W_M1Qp}C~$nQckFzn!LnmlzB zq<<1~159@xmxCzhe1kM|-wwgcpZ?n!p{gWr1Q!?EOsv3kBv;G%r-It@) z7vtVLPnkP2(G(p5m;9=iXr|{)-CP@6R~uKCTa0^TjK}*5v!b?FUC~!4rsv=5u9Ww$ z0M|!10yqD>&&l<#GlvdeqJ`cFl{1xY=)Y~Tkep`_-mD4SV1s~hz1LHp7eK}1g6^C0 zg`0gxG-RS%*0@`+oLhP#SjO!O0;B^XqDvB{yH|y~+C(%epA-7+dx4x=Mxuw%t=sQR z2XnUf$I}buq+Q56%^;?f1 zf1lv{%_Y~c!3Sf^zm=T#@tCE_?YFnrw`nATStKzQJG3Q(f2%nk>)Rg^2KT$5=lvd` z^=a5+ySGyx&>GSG#r9`3=o8>4v?JsXg-9rts+1=JKqL?d#8%!D3&&v4A52x=mxw0d z_lCq#As17ikk1xNQ=y=eWnd8Z$N2#|l1pdP+ZuHHb2yci?fD6bt9qi8FCIlGp00YT zQY@b(5G2z$Pg$;3r9YIecD^L9hv^N4r+%SXZ~8Y|JVX6byOC_OI|xtXO1ItV@NTe! z_4sc(`}5~VhK9Xre-I*(M5gBN-7;MQLGO#COOx>=27}>Dt?$AeS^PfG1lkXJeMMT} z5?R_!3iGsBJ;4MzPZujuB-_LCYIkN^U0xt)!pFMiMKsuLE$*@l(KJI)O-nUd?m)gur4x0uorwLhbv({aSwglb zVZd;=sE#bwRw%qyO}DH9d9u8Y4l7vz_W*J_Dq>GA(wJr+?d33FNOX@tfLk?Ot@psJkWWV0YRNcGUn<6e5wo(g;y^XOq0O zHbf4+)*BYmNEI`+5GHws6;;g0d?hhVPA(srAW#g(4?JxBuMstLp$yPzB@yMQ*pCv@ z7%qqlt58lPSYUP&-E(huHLY@Cc_RZ^O#Jia85m{Ls|1#Z{~nRQYO zU%;(hY0o~igV#a3 zz4GkR&STnp*cPv=+8j--W5E_oCADvC)=-PP!Wl(MGOXwc-;!{}M5&c4$P@Er8%`)# zc-W%+L9qo-@1?||R-MNjTD;6L4K($wpUIn*Xu|7dq+xi^*& zzSH2_!Byvt*OW>Y8LQN_s+zGkc_;>z!xp)Vmv4=iZwy$e6SXdqIIF1|P|)%Z@u=4q z&s0W<(B1a8FTFP{RtYzm>S}Omv$) zkPjiVqQnJW`Q%P?873|2xg}@x^!9@z=US; z65nV-RQx*#{cazg)ntsBpy|pK%o1)|$%HFdH55Nk*J<4OQ}1!bQcztlwu9Y-ly`De zQ1tF5F_VRWGfh`9xIXF!=OKm0>iA5*!(=`_C!<}&m_e{=su9Q0WfHfRxx{_;0zNCp z$n;;DdxuOHcJtUp9DSGJKYt!4e+o#-O&mkARqgPdq+qNWcuyJSn>SfVWQ@&z_=6hJ zD54tpG_+Mxh9C0w+bk8}*XBu}n?}W|6jeOJ=Ss4b%hj^5^P1BYYJ%DKOuVY)*4uxy zPC3mx%U9{Yzgc8`b50AEzfeP4SP2Dpu7_x}PGf)1o9FS;bRlC4--X-;o$`CDiU zalXEDq|!LDnd1OD+c;s&Xk8{acdn74PLLWThW6thJ;d13QF-oMR<~2LjEnd!#D#xz z(gzE|+quJh2?pxl&3K*5ba3uFHGs@y)E4@X0tk8jXI^OewGp zW`iZ}(&pHGYM(K??cFjuU}YB2WY2H2gx}&DqqGbYQ{G>4>X|JCiGQI4Fw{9y-_uj9< zCM10Xlu7S^eJ@O5PEkT{R)G-mt{_4sBIJh9_=ylwV{;_9FlfFP#iRxGArpS@bw3?nDE&e|zG@pIc|RrOKppY`n6k+Cz;Grx(LY)P_ZFg@j&NL& zqIapHJR2g#^#k88`uHxQ_zy%`ty=|6`i0QN+Q5dq-4dcO&Na2rYMM`444G)!%ytj_1O-goH zj2L=Kk|B(?V~^_6OA+x&wM$89>W<=COOdWgj-QQCzm3ReO0~I4sYXg+Zj82`NGbgr zMM02m#g>+%A4B9Dvnm^%Y9Cda92K`7qj#5{50@rOlNPQVi<6R;(v^n2kdAwi7DE_^ z#Fyc2A6p)mW?vhVmK%?u9v9@30Yu7_>5lJM$+)|XD|dp%qf=#gD`oz@%hXQF9InY! ztBM(u_tmSWG!#}hqKDT73buGoxNWeup$|8i$Tru?Mge6x3uSNLCnAAz-|^+n=qBa- zW$))?bEV|=bS5LXCK)5;)-&WHO66!fC+8+6!&fFLE+)s`<I8@|D~O3I96sx@LYO^evSoh$_m~<^4~^E-z{+8>n7x! z@EzS}$tkr)z4H?+$i6sozZMqfq{`>h@$xG5xg#6J9~`ZM<#$#sQ3)O>HI?3^`CN%Qdc_}88V_GwEP5iRtt{lT=&m}g~Yk4 z!a1h~mC1KLGE;W)!-?)l!F!Vg%*8o^tT}S_Q3@NO=gv_^f)=JGA?`~_o=py3_DJ~2 zADpIATwU`Fdh$X^Qo@%%_^KrNlQ;wz5Crv9MWLj{yi~;zB_-rJBqTjvrDZl% zW&cXZA#%t^!6^`^DVjPo-4^kJ2pPs=NQTfYmxu|A^q5i8&z3xWc z5K1FRZmC^YoyCcRwW`sft*Iau154PbwUgc*N<=qc6E-!IS;i-&&63U zn|bd?4TVIF&c-QJ3e7P#O{^h_P_>mH)1ly~`JZ0Xp+l2lPJDh03lT$_+K{U&`zzd! zn$vu%)EHVO6sz%<;+G3c1PAJx5SnIHn%H_;bJnX*a;t(-T4@XtY*`xr95i11c$+7+ zoV%I}7qg3OhI0sJvz(S(DAvBX0X^^!Yf=Q-`8L{KP1@dH?8_nb0;d(Srj>ej9fwWr z?>riz7pqI2I=Mh~#}Dl^#PtAz^+410<|gtef`ydJl}?7$uA-H7U7aGRjfSGt0>oPP zuGJr+8?oXme(buDRl123x=DXGf;M$ix;ElI)`v|s8#h;`FxKYSHzUP2`v~+>O*fa= zwff{2sxKF&AD3sKv=<-OR*N>%OsCS5`o;oo zLx)<&1j|fN#B;kHMsEZq;=-jEgk=K7Eu@mKP=634Jo;~79I>C0AGJC?@|gjC6g>CmI~I^cERIZ2fN$kDxpV@wa=R){ zL(xPEo#|E~x6<+7cv$7?caevPWF|7|a9z%R5m<{~+}r%E5Gw?|bN4vjs8%m_FGEw2lz#mc1D z;VJmGh^JAj(;f=;%paL*Zx|YYM|YI!vY9er(CCSy{Ia{@L`I(|#Ny6o$&0=2PpKSg zLxGA*8Kov3~G4vu&{>zil3nZF+$B} z{u4jj)cns|OQd}5jFZ*84+uQ1a{McPmab7JlJwg2l|I8BFkY0w{7?D}u^a=B{VRRO z$Y10X-o&KS=?jtYP`JFe(ft$e)Ax3NY;QP*;CnKHo5KN7>i1t;lWh&h(-{mA)pF_e zrxOK=B@%k+O=n9LKL@fu+MCbU>urvQ<=X*;-MyeT8sdQ8r&L)0WM=W$HsNEaJcvUR znKz+x@H&W@sSm^F`)}IL2L_S zXcn5Ndl4kjQlw%~sv`c-34DbkXv|Ngxsl&_RDHq8IYmkO(gL2npeZRj!>c~NWS=gN^+XTOvZ)Zfl)Ic#Ju>e!vaF6tSx#xELZ z>)ShUPybSen4?{#o}C3P#wE zPB6+jjD5@Lv>v2IlKnj~ppVc{gFF52*9gZsoWoRm^yHr^N)UqMxXALj!)#S|q+}3K5o0c=$cJhwPS%iqq1u`+07V~CAa87HX3V)k()O#+?mmJo| zoHm_(qFS#i*YT z6CrurS^S;)r_n4ZSdlb0Fy!&5{}Jt_)^BH^cDtn*MU8pn&WZI3_Q1mjG8*NX-;B9$>*5 zlZjqPrNbSobo4vXjh=#DQCc>j^e(yb;*hwEYSM3zHQtHVj6t+i4zM#9-QGxsjqxhW z3w;+`lW2yX0qd*vG?%>eKulS{Jz_~%pZ=$_n7Bb}%7$Y;qY=`aXNX(cp?@~hUyKTW z@f9TLyfvRS9%xQ?*rw=CJC|)Ucue%TD&c)Mn*$4F!2%U78zNSUi!m-4Tt1uU z7I-2-K`R&TJ(o{3R7%Y5E)jh+TVM`ZMj*bX68|(;m?~x|r}idg44s(rH;@L$lt(f> z{x-E(&P*w-Y|?xxu1FH9oHYnnBENsO)JW`%F^Na57<0bNKF~_NNL#MleYV_YupFz2 zR{m?+R7F4JFTA04^?JDZN*%EZ+{JeB=H&S*rl4O;hvgFOPm9&t_&>itx=VB$&(=Ic zRuVYzsE!FNmY|7S^I@zn7-G)XsRde#Fu+TWt0y)78M2mU=T(~WUTo-xtU?#>P+kC) z&o-JjUE(0VYc8oTH2n^;VKRLeTWeNrF&VVckHeGL3SMfVg1Rycl9kS$Z{iHHRVZ55J8Pb6mma**Z1T{!YM$#balEn~;+5_+j;k{ft3g@R(RiXv&hRy< z#yZr|eN##3yk=*jedHDUBwXsrfU5lt)hP~cu-tnr#!P#QCjkvH+t(Fj|BC`)`^Y~f znK7#}Sa8gU$-Om%*tE2TA8La&Fc%-3bw5b+}5T`yaCV7 zF5P`yw%VWTOJGPpJJYzXNeDOmVPRhSce}0$pWnlfIGqQ(xNgbS@k6mdIfla|-M{@f zJCjISo<>_V9mujfLuf*F#>KZB!d&ntiOh{B|MLH#U-As)TsE4*Y5kM-oOvLK-Oc6)p*5JZIPX0LhVcqFCa#V9M`ArcSV{kz%~$(dvZjyyV^+YWaye_q>d2{r<<-gyCSw~4bg@Up?b?No$*F^q<83HZoF;x;(`TD=$jPsab%j~W_Xo-mUV4%k-+7F zyW}0za&2(swB=4vy*2i?XZJj zB-`rs?zDZr?_%_FNC3h}Ci*-!usZP*c-w_GNG>Tcc?=P`m;4Q(De?^CGGH!t+9GmJ zC349rcwOS#GveEYrGOFY0>YAhT~>Ria{AEF!)uiWvoMFD7J;&Gpn4I8rFMnq5_~VqiV{s{RpSY8?$`Z8d5lK0dxatd*%JX7t@t=4~yDH04dr7`%2(Rdi zTDXX}8Y;gi%4_N<0=;!wf9OehYjJ7+NYzveb^UYXQ^cuEabiU`DokVX$1YWvA=HDZ zG{C&nEm6ecCzpyrs$N)&9#e>nQ>mxRiYSYw$oElU_7wq6kVS|QwU3XhcASNem#eO6 ztCrh|hliA9imR&4sOujK;oqT-1zf^Gqh2ASff=EGSyI+HscLyrZY4nBqEe@(QK7OE z?+RDZFlv#ARN<&nff$R>Bq`s7RGpDf-#Sf=v=!~D(SRBY`@bt14JU4GsX{HI&Z`ze z9n^kZCqc8CN*XWH1Fix?rQs8y{$s7W=cE3MD>~CJI{PPq^VDv?G*yV8&uW#=vRj4Mf%%h$`z+G)u)Y>%t;aGc1##EK_(mQzkT( zOg-aHI`r!FR|BlYLt3ygY+_ZI#iN$V!$%R7>~YXm?q$I9@ z^jV%PTpTZLNb_j{=eVmdTqvq@kwRM0Rzs1>S`pepA@V~ZG3 zRavc6S+;o@QgE4jaarbY8S8ObZc1*nFmaSHv6>KZt#*Z?5>)G3c{?0#=UPQKZbh$E zMPGYGzjo!Id*wiS<#2iBXnW<V=V;STB~xu0lFK)-j>7M)7Cu7)ZAIsyoT4@yVrcS*SITHgY(ux>U^b{ zYJnNGa2>S>>$TtBYmxElP$z0545`OvVLj|&0KP!L43zE+6krE7Vn&`^hXxCem=PR6 z!;4H;Q4cv=&-`A`I#G`c4j_~BXxL<{&&qA!%V-ezYUNvR5aMn8>iZM+XcUWRl*nk5 z>u6M{XjEEnRDN$%(P>hZZ3+!U{v(9!0f0q?hLMOT+kk{gBS!Iu6a3#bIRRJ|Knd*p z|3hG6(>aX&-vU#*-lPv)k3Q{7V1AVan14#p3lSGh{-?ibW9*NiA1Srk6aIhoH^IUO zhX43MJ@+5!O(6Xrr72uuwK?AOtG^sn&G9JHT&We5ZS0R2v3RuFV&CB|^X|Z~UibU{ zjGrgPR<^^s;fg>G+uWds4s6>=r?qZBJ_IF26+iWMedTN(9{Hy z5X8Z2e&UZ#$o|kQn(O|^9N4=7sOnO?L6F?3yMcJxD&M!Zh^I5wasTT7ll=X6EwBssN>cR0pH-O zYReVQFB+Dv!fcuyWhO407QG|P>-R|8ZQ6ciM=+*uRrA`mUUje9cK#`sWlFiA(6aA`yPvp;|0YOJ-jDj;VLzn$8S#7I#&*(VkZ@V*_b9T9 z++7@54&Ut%ZOx>^gs?xtALlQDc{{;DW$7@*R6N-b&IQ6RniR2X`7_Ig7S-q~%yg;y zSK%DrX-rZv%XD7dE`@zu#%@z{Ss&@>&x)LeJcpT5&L;kf)xePRx?L2aON!aFUh0PP zc9heWaS!Nki>?PKi*n0<a@Z9)e$r(b@ zr4~Rr*pmSN+L-Tm{5Hn81X!jJue>56@Gy;1lNKqnKJW0_b$OlOs z>LSdoiLeJ!B_5mNJoOiL@dl`dS;p<7=rYL&9#8oBj^bk0Axs|kQioW-)kQmd8j%W< zjtX1)J1oNrk~$;_&=CjtKNzD|agh+eku~MH; zuS_^%t4rgtjh;%wc%VG-hMmL|{t>^nkjpjVrr=n7A9(3$#+Rd|aOH;+XCz@KgQ$kbNHilm4bLwM;04n z#PxJl;8Rrmwp7F9*)0_CTo;5Glgbo>tkmjU7JJ0i%70uMYP@nRbtWxVnm4Vh6SS#I zH7~|nX6fjzKh3*OsoA&;{nDR)Qg3}yOV%nnF|g#;+*xeO^gz@x#^}(j!C0z|+SE3w zdQ$5PW)Dxu(l#q-)|zr&vW#IcvxwndTT^q6D6%=SQKm=ZB}~%(2|IJhbbmr>*Dh z{Ok|{^+gxv(7#J=vDk*Xb_;6O&t^zzU#&874{O&;9oFc6el+ryWjA=catV2Cy7mdc zHmtx*t@vQK^4Rn+grw3eLmIXUq~J3I*KqCQ7_bj!=WBq4zv`lXWY*$F_>NjZ-&w(S zJEq$CWzowGk_EG9<6WrZR4fkLi{6szU9b>7uZ-Xg{EqgEWFeJB7~JS$j*pYsKgM|- zRrF4cJ;gN*R$DccHLH)W;bUfmn4BLI-$V0FW!$M$u~b?Q&f2aiQ>Ji$4n{Zf{BF^38%I#1aq~BiN%e*#ssY@EiF8rg~n>9YW{Oex$EVn zmMOMcipz6@0iLBK%FAlu4(of4_0^PNr+WE!>jy$!okg?jrm7|z3&f0-xhal-5{3=^ z{k4sL%4hZ={ws@;j+NIB4wG3MYbUalP408&1}Pg!FS?0s5cEskO_!0s%gPQzLQBzr z&9yD*zd@uB*Fhb*Yga`+6F80Lj&sE8cQem4M3o5bM!^zXeS9XQ5berHx@q6d~YLr`*fWl?C2j8)}dV6KzRe6$V z+*w7x`%+UhZK>quIqd#h<)FY*?tS9vxrF=N*c5F;4#9Z zUFVmL?f~7H`!z!+&m;C2`f-h|dt~UY zKyk(00$H8QWpUo$Ayc>c1>LRcoMZ<-&Di^g=5Fq*A61`a#Xa zGit2}4AN?K(isgBdkN40+PptI3IKieQt8xMnankvf2}whS=iVCLw=Xqn7Rhqthl&@ zh8S1`|5>s9`QqxdqHQU~>^4dl#N`$eN)zD9999|{0U8bUbJdFBqLez(^GdZ#D%DTX zw2#A9k8*YU>f^~-aVwAt_pk`baJ4Th4X@BNu1dAf8)fl1@qu4)mp=(a;)=j3ji7i5 zXTXejQ3>x|4*Nk7*?Sz(Ga4bT8S$?rP|m`7F!e`oD@9sqSP6CLaH!|#i}Acv)Vh?z zYH8FsFnF0dY}b`~Gn8U6)n%6J$7w3dH85sd>d*VKt`3`ex*SAU6Sbhc*9#n-^D z)QIVo$QRcb1?pG~v>2uSSj5v$zw$s5n*8 zIG&M67xTasU_7TqyoyA;>Oc%#S=`)c{G)LKCnzjIl{y{=DnYj(LH#7&s3kh7B>o7P zXf>LEa+ttKmMD6fs3(z_XPyWOO*{opQnpMAq)w2OPPi&fGU-p0myU&Ti->_qHVR9| zFGw~ZP3GH6wu4RK?N7F$N#QU~;R#4`3W+zDPQsH;=_^fy2}dNdG~a^kXcg(IVL#HoaRi z-Ptr9i6*1rG%1xGE0Y;Epf)2c7B&M6_BV27i89fkR_1UUEO>n8cw6S=YUWf~Cf#rB z%wE{mG}tBStPmDh=UNzN=FE}TtevpTX;1F#1Fh^Mw`_XUteLtDKrbwy7FxR&`Z5jk zL7EgWb~UEHcLg}e-|YGkKU&~ zco_f_fc0e(mj1^I1Lj6he3^u#^oB}#qW+LTHqVn`UGf5b<11r_J&1eMI^A1|CjX%1qcTGf=Gw_fA{geFcSaE$AeV{hx&$&8~Zc>g7{=8Yh5nIHK4NJK@Z|0hP`za38P8*^SSQ2kk4o^Sc`|Ds56o6dx%G+iRW zS?rHz$+lbx!@1t#u1+jVMRdC90HObzB7sJyKlvrIMq0ml? zMI#6|6QPyn*&H`!luaKk=b0^3vq36go?8pzwPdL zGLJV#!K?l8Xr;)!ru(D)>3XBj161;@v-5RZkjrK1PXXQOe`HpOC7e*PTo!@?YN|yy z2*jxcE-G;5=KLj&DNA?!u@6Uf0t&RieJRk@Ep~&j%`A42q78@A1lUR;cfT+aq3ZaQ z#V^^xahh{`5sbsY{Ychfi(N8iuDck4w~c#9n5#1CgIF=bA?Y}&wJ2cV)z-7mk^U^dGsHOL~(i+wFpG4PB6`JrsI;O`43DEw=NTbK<>%F%PiN;fz0h6iw*Ctvk^JlGKNyLt_KWg&d173A?90xFCA|4YwDPx6 z(jDLRs~!Lh{Y@_Z1c+B?fcXhHzUL$naJI5 zwDqII)FqX7B#^2V#EX#JAw zuE$j_t?lEW%ja$2T$B`%%eBqRuFW&z%U)y*hYe*W-yQ94sD|z9d^qP9M#6%h@O3rO zfbs2w!F=lVwAACU?Z5&{;mh!Z_;5aJT%KAu%k3b1zv_grZolk>$w9lSdbZ`;8c<5O zz3%6{{wF&al6}Q2u4&&3v{&-{_q2hD7EYOGKflC$=$O&j`aB5^M_rtl!+1YE!UU3> zJ-4lSeehC2b^*Ruv7n+*{HfZ~Ar=5I=+<^-3m zFw`J6mO(A*SiHQM-B+H1uqm+uj4T{U*jcC^MD5#fgj3@7o8xX9We6~<++ikXUw_o* zSV*M8Vc^;gl02Z7P^(M~rm}l1zy45_Lk3UqFR1kr|#m+l? z%=!J}| zR>q5yigSJufH{%?o3$&05GTV0X!D{-HT|OFCas=XSTwz>tExbQVg$xYP_X`wqEC+x&efm-p{gz zJY&nm4~Vt?FnAl_ysfGyHj;Rhaz^OBE`8XH%f3XDM)gKZ#h%0f^mRXQ)Qenb-jlv> zDxDAsW-mi~YxW`L^yDk?sZf}Kb(Hnn`NG;h1_)H#^z{<;TE?N2NNuwG~H2o8aE_V;p~t*s(j#@w4nT(I<}ShK-j?% zZn}rU9z0XRjQZ_2C4!%?cDfDNLIn)N$`GXXF&Fsf`VXa7$16^mvz z-*OQfSo=}oz@f5~d#OKJAixoJR_Qg_`wqgUh>KOwB-o%(ow%a4kALXw1A_$Qz_b)# z)|O9-;cM@ud^JI$sAMORP{|)^;FS=ebA&6b_bZbE<8RZx8|ri-ey3yZ^qatuKcz*I zvWTwdD`U_>uxeR${(kXk-#U*3+im(EQBcrR8AB*H_!;%7{vGgxw4R%-RvBEWJXS$3 zP(yT#wO)3=5(_dP1#@ZSt$6Hx&sn+0L!!}==G!-SCgBFx{Q;>h{MW=KNb(o0O(`F{JlPOj|(*631$Uq$;R2)F={j@grp-%?f_pQ>{S9LOuEr8o0nY zXW#zF+|lH|AqCfRY&{*`%TvXG^dviUV#JM9D7bHcdpiVTBJjddqAd6T3wol7gM z5wBm$5^qEtoGCN#({D-m`Z{(^*}Chj;V9_7kV%GMlb||icugV1_N~q>_3gL4fP=^W zmg?&4zyFSSL9a70-47*-A7?tCx0UJcr~dg~Zl^ z9`!}n^uu)Zdq4JN1@l9})%a0QBK`tz-z3cLjHq6dnv1S*sUNQMSdmj+%81uD7* zX;S+ukD^LT1#O@O3L*#7pah#}21CULS)>MEw**yl2I*t#8@^z|5CagELY!9s_%{&e zFks9#5FEq+czX&uWB}BTwUuiq*Jv>LY>=G=pCL#J3|t8i&lMKW1whOVnbQE^%!CNm zf`c1|PHO;|Yr!Fi0T8j^*)L(ap`n#0K8U#x#jalAT&R&Np`B=f==OklmoRgn7gQ`b zi7}wY6(S7?0p07#!yH=G8VTYAW44E26AE48j38BxXvXFh29kHYKy5J z0L7z#5|&Ve-Y~&lursM}bbAVtyD(sENLDFe(lust8Ne(A21N|PN*uA^>S^m5)dLlb z_*GD%_U1GM1Y<||0mIOQAYxqs%rk)C)JQO901^%0+5!L*3l1L!7z>Rd5A(vUjj0<2 z18I7J(ZsSR@j|xgLWjl9j(}yihC_g2!LnbhafBlCEdaZr;W(!NPGkV25Mb;jVJi9S zI-24&ai%0Db|nIw7y=(TOkyRTz$l7^n+G~Ac4{PI76u?6mLOsoE{7Awd=oNV3=vBW zc#sOFy8)bG2P42HxvqM0#zssn$KVTjX`iC#t|q12C(ThM&pby7xg{d)gng?`02fMz zxJf9+2C(Hu#?S!jumO&+iAAo7ZaC>szJR%qq&ckAuTTq}s6*fw!>g|f#?RDQDli`! zFMQ;fx0A4KU>pVw0NpZf+Bw-zDjKOawxkstIybBkJAE=O%r`6@PuYj8*OS)Ko$ECGzV9rAqOaNZzJ^*oBd|X0Z5-piJPJfgW0i8;`X9-&*ez>f-TQwqOm$*1y6r= zY&C;|^96jbwH#oEfXNlXT(F+RvH{tP0F<%_Ea39gWCn6!iI%?|e2sDP6?y|?%pz;& z?qt30h%yyL%(g_kScJ5@<_r23bK;iJrxxIlWcf1-Kq|>lb0Dn6LK696yc&WT+#vS! zf?qodJq6pX0RZ-(Uc{aoa1sY(>{ggglf<{C;ChQKx~pHe=EIK0a%Sml4S{y zIm844i6Jd>Vb2bPtZE@(VwHN*F{lA<8_4w=$etAvMOk$!xa~C`aK(8+?zyT6!YSEs zybYqGwHP3htn_8fH+z8Jg7f%Kq>d6;80OzuMo_%D4TqNA7`XD-;TYcLNQVP8JQc7% z8WkCSA>|puEx8r5tv7L7G@u)mY~%wBl)g3efk zBZ}`7qO@j}le;R83%n9H?L)vl958qu$grL_h4Ype(xE)URJgIZQ6s}i`vUYdAb`05 zapL^WV~^hFLS6s>E*7$KHN7saH6o*(c%?P^yu4T^ptPdBUe>P?4<+EWJ@{u(Lq`XW zW(U&mj#ggJx`~d#b)W9{j%vJ4S+35Z_0C(D&Tn9y;~89rFkQ;jUGp6rgXUdgAziEI z?CYS3F6!g1?Fsf>x^7s>?!yS~#fWa)((dzhw&U~e6X>4X4z_#Qo-VVV=kxEc6+PL* zJs%Nzm+-yARJ{j=2o^As{ogG5sXW_{i~H|K`f&}F1^|F9XT~C(-zGsU1PB8flv&dffX{o`%X~0D zF+_Ty!4v;1)8Q849oleFbicWnLkYyGyoKX1~tl&=?-0z#5K$FMxXq>#XjfcMV2X9(}zGVf69x9x{luQ`WU^1(>F)Gr;t!L zCP}&0+dHaW7={V$3}fzPB`pK$9`$*qF6L+BU@xt8YnWuj${<59)D((R$GOiTO5 zo{LJXUv3%du%m;B8B1Vw;lnmylV3mQ*v^%S?KLpJ#;4zY=h~aRsH*JCP3?y?ti?}h zk&HI!jutRCLUuTVl!p5%`3iCY_*Vaev9}7UBkUSAd*kjN2pe~I2(WR7jRc6`!5xCT zyK5l0yL*t}?iNCD4;I|_@O@|MOij%{Rp+iRy06}JZPt1fo%e3o+TwTXE14K z4e}qNzz#Vf*i8*cr1l%pWQf-gp6HC%F&mA;B+L-I{>NkoGBTMDaFmDomTkI)Z+4X% zou~)SAQzQ3e)b>P_$E!gBe`KVtEF7?#G2V?CfcMem2^cVo9OBsh1sMp#;|Y7EG!;@ zOcZF^gLd0MTdpw?YAplLl|&4sIFe>59>@Hf96wbUDtTe6j47FrHLc0o9pM%;yivS| z4lP6u+5G7g|HFn!hFOd=C?qpp)iaI~KTk4%>EHlJN5ciJV8tqrFVdm{hSHL?@as63 z+MIx;!^`(AqMM#n$%yJe|oYe*yh^uiJ%CI z<4OuJ&O#Y5Dw!=Pj}y*uppwQGw2GtrApzTR8id>fM|zE!$0Fqq0R9VJ-C=|&oE{YC zh_t^`to8mNB6X`S^pdZRBE0-uV&g=P-Jq@5Ns2GTZ{uQ`wEHkb9b0G7E>^)~LG?Q5?s z2zxg(`;M*3j!bN~T6p@{E%mF|m+!hyQ>ZK6dSXUfb1w`41t&SZwc${+^ld zo<;WFD5_`O zQBT~n1Cz1e#5V>F{ncI^Fjz%fNUDL=BPmQTs3`)VY_16`NR=xDl~-}YY)Ie-O>r*j zfR$Z9>e^1zqP*k!GC4+Nu*g0Y!7LTNZXMo`vZ7%4tx;nH~X$M>g;2gjG zd-i6`seWNeO8`)8gh%6S7g|&*HuMs1#FA8OHoz(S&Eo@sFcg+hrlE>why7GJ}ddoeUuCa@$k z;BWaMOp(%Dx+)iI^a1j4j=qDllVETt0jurfl%$9^X(+i@hKWDy5hw=c0`xMNL=F>C zgQQIBqz)T}zhT(T@6b5nu>qXdCNj`8h#3e*qS=^X(`p40%!b4RVOCjEd(Enm2 zaE%*3V5+3Bkg$FH=~#ub*Wsq7Der?i@-6Y;yk`_1_}mpts`*Nh;Im!p|E9UGnl0+{ z|D{ND?esI@m&iq;TyG|6Y@#TjvLR51!`b}gP(Vp#Tej;N^y5Kc1kULw`#vBjlCdV@ zU`5rMpr+DrvZLnuDXgT|bKd3^0N|% zm8`4IItXz&)2?@@*}!5k%EMp9mH*k|mk0G`g4r(uP6+Ta)nfi1f2yM$h2Fc6kx1%5_rStZoX?4^nB~5JTXS#+9S3lg3w*#x33~tw*bv zl(_(1Zeq$YSegT1Qh1r;I0e({6cyY^%1gKXSjlF&>Jz+qab`?vuJi6f{rY%)S2 zG5xO23iyjQ-LT*xQu#8`qhfONaH{apMA8$eC>zD)x=4adyU{?rhy>AZx2AcpH4%8B z63BFoP$_IoE7-u+XK6b?jtvK{Y2PjxYv_r8OBN(YLAWIz@}q$h6&-7NGcAx_1*Mh` z%~-h+!VA78CLoyJAJa;LLIOD8B%D>yF7|h*uDoQX>tKR3h;yN+Mc?b}B(=!tcDKdA z4~BTVYW{C`I8vPuC!acy*sRqEt`kwvID( z)g_Y@9kUO5@hYK0X5Y;DiFQ-%2ZhY}42s*#bNrB21TtOd+bs&-&k9%+rJ1!K7KDlk zTa`KQ{eoFm)C{&;mX_?b|Bb1b6t<}wdh20bGbP)hUpr#fVcRmqCt};SkL7yOayBSp zFS%XZVc#vH{L%g=GQ*Q}7fkk@WB4)q562-&^CO1=hLU&dDd3@Z&JziVKb)qX&Agpw z2zDq8q{;!bliJ(+m0&{y4y_C zX>;Gn2^0IWE2@-_#CN-ToJoNN4iJGm`PaBROMpf<>8d69@f2nh)!mt ziS9}=NOi~CP!i7DP6IKo>Cq9(>}Gd5<^kDHMpNb%zPTE<=m z!wC`_eRNdOoH#Ta*+`Y?I4w4j9I#RZG_2eJe)luVVlV*WN6erAB;gR^4TVVHfv2oU zowoYk&$Anh5?GMXyL@>GB552=D7;Vc`Mk^(2u;NTj^KcYj*(U#2Xlmp7jb4%R9;~; z^7|k`o-Q)Vhk|&oU}K_|#yqpt78zqAd>Q5^Ij0>2QmQ-vl!&50)(!FJXr!T3fW^;> zLmd5~-_<~R>hdCz5&&4C77xIY7F3o9yrY$-{JZ><*#9a{ojez5^-fJJmKVd8wM@aY zGFWVo3yycigaV;wH<(Nt3+mnH)sk$;8`{c1qxz4s>Pe11D!kL!Uouaf6-9PvCfBL4 z_btYiiFBx?a%wZL;4rNO9kL;aTeu`^b{-;8$_3nz2fz0?oCNUlLA~!7IWcep$tZDP|#-ragqgzy$H)R=NNd z#(fBsPRc*y1s=#V49B`J@PBh{#lcT4?^kW59#&;Sl>5A${b~@8JUtAjL>-YoRmY2= z#`PlG49AwB#!E~OZu+?S*C3^1wGhk`jd+h{RluV+a$L%0AAzogKN|^Xw>0F)lxt{4 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 literal 0 HcmV?d00001 diff --git a/frontend/resources/images/features/export.gif b/frontend/resources/images/features/export.gif new file mode 100644 index 0000000000000000000000000000000000000000..a89874b2138b775eba2e3319aad09aa22957032f GIT binary patch literal 196468 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 UDi(A~J_rfahQE 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 z

    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 z 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