0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-13 16:21:57 -05:00

🎉 Link files to libraries

This commit is contained in:
Andrés Moya 2020-08-05 13:30:26 +02:00 committed by Andrey Antukh
parent 5ff0a723d5
commit 752038039c
22 changed files with 1080 additions and 318 deletions

View file

@ -0,0 +1,15 @@
CREATE TABLE file_library_rel (
file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE,
library_file_id uuid NOT NULL REFERENCES file(id) ON DELETE RESTRICT,
created_at timestamptz NOT NULL DEFAULT clock_timestamp(),
PRIMARY KEY (file_id, library_file_id)
);
COMMENT ON TABLE file_library_rel
IS 'Relation between files and the shared library files they use (NM)';
CREATE INDEX file_library_rel__file_id__idx
ON file_library_rel(file_id);

View file

@ -79,7 +79,11 @@
{:desc "Truncate & alter tokens tables"
:name "0016-truncate-and-alter-tokens-table"
:fn (mg/resource "migrations/0016-truncate-and-alter-tokens-table.sql")}]})
:fn (mg/resource "migrations/0016-truncate-and-alter-tokens-table.sql")}
{:desc "Link files to libraries"
:name "0017-link-files-to-libraries"
:fn (mg/resource "migrations/0017-link-files-to-libraries.sql")}]})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Entry point

View file

@ -123,7 +123,7 @@
{:id id}))
;; --- Mutation: Delete Project File
;; --- Mutation: Delete File
(declare mark-file-deleted)
@ -149,3 +149,47 @@
{:id id})
nil)
;; --- Mutation: Link file to library
(declare link-file-to-library)
(s/def ::link-file-to-library
(s/keys :req-un [::profile-id ::file-id ::library-id]))
(sm/defmutation ::link-file-to-library
[{:keys [profile-id file-id library-id] :as params}]
(when (= file-id library-id)
(ex/raise :type :validation
:code :invalid-library
:hint "A file cannot be linked to itself"))
(db/with-atomic [conn db/pool]
(files/check-edition-permissions! conn profile-id file-id)
(link-file-to-library conn params)))
(defn- link-file-to-library
[conn {:keys [file-id library-id] :as params}]
(db/insert! conn :file-library-rel
{:file-id file-id
:library-file-id library-id}))
;; --- Mutation: Unlink file from library
(declare unlink-file-from-library)
(s/def ::unlink-file-from-library
(s/keys :req-un [::profile-id ::file-id ::library-id]))
(sm/defmutation ::unlink-file-from-library
[{:keys [profile-id file-id library-id] :as params}]
(db/with-atomic [conn db/pool]
(files/check-edition-permissions! conn profile-id file-id)
(unlink-file-from-library conn params)))
(defn- unlink-file-from-library
[conn {:keys [file-id library-id] :as params}]
(db/delete! conn :file-library-rel
{:file-id file-id
:library-file-id library-id}))

View file

@ -148,32 +148,6 @@
(mapv decode-row)))
;; --- Query: Shared Files
(def ^:private sql:shared-files
"select distinct
f.*,
array_agg(pg.id) over pages_w as pages,
first_value(pg.data) over pages_w as data
from file as f
left join page as pg on (f.id = pg.file_id)
where is_shared = true
and f.deleted_at is null
and pg.deleted_at is null
window pages_w as (partition by f.id order by pg.ordering
range between unbounded preceding
and unbounded following)
order by f.modified_at desc")
(s/def ::shared-files
(s/keys :req-un [::profile-id]))
(sq/defquery ::shared-files
[{:keys [profile-id] :as params}]
(->> (db/exec! db/pool [sql:shared-files])
(mapv decode-row)))
;; --- Query: File Permissions
(def ^:private sql:file-permissions
@ -236,6 +210,25 @@
range between unbounded preceding
and unbounded following)")
(defn retrieve-file
[conn id]
(let [row (db/exec-one! conn [sql:file id])]
(when-not row
(ex/raise :type :not-found))
(decode-row row)))
(s/def ::file
(s/keys :req-un [::profile-id ::id]))
(sq/defquery ::file
[{:keys [profile-id id] :as params}]
(db/with-atomic [conn db/pool]
(check-edition-permissions! conn profile-id id)
(retrieve-file conn id)))
;; --- Query: File users
(def ^:private sql:file-users
"select pf.id, pf.fullname, pf.photo
from profile as pf
@ -249,13 +242,6 @@
inner join file as f on (p.id = f.project_id)
where f.id = ?")
(defn retrieve-file
[conn id]
(let [row (db/exec-one! conn [sql:file id])]
(when-not row
(ex/raise :type :not-found))
(decode-row row)))
(defn retrieve-file-users
[conn id]
(->> (db/exec! conn [sql:file-users id id])
@ -270,14 +256,93 @@
(check-edition-permissions! conn profile-id id)
(retrieve-file-users conn id)))
(s/def ::file
(s/keys :req-un [::profile-id ::id]))
(sq/defquery ::file
[{:keys [profile-id id] :as params}]
;; --- Query: Shared Library Files
(def ^:private sql:shared-files
"select distinct
f.*,
array_agg(pg.id) over pages_w as pages,
first_value(pg.data) over pages_w as data,
(select count(*) from color as c
where c.file_id = f.id
and c.deleted_at is null) as colors_count,
(select count(*) from media_object as m
where m.file_id = f.id
and m.is_local = false
and m.deleted_at is null) as graphics_count
from file as f
left join page as pg on (f.id = pg.file_id)
where is_shared = true
and f.deleted_at is null
and pg.deleted_at is null
window pages_w as (partition by f.id order by pg.ordering
range between unbounded preceding
and unbounded following)
order by f.modified_at desc")
(s/def ::shared-files
(s/keys :req-un [::profile-id]))
(sq/defquery ::shared-files
[{:keys [profile-id] :as params}]
(->> (db/exec! db/pool [sql:shared-files])
(mapv decode-row)))
;; --- Query: File Libraries used by a File
(def ^:private sql:file-libraries
"select fl.*,
array_agg(pg.id) over pages_w as pages,
first_value(pg.data) over pages_w as data
from file as fl
left join page as pg on (fl.id = pg.file_id)
inner join file_library_rel as flr on (flr.library_file_id = fl.id)
where flr.file_id = ?
and fl.deleted_at is null
and pg.deleted_at is null
window pages_w as (partition by fl.id order by pg.ordering
range between unbounded preceding
and unbounded following)")
(defn retrieve-file-libraries
[conn file-id]
(->> (db/exec! conn [sql:file-libraries file-id])
(mapv decode-row)))
(s/def ::file-libraries
(s/keys :req-un [::profile-id ::file-id]))
(sq/defquery ::file-libraries
[{:keys [profile-id file-id] :as params}]
(db/with-atomic [conn db/pool]
(check-edition-permissions! conn profile-id id)
(retrieve-file conn id)))
(check-edition-permissions! conn profile-id file-id)
(retrieve-file-libraries conn file-id)))
;; --- Query: Single File Library
(def ^:private sql:file-library
"select fl.*
from file as fl
where fl.id = ?")
(defn retrieve-file-library
[conn file-id]
(let [row (db/exec-one! conn [sql:file-library file-id])]
(when-not row
(ex/raise :type :not-found))
row))
(s/def ::file-library
(s/keys :req-un [::profile-id ::file-id]))
(sq/defquery ::file-library
[{:keys [profile-id file-id] :as params}]
(db/with-atomic [conn db/pool]
(check-edition-permissions! conn profile-id file-id) ;; TODO: this should check read permissions
(retrieve-file-library conn file-id)))
;; --- Helpers

View file

@ -100,6 +100,15 @@
[coll v]
(index-of-pred coll #(= % v)))
(defn replace-by-id
([value]
(map (fn [item]
(if (= (:id item) (:id value))
value
item))))
([coll value]
(sequence (replace-by-id value) coll)))
(defn remove-nil-vals
"Given a map, return a map removing key-value
pairs when value is `nil`."

View file

@ -1 +1 @@
<svg height="500" viewBox="0 0 500 500.00001" width="500" xmlns="http://www.w3.org/2000/svg"><path d="m374.18867.0267054c-35.64828-.2594519-69.82413 17.0404356-93.20368 43.4592726-15.96943 15.815633-34.18905 30.514355-45.18713 50.405678-5.1515 18.457664 15.53601 39.058944 33.81154 31.036824 12.22641-5.78727 20.9306-16.63075 30.89383-25.454779 16.22461-15.903949 31.60842-34.624491 53.26814-43.214287 30.62802-9.50856 68.02579 2.749108 83.55033 31.696765 16.13296 27.355411 11.40609 65.900651-12.9823 86.952601-33.6878 33.49189-65.99089 68.54217-101.81764 99.77497-25.67623 17.23892-62.49311 13.96445-85.18151-6.89399-9.89211-7.80227-24.66431-13.50914-36.06589-5.38341-13.15171 8.62196-16.04732 29.62343-4.15345 40.55617 15.93267 16.29843 36.91504 27.64936 59.09307 32.78437 40.13279 9.48411 85.67216-1.55446 114.36469-32.06287 33.07787-32.27923 66.63084-64.14607 97.86433-98.23413 23.04904-23.96391 33.83451-58.70201 31.15623-91.46246-2.70232-43.764061-31.22711-84.958851-71.53209-102.670553-16.87008-7.7105388-35.48438-11.6840846-53.87847-11.2901716zm-159.2861 160.6206946c-39.01926-.33444-76.68538 19.58857-100.83485 49.88477-31.172069 33.41854-67.205805 62.36033-94.800257 98.98025-26.9880443 41.08393-25.4588562 98.63597 3.545885 138.48623 27.24324 38.81883 78.068962 59.33957 124.213102 49.59632 24.72587-4.46394 47.81335-17.32539 65.23405-35.18393 17.52414-16.41671 34.99703-33.22162 49.95378-51.99502 8.83127-16.43283-6.03228-38.57632-24.49974-37.35978-12.4053.64489-20.59493 11.5371-29.6011 18.68791-19.15239 17.81799-35.79382 39.82048-60.06572 51.03535-30.55108 10.5216-68.21902-1.2911-84.667542-29.78775-16.559305-26.43812-13.141765-64.30146 9.984965-85.87978 34.067387-34.21788 67.149797-69.52054 103.267867-101.61931 26.30278-18.27341 64.67227-14.35 87.64732 7.61083 10.00553 7.94798 25.88113 12.00271 36.36258 2.66107 12.25723-9.77484 13.27481-30.9153.40352-40.68432-22.71929-22.32063-54.40976-34.65193-86.14386-34.43284z" fill="#060606"/></svg>
<svg height="500" viewBox="0 0 500 500.00001" width="500" xmlns="http://www.w3.org/2000/svg"><path d="m374.18867.0267054c-35.64828-.2594519-69.82413 17.0404356-93.20368 43.4592726-15.96943 15.815633-34.18905 30.514355-45.18713 50.405678-5.1515 18.457664 15.53601 39.058944 33.81154 31.036824 12.22641-5.78727 20.9306-16.63075 30.89383-25.454779 16.22461-15.903949 31.60842-34.624491 53.26814-43.214287 30.62802-9.50856 68.02579 2.749108 83.55033 31.696765 16.13296 27.355411 11.40609 65.900651-12.9823 86.952601-33.6878 33.49189-65.99089 68.54217-101.81764 99.77497-25.67623 17.23892-62.49311 13.96445-85.18151-6.89399-9.89211-7.80227-24.66431-13.50914-36.06589-5.38341-13.15171 8.62196-16.04732 29.62343-4.15345 40.55617 15.93267 16.29843 36.91504 27.64936 59.09307 32.78437 40.13279 9.48411 85.67216-1.55446 114.36469-32.06287 33.07787-32.27923 66.63084-64.14607 97.86433-98.23413 23.04904-23.96391 33.83451-58.70201 31.15623-91.46246-2.70232-43.764061-31.22711-84.958851-71.53209-102.670553-16.87008-7.7105388-35.48438-11.6840846-53.87847-11.2901716zm-159.2861 160.6206946c-39.01926-.33444-76.68538 19.58857-100.83485 49.88477-31.172069 33.41854-67.205805 62.36033-94.800257 98.98025-26.9880443 41.08393-25.4588562 98.63597 3.545885 138.48623 27.24324 38.81883 78.068962 59.33957 124.213102 49.59632 24.72587-4.46394 47.81335-17.32539 65.23405-35.18393 17.52414-16.41671 34.99703-33.22162 49.95378-51.99502 8.83127-16.43283-6.03228-38.57632-24.49974-37.35978-12.4053.64489-20.59493 11.5371-29.6011 18.68791-19.15239 17.81799-35.79382 39.82048-60.06572 51.03535-30.55108 10.5216-68.21902-1.2911-84.667542-29.78775-16.559305-26.43812-13.141765-64.30146 9.984965-85.87978 34.067387-34.21788 67.149797-69.52054 103.267867-101.61931 26.30278-18.27341 64.67227-14.35 87.64732 7.61083 10.00553 7.94798 25.88113 12.00271 36.36258 2.66107 12.25723-9.77484 13.27481-30.9153.40352-40.68432-22.71929-22.32063-54.40976-34.65193-86.14386-34.43284z"/></svg>

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
<path d="M188 37v425h50V37zM176 0c-14 0-25 11-25 25v450c0 14 11 25 25 25h75c14 0 25-11 25-25V165l103 318c5 13 19 20 32 16l72-23c13-5 20-19 16-32L391 111c-5-13-19-20-32-16l-72 23c-4 2-8 4-11 8V25c0-14-11-25-25-25zM38 137v325h50V137zm-13-37c-14 0-25 11-25 25v350c0 14 11 25 25 25h75c14 0 25-11 25-25V125c0-14-11-25-25-25zm386 359L311 150l48-15 100 309z" clip-rule="evenodd" fill-rule="evenodd"/>
</svg>

After

Width:  |  Height:  |  Size: 466 B

View file

@ -18,7 +18,7 @@
}
},
"auth.create-demo-profile" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:116", "src/uxbox/main/ui/auth/login.cljs:115" ],
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:116", "src/uxbox/main/ui/auth/login.cljs:121" ],
"translations" : {
"en" : "Create demo account",
"fr" : "Créer un compte de démonstration",
@ -27,7 +27,7 @@
}
},
"auth.create-demo-profile-label" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:113", "src/uxbox/main/ui/auth/login.cljs:112" ],
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:113", "src/uxbox/main/ui/auth/login.cljs:118" ],
"translations" : {
"en" : "Just wanna try it?",
"fr" : "Vous voulez juste essayer?",
@ -45,7 +45,7 @@
}
},
"auth.email-label" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:82", "src/uxbox/main/ui/auth/recovery_request.cljs:46", "src/uxbox/main/ui/auth/login.cljs:73" ],
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:82", "src/uxbox/main/ui/auth/recovery_request.cljs:46", "src/uxbox/main/ui/auth/login.cljs:74" ],
"translations" : {
"en" : "Email",
"fr" : "Adresse email",
@ -54,7 +54,7 @@
}
},
"auth.forgot-password" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:97" ],
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:103" ],
"translations" : {
"en" : "Forgot your password?",
"fr" : "Mot de passe oublié?",
@ -99,7 +99,7 @@
}
},
"auth.login-submit-label" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:81" ],
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:82" ],
"translations" : {
"en" : "Sign in",
"fr" : "Se connecter",
@ -107,17 +107,8 @@
"es" : "Entrar"
}
},
"auth.login-with-ldap-submit-label" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:108" ],
"translations" : {
"en" : "Sign in with LDAP",
"fr" : "Se connecter via LDAP",
"es" : "Entrar con LDAP",
"ru" : "Вход через LDAP"
}
},
"auth.login-subtitle" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:89" ],
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:95" ],
"translations" : {
"en" : "Enter your details below",
"fr" : "Entrez vos informations ci-dessous",
@ -126,7 +117,7 @@
}
},
"auth.login-title" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:88" ],
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:94" ],
"translations" : {
"en" : "Great to see you again!",
"fr" : "Ravi de vous revoir!",
@ -134,6 +125,15 @@
"es" : "Encantados de volverte a ver"
}
},
"auth.login-with-ldap-submit-label" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:86" ],
"translations" : {
"en" : "Sign in with LDAP",
"fr" : "Se connecter via LDAP",
"ru" : "Вход через LDAP",
"es" : "Entrar con LDAP"
}
},
"auth.new-password-label" : {
"used-in" : [ "src/uxbox/main/ui/auth/recovery.cljs:73" ],
"translations" : {
@ -171,7 +171,7 @@
}
},
"auth.password-label" : {
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:86", "src/uxbox/main/ui/auth/login.cljs:79" ],
"used-in" : [ "src/uxbox/main/ui/auth/register.cljs:86", "src/uxbox/main/ui/auth/login.cljs:80" ],
"translations" : {
"en" : "Password",
"fr" : "Mot de passe",
@ -225,7 +225,7 @@
}
},
"auth.register" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:103" ],
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:109" ],
"translations" : {
"en" : "Sign up here",
"fr" : "Inscrivez-vous ici",
@ -234,7 +234,7 @@
}
},
"auth.register-label" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:100" ],
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:106" ],
"translations" : {
"en" : "No account yet?",
"fr" : "Pas encore de compte?",
@ -504,48 +504,48 @@
"unused" : true
},
"dashboard.library.menu.icons" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:89" ],
"translations" : {
"en" : "Icons",
"fr" : "Icônes",
"ru" : "Иконки",
"es" : "Iconos"
}
},
"unused" : true
},
"dashboard.library.menu.images" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:93" ],
"translations" : {
"en" : "Images",
"fr" : "Images",
"ru" : "Изображения",
"es" : "Imágenes"
}
},
"unused" : true
},
"dashboard.library.menu.palettes" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:97" ],
"translations" : {
"en" : "Palettes",
"fr" : "Palettes",
"ru" : "Палитры",
"es" : "Paletas"
}
},
"unused" : true
},
"dashboard.search.no-matches-for" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/search.cljs:48" ],
"translations" : {
"en" : "No matches found for \"%s\"",
"fr" : "Aucune correspondance pour \"%s\"",
"ru" : "Совпадений для \"%s\" не найдено",
"es" : "No se encuentra \"%s\""
"en" : "No matches found for “%s“",
"fr" : "Aucune correspondance pour “%s“",
"ru" : "Совпадений для “%s“ не найдено",
"es" : "No se encuentra “%s“"
}
},
"dashboard.search.searching-for" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/search.cljs:44" ],
"translations" : {
"en" : "Searching for \"%s\"...",
"fr" : "Recherche de \"%s\"...",
"ru" : "Ищу \"%s\"...",
"es" : "Buscando \"%s\"..."
"en" : "Searching for “%s“...",
"fr" : "Recherche de “%s“...",
"ru" : "Ищу “%s“...",
"es" : "Buscando “%s“..."
}
},
"dashboard.search.type-something" : {
@ -594,25 +594,25 @@
}
},
"ds.button.delete" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:145", "src/uxbox/main/ui/dashboard/library.cljs:210", "src/uxbox/main/ui/dashboard/library.cljs:249", "src/uxbox/main/ui/dashboard/library.cljs:290" ],
"translations" : {
"en" : "Delete",
"fr" : "Supprimer",
"ru" : "Удалить",
"es" : "Borrar"
}
},
"unused" : true
},
"ds.button.rename" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:142" ],
"translations" : {
"en" : "Rename",
"fr" : "Renommer",
"ru" : "Переименовать",
"es" : "Renombrar"
}
},
"unused" : true
},
"ds.button.save" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:55", "src/uxbox/main/ui/workspace/sidebar/assets.cljs:69" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:62" ],
"translations" : {
"en" : "Save",
"fr" : "Sauvegarder",
@ -711,7 +711,7 @@
}
},
"errors.auth.unauthorized" : {
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:62" ],
"used-in" : [ "src/uxbox/main/ui/auth/login.cljs:63" ],
"translations" : {
"en" : "Username or password seems to be wrong.",
"fr" : "Le nom d'utilisateur ou le mot de passe semble être faux.",
@ -738,7 +738,7 @@
}
},
"errors.generic" : {
"used-in" : [ "src/uxbox/main/ui.cljs:204", "src/uxbox/main/ui/settings/profile.cljs:38", "src/uxbox/main/ui/auth.cljs:91" ],
"used-in" : [ "src/uxbox/main/ui.cljs:183", "src/uxbox/main/ui/settings/profile.cljs:38", "src/uxbox/main/ui/auth.cljs:91" ],
"translations" : {
"en" : "Something wrong has happened.",
"fr" : "Quelque chose c'est mal passé.",
@ -747,7 +747,7 @@
}
},
"errors.media-format-unsupported" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:406", "src/uxbox/main/data/users.cljs:177", "src/uxbox/main/data/images.cljs:376" ],
"used-in" : [ "src/uxbox/main/data/media.cljs:39" ],
"translations" : {
"en" : "The image format is not supported (must be svg, jpg or png).",
"fr" : "Le format d'image n'est pas supporté (doit être svg, jpg ou png).",
@ -756,7 +756,7 @@
}
},
"errors.media-too-large" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:404", "src/uxbox/main/data/users.cljs:175", "src/uxbox/main/data/images.cljs:374" ],
"used-in" : [ "src/uxbox/main/data/media.cljs:37" ],
"translations" : {
"en" : "The image is too large to be inserted (must be under 5mb).",
"fr" : "L'image est trop grande (doit être inférieure à 5 Mo).",
@ -765,7 +765,7 @@
}
},
"errors.media-type-mismatch" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:371", "src/uxbox/main/data/workspace/persistence.cljs:421", "src/uxbox/main/data/users.cljs:191", "src/uxbox/main/data/images.cljs:391" ],
"used-in" : [ "src/uxbox/main/data/media.cljs:62" ],
"translations" : {
"en" : "Seems that the contents of the image does not match the file extension.",
"fr" : "",
@ -774,7 +774,7 @@
}
},
"errors.media-type-not-allowed" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:368", "src/uxbox/main/data/workspace/persistence.cljs:418", "src/uxbox/main/data/users.cljs:188", "src/uxbox/main/data/images.cljs:388" ],
"used-in" : [ "src/uxbox/main/data/media.cljs:59" ],
"translations" : {
"en" : "Seems that this is not a valid image.",
"fr" : "",
@ -783,7 +783,7 @@
}
},
"errors.network" : {
"used-in" : [ "src/uxbox/main/ui.cljs:198" ],
"used-in" : [ "src/uxbox/main/ui.cljs:177" ],
"translations" : {
"en" : "Unable to connect to backend server.",
"fr" : "Impossible de se connecter au serveur principal.",
@ -819,7 +819,7 @@
}
},
"errors.unexpected-error" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:374", "src/uxbox/main/data/workspace/persistence.cljs:424", "src/uxbox/main/data/users.cljs:194", "src/uxbox/main/data/images.cljs:394", "src/uxbox/main/ui/settings/change_email.cljs:51", "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:65", "src/uxbox/main/ui/auth/register.cljs:54" ],
"used-in" : [ "src/uxbox/main/data/media.cljs:65", "src/uxbox/main/ui/settings/change_email.cljs:51", "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:65", "src/uxbox/main/ui/auth/register.cljs:54" ],
"translations" : {
"en" : "An unexpected error occurred.",
"fr" : "Une erreur inattendue c'est produite",
@ -864,7 +864,7 @@
}
},
"media.loading" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:382", "src/uxbox/main/data/workspace/persistence.cljs:433", "src/uxbox/main/data/users.cljs:201", "src/uxbox/main/data/images.cljs:403" ],
"used-in" : [ "src/uxbox/main/data/media.cljs:44" ],
"translations" : {
"en" : "Loading image...",
"fr" : "Chargement de l'image...",
@ -873,7 +873,7 @@
}
},
"modal.create-color.new-color" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:49", "src/uxbox/main/ui/workspace/sidebar/assets.cljs:62" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:55" ],
"translations" : {
"en" : "New Color",
"fr" : "Nouvelle couleur",
@ -1053,7 +1053,7 @@
}
},
"settings.multiple" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:132", "src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs:117", "src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs:126", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:124", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:227", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:240" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:137", "src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs:117", "src/uxbox/main/ui/workspace/sidebar/options/rows/color_row.cljs:126", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:124", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:227", "src/uxbox/main/ui/workspace/sidebar/options/text.cljs:240" ],
"translations" : {
"en" : "Mixed",
"fr" : null,
@ -1089,7 +1089,7 @@
}
},
"settings.notifications.email-not-verified" : {
"used-in" : [ "src/uxbox/main/ui/dashboard.cljs:118" ],
"used-in" : [ "src/uxbox/main/ui/dashboard.cljs:98" ],
"translations" : {
"en" : "Your email address has not been verified yet. Please check your inbox at “%s” for a confirmation email.",
"fr" : "Votre adresse e-mail n'a pas encore été vérifiée. Veuillez vérifier votre boîte de réception à “%s” pour un e-mail de confirmation.",
@ -1116,7 +1116,7 @@
}
},
"settings.notifications.profile-deletion-not-allowed" : {
"used-in" : [ "src/uxbox/main/data/auth.cljs:136" ],
"used-in" : [ "src/uxbox/main/data/auth.cljs:160" ],
"translations" : {
"en" : "You can't delete you profile. Reasign your teams before proceed.",
"fr" : "Vous ne pouvez pas supprimer votre profil. Réassignez vos équipes avant de continuer.",
@ -1449,7 +1449,7 @@
}
},
"workspace.assets.assets" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:331" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:328" ],
"translations" : {
"en" : "Assets",
"fr" : "",
@ -1485,7 +1485,7 @@
}
},
"workspace.assets.colors" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:247" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:240" ],
"translations" : {
"en" : "Colors",
"fr" : "",
@ -1494,7 +1494,7 @@
}
},
"workspace.assets.delete" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:136", "src/uxbox/main/ui/workspace/sidebar/assets.cljs:231" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:129", "src/uxbox/main/ui/workspace/sidebar/assets.cljs:224" ],
"translations" : {
"en" : "Delete",
"fr" : "",
@ -1503,7 +1503,7 @@
}
},
"workspace.assets.edit" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:230" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:223" ],
"translations" : {
"en" : "Edit",
"fr" : "",
@ -1512,7 +1512,7 @@
}
},
"workspace.assets.file-library" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:272" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:354" ],
"translations" : {
"en" : "File library",
"fr" : "",
@ -1521,7 +1521,7 @@
}
},
"workspace.assets.graphics" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:113" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:106" ],
"translations" : {
"en" : "Graphics",
"fr" : "",
@ -1529,8 +1529,17 @@
"es" : "Gráficos"
}
},
"workspace.assets.libraries" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:331" ],
"translations" : {
"en" : "Libraries",
"fr" : "",
"ru" : "",
"es" : "Bibliotecas"
}
},
"workspace.assets.not-found" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:287" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:281" ],
"translations" : {
"en" : "No assets found",
"fr" : "",
@ -1539,7 +1548,7 @@
}
},
"workspace.assets.rename" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:229" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:222" ],
"translations" : {
"en" : "Rename",
"fr" : "",
@ -1557,7 +1566,7 @@
}
},
"workspace.assets.shared" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:274" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:268" ],
"translations" : {
"en" : "SHARED",
"fr" : "",
@ -1601,6 +1610,15 @@
"es" : "Alinear a la rejilla"
}
},
"workspace.header.menu.hide-assets" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:132" ],
"translations" : {
"en" : "Hide assets",
"fr" : "",
"ru" : "",
"es" : "Ocultar recursos"
}
},
"workspace.header.menu.hide-grid" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:104" ],
"translations" : {
@ -1619,15 +1637,6 @@
"es" : "Ocultar capas"
}
},
"workspace.header.menu.hide-assets" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:132" ],
"translations" : {
"en" : "Hide assets",
"fr" : "",
"ru" : "",
"es" : "Ocultar recursos"
}
},
"workspace.header.menu.hide-palette" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:125" ],
"translations" : {
@ -1646,6 +1655,15 @@
"es" : "Ocultar reglas"
}
},
"workspace.header.menu.show-assets" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:133" ],
"translations" : {
"en" : "Show assets",
"fr" : "",
"ru" : "",
"es" : "Mostrar recursos"
}
},
"workspace.header.menu.show-grid" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:105" ],
"translations" : {
@ -1664,15 +1682,6 @@
"es" : "Mostrar capas"
}
},
"workspace.header.menu.show-assets" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:133" ],
"translations" : {
"en" : "Show assets",
"fr" : "",
"ru" : "",
"es" : "Mostrar recursos"
}
},
"workspace.header.menu.show-palette" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:126" ],
"translations" : {
@ -1700,59 +1709,167 @@
"es" : "Modo de visualización (Ctrl + P)"
}
},
"workspace.libraries.add" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:98" ],
"translations" : {
"en" : "Add",
"fr" : "",
"ru" : "",
"es" : "Añadir"
}
},
"workspace.libraries.colors" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:55" ],
"translations" : {
"en" : "%s colors",
"fr" : "",
"ru" : "",
"es" : "%s colors"
}
},
"workspace.libraries.file-library" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:61" ],
"translations" : {
"en" : "File library",
"fr" : "",
"ru" : "",
"es" : "Biblioteca de este archivo"
}
},
"workspace.libraries.graphics" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:52" ],
"translations" : {
"en" : "%s graphics",
"fr" : "",
"ru" : "",
"es" : "%s gráficos"
}
},
"workspace.libraries.in-this-file" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:58" ],
"translations" : {
"en" : "LIBRARIES IN THIS FILE",
"fr" : "",
"ru" : "",
"es" : "BIBLIOTECAS EN ESTE ARCHIVO"
}
},
"workspace.libraries.libraries" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:140" ],
"translations" : {
"en" : "LIBRARIES",
"fr" : "",
"ru" : "",
"es" : "BIBLIOTECAS"
}
},
"workspace.libraries.no-matches-for" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:104" ],
"translations" : {
"en" : "No matches found for “%s“",
"fr" : "Aucune correspondance pour “%s“",
"ru" : "Совпадений для “%s“ не найдено",
"es" : "No se encuentra “%s“"
}
},
"workspace.libraries.no-shared-libraries-available" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:103" ],
"translations" : {
"en" : "There are no Shared Libraries available",
"fr" : "",
"ru" : "",
"es" : "No hay bibliotecas compartidas disponibles"
}
},
"workspace.libraries.remove" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:72" ],
"translations" : {
"en" : "Remove",
"fr" : "",
"ru" : "",
"es" : "Quitar"
}
},
"workspace.libraries.search-shared-libraries" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:79" ],
"translations" : {
"en" : "Search shared libraries",
"fr" : "",
"ru" : "",
"es" : "Buscar bibliotecas compartidas"
}
},
"workspace.libraries.shared-libraries" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:76" ],
"translations" : {
"en" : "SHARED LIBRARIES",
"fr" : "",
"ru" : "",
"es" : "BIBLIOTECAS COMPARTIDAS"
}
},
"workspace.libraries.updates" : {
"used-in" : [ "src/uxbox/main/ui/workspace/libraries.cljs:144" ],
"translations" : {
"en" : "UPDATES",
"fr" : "",
"ru" : "",
"es" : "ACTUALIZACIONES"
}
},
"workspace.library.all" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:128" ],
"translations" : {
"en" : "All libraries",
"fr" : "Toutes les librairies",
"ru" : "Все библиотеки",
"es" : "Todas"
}
},
"unused" : true
},
"workspace.library.icons" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:178" ],
"translations" : {
"en" : "Icons",
"fr" : "Icônes",
"ru" : "Иконки",
"es" : "Iconos"
}
},
"unused" : true
},
"workspace.library.images" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:183" ],
"translations" : {
"en" : "Images",
"fr" : "Images",
"ru" : "Изображения",
"es" : "Imágenes"
}
},
"unused" : true
},
"workspace.library.libraries" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:160" ],
"translations" : {
"en" : "Libraries",
"fr" : "Librairies",
"ru" : "Библиотеки",
"es" : "Bibliotecas"
}
},
"unused" : true
},
"workspace.library.own" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:129" ],
"translations" : {
"en" : "My libraries",
"fr" : "Mes librairies",
"ru" : "Мои библиотеки",
"es" : "Mis bibliotecas"
}
},
"unused" : true
},
"workspace.library.store" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/libraries.cljs:130" ],
"translations" : {
"en" : "Store libraries",
"fr" : "Prédéfinies",
"ru" : "Сохраненные библиотеки",
"es" : "Predefinidas"
}
},
"unused" : true
},
"workspace.options.canvas-background" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/page.cljs:37" ],
@ -1780,21 +1897,21 @@
}
},
"workspace.options.export-object" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:154" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:155" ],
"translations" : {
"en" : "Export shape",
"ru" : "Экспорт фигуры"
}
},
"workspace.options.exporting-object" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:153" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/exports.cljs:154" ],
"translations" : {
"en" : "Exporting...",
"ru" : "Экспортирую ..."
}
},
"workspace.options.fill" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:50" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:52" ],
"translations" : {
"en" : "Fill",
"fr" : "Remplissage",
@ -1992,7 +2109,7 @@
}
},
"workspace.options.group-fill" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:49" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:51" ],
"translations" : {
"en" : "Group fill",
"fr" : null,
@ -2001,7 +2118,7 @@
}
},
"workspace.options.group-stroke" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:65" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:70" ],
"translations" : {
"en" : "Group stroke",
"fr" : null,
@ -2082,7 +2199,7 @@
}
},
"workspace.options.selection-fill" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:48" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/fill.cljs:50" ],
"translations" : {
"en" : "Selection fill",
"fr" : null,
@ -2091,7 +2208,7 @@
}
},
"workspace.options.selection-stroke" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:64" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:69" ],
"translations" : {
"en" : "Selection stroke",
"fr" : null,
@ -2118,7 +2235,7 @@
}
},
"workspace.options.stroke" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:66" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:71" ],
"translations" : {
"en" : "Stroke",
"fr" : "Bordure",
@ -2127,7 +2244,7 @@
}
},
"workspace.options.stroke.center" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:139" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:144" ],
"translations" : {
"en" : "Center",
"fr" : "Centre",
@ -2136,7 +2253,7 @@
}
},
"workspace.options.stroke.dashed" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:149" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:154" ],
"translations" : {
"en" : "Dashed",
"fr" : "Tiré",
@ -2145,7 +2262,7 @@
}
},
"workspace.options.stroke.dotted" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:148" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:153" ],
"translations" : {
"en" : "Dotted",
"fr" : "Pointillé",
@ -2154,7 +2271,7 @@
}
},
"workspace.options.stroke.inner" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:140" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:145" ],
"translations" : {
"en" : "Inside",
"fr" : "Intérieur",
@ -2163,7 +2280,7 @@
}
},
"workspace.options.stroke.mixed" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:150" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:155" ],
"translations" : {
"en" : "Mixed",
"fr" : "Mixte",
@ -2172,7 +2289,7 @@
}
},
"workspace.options.stroke.outer" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:141" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:146" ],
"translations" : {
"en" : "Outside",
"fr" : "Extérieur",
@ -2181,7 +2298,7 @@
}
},
"workspace.options.stroke.solid" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:147" ],
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/options/stroke.cljs:152" ],
"translations" : {
"en" : "Solid",
"fr" : "Solide",
@ -2404,7 +2521,7 @@
}
},
"workspace.toolbar.assets" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:105" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:99" ],
"translations" : {
"en" : "Assets (Ctrl + I)",
"fr" : "",
@ -2413,7 +2530,7 @@
}
},
"workspace.toolbar.circle" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:64" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:63" ],
"translations" : {
"en" : "Circle (E)",
"fr" : "Cercle (E)",
@ -2422,7 +2539,7 @@
}
},
"workspace.toolbar.color-palette" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:113" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:107" ],
"translations" : {
"en" : "Color Palette (---)",
"fr" : "Palette de couleurs (---)",
@ -2431,7 +2548,7 @@
}
},
"workspace.toolbar.curve" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:83" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:82" ],
"translations" : {
"en" : "Curve",
"fr" : "Courbe",
@ -2440,7 +2557,7 @@
}
},
"workspace.toolbar.frame" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:54" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:53" ],
"translations" : {
"en" : "Artboard (A)",
"fr" : "Plan de travail (A)",
@ -2449,7 +2566,7 @@
}
},
"workspace.toolbar.image" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:74" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:73" ],
"translations" : {
"en" : "Image (I)",
"fr" : "Image (I)",
@ -2467,7 +2584,7 @@
"unused" : true
},
"workspace.toolbar.path" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:88" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:87" ],
"translations" : {
"en" : "Path",
"fr" : "Chemin",
@ -2476,7 +2593,7 @@
}
},
"workspace.toolbar.rect" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:59" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:58" ],
"translations" : {
"en" : "Box (B)",
"fr" : "Boîte (B)",
@ -2485,7 +2602,7 @@
}
},
"workspace.toolbar.text" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:69" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:68" ],
"translations" : {
"en" : "Text (T)",
"fr" : "Texte (T)",
@ -2494,7 +2611,7 @@
}
},
"workspace.viewport.click-to-close-path" : {
"used-in" : [ "src/uxbox/main/ui/workspace/drawarea.cljs:55" ],
"used-in" : [ "src/uxbox/main/ui/workspace/drawarea.cljs:59" ],
"translations" : {
"en" : "Click to close the path",
"fr" : "Cliquez pour fermer le chemin",

View file

@ -26,6 +26,7 @@
@import 'main/layouts/main-layout';
@import 'main/layouts/login';
@import 'main/layouts/projects-page';
@import 'main/layouts/libraries-page';
@import 'main/layouts/recent-files-page';
@import "main/layouts/not-found";
@import "main/layouts/viewer";

View file

@ -0,0 +1,5 @@
.libraries-page {
padding: 1rem;
height: 100%;
background-color: $color-white;
}

View file

@ -136,3 +136,156 @@
}
}
.libraries-dialog {
width: 920px;
height: 664px;
.modal-content {
display: flex;
flex-grow: 1;
flex-direction: column;
padding: 0;
}
.libraries-header {
border-bottom: 1px solid $color-gray-20;
padding: 2rem 1rem 0.5rem 1rem;
display: flex;
justify-content: center;
.header-item {
cursor: pointer;
color: $color-gray-40;
font-size: $fs15;
&.active {
color: $color-gray-60;
border-bottom: 2px solid $color-primary;
}
&:not(:first-child) {
margin-left: 3rem;
}
}
}
.libraries-content {
display: flex;
justify-content: stretch;
align-items: stretch;
flex-grow: 1;
padding: 0 $size-4;
color: $color-gray-40;
.section {
flex-basis: 0;
flex-grow: 1;
padding: $size-4 0;
display: flex;
flex-direction: column;
&:not(:first-child) {
border-left: 1px solid $color-gray-20;
}
}
.section-title {
font-size: $fs15;
padding: 0 $size-4;
}
.section-list {
flex-basis: 0;
flex-grow: 1;
padding: 0 $size-4;
overflow: auto;
.section-list-item {
padding: $size-4 0;
border-bottom: 1px solid $color-gray-20;
position: relative;
.item-name {
color: $color-gray-60;
font-size: $fs14;
}
.item-contents {
color: $color-gray-40;
font-size: $fs12;
}
.item-button {
position: absolute;
top: $size-4;
right: 0;
border: 1px solid $color-primary;
border-radius: 2px;
width: 4.5rem;
background: $color-primary;
color: $color-black;
padding: $size-2;
&:hover {
color: $color-primary;
background: $color-black;
}
}
}
}
.section-list-empty {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 0 $size-4;
flex-grow: 1;
& svg {
fill: $color-gray-20;
height: 50px;
width: 50px;
margin-bottom: $size-4;
}
}
.libraries-search {
border: 1px solid $color-gray-30;
margin: $size-4;
padding: $x-small;
display: flex;
align-items: center;
&:focus-within {
border-color: $color-primary;
}
& .search-input {
border: none;
color: $color-gray-60;
font-size: $fs12;
margin: 0;
padding: 0;
flex-grow: 1;
}
& .search-icon {
display: flex;
align-items: center;
svg {
fill: $color-gray-30;
height: 16px;
width: 16px;
}
&.search-close {
transform: rotate(45deg);
cursor: pointer;
}
}
}
}
}

View file

@ -13,6 +13,30 @@
color: $color-gray-10;
font-size: $fs14;
margin: $small $small 0 $small;
display: flex;
align-items: center;
cursor: pointer;
& .libraries-button {
margin-left: auto;
display: flex;
align-items: center;
svg {
fill: $color-gray-30;
height: 20px;
width: 20px;
padding-right: $x-small;
}
}
& .libraries-button:hover {
color: $color-primary;
& svg {
fill: $color-primary;
}
}
}
.search-block {
@ -22,14 +46,14 @@
display: flex;
align-items: center;
&:focus-within {
border-color: $color-primary !important;
}
&:hover {
border-color: $color-gray-20;
}
&:focus-within {
border-color: $color-primary;
}
& .search-input {
background-color: $color-gray-50;
border: none;
@ -50,8 +74,8 @@
svg {
fill: $color-gray-30;
height: 15px;
width: 15px;
height: 16px;
width: 16px;
}
&.close {

View file

@ -99,6 +99,22 @@ $width-settings-bar: 15rem;
margin-left: auto;
}
span.tool-link {
margin-left: auto;
svg {
fill: $color-gray-30;
height: 16px;
width: 16px;
}
&:hover {
svg {
fill: $color-primary;
}
}
}
.tool-window-icon {
margin-right: $small;
display: none;

View file

@ -41,7 +41,7 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-colors-library (:id color)] color)
(update-in [:workspace-file :colors] #(conj % color))
(assoc-in [:workspace-local :color-for-rename] (:id color))))))
(def clear-color-for-rename
@ -67,7 +67,7 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-colors-library (:id color)] color)))))
(update-in [:workspace-file :colors] #(d/replace-by-id % color))))))
(declare update-color-result)
@ -86,7 +86,7 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-colors-library (:id color)] color)))))
(update-in [:workspace-file :colors] #(d/replace-by-id % color))))))
(declare delete-color-result)
@ -104,5 +104,6 @@
ptk/UpdateEvent
(update [_ state]
(-> state
(d/dissoc-in [:workspace-colors-library color-id])))))
(update-in [:workspace-file :colors]
(fn [colors] (filter #(not= (:id %) color-id) colors)))))))

View file

@ -1433,8 +1433,9 @@
;; Persistence
(def set-file-shared dwp/set-file-shared)
(def fetch-media-library dwp/fetch-media-library)
(def fetch-colors-library dwp/fetch-colors-library)
(def fetch-shared-files dwp/fetch-shared-files)
(def link-file-to-library dwp/link-file-to-library)
(def unlink-file-from-library dwp/unlink-file-from-library)
(def add-media-object-from-url dwp/add-media-object-from-url)
(def upload-media-objects dwp/upload-media-objects)
(def delete-media-object dwp/delete-media-object)

View file

@ -137,6 +137,7 @@
::ordering
::data]))
(declare fetch-libraries-content)
(declare bundle-fetched)
(defn- fetch-bundle
@ -147,10 +148,18 @@
(->> (rx/zip (rp/query :file {:id file-id})
(rp/query :file-users {:id file-id})
(rp/query :project-by-id {:project-id project-id})
(rp/query :pages {:file-id file-id}))
(rp/query :pages {:file-id file-id})
(rp/query :media-objects {:file-id file-id :is-local false})
(rp/query :colors {:file-id file-id})
(rp/query :file-libraries {:file-id file-id}))
(rx/first)
(rx/map (fn [[file users project pages]]
(bundle-fetched file users project pages)))
(rx/mapcat
(fn [bundle]
(->> (fetch-libraries-content (get bundle 6))
(rx/map (fn [[lib-media-objects lib-colors]]
(conj bundle lib-media-objects lib-colors))))))
(rx/map (fn [bundle]
(apply bundle-fetched bundle)))
(rx/catch (fn [{:keys [type code] :as error}]
(cond
(= :not-found type)
@ -163,27 +172,78 @@
:else
(throw error))))))))
(defn- fetch-libraries-content
[libraries]
(if (empty? libraries)
(rx/of [{} {}])
(rx/zip
(->> ;; fetch media-objects list of each library, and concatenate in a sequence
(apply rx/zip (for [library libraries]
(->> (rp/query :media-objects {:file-id (:id library)
:is-local false})
(rx/map (fn [media-objects]
[(:id library) media-objects])))))
;; reorganize the sequence as a map {library-id -> media-objects}
(rx/map (fn [media-list]
(reduce (fn [result, [library-id media-objects]]
(assoc result library-id media-objects))
{}
media-list))))
(->> ;; fetch colorss list of each library, and concatenate in a vector
(apply rx/zip (for [library libraries]
(->> (rp/query :colors {:file-id (:id library)})
(rx/map (fn [colors]
[(:id library) colors])))))
;; reorganize the sequence as a map {library-id -> colors}
(rx/map (fn [colors-list]
(reduce (fn [result, [library-id colors]]
(assoc result library-id colors))
{}
colors-list)))))))
(defn- bundle-fetched
[file users project pages]
[file users project pages media-objects colors libraries lib-media-objects lib-colors]
(ptk/reify ::bundle-fetched
IDeref
(-deref [_]
{:file file
:users users
:project project
:pages pages})
:pages pages
:media-objects media-objects
:colors colors
:libraries libraries})
ptk/UpdateEvent
(update [_ state]
(let [assoc-page #(assoc-in %1 [:workspace-pages (:id %2)] %2)]
(let [assoc-page
#(assoc-in %1 [:workspace-pages (:id %2)] %2)
assoc-media-objects
#(assoc-in %1 [:workspace-libraries %2 :media-objects]
(get lib-media-objects %2))
assoc-colors
#(assoc-in %1 [:workspace-libraries %2 :colors]
(get lib-colors %2))]
(as-> state $$
(assoc $$
:workspace-file file
:workspace-file (assoc file
:media-objects media-objects
:colors colors)
:workspace-users (d/index-by :id users)
:workspace-pages {}
:workspace-project project)
:workspace-project project
:workspace-libraries (d/index-by :id libraries))
(reduce assoc-media-objects $$ (keys lib-media-objects))
(reduce assoc-colors $$ (keys lib-colors))
(reduce assoc-page $$ pages))))))
;; --- Set File shared
(defn set-file-shared
@ -200,6 +260,79 @@
(->> (rp/mutation :set-file-shared params)
(rx/ignore))))))
;; --- Fetch Shared Files
(declare shared-files-fetched)
(defn fetch-shared-files
[]
(ptk/reify ::fetch-shared-files
ptk/WatchEvent
(watch [_ state stream]
(let [params {}]
(->> (rp/query :shared-files params)
(rx/map shared-files-fetched))))))
(defn shared-files-fetched
[files]
(us/verify (s/every ::file) files)
(ptk/reify ::shared-files-fetched
ptk/UpdateEvent
(update [_ state]
(let [state (dissoc state :files)]
(assoc state :workspace-shared-files files)))))
;; --- Link and unlink Files
(declare file-linked)
(defn link-file-to-library
[file-id library-id]
(ptk/reify ::link-file-to-library
ptk/WatchEvent
(watch [_ state stream]
(let [params {:file-id file-id
:library-id library-id}]
(->> (->> (rp/mutation :link-file-to-library params)
(rx/mapcat
#(rx/zip (rp/query :file-library {:file-id library-id})
(rp/query :media-objects {:file-id library-id
:is-local false})
(rp/query :colors {:file-id library-id}))))
(rx/map file-linked))))))
(defn file-linked
[[library media-objects colors]]
(ptk/reify ::file-linked
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-libraries (:id library)]
(assoc library
:media-objects media-objects
:colors colors)))))
(declare file-unlinked)
(defn unlink-file-from-library
[file-id library-id]
(ptk/reify ::unlink-file-from-library
ptk/WatchEvent
(watch [_ state stream]
(let [params {:file-id file-id
:library-id library-id}]
(->> (rp/mutation :unlink-file-from-library params)
(rx/map #(file-unlinked file-id library-id)))))))
(defn file-unlinked
[file-id library-id]
(ptk/reify ::file-unlinked
ptk/UpdateEvent
(update [_ state]
(d/dissoc-in state [:workspace-libraries library-id]))))
;; --- Fetch Pages
(declare page-fetched)
@ -298,46 +431,6 @@
(rx/of go-to-file)
(rx/empty))))))))))
;; --- Fetch Workspace Media library
(declare media-library-fetched)
(defn fetch-media-library
[file-id]
(ptk/reify ::fetch-media-library
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :media-objects {:file-id file-id :is-local false})
(rx/map media-library-fetched)))))
(defn media-library-fetched
[media-objects]
(ptk/reify ::media-library-fetched
ptk/UpdateEvent
(update [_ state]
(let [media-objects (d/index-by :id media-objects)]
(assoc state :workspace-media-library media-objects)))))
;; --- Fetch Workspace Colors library
(declare colors-library-fetched)
(defn fetch-colors-library
[file-id]
(ptk/reify ::fetch-colors-library
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :colors {:file-id file-id})
(rx/map colors-library-fetched)))))
(defn colors-library-fetched
[colors]
(ptk/reify ::colors-library-fetched
ptk/UpdateEvent
(update [_ state]
(let [colors (d/index-by :id colors)]
(assoc state :workspace-colors-library colors)))))
;; --- Upload local media objects
@ -357,6 +450,8 @@
on-error #(do (di/notify-finished-loading)
(di/process-error %))
is-library (not= file-id (:id (:workspace-file state)))
prepare
(fn [url]
@ -370,7 +465,7 @@
(rx/map prepare)
(rx/mapcat #(rp/mutation! :add-media-object-from-url %))
(rx/do on-success)
(rx/map (partial upload-media-objects-result file-id is-local))
(rx/map (partial upload-media-objects-result file-id is-local is-library))
(rx/catch on-error)))))))
(defn upload-media-objects
@ -389,6 +484,8 @@
on-error #(do (di/notify-finished-loading)
(di/process-error %))
is-library (not= file-id (:id (:workspace-file state)))
prepare
(fn [js-file]
{:name (.-name js-file)
@ -403,32 +500,43 @@
(rx/map prepare)
(rx/mapcat #(rp/mutation! :upload-media-object %))
(rx/do on-success)
(rx/map (partial upload-media-objects-result file-id is-local))
(rx/map (partial upload-media-objects-result file-id is-local is-library))
(rx/catch on-error)))))))
(defn upload-media-objects-result
[file-id is-local media-object]
[file-id is-local is-library media-object]
(us/verify ::us/uuid file-id)
(us/verify ::us/boolean is-local)
(us/verify ::cm/media-object media-object)
(ptk/reify ::upload-media-objects-result
ptk/UpdateEvent
(update [_ state]
(if is-local
(if is-local ;; the media-object is local to the file, not for its library
state
(assoc-in state
[:workspace-media-library (:id media-object)]
media-object)))))
(if is-library ;; the file is not the currently editing one, but a linked shared file
(update-in state
[:workspace-libraries file-id :media-objects]
#(conj % media-object))
(update-in state
[:workspace-file :media-objects]
#(conj % media-object)))))))
;; --- Delete media object
(defn delete-media-object
[id]
[file-id id]
(ptk/reify ::delete-media-object
ptk/UpdateEvent
(update [_ state]
(update state :workspace-media-library dissoc id))
(let [is-library (not= file-id (:id (:workspace-file state)))]
(if is-library
(update-in state
[:workspace-libraries file-id :media-objects]
(fn [media-objects] (filter #(not= (:id %) id) media-objects)))
(update-in state
[:workspace-file :media-objects]
(fn [media-objects] (filter #(not= (:id %) id) media-objects))))))
ptk/WatchEvent
(watch [_ state stream]

View file

@ -57,11 +57,11 @@
(def workspace-project
(l/derived :workspace-project st/state))
(def workspace-media-library
(l/derived :workspace-media-library st/state))
(def workspace-shared-files
(l/derived :workspace-shared-files st/state))
(def workspace-colors-library
(l/derived :workspace-colors-library st/state))
(def workspace-libraries
(l/derived :workspace-libraries st/state))
(def workspace-users
(l/derived :workspace-users st/state))

View file

@ -56,6 +56,7 @@
(def layers (icon-xref :layers))
(def letter-spacing (icon-xref :letter-spacing))
(def library (icon-xref :library))
(def libraries (icon-xref :libraries))
(def line (icon-xref :line))
(def line-height (icon-xref :line-height))
(def loader (icon-xref :loader))

View file

@ -18,7 +18,6 @@
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.streams :as ms]
[uxbox.main.ui.confirm]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.hooks :as hooks]
[uxbox.main.ui.workspace.viewport :refer [viewport coordinates]]

View file

@ -0,0 +1,155 @@
;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
(ns uxbox.main.ui.workspace.libraries
(:require
[rumext.alpha :as mf]
[cuerdas.core :as str]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :refer (tr)]
[uxbox.util.data :refer [classnames matches-search]]
[uxbox.main.store :as st]
[uxbox.main.refs :as refs]
[uxbox.main.data.workspace :as dw]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.modal :as modal]))
(mf/defc libraries-tab
[{:keys [file libraries shared-files] :as props}]
(let [state (mf/use-state {:search-term ""})
sorted-libraries (->> (vals libraries)
(sort-by #(str/lower (:name %))))
filtered-files (->> shared-files
(filter #(not= (:id %) (:id file)))
(filter #(nil? (get libraries (:id %))))
(filter #(matches-search (:name %) (:search-term @state)))
(sort-by #(str/lower (:name %))))
on-search-term-change (fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value))]
(swap! state assoc :search-term value)))
on-search-clear-click (fn [event]
(swap! state assoc :search-term ""))
link-library (fn [library-id]
(st/emit! (dw/link-file-to-library (:id file) library-id)))
unlink-library (fn [library-id]
(st/emit! (dw/unlink-file-from-library (:id file) library-id)))
contents-str (fn [library graphics-count colors-count]
(str
(str/join " · "
(cond-> []
(< 0 graphics-count)
(conj (tr "workspace.libraries.graphics" graphics-count))
(< 0 colors-count)
(conj (tr "workspace.libraries.colors" colors-count))))
"\u00A0"))] ;; Include a &nbsp; so this block has always some content
[:*
[:div.section
[:div.section-title (tr "workspace.libraries.in-this-file")]
[:div.section-list
[:div.section-list-item
[:div.item-name (tr "workspace.libraries.file-library")]
[:div.item-contents (contents-str file
(count (:media-objects file))
(count (:colors file)))]]
(for [library sorted-libraries]
[:div.section-list-item {:key (:id library)}
[:div.item-name (:name library)]
[:div.item-contents (contents-str library
(count (:media-objects library))
(count (:colors library)))]
[:input.item-button {:type "button"
:value (tr "workspace.libraries.remove")
:on-click #(unlink-library (:id library))}]])
]]
[:div.section
[:div.section-title (tr "workspace.libraries.shared-libraries")]
[:div.libraries-search
[:input.search-input
{:placeholder (tr "workspace.libraries.search-shared-libraries")
:type "text"
:value (:search-term @state)
:on-change on-search-term-change}]
(if (str/empty? (:search-term @state))
[:div.search-icon
i/search]
[:div.search-icon.search-close
{:on-click on-search-clear-click}
i/close])]
(if (> (count filtered-files) 0)
[:div.section-list
(for [file filtered-files]
[:div.section-list-item {:key (:id file)}
[:div.item-name (:name file)]
[:div.item-contents (contents-str file
(:graphics-count file)
(:colors-count file))]
[:input.item-button {:type "button"
:value (tr "workspace.libraries.add")
:on-click #(link-library (:id file))}]])]
[:div.section-list-empty
i/library
(if (str/empty? (:search-term @state))
(tr "workspace.libraries.no-shared-libraries-available")
(tr "workspace.libraries.no-matches-for" (:search-term @state)))])]]))
(mf/defc updates-tab
[]
[:div])
(mf/defc libraries-dialog
[{:keys [] :as ctx}]
(let [state (mf/use-state {:current-tab :libraries})
current-tab (:current-tab @state)
file (mf/deref refs/workspace-file)
libraries (mf/deref refs/workspace-libraries)
shared-files (mf/deref refs/workspace-shared-files)
change-tab (fn [tab]
(swap! state assoc :current-tab tab))
close (fn [event]
(dom/prevent-default event)
(modal/hide!))]
(mf/use-effect
#(st/emit! (dw/fetch-shared-files)))
[:div.modal-overlay
[:div.modal.libraries-dialog
[:a.close {:on-click close} i/close]
[:div.modal-content
[:div.libraries-header
[:div.header-item
{:class (classnames :active (= current-tab :libraries))
:on-click #(change-tab :libraries)}
(tr "workspace.libraries.libraries")]
[:div.header-item
{:class (classnames :active (= current-tab :updates))
:on-click #(change-tab :updates)}
(tr "workspace.libraries.updates")]]
[:div.libraries-content
(case current-tab
:libraries
[:& libraries-tab {:file file
:libraries libraries
:shared-files shared-files}]
:updates
[:& updates-tab {}])]]]]))

View file

@ -29,20 +29,14 @@
[uxbox.util.timers :as timers]
[uxbox.common.uuid :as uuid]
[uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.data :refer [classnames]]
[uxbox.util.data :refer [classnames matches-search]]
[uxbox.util.router :as rt]
[uxbox.main.ui.modal :as modal]
[uxbox.main.ui.colorpicker :refer [colorpicker most-used-colors]]
[uxbox.main.ui.components.tab-container :refer [tab-container tab-element]]
[uxbox.main.ui.components.file-uploader :refer [file-uploader]]
[uxbox.main.ui.components.context-menu :refer [context-menu]]))
(defn matches-search
[name search-term]
(if (str/empty? search-term)
true
(let [st (str/trim (str/lower search-term))
nm (str/trim (str/lower name))]
(str/includes? nm st))))
[uxbox.main.ui.components.context-menu :refer [context-menu]]
[uxbox.main.ui.workspace.libraries :refer [libraries-dialog]]))
(mf/defc modal-edit-color
[{:keys [color-value on-accept on-cancel] :as ctx}]
@ -72,7 +66,7 @@
[:a.close {:href "#" :on-click cancel} i/close]]])))
(mf/defc graphics-box
[{:keys [file-id media-objects] :as props}]
[{:keys [file-id local-library? media-objects] :as props}]
(let [state (mf/use-state {:menu-open false
:top nil
:left nil
@ -84,7 +78,7 @@
#(dom/click (mf/ref-val file-input))
delete-graphic
#(st/emit! (dw/delete-media-object (:object-id @state)))
#(st/emit! (dw/delete-media-object file-id (:object-id @state)))
on-files-selected
(fn [js-files]
@ -93,14 +87,15 @@
on-context-menu
(fn [object-id]
(fn [event]
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(swap! state assoc :menu-open true
:top top
:left left
:object-id object-id))))
(when local-library?
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(swap! state assoc :menu-open true
:top top
:left left
:object-id object-id)))))
on-drag-start
(fn [uri]
@ -112,14 +107,15 @@
[:div.group-title
(tr "workspace.assets.graphics")
[:span (str "\u00A0(") (count media-objects) ")"] ;; Unicode 00A0 is non-breaking space
[:div.group-button {:on-click add-graphic}
i/plus
[:& file-uploader {:accept cm/str-media-types
:multi true
:input-ref file-input
:on-selected on-files-selected}]]]
(when local-library?
[:div.group-button {:on-click add-graphic}
i/plus
[:& file-uploader {:accept cm/str-media-types
:multi true
:input-ref file-input
:on-selected on-files-selected}]])]
[:div.group-grid
(for [object (sort-by :name media-objects)]
(for [object media-objects]
[:div.grid-cell {:key (:id object)
:draggable true
:on-context-menu (on-context-menu (:id object))
@ -127,17 +123,18 @@
[:img {:src (:thumb-uri object)
:draggable false}] ;; Also need to add css pointer-events: none
[:div.cell-name (:name object)]])
[:& context-menu
{:selectable false
:show (:menu-open @state)
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [[(tr "workspace.assets.delete") delete-graphic]]}]]]))
(when local-library?
[:& context-menu
{:selectable false
:show (:menu-open @state)
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [[(tr "workspace.assets.delete") delete-graphic]]}])]]))
(mf/defc color-item
[{:keys [color file-id] :as props}]
[{:keys [color file-id local-library?] :as props}]
(let [workspace-local @refs/workspace-local
color-for-rename (:color-for-rename workspace-local)
@ -189,14 +186,15 @@
on-context-menu
(fn [event]
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(swap! state assoc
:menu-open true
:top top
:left left)))]
(when local-library?
(let [pos (dom/get-client-position event)
top (:y pos)
left (- (:x pos) 20)]
(dom/prevent-default event)
(swap! state assoc
:menu-open true
:top top
:left left))))]
(mf/use-effect
(mf/deps (:editing @state))
@ -220,18 +218,19 @@
(:name color)
(when-not (= (:name color) (:content color))
[:span (:content color)])])
[:& context-menu
{:selectable false
:show (:menu-open @state)
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [[(tr "workspace.assets.rename") rename-color-clicked]
[(tr "workspace.assets.edit") edit-color-clicked]
[(tr "workspace.assets.delete") delete-color]]}]]))
(when local-library?
[:& context-menu
{:selectable false
:show (:menu-open @state)
:on-close #(swap! state assoc :menu-open false)
:top (:top @state)
:left (:left @state)
:options [[(tr "workspace.assets.rename") rename-color-clicked]
[(tr "workspace.assets.edit") edit-color-clicked]
[(tr "workspace.assets.delete") delete-color]]}])]))
(mf/defc colors-box
[{:keys [file-id colors] :as props}]
[{:keys [file-id local-library? colors] :as props}]
(let [add-color
(fn [value opacity]
(st/emit! (dcol/create-color file-id value)))
@ -246,15 +245,18 @@
[:div.group-title
(tr "workspace.assets.colors")
[:span (str "\u00A0(") (count colors) ")"] ;; Unicode 00A0 is non-breaking space
[:div.group-button {:on-click add-color-clicked} i/plus]]
(when local-library?
[:div.group-button {:on-click add-color-clicked} i/plus])]
[:div.group-list
(for [color (sort-by :name colors)]
(for [color colors]
[:& color-item {:key (:id color)
:color color
:file-id file-id}])]]))
:file-id file-id
:local-library? local-library?}])]]))
(mf/defc file-library-toolbox
[{:keys [file-id
[{:keys [library
local-library?
shared?
media-objects
colors
@ -262,26 +264,41 @@
search-term
box-filter] :as props}]
(let [open? (mf/use-state initial-open?)
toggle-open #(swap! open? not)]
toggle-open #(swap! open? not)
router (mf/deref refs/router)
library-url (rt/resolve router :workspace
{:project-id (:project-id library)
:file-id (:id library)}
{:page-id (first (:pages library))})]
[:div.tool-window
[:div.tool-window-bar
[:div.collapse-library
{:class (classnames :open @open?)
:on-click toggle-open}
i/arrow-slide]
[:span (tr "workspace.assets.file-library")]
(when shared?
[:span.tool-badge (tr "workspace.assets.shared")])]
(if local-library?
[:*
[:span (tr "workspace.assets.file-library")]
(when shared?
[:span.tool-badge (tr "workspace.assets.shared")])]
[:*
[:span (:name library)]
[:span.tool-link
[:a {:href (str "#" library-url) :target "_blank"} i/chain]]])]
(when @open?
(let [show-graphics (and (or (= box-filter :all) (= box-filter :graphics))
(or (> (count media-objects) 0) (str/empty? search-term)))
show-colors (and (or (= box-filter :all) (= box-filter :colors))
(or (> (count colors) 0) (str/empty? search-term)))]
(or (> (count media-objects) 0) (str/empty? search-term)))
show-colors (and (or (= box-filter :all) (= box-filter :colors))
(or (> (count colors) 0) (str/empty? search-term)))]
[:div.tool-window-content
(when show-graphics
[:& graphics-box {:file-id file-id :media-objects media-objects}])
[:& graphics-box {:file-id (:id library)
:local-library? local-library?
:media-objects media-objects}])
(when show-colors
[:& colors-box {:file-id file-id :colors colors}])
[:& colors-box {:file-id (:id library)
:local-library? local-library?
:colors colors}])
(when (and (not show-graphics) (not show-colors))
[:div.asset-group
[:div.group-title (tr "workspace.assets.not-found")]])]))]))
@ -290,19 +307,27 @@
[]
(let [team-id (-> refs/workspace-project mf/deref :team-id)
file (mf/deref refs/workspace-file)
file-id (:id file)
file-media (mf/deref refs/workspace-media-library)
file-colors (mf/deref refs/workspace-colors-library)
libraries (mf/deref refs/workspace-libraries)
sorted-libraries (->> (vals libraries)
(sort-by #(str/lower (:name %))))
state (mf/use-state {:search-term ""
:box-filter :all})
filtered-media-objects (filter #(matches-search (:name %) (:search-term @state))
(vals file-media))
filtered-media-objects (fn [library-id]
(as-> libraries $$
(assoc $$ (:id file) file)
(get-in $$ [library-id :media-objects])
(filter #(matches-search (:name %) (:search-term @state)) $$)
(sort-by #(str/lower (:name %)) $$)))
filtered-colors (filter #(or (matches-search (:name %) (:search-term @state))
(matches-search (:content %) (:search-term @state)))
(vals file-colors))
filtered-colors (fn [library-id]
(as-> libraries $$
(assoc $$ (:id file) file)
(get-in $$ [library-id :colors])
(filter #(or (matches-search (:name %) (:search-term @state))
(matches-search (:content %) (:search-term @state))) $$)
(sort-by #(str/lower (:name %)) $$)))
on-search-term-change (fn [event]
(let [value (-> (dom/get-target event)
@ -318,17 +343,15 @@
(d/read-string))]
(swap! state assoc :box-filter value)))]
(mf/use-effect
(mf/deps file-id)
#(when file-id
(st/emit! (dw/fetch-media-library file-id))
(st/emit! (dw/fetch-colors-library file-id))))
[:div.assets-bar
[:div.tool-window
[:div.tool-window-content
[:div.assets-bar-title (tr "workspace.assets.assets")]
[:div.assets-bar-title
(tr "workspace.assets.assets")
[:div.libraries-button {:on-click #(modal/show! libraries-dialog {})}
i/libraries
(tr "workspace.assets.libraries")]]
[:div.search-block
[:input.search-input
@ -347,14 +370,25 @@
:on-change on-box-filter-change}
[:option {:value ":all"} (tr "workspace.assets.box-filter-all")]
[:option {:value ":graphics"} (tr "workspace.assets.box-filter-graphics")]
[:option {:value ":colors"} (tr "workspace.assets.box-filter-colors")]]
]]
[:option {:value ":colors"} (tr "workspace.assets.box-filter-colors")]]]]
[:& file-library-toolbox {:file-id file-id
[:& file-library-toolbox {:key (:id file)
:library file
:local-library? true
:shared? (:is-shared file)
:media-objects filtered-media-objects
:colors filtered-colors
:media-objects (filtered-media-objects (:id file))
:colors (filtered-colors (:id file))
:initial-open? true
:search-term (:search-term @state)
:box-filter (:box-filter @state)}]]))
:box-filter (:box-filter @state)}]
(for [library sorted-libraries]
[:& file-library-toolbox {:key (:id library)
:library library
:local-library? false
:shared? (:is-shared library)
:media-objects (filtered-media-objects (:id library))
:colors (filtered-colors (:id library))
:initial-open? false
:search-term (:search-term @state)
:box-filter (:box-filter @state)}])]))

View file

@ -180,6 +180,13 @@
"className"
(str/camel (name key))))))
(defn matches-search
[name search-term]
(if (str/empty? search-term)
true
(let [st (str/trim (str/lower search-term))
nm (str/trim (str/lower name))]
(str/includes? nm st))))
;; (defn coalesce
;; [^number v ^number n]