0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-03-18 10:41:29 -05:00

Merge pull request #287 from uxbox/453/assets-panel

453/assets panel
This commit is contained in:
Andrey Antukh 2020-08-05 12:33:26 +02:00 committed by GitHub
commit a38430fb80
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1512 additions and 459 deletions

View file

@ -0,0 +1,29 @@
-- ALTER TABLE color_library
-- DROP COLUMN team_id,
-- DROP COLUMN name,
-- ADD COLUMN file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE;
--
-- CREATE INDEX color_library__file_id__idx
-- ON color_library(file_id);
TRUNCATE TABLE color;
TRUNCATE TABLE color_library CASCADE;
TRUNCATE TABLE image;
TRUNCATE TABLE image_library CASCADE;
TRUNCATE TABLE icon;
TRUNCATE TABLE icon_library CASCADE;
ALTER TABLE color
DROP COLUMN library_id,
ADD COLUMN file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE;
CREATE INDEX color__file_id__idx
ON color(file_id);
ALTER TABLE image
DROP COLUMN library_id,
ADD COLUMN file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE;
CREATE INDEX image__file_id__idx
ON image(file_id);

2
backend/scripts/psql.sh Executable file
View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
PGPASSWORD=$UXBOX_DATABASE_PASSWORD psql $UXBOX_DATABASE_URI -U $UXBOX_DATABASE_USERNAME

View file

@ -48,7 +48,7 @@
(s/def ::width integer?)
(s/def ::height integer?)
(s/def ::format #{:jpeg :webp :png})
(s/def ::format #{:jpeg :webp :png :svg})
(s/def ::quality #(< 0 % 101))
(s/def ::thumbnail-params
@ -62,21 +62,24 @@
(case format
:png ".png"
:jpeg ".jpg"
:webp ".webp"))
:webp ".webp"
:svg ".svg"))
(defn format->mtype
[format]
(case format
:png "image/png"
:jpeg "image/jpeg"
:webp "image/webp"))
:webp "image/webp"
:svg "image/svg+xml"))
(defn mtype->format
[mtype]
(case mtype
"image/jpeg" :jpeg
"image/webp" :webp
"image/png" :png
"image/png" :png
"image/jpeg" :jpeg
"image/webp" :webp
"image/svg+xml" :svg
nil))
(defn- generic-process
@ -127,18 +130,21 @@
(defmethod process :info
[{:keys [input] :as params}]
(us/assert ::input input)
(let [{:keys [path mtype]} input
instance (Info. (str path))
mtype' (.getProperty instance "Mime type")]
(when (and (string? mtype)
(not= mtype mtype'))
(ex/raise :type :validation
:code :image-type-mismatch
:hint "Seems like you are uploading a file whose content does not match the extension."))
{:width (.getImageWidth instance)
:height (.getImageHeight instance)
:mtype mtype'}))
(let [{:keys [path mtype]} input]
(if (= mtype "image/svg+xml")
{:width 100
:height 100
:mtype mtype}
(let [instance (Info. (str path))
mtype' (.getProperty instance "Mime type")]
(when (and (string? mtype)
(not= mtype mtype'))
(ex/raise :type :validation
:code :image-type-mismatch
:hint "Seems like you are uploading a file whose content does not match the extension."))
{:width (.getImageWidth instance)
:height (.getImageHeight instance)
:mtype mtype'}))))
(defmethod process :default
[{:keys [cmd] :as params}]
@ -164,13 +170,15 @@
(defn resolve-urls
[row src dst]
(s/assert map? row)
(let [src (if (vector? src) src [src])
dst (if (vector? dst) dst [dst])
value (get-in row src)]
(if (empty? value)
row
(let [url (ust/public-uri media/media-storage value)]
(assoc-in row dst (str url))))))
(if (and src dst)
(let [src (if (vector? src) src [src])
dst (if (vector? dst) dst [dst])
value (get-in row src)]
(if (empty? value)
row
(let [url (ust/public-uri media/media-storage value)]
(assoc-in row dst (str url)))))
row))
(defn- resolve-uri
[storage row src dst]

View file

@ -29,6 +29,8 @@
[uxbox.util.blob :as blob]
[uxbox.common.uuid :as uuid]
[uxbox.util.data :as data]
[uxbox.services.mutations.projects :as projects]
[uxbox.services.mutations.files :as files]
[uxbox.services.mutations.colors :as colors]
[uxbox.services.mutations.icons :as icons]
[uxbox.services.mutations.images :as images]
@ -49,120 +51,243 @@
(s/def ::path ::us/string)
(s/def ::regex #(instance? java.util.regex.Pattern %))
(s/def ::colors
;; (s/def ::colors
;; (s/* (s/cat :name ::us/string :color ::us/color)))
;;
;; (s/def ::import-item-media
;; (s/keys :req-un [::name ::path ::regex]))
;;
;; (s/def ::import-item-color
;; (s/keys :req-un [::name ::id ::colors]))
(s/def ::import-images
(s/keys :req-un [::path ::regex]))
(s/def ::import-color
(s/* (s/cat :name ::us/string :color ::us/color)))
(s/def ::import-item-media
(s/keys :req-un [::name ::path ::regex]))
(s/def ::import-colors (s/coll-of ::import-color))
(s/def ::import-item-color
(s/keys :req-un [::name ::id ::colors]))
(s/def ::import-library
(s/keys :req-un [::name]
:opt-un [::import-images ::import-colors]))
(defn exit!
([] (exit! 0))
([code]
(System/exit code)))
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;; Icons Libraries Importer
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; (defn- icon-library-exists?
;; [conn id]
;; (s/assert ::us/uuid id)
;; (let [row (db/get-by-id conn :icon-library id)]
;; (if row true false)))
;;
;; (defn- create-icons-library
;; [conn {:keys [name] :as item}]
;; (let [id (uuid/namespaced +icons-uuid-ns+ name)]
;; (log/info "Creating icons library:" name)
;; (icons/create-library conn {:team-id uuid/zero
;; :id id
;; :name name})))
;;
;; (defn- create-icons-library-if-not-exists
;; [conn {:keys [name] :as item}]
;; (let [id (uuid/namespaced +icons-uuid-ns+ name)]
;; (when-not (icon-library-exists? conn id)
;; (create-icons-library conn item))
;; id))
;;
;; (defn- create-icon
;; [conn library-id icon-id localpath]
;; (s/assert fs/path? localpath)
;; (s/assert ::us/uuid library-id)
;; (s/assert ::us/uuid icon-id)
;; (let [filename (fs/name localpath)
;; extension (second (fs/split-ext filename))
;; data (svg/parse localpath)
;; mdata (select-keys data [:width :height :view-box])]
;;
;; (log/info "Creating or updating icon" filename icon-id)
;; (icons/create-icon conn {:id icon-id
;; :library-id library-id
;; :name (:name data filename)
;; :content (:content data)
;; :metadata mdata})))
;;
;; (defn- icon-exists?
;; [conn id]
;; (s/assert ::us/uuid id)
;; (let [row (db/get-by-id conn :icon id)]
;; (if row true false)))
;;
;; (defn- import-icon-if-not-exists
;; [conn library-id fpath]
;; (s/assert ::us/uuid library-id)
;; (s/assert fs/path? fpath)
;; (let [icon-id (uuid/namespaced +icons-uuid-ns+ (str library-id (fs/name fpath)))]
;; (when-not (icon-exists? conn icon-id)
;; (create-icon conn library-id icon-id fpath))
;; icon-id))
;;
;; (defn- import-icons
;; [conn library-id {:keys [path regex] :as item}]
;; (run! (fn [fpath]
;; (when (re-matches regex (str fpath))
;; (import-icon-if-not-exists conn library-id fpath)))
;; (->> (fs/list-dir path)
;; (filter fs/regular-file?))))
;;
;; (defn- process-icons-library
;; [conn basedir {:keys [path regex] :as item}]
;; (s/assert ::import-item-media item)
;; (let [library-id (create-icons-library-if-not-exists conn item)]
;; (->> (assoc item :path (fs/join basedir path))
;; (import-icons conn library-id))))
;;
;;
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;; --- Images Libraries Importer
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; (defn- image-library-exists?
;; [conn id]
;; (s/assert ::us/uuid id)
;; (let [row (db/get-by-id conn :image-library id)]
;; (if row true false)))
;;
;; (defn- create-images-library
;; [conn {:keys [name] :as item}]
;; (let [id (uuid/namespaced +images-uuid-ns+ name)]
;; (log/info "Creating image library:" name)
;; (images/create-library conn {:id id
;; :team-id uuid/zero
;; :name name})))
;;
;; (defn- create-images-library-if-not-exists
;; [conn {:keys [name] :as item}]
;; (let [id (uuid/namespaced +images-uuid-ns+ name)]
;; (when-not (image-library-exists? conn id)
;; (create-images-library conn item)
;; id)))
;;
;; (defn- create-image
;; [conn library-id image-id localpath]
;; (s/assert fs/path? localpath)
;; (s/assert ::us/uuid library-id)
;; (s/assert ::us/uuid image-id)
;; (let [filename (fs/name localpath)
;; extension (second (fs/split-ext filename))
;; file (io/as-file localpath)
;; mtype (case extension
;; ".jpg" "image/jpeg"
;; ".png" "image/png"
;; ".webp" "image/webp")]
;; (log/info "Creating image" filename image-id)
;; (images/create-image conn {:content {:tempfile localpath
;; :filename filename
;; :content-type mtype
;; :size (.length file)}
;; :id image-id
;; :library-id library-id
;; :user uuid/zero
;; :name filename})))
;;
;; (defn- image-exists?
;; [conn id]
;; (s/assert ::us/uuid id)
;; (let [row (db/get-by-id conn :image id)]
;; (if row true false)))
;;
;; (defn- import-image-if-not-exists
;; [conn library-id fpath]
;; (s/assert ::us/uuid library-id)
;; (s/assert fs/path? fpath)
;; (let [image-id (uuid/namespaced +images-uuid-ns+ (str library-id (fs/name fpath)))]
;; (when-not (image-exists? conn image-id)
;; (create-image conn library-id image-id fpath))
;; image-id))
;;
;; (defn- import-images
;; [conn library-id {:keys [path regex] :as item}]
;; (run! (fn [fpath]
;; (when (re-matches regex (str fpath))
;; (import-image-if-not-exists conn library-id fpath)))
;; (->> (fs/list-dir path)
;; (filter fs/regular-file?))))
;;
;; (defn- process-images-library
;; [conn basedir {:keys [path regex] :as item}]
;; (s/assert ::import-item-media item)
;; (let [library-id (create-images-library-if-not-exists conn item)]
;; (->> (assoc item :path (fs/join basedir path))
;; (import-images conn library-id))))
;;
;;
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ;; Colors Libraries Importer
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; (defn- color-library-exists?
;; [conn id]
;; (s/assert ::us/uuid id)
;; (let [row (db/get-by-id conn :file id)]
;; (if row true false)))
;;
;; (defn- create-colors-library
;; [conn {:keys [name] :as item}]
;; (let [id (uuid/namespaced +colors-uuid-ns+ name)]
;; (log/info "Creating color library:" name)
;; (colors/create-library conn {:id id
;; :team-id uuid/zero
;; :name name})))
;;
;;
;; (defn- create-colors-library-if-not-exists
;; [conn {:keys [name] :as item}]
;; (let [id (uuid/namespaced +colors-uuid-ns+ name)]
;; (when-not (color-library-exists? conn id)
;; (create-colors-library conn item))
;; id))
;;
;; (defn- create-color
;; [conn library-id name content]
;; (s/assert ::us/uuid library-id)
;; (s/assert ::us/color content)
;; (let [color-id (uuid/namespaced +colors-uuid-ns+ (str library-id content))]
;; (log/info "Creating color" color-id "-" name content)
;; (colors/create-color conn {:id color-id
;; :library-id library-id
;; :name name
;; :content content})
;; color-id))
;;
;; (defn- import-colors
;; [conn library-id {:keys [colors] :as item}]
;; (db/delete! conn :color {:library-id library-id})
;; (run! (fn [[name content]]
;; (create-color conn library-id name content))
;; (partition-all 2 colors)))
;;
;; (defn- process-colors-library
;; [conn {:keys [name id colors] :as item}]
;; (us/verify ::import-item-color item)
;; (let [library-id (create-colors-library-if-not-exists conn item)]
;; (import-colors conn library-id item)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Icons Libraries Importer
;; Images Importer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- icon-library-exists?
[conn id]
(s/assert ::us/uuid id)
(let [row (db/get-by-id conn :icon-library id)]
(if row true false)))
(defn- create-icons-library
[conn {:keys [name] :as item}]
(let [id (uuid/namespaced +icons-uuid-ns+ name)]
(log/info "Creating icons library:" name)
(icons/create-library conn {:team-id uuid/zero
:id id
:name name})))
(defn- create-icons-library-if-not-exists
[conn {:keys [name] :as item}]
(let [id (uuid/namespaced +icons-uuid-ns+ name)]
(when-not (icon-library-exists? conn id)
(create-icons-library conn item))
id))
(defn- create-icon
[conn library-id icon-id localpath]
(s/assert fs/path? localpath)
(s/assert ::us/uuid library-id)
(s/assert ::us/uuid icon-id)
(let [filename (fs/name localpath)
extension (second (fs/split-ext filename))
data (svg/parse localpath)
mdata (select-keys data [:width :height :view-box])]
(log/info "Creating or updating icon" filename icon-id)
(icons/create-icon conn {:id icon-id
:library-id library-id
:name (:name data filename)
:content (:content data)
:metadata mdata})))
(defn- icon-exists?
[conn id]
(s/assert ::us/uuid id)
(let [row (db/get-by-id conn :icon id)]
(if row true false)))
(defn- import-icon-if-not-exists
[conn library-id fpath]
(s/assert ::us/uuid library-id)
(s/assert fs/path? fpath)
(let [icon-id (uuid/namespaced +icons-uuid-ns+ (str library-id (fs/name fpath)))]
(when-not (icon-exists? conn icon-id)
(create-icon conn library-id icon-id fpath))
icon-id))
(defn- import-icons
[conn library-id {:keys [path regex] :as item}]
(run! (fn [fpath]
(when (re-matches regex (str fpath))
(import-icon-if-not-exists conn library-id fpath)))
(->> (fs/list-dir path)
(filter fs/regular-file?))))
(defn- process-icons-library
[conn basedir {:keys [path regex] :as item}]
(s/assert ::import-item-media item)
(let [library-id (create-icons-library-if-not-exists conn item)]
(->> (assoc item :path (fs/join basedir path))
(import-icons conn library-id))))
;; --- Images Libraries Importer
(defn- image-library-exists?
[conn id]
(s/assert ::us/uuid id)
(let [row (db/get-by-id conn :image-library id)]
(if row true false)))
(defn- create-images-library
[conn {:keys [name] :as item}]
(let [id (uuid/namespaced +images-uuid-ns+ name)]
(log/info "Creating image library:" name)
(images/create-library conn {:id id
:team-id uuid/zero
:name name})))
(defn- create-images-library-if-not-exists
[conn {:keys [name] :as item}]
(let [id (uuid/namespaced +images-uuid-ns+ name)]
(when-not (image-library-exists? conn id)
(create-images-library conn item)
id)))
(defn- create-image
[conn library-id image-id localpath]
[conn file-id image-id localpath]
(s/assert fs/path? localpath)
(s/assert ::us/uuid library-id)
(s/assert ::us/uuid file-id)
(s/assert ::us/uuid image-id)
(let [filename (fs/name localpath)
extension (second (fs/split-ext filename))
@ -170,14 +295,15 @@
mtype (case extension
".jpg" "image/jpeg"
".png" "image/png"
".webp" "image/webp")]
".webp" "image/webp"
".svg" "image/svg+xml")]
(log/info "Creating image" filename image-id)
(images/create-image conn {:content {:tempfile localpath
:filename filename
:content-type mtype
:size (.length file)}
:id image-id
:library-id library-id
:file-id file-id
:user uuid/zero
:name filename})))
@ -188,85 +314,100 @@
(if row true false)))
(defn- import-image-if-not-exists
[conn library-id fpath]
(s/assert ::us/uuid library-id)
[conn file-id fpath]
(s/assert ::us/uuid file-id)
(s/assert fs/path? fpath)
(let [image-id (uuid/namespaced +images-uuid-ns+ (str library-id (fs/name fpath)))]
(let [image-id (uuid/namespaced +images-uuid-ns+ (str file-id (fs/name fpath)))]
(when-not (image-exists? conn image-id)
(create-image conn library-id image-id fpath))
(create-image conn file-id image-id fpath))
image-id))
(defn- import-images
[conn library-id {:keys [path regex] :as item}]
[conn file-id {:keys [path regex] :as images}]
(run! (fn [fpath]
(when (re-matches regex (str fpath))
(import-image-if-not-exists conn library-id fpath)))
(import-image-if-not-exists conn file-id fpath)))
(->> (fs/list-dir path)
(filter fs/regular-file?))))
(defn- process-images-library
[conn basedir {:keys [path regex] :as item}]
(s/assert ::import-item-media item)
(let [library-id (create-images-library-if-not-exists conn item)]
(->> (assoc item :path (fs/join basedir path))
(import-images conn library-id))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Colors Libraries Importer
;; Colors Importer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- color-library-exists?
[conn id]
(s/assert ::us/uuid id)
(let [row (db/get-by-id conn :color-library id)]
(if row true false)))
(defn- create-colors-library
[conn {:keys [name] :as item}]
(let [id (uuid/namespaced +colors-uuid-ns+ name)]
(log/info "Creating color library:" name)
(colors/create-library conn {:id id
:team-id uuid/zero
:name name})))
(defn- create-colors-library-if-not-exists
[conn {:keys [name] :as item}]
(let [id (uuid/namespaced +colors-uuid-ns+ name)]
(when-not (color-library-exists? conn id)
(create-colors-library conn item))
id))
(defn- create-color
[conn library-id name content]
(s/assert ::us/uuid library-id)
[conn file-id name content]
(s/assert ::us/uuid file-id)
(s/assert ::us/color content)
(let [color-id (uuid/namespaced +colors-uuid-ns+ (str library-id content))]
(let [color-id (uuid/namespaced +colors-uuid-ns+ (str file-id content))]
(log/info "Creating color" color-id "-" name content)
(colors/create-color conn {:id color-id
:library-id library-id
:file-id file-id
:name name
:content content})
color-id))
(defn- import-colors
[conn library-id {:keys [colors] :as item}]
(db/delete! conn :color {:library-id library-id})
[conn file-id colors]
(db/delete! conn :color {:file-id file-id})
(run! (fn [[name content]]
(create-color conn library-id name content))
(create-color conn file-id name content))
(partition-all 2 colors)))
(defn- process-colors-library
[conn {:keys [name id colors] :as item}]
(us/verify ::import-item-color item)
(let [library-id (create-colors-library-if-not-exists conn item)]
(import-colors conn library-id item)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Libraries Importer
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- library-file-exists?
[conn id]
(s/assert ::us/uuid id)
(let [row (db/get-by-id conn :file id)]
(if row true false)))
(defn- create-library-file-if-not-exists
[conn project-id {:keys [name] :as library-file}]
(let [id (uuid/namespaced +colors-uuid-ns+ name)]
(when-not (library-file-exists? conn id)
(log/info "Creating library-file:" name)
(files/create-file conn {:id id
:profile-id uuid/zero
:project-id project-id
:name name})
(files/create-page conn {:file-id id}))
id))
(defn- process-library
[conn basedir project-id {:keys [name images colors] :as library}]
(us/verify ::import-library library)
(let [library-file-id (create-library-file-if-not-exists conn project-id library)]
(when images
(->> (assoc images :path (fs/join basedir (:path images)))
(import-images conn library-file-id)))
(when colors
(import-colors conn library-file-id colors))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Entry Point
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- project-exists?
[conn id]
(s/assert ::us/uuid id)
(let [row (db/get-by-id conn :project id)]
(if row true false)))
(defn- create-project-if-not-exists
[conn {:keys [name] :as project}]
(let [id (uuid/namespaced +colors-uuid-ns+ name)]
(when-not (project-exists? conn id)
(log/info "Creating project" name)
(projects/create-project conn {:id id
:team-id uuid/zero
:name name
:default? false}))
id))
(defn- validate-path
[path]
(when-not path
@ -295,20 +436,21 @@
[]
(mount/stop))
(defn- importer
[conn basedir data]
(let [images (:images data)
icons (:icons data)
colors (:colors data)]
(run! #(process-images-library conn basedir %) images)
(run! #(process-icons-library conn basedir %) icons)
(run! #(process-colors-library conn %) colors)))
;; (defn- importer
;; [conn basedir data]
;; (let [images (:images data)
;; icons (:icons data)
;; colors (:colors data)]
;; (run! #(process-images-library conn basedir %) images)
;; (run! #(process-icons-library conn basedir %) icons)
;; (run! #(process-colors-library conn %) colors)))
(defn run
[path]
(let [[basedir data] (read-file path)]
(let [[basedir libraries] (read-file path)]
(db/with-atomic [conn db/pool]
(importer conn basedir data))))
(let [project-id (create-project-if-not-exists conn {:name "System libraries"})]
(run! #(process-library conn basedir project-id %) libraries)))))
(defn -main
[& [path]]

View file

@ -59,7 +59,11 @@
{:desc "Add session_id field to page_change table"
:name "0011-add-session-id-field-to-page-change-table"
:fn (mg/resource "migrations/0011-add-session-id-field-to-page-change-table.sql")}]})
:fn (mg/resource "migrations/0011-add-session-id-field-to-page-change-table.sql")}
{:desc "Make libraries linked to a file"
:name "0012-make-libraries-linked-to-a-file"
:fn (mg/resource "migrations/0012-make-libraries-linked-to-a-file.sql")}]})
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Entry point

View file

@ -119,26 +119,27 @@
(declare create-color)
(s/def ::create-color
(s/keys :req-un [::profile-id ::name ::content ::library-id]
(s/keys :req-un [::profile-id ::name ::content ::file-id]
:opt-un [::id]))
(sm/defmutation ::create-color
[{:keys [profile-id library-id] :as params}]
[{:keys [profile-id file-id] :as params}]
(db/with-atomic [conn db/pool]
(let [lib (select-library-for-update conn library-id)]
(teams/check-edition-permissions! conn profile-id (:team-id lib))
(create-color conn params))))
(create-color conn params)))
;; (let [lib (select-library-for-update conn library-id)]
;; (teams/check-edition-permissions! conn profile-id (:team-id lib))
;; (create-color conn params))))
(def ^:private sql:create-color
"insert into color (id, name, library_id, content)
"insert into color (id, name, file_id, content)
values ($1, $2, $3, $4) returning *")
(defn create-color
[conn {:keys [id name library-id content]}]
[conn {:keys [id name file-id content]}]
(let [id (or id (uuid/next))]
(db/insert! conn :color {:id id
:name name
:library-id library-id
:file-id file-id
:content content})))
@ -160,9 +161,10 @@
(def ^:private sql:select-color-for-update
"select c.*,
lib.team_id as team_id
p.team_id as team_id
from color as c
inner join color_library as lib on (lib.id = c.library_id)
inner join file as f on f.id = c.file_id
inner join project as p on p.id = f.project_id
where c.id = ?
for update of c")
@ -174,6 +176,26 @@
row))
;; --- Mutation: Update Color
(s/def ::update-color
(s/keys :req-un [::profile-id ::id ::content]))
(sm/defmutation ::update-color
[{:keys [profile-id id content] :as params}]
(db/with-atomic [conn db/pool]
(let [clr (select-color-for-update conn id)
;; IMPORTANT: if the previous name was equal to the hex content,
;; we must rename it in addition to changing the value.
new-name (if (= (:name clr) (:content clr))
content
(:name clr))]
(teams/check-edition-permissions! conn profile-id (:team-id clr))
(db/update! conn :color
{:name new-name
:content content}
{:id id}))))
;; --- Delete Color
(declare delete-color)

View file

@ -62,7 +62,7 @@
:is-admin true
:can-edit true}))
(defn- create-file
(defn create-file
[conn {:keys [id profile-id name project-id] :as params}]
(let [id (or id (uuid/next))
file (db/insert! conn :file {:id id :project-id project-id :name name})]
@ -70,7 +70,7 @@
(create-file-profile conn))
file))
(defn- create-page
(defn create-page
[conn {:keys [file-id] :as params}]
(let [id (uuid/next)]
(db/insert! conn :page
@ -133,6 +133,7 @@
(declare create-file-image)
(s/def ::file-id ::us/uuid)
(s/def ::image-id ::us/uuid)
(s/def ::content ::imgs/upload)
(s/def ::add-file-image-from-url
@ -171,7 +172,11 @@
opts (assoc imgs/thumbnail-options
:input {:mtype (:mtype info)
:path path})
thumb (imgs/persist-image-thumbnail-on-fs opts)]
thumb (if-not (= (:mtype info) "image/svg+xml")
(imgs/persist-image-thumbnail-on-fs opts)
(assoc info
:path path
:quality 0))]
(-> (db/insert! conn :file-image
{:file-id file-id
@ -189,6 +194,33 @@
(images/resolve-urls :thumb-path :thumb-uri))))
;; --- Mutation: Delete File Image
(declare mark-file-image-deleted)
(s/def ::delete-file-image
(s/keys :req-un [::file-id ::image-id ::profile-id]))
(sm/defmutation ::delete-file-image
[{:keys [file-id image-id profile-id] :as params}]
(db/with-atomic [conn db/pool]
(files/check-edition-permissions! conn profile-id file-id)
;; Schedule object deletion
(tasks/submit! conn {:name "delete-object"
:delay cfg/default-deletion-delay
:props {:id image-id :type :file-image}})
(mark-file-image-deleted conn params)))
(defn mark-file-image-deleted
[conn {:keys [image-id] :as params}]
(db/update! conn :file-image
{:deleted-at (dt/now)}
{:id image-id})
nil)
;; --- Mutation: Import from collection
(declare copy-image)

View file

@ -33,7 +33,7 @@
(s/def ::id ::us/uuid)
(s/def ::name ::us/string)
(s/def ::profile-id ::us/uuid)
(s/def ::library-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::team-id ::us/uuid)
(s/def ::url ::us/url)
@ -62,7 +62,7 @@
;; --- Rename Library
(declare select-library-for-update)
(declare select-file-for-update)
(s/def ::rename-image-library
(s/keys :req-un [::id ::profile-id ::name]))
@ -70,15 +70,26 @@
(sm/defmutation ::rename-image-library
[{:keys [profile-id id name] :as params}]
(db/with-atomic [conn db/pool]
(let [lib (select-library-for-update conn id)]
(let [lib (select-file-for-update conn id)]
(teams/check-edition-permissions! conn profile-id (:team-id lib))
(db/update! conn :image-library
{:name name}
{:id id}))))
(defn- select-library-for-update
(def ^:private sql:select-file-for-update
"select file.*,
project.team_id as team_id
from file
inner join project on (project.id = file.project_id)
where file.id = ?
for update of file")
(defn- select-file-for-update
[conn id]
(db/get-by-id conn :image-library id {:for-update true}))
(let [row (db/exec-one! conn [sql:select-file-for-update id])]
(when-not row
(ex/raise :type :not-found))
row))
;; --- Delete Library
@ -91,7 +102,7 @@
(sm/defmutation ::delete-image-library
[{:keys [id profile-id] :as params}]
(db/with-atomic [conn db/pool]
(let [lib (select-library-for-update conn id)]
(let [lib (select-file-for-update conn id)]
(teams/check-edition-permissions! conn profile-id (:team-id lib))
;; Schedule object deletion
@ -112,7 +123,7 @@
(declare persist-image-thumbnail-on-fs)
(def valid-image-types?
#{"image/jpeg", "image/png", "image/webp"})
#{"image/jpeg", "image/png", "image/webp", "image/svg+xml"})
(s/def :uxbox$upload/filename ::us/string)
(s/def :uxbox$upload/size ::us/integer)
@ -128,32 +139,32 @@
(s/def ::content ::upload)
(s/def ::add-image-from-url
(s/keys :req-un [::profile-id ::library-id ::url]
(s/keys :req-un [::profile-id ::file-id ::url]
:opt-un [::id]))
(s/def ::upload-image
(s/keys :req-un [::profile-id ::library-id ::name ::content]
(s/keys :req-un [::profile-id ::file-id ::name ::content]
:opt-un [::id]))
(sm/defmutation ::add-image-from-url
[{:keys [profile-id library-id url] :as params}]
[{:keys [profile-id file-id url] :as params}]
(db/with-atomic [conn db/pool]
(let [lib (select-library-for-update conn library-id)]
(teams/check-edition-permissions! conn profile-id (:team-id lib))
(let [file (select-file-for-update conn file-id)]
(teams/check-edition-permissions! conn profile-id (:team-id file))
(let [content (images/download-image url)
params' (merge params {:content content
:name (:filename content)})]
(create-image conn params')))))
(sm/defmutation ::upload-image
[{:keys [profile-id library-id] :as params}]
[{:keys [profile-id file-id] :as params}]
(db/with-atomic [conn db/pool]
(let [lib (select-library-for-update conn library-id)]
(teams/check-edition-permissions! conn profile-id (:team-id lib))
(let [file (select-file-for-update conn file-id)]
(teams/check-edition-permissions! conn profile-id (:team-id file))
(create-image conn params))))
(defn create-image
[conn {:keys [id content library-id name]}]
[conn {:keys [id content file-id name]}]
(when-not (valid-image-types? (:content-type content))
(ex/raise :type :validation
:code :image-type-not-allowed
@ -165,11 +176,15 @@
opts (assoc thumbnail-options
:input {:mtype (:mtype info)
:path path})
thumb (persist-image-thumbnail-on-fs opts)]
thumb (if-not (= (:mtype info) "image/svg+xml")
(persist-image-thumbnail-on-fs opts)
(assoc info
:path path
:quality 0))]
(-> (db/insert! conn :image
{:id (or id (uuid/next))
:library-id library-id
:file-id file-id
:name name
:path (str path)
:width (:width info)
@ -236,8 +251,6 @@
(ex/raise :type :not-found))
row))
;; --- Delete Image
(s/def ::delete-image

View file

@ -71,7 +71,8 @@
(defn check-edition-permissions!
[conn profile-id team-id]
(let [row (db/exec-one! conn [sql:team-permissions profile-id team-id])]
(when-not (or (:can-edit row)
(when-not (or (= team-id uuid/zero)
(:can-edit row)
(:is-admin row)
(:is-owner row))
(ex/raise :type :validation

View file

@ -28,9 +28,10 @@
(s/def ::id ::us/uuid)
(s/def ::profile-id ::us/uuid)
(s/def ::team-id ::us/uuid)
(s/def ::file-id ::us/uuid)
(s/def ::library-id (s/nilable ::us/uuid))
;; --- Query: Colors Librarys
;; --- Query: Colors Libraries
(def ^:private sql:libraries
"select lib.*,
@ -78,31 +79,31 @@
(ex/raise :type :not-found))
row))
;; --- Query: Colors (by library)
;; --- Query: Colors (by file)
(declare retrieve-colors)
(s/def ::colors
(s/keys :req-un [::profile-id ::library-id]))
(s/keys :req-un [::profile-id ::file-id]))
(sq/defquery ::colors
[{:keys [profile-id library-id] :as params}]
[{:keys [profile-id file-id] :as params}]
(db/with-atomic [conn db/pool]
(let [lib (retrieve-library conn library-id)]
(teams/check-read-permissions! conn profile-id (:team-id lib))
(retrieve-colors conn library-id))))
(retrieve-colors conn file-id)))
;; (let [lib (retrieve-library conn library-id)]
;; (teams/check-read-permissions! conn profile-id (:team-id lib))
;; (retrieve-colors conn library-id))))
(def ^:private sql:colors
"select color.*
from color as color
inner join color_library as lib on (lib.id = color.library_id)
"select *
from color
where color.deleted_at is null
and color.library_id = ?
and color.file_id = ?
order by created_at desc")
(defn- retrieve-colors
[conn library-id]
(db/exec! conn [sql:colors library-id]))
[conn file-id]
(db/exec! conn [sql:colors file-id]))
;; --- Query: Color (by ID)

View file

@ -53,6 +53,11 @@
and (ppr.is_admin = true or
ppr.is_owner = true or
ppr.can_edit = true)
union
select p.*
from project as p
where p.team_id = uuid_nil()
and p.deleted_at is null
)
select distinct
file.*,
@ -80,23 +85,52 @@
(mapv decode-row rows)))
;; --- Query: Draft Files
;; --- Query: Project Files
(def ^:private sql:files
"select distinct
"with projects as (
select p.*
from project as p
inner join team_profile_rel as tpr on (tpr.team_id = p.team_id)
where tpr.profile_id = ?
and p.deleted_at is null
and (tpr.is_admin = true or
tpr.is_owner = true or
tpr.can_edit = true)
union
select p.*
from project as p
inner join project_profile_rel as ppr on (ppr.project_id = p.id)
where ppr.profile_id = ?
and p.deleted_at is null
and (ppr.is_admin = true or
ppr.is_owner = true or
ppr.can_edit = true)
union
select p.*
from project as p
where p.team_id = uuid_nil()
and p.deleted_at is null
)
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
inner join file_profile_rel as fp_r on (fp_r.file_id = f.id)
left join page as pg on (f.id = pg.file_id)
where fp_r.profile_id = ?
and f.project_id = ?
where f.project_id = ?
and (exists (select *
from file_profile_rel as fp_r
where fp_r.profile_id = ?
and fp_r.file_id = f.id
and (fp_r.is_admin = true or
fp_r.is_owner = true or
fp_r.can_edit = true))
or exists (select *
from projects as p
where p.id = f.project_id))
and f.deleted_at is null
and pg.deleted_at is null
and (fp_r.is_admin = true or
fp_r.is_owner = true or
fp_r.can_edit = true)
window pages_w as (partition by f.id order by pg.ordering
range between unbounded preceding
and unbounded following)
@ -108,7 +142,9 @@
(sq/defquery ::files
[{:keys [profile-id project-id] :as params}]
(->> (db/exec! db/pool [sql:files profile-id project-id])
(->> (db/exec! db/pool [sql:files
profile-id profile-id
project-id profile-id])
(mapv decode-row)))
;; --- Query: File Permissions
@ -136,7 +172,12 @@
from project_profile_rel as ppr
inner join file as f on (f.project_id = ppr.project_id)
where f.id = ?
and ppr.profile_id = ?;")
and ppr.profile_id = ?
union all
select true, true, true
from file as f
inner join project as p on (f.project_id = p.id)
and p.team_id = uuid_nil();")
(defn check-edition-permissions!
[conn profile-id file-id]
@ -169,7 +210,8 @@
(def ^:private sql:file-images
"select fi.*
from file_image as fi
where fi.file_id = ?")
where fi.file_id = ?
and fi.deleted_at is null")
(defn retrieve-file-images
[conn {:keys [file-id] :as params}]

View file

@ -21,7 +21,7 @@
(s/def ::name ::us/string)
(s/def ::profile-id ::us/uuid)
(s/def ::team-id ::us/uuid)
(s/def ::library-id ::us/uuid)
(s/def ::file-id ::us/uuid)
;; --- Query: Image Librarys
@ -77,32 +77,34 @@
(declare retrieve-images)
(s/def ::images
(s/keys :req-un [::profile-id ::library-id]))
(s/keys :req-un [::profile-id ::file-id]))
;; TODO: check if we can resolve url with transducer for reduce
;; garbage generation for each request
(sq/defquery ::images
[{:keys [profile-id library-id] :as params}]
[{:keys [profile-id file-id] :as params}]
(db/with-atomic [conn db/pool]
(let [lib (retrieve-library conn library-id)]
(teams/check-read-permissions! conn profile-id (:team-id lib))
(->> (retrieve-images conn library-id)
(mapv #(images/resolve-urls % :path :uri))
(mapv #(images/resolve-urls % :thumb-path :thumb-uri))))))
(->> (retrieve-images conn file-id)
(mapv #(images/resolve-urls % :path :uri))
(mapv #(images/resolve-urls % :thumb-path :thumb-uri)))))
;; (let [lib (retrieve-library conn file-id)]
;; (teams/check-read-permissions! conn profile-id (:team-id lib))
;; (->> (retrieve-images conn file-id)
;; (mapv #(images/resolve-urls % :path :uri))
;; (mapv #(images/resolve-urls % :thumb-path :thumb-uri))))))
(def ^:private sql:images
"select img.*
"select *
from image as img
inner join image_library as lib on (lib.id = img.library_id)
where img.deleted_at is null
and img.library_id = ?
and img.file_id = ?
order by created_at desc")
(defn- retrieve-images
[conn library-id]
(db/exec! conn [sql:images library-id]))
[conn file-id]
(db/exec! conn [sql:images file-id]))
@ -125,9 +127,9 @@
(def ^:private sql:single-image
"select img.*,
lib.team_id as team_id
file.team_id as team_id
from image as img
inner join image_library as lib on (lib.id = img.library_id)
inner join file on (file.id = img.file_id)
where img.deleted_at is null
and img.id = ?
order by created_at desc")

View file

@ -48,10 +48,19 @@
and (ppr.is_admin = true or
ppr.is_owner = true or
ppr.can_edit = true)
union
select p.*,
(select count(*) from file as f
where f.project_id = p.id
and deleted_at is null)
from project as p
where p.team_id = uuid_nil()
and p.deleted_at is null
)
select *
from projects
where team_id = ?
or team_id = uuid_nil()
order by modified_at desc")
(def ^:private sql:project-by-id

View file

@ -30,7 +30,8 @@
(defn check-edition-permissions!
[conn profile-id team-id]
(let [row (db/exec-one! conn [sql:team-permissions profile-id team-id])]
(when-not (or (:can-edit row)
(when-not (or (= team-id uuid/zero) ;; We can write global-project owned items
(:can-edit row)
(:is-admin row)
(:is-owner row))
(ex/raise :type :validation
@ -39,10 +40,9 @@
(defn check-read-permissions!
[conn profile-id team-id]
(let [row (db/exec-one! conn [sql:team-permissions profile-id team-id])]
(when-not (or (:can-edit row)
(when-not (or (= team-id uuid/zero) ;; We can read global-project owned items
(:can-edit row)
(:is-admin row)
(:is-owner row)
;; We can read global-project owned items
(= team-id #uuid "00000000-0000-0000-0000-000000000000"))
(:is-owner row))
(ex/raise :type :validation
:code :not-authorized))))

View file

@ -522,7 +522,7 @@
}
},
"ds.button.save" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:55" ],
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:55", "src/uxbox/main/ui/workspace/sidebar/assets.cljs:69" ],
"translations" : {
"en" : "Save",
"fr" : "Sauvegarder",
@ -657,7 +657,7 @@
}
},
"errors.image-format-unsupported" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:370", "src/uxbox/main/data/users.cljs:177", "src/uxbox/main/data/images.cljs:376" ],
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:390", "src/uxbox/main/data/users.cljs:177", "src/uxbox/main/data/images.cljs:376" ],
"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).",
@ -666,7 +666,7 @@
}
},
"errors.image-too-large" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:368", "src/uxbox/main/data/users.cljs:175", "src/uxbox/main/data/images.cljs:374" ],
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:388", "src/uxbox/main/data/users.cljs:175", "src/uxbox/main/data/images.cljs:374" ],
"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).",
@ -675,7 +675,7 @@
}
},
"errors.image-type-mismatch" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:335", "src/uxbox/main/data/workspace/persistence.cljs:385", "src/uxbox/main/data/users.cljs:191", "src/uxbox/main/data/images.cljs:391" ],
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:355", "src/uxbox/main/data/workspace/persistence.cljs:405", "src/uxbox/main/data/users.cljs:191", "src/uxbox/main/data/images.cljs:391" ],
"translations" : {
"en" : "Seems that the contents of the image does not match the file extension.",
"fr" : "",
@ -684,7 +684,7 @@
}
},
"errors.image-type-not-allowed" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:332", "src/uxbox/main/data/workspace/persistence.cljs:382", "src/uxbox/main/data/users.cljs:188", "src/uxbox/main/data/images.cljs:388" ],
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:352", "src/uxbox/main/data/workspace/persistence.cljs:402", "src/uxbox/main/data/users.cljs:188", "src/uxbox/main/data/images.cljs:388" ],
"translations" : {
"en" : "Seems that this is not a valid image.",
"fr" : "",
@ -729,7 +729,7 @@
}
},
"errors.unexpected-error" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:338", "src/uxbox/main/data/workspace/persistence.cljs:388", "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/workspace/persistence.cljs:358", "src/uxbox/main/data/workspace/persistence.cljs:408", "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" ],
"translations" : {
"en" : "An unexpected error occurred.",
"fr" : "Une erreur inattendue c'est produite",
@ -774,7 +774,7 @@
}
},
"image.loading" : {
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:346", "src/uxbox/main/data/workspace/persistence.cljs:397", "src/uxbox/main/data/users.cljs:201", "src/uxbox/main/data/images.cljs:403" ],
"used-in" : [ "src/uxbox/main/data/workspace/persistence.cljs:366", "src/uxbox/main/data/workspace/persistence.cljs:417", "src/uxbox/main/data/users.cljs:201", "src/uxbox/main/data/images.cljs:403" ],
"translations" : {
"en" : "Loading image...",
"fr" : "Chargement de l'image...",
@ -783,7 +783,7 @@
}
},
"modal.create-color.new-color" : {
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:49" ],
"used-in" : [ "src/uxbox/main/ui/dashboard/library.cljs:49", "src/uxbox/main/ui/workspace/sidebar/assets.cljs:62" ],
"translations" : {
"en" : "New Color",
"fr" : "Nouvelle couleur",
@ -1358,6 +1358,114 @@
"es" : "Alinear arriba"
}
},
"workspace.assets.assets" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:325" ],
"translations" : {
"en" : "Assets",
"fr" : "",
"ru" : "",
"es" : "Recursos"
}
},
"workspace.assets.box-filter-all" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:342" ],
"translations" : {
"en" : "All assets",
"fr" : "",
"ru" : "",
"es" : "Todos"
}
},
"workspace.assets.box-filter-colors" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:344" ],
"translations" : {
"en" : "Colors",
"fr" : "",
"ru" : "",
"es" : "Colores"
}
},
"workspace.assets.box-filter-graphics" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:343" ],
"translations" : {
"en" : "Graphics",
"fr" : "",
"ru" : "",
"es" : "Gráficos"
}
},
"workspace.assets.colors" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:247" ],
"translations" : {
"en" : "Colors",
"fr" : "",
"ru" : "",
"es" : "Colores"
}
},
"workspace.assets.delete" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:136", "src/uxbox/main/ui/workspace/sidebar/assets.cljs:231" ],
"translations" : {
"en" : "Delete",
"fr" : "",
"ru" : "",
"es" : "Borrar"
}
},
"workspace.assets.edit" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:230" ],
"translations" : {
"en" : "Edit",
"fr" : "",
"ru" : "",
"es" : "Editar"
}
},
"workspace.assets.file-library" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:271" ],
"translations" : {
"en" : "File library",
"fr" : "",
"ru" : "",
"es" : "Bilioteca del archivo"
}
},
"workspace.assets.graphics" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:113" ],
"translations" : {
"en" : "Graphics",
"fr" : "",
"ru" : "",
"es" : "Gráficos"
}
},
"workspace.assets.not-found" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:282" ],
"translations" : {
"en" : "No assets found",
"fr" : "",
"ru" : "",
"es" : "No se encontraron recursos"
}
},
"workspace.assets.rename" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:229" ],
"translations" : {
"en" : "Rename",
"fr" : "",
"ru" : "",
"es" : "Renombrar"
}
},
"workspace.assets.search" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:329" ],
"translations" : {
"en" : "Search assets",
"fr" : "",
"ru" : "",
"es" : "Buscar recursos"
}
},
"workspace.header.menu.disable-dynamic-alignment" : {
"used-in" : [ "src/uxbox/main/ui/workspace/header.cljs:117" ],
"translations" : {
@ -2179,13 +2287,13 @@
}
},
"workspace.sidebar.icons" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/icons.cljs:89" ],
"translations" : {
"en" : "Icons",
"fr" : "Icône",
"ru" : "Иконки",
"es" : "Iconos"
}
},
"unused" : true
},
"workspace.sidebar.sitemap" : {
"used-in" : [ "src/uxbox/main/ui/workspace/sidebar/sitemap.cljs:150" ],
@ -2196,6 +2304,15 @@
"es" : "Páginas"
}
},
"workspace.toolbar.assets" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:105" ],
"translations" : {
"en" : "Assets (Ctrl + I)",
"fr" : "",
"ru" : "",
"es" : "Recursos (Ctrl + I)"
}
},
"workspace.toolbar.circle" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:64" ],
"translations" : {
@ -2206,7 +2323,7 @@
}
},
"workspace.toolbar.color-palette" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:108" ],
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:113" ],
"translations" : {
"en" : "Color Palette (---)",
"fr" : "Palette de couleurs (---)",
@ -2242,13 +2359,13 @@
}
},
"workspace.toolbar.libraries" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:100" ],
"translations" : {
"en" : "Libraries (Ctrl + Shift + L)",
"fr" : "Librairies (Ctrl + Shift + L)",
"ru" : "Библиотеки (Ctrl + Shift + L)",
"es" : "Bibliotecas (Ctrl + Mays + L)"
}
},
"unused" : true
},
"workspace.toolbar.path" : {
"used-in" : [ "src/uxbox/main/ui/workspace/left_toolbar.cljs:88" ],

View file

@ -72,6 +72,7 @@
@import 'main/partials/sidebar-layers';
@import 'main/partials/sidebar-sitemap';
@import 'main/partials/sidebar-tools';
@import 'main/partials/sidebar-assets';
@import 'main/partials/tab-container';
@import 'main/partials/tool-bar';
@import 'main/partials/user-settings';

View file

@ -0,0 +1,199 @@
// 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) 2015-2016 Andrey Antukh <niwi@niwi.nz>
// Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
.assets-bar {
display: flex;
flex-direction: column;
.assets-bar-title {
color: $color-gray-10;
font-size: $fs14;
margin: $small $small 0 $small;
}
.search-block {
border: 1px solid $color-gray-30;
margin: $small $small 0 $small;
padding: $x-small;
display: flex;
align-items: center;
&:focus-within {
border-color: $color-primary !important;
}
&:hover {
border-color: $color-gray-20;
}
& .search-input {
background-color: $color-gray-50;
border: none;
color: $color-gray-10;
font-size: $fs12;
margin: 0;
padding: 0;
flex-grow: 1;
&:focus {
color: lighten($color-gray-10, 8%);
}
}
& .search-icon {
display: flex;
align-items: center;
svg {
fill: $color-gray-30;
height: 15px;
width: 15px;
}
&.close {
transform: rotate(45deg);
cursor: pointer;
}
}
}
.input-select {
background-color: $color-gray-50;
color: $color-gray-10;
border: 1px solid transparent;
border-bottom-color: $color-gray-40;
padding: $x-small $x-small 0 $x-small;
margin: $small $small $medium $small;
&:focus {
color: lighten($color-gray-10, 8%);
}
option {
color: $color-gray-60;
background: $color-white;
font-size: $fs11;
}
}
.collapse-library {
margin-right: $small;
cursor: pointer;
&.open svg {
transform: rotate(90deg);
}
}
.asset-group {
background-color: $color-gray-60;
padding: $small;
font-size: $fs11;
color: $color-gray-20;
.group-title {
display: flex;
& span {
color: $color-gray-30;
}
}
.group-button {
margin-left: auto;
cursor: pointer;
& svg {
width: 0.7rem;
height: 0.7rem;
fill: #F0F0F0;
}
}
.group-grid {
margin-top: $small;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-auto-rows: 7vh;
column-gap: 0.5rem;
row-gap: 0.5rem;
}
.grid-cell {
background-color: $color-white;
border-radius: 4px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
position: relative;
cursor: pointer;
& img {
max-height: 100%;
max-width: 100%;
height: auto;
width: auto;
pointer-events: none;
}
}
.cell-name {
background-color: $color-gray-60;
font-size: $fs9;
display: none;
position: absolute;
left: 0;
bottom: 0;
width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.grid-cell:hover {
border: 1px solid $color-primary;
& .cell-name {
display: block;
}
}
.group-list {
max-height: 30rem;
overflow-y: scroll;
}
.group-list-item {
display: flex;
align-items: center;
margin-top: $x-small;
font-size: $fs11;
color: $color-white;
cursor: pointer;
& .color-block {
width: 20px;
height: 20px;
border-radius: 10px;
margin-right: $x-small;
}
& span {
margin-left: $x-small;
color: $color-gray-30;
text-transform: uppercase;
}
}
.context-menu {
position: absolute;
top: 10px;
left: 10px;
}
}
}

View file

@ -25,7 +25,7 @@ $width-settings-bar: 15rem;
}
.settings-bar-inside {
align-items: center;
align-items: flex-start;
display: grid;
grid-template-columns: 100%;

View file

@ -10,6 +10,7 @@
[beicon.core :as rx]
[clojure.set :as set]
[potok.core :as ptk]
[uxbox.common.data :as d]
[uxbox.common.spec :as us]
[uxbox.main.repo :as rp]
[uxbox.main.store :as st]
@ -249,21 +250,85 @@
(declare create-color-result)
(defn create-color
[library-id color]
(s/assert (s/nilable uuid?) library-id)
[file-id color]
(s/assert (s/nilable uuid?) file-id)
(ptk/reify ::create-color
ptk/WatchEvent
(watch [_ state s]
(->> (rp/mutation! :create-color {:library-id library-id
(->> (rp/mutation! :create-color {:file-id file-id
:content color
:name color})
(rx/map (partial create-color-result library-id))))))
(rx/map (partial create-color-result file-id))))))
(defn create-color-result
[library-id item]
[file-id color]
(ptk/reify ::create-color-result
ptk/UpdateEvent
(update [_ state]
(-> state
(update-in [:library-items :palettes library-id] #(into [item] %) )))))
(assoc-in [:workspace-colors (:id color)] color)
(assoc-in [:workspace-local :color-for-rename] (:id color))))))
(def clear-color-for-rename
(ptk/reify ::clear-color-for-rename
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:workspace-local :color-for-rename] nil))))
(declare rename-color-result)
(defn rename-color
[file-id color-id name]
(ptk/reify ::rename-color
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/mutation! :rename-color {:id color-id
:name name})
(rx/map (partial rename-color-result file-id))))))
(defn rename-color-result
[file-id color]
(ptk/reify ::rename-color-result
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-colors (:id color)] color)))))
(declare update-color-result)
(defn update-color
[file-id color-id content]
(ptk/reify ::update-color
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/mutation! :update-color {:id color-id
:content content})
(rx/map (partial update-color-result file-id))))))
(defn update-color-result
[file-id color]
(ptk/reify ::update-color-result
ptk/UpdateEvent
(update [_ state]
(-> state
(assoc-in [:workspace-colors (:id color)] color)))))
(declare delete-color-result)
(defn delete-color
[file-id color-id]
(ptk/reify ::delete-color
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/mutation! :delete-color {:id color-id})
(rx/map #(delete-color-result file-id color-id))))))
(defn delete-color-result
[file-id color-id]
(ptk/reify ::delete-color-result
ptk/UpdateEvent
(update [_ state]
(-> state
(d/dissoc-in [:workspace-colors color-id])))))

View file

@ -353,7 +353,7 @@
;; --- Create Image
(declare create-images-result)
(def allowed-file-types #{"image/jpeg" "image/png" "image/webp"})
(def allowed-file-types #{"image/jpeg" "image/png" "image/webp" "image/svg+xml"})
(def max-file-size (* 5 1024 1024))
;; TODO: unify with upload-image at main/data/workspace/persistence.cljs
@ -361,9 +361,9 @@
;; https://tree.taiga.io/project/uxboxproject/us/440
(defn create-images
([library-id files] (create-images library-id files identity))
([library-id files on-uploaded]
(us/verify (s/nilable ::us/uuid) library-id)
([file-id files] (create-images file-id files identity))
([file-id files on-uploaded]
(us/verify (s/nilable ::us/uuid) file-id)
(us/verify fn? on-uploaded)
(ptk/reify ::create-images
ptk/WatchEvent
@ -397,7 +397,7 @@
prepare
(fn [file]
{:name (.-name file)
:library-id library-id
:file-id file-id
:content file})]
(st/emit! (dm/show {:content (tr "image.loading")
@ -411,17 +411,17 @@
(rx/reduce conj [])
(rx/do on-success)
(rx/mapcat identity)
(rx/map (partial create-images-result library-id))
(rx/map (partial create-images-result file-id))
(rx/catch on-error)))))))
;; --- Image Created
(defn create-images-result
[library-id item]
#_(us/verify ::image item)
[file-id image]
#_(us/verify ::image image)
(ptk/reify ::create-images-result
ptk/UpdateEvent
(update [_ state]
(-> state
(update-in [:library-items :images library-id] #(into [item] %))))))
(assoc-in [:workspace-images (:id image)] image)))))

View file

@ -54,23 +54,39 @@
;; Workspace Initialization
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(declare initialized)
(declare file-initialized)
;; --- Initialize Workspace
(s/def ::layout-flag
#{:sitemap
:sitemap-pages
:layers
:libraries
:assets
:document-history
:colorpalette
:element-options
:rules
:display-grid
:snap-grid
:dynamic-alignment})
(s/def ::layout-flags (s/coll-of ::layout-flag))
(def default-layout
#{:sitemap
:sitemap-pages
:layers
:element-options
:rules
:dynamic-alignment
:display-grid
:snap-grid})
:snap-grid
:dynamic-alignment})
(s/def ::options-mode #{:design :prototype})
(def workspace-default
(def workspace-local-default
{:zoom 1
:flags #{}
:selected (d/ordered-set)
@ -81,7 +97,8 @@
:options-mode :design
:draw-interaction-to nil
:left-sidebar? true
:right-sidebar? true})
:right-sidebar? true
:color-for-rename nil})
(def initialize-layout
(ptk/reify ::initialize-layout
@ -89,12 +106,12 @@
(update [_ state]
(assoc state :workspace-layout default-layout))))
(defn initialize
(defn initialize-file
[project-id file-id]
(us/verify ::us/uuid project-id)
(us/verify ::us/uuid file-id)
(ptk/reify ::initialize
(ptk/reify ::initialize-file
ptk/UpdateEvent
(update [_ state]
(assoc state :workspace-presence {}))
@ -118,9 +135,9 @@
(->> stream
(rx/filter #(= ::dwc/index-initialized %))
(rx/map (constantly
(initialized project-id file-id))))))))
(file-initialized project-id file-id))))))))
(defn- initialized
(defn- file-initialized
[project-id file-id]
(ptk/reify ::initialized
ptk/UpdateEvent
@ -131,7 +148,7 @@
(assoc file :initialized true)
file))))))
(defn finalize
(defn finalize-file
[project-id file-id]
(ptk/reify ::finalize
ptk/UpdateEvent
@ -149,7 +166,7 @@
ptk/UpdateEvent
(update [_ state]
(let [page (get-in state [:workspace-pages page-id])
local (get-in state [:workspace-cache page-id] workspace-default)]
local (get-in state [:workspace-cache page-id] workspace-local-default)]
(-> state
(assoc :current-page-id page-id ; mainly used by events
:workspace-local local
@ -276,6 +293,7 @@
(defn- toggle-layout-flag
[state flag]
(us/assert ::layout-flag flag)
(update state :workspace-layout
(fn [flags]
(if (contains? flags flag)
@ -288,20 +306,36 @@
left-sidebar? (not (empty? (keep layout [:layers
:sitemap
:document-history
:libraries])))
right-sidebar? (not (empty? (keep layout [:icons
:element-options])))]
:libraries
:assets])))
right-sidebar? (not (empty? (keep layout [:element-options])))]
(update-in state [:workspace-local]
assoc :left-sidebar? left-sidebar?
:right-sidebar? right-sidebar?)))
(defn- check-auto-flags
[state flags-to-toggle]
(update state :workspace-layout
(fn [flags]
(cond
(contains? (set flags-to-toggle) :assets)
(disj flags :sitemap :layers)
(contains? (set flags-to-toggle) :sitemap)
(disj flags :assets)
:else
flags))))
(defn toggle-layout-flags
[& flags]
(us/assert ::layout-flags flags)
(ptk/reify ::toggle-layout-flags
ptk/UpdateEvent
(update [_ state]
(-> (reduce toggle-layout-flag state flags)
(check-sidebars)))))
(check-sidebars)
(check-auto-flags flags)))))
;; --- Set element options mode
@ -1402,8 +1436,11 @@
;; Persistence
(def fetch-images dwp/fetch-images)
(def add-image-from-url dwp/add-image-from-url)
(def upload-image dwp/upload-image)
(def delete-file-image dwp/delete-file-image)
(def fetch-colors dwp/fetch-colors)
(def rename-page dwp/rename-page)
(def delete-page dwp/delete-page)
(def create-empty-page dwp/create-empty-page)

View file

@ -289,7 +289,7 @@
(ptk/reify ::fetch-images
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :file-images {:file-id file-id})
(->> (rp/query :images {:file-id file-id})
(rx/map images-fetched)))))
(defn images-fetched
@ -300,11 +300,31 @@
(let [images (d/index-by :id images)]
(assoc state :workspace-images images)))))
;; --- Fetch Workspace Colors
(declare colors-fetched)
(defn fetch-colors
[file-id]
(ptk/reify ::fetch-colors
ptk/WatchEvent
(watch [_ state stream]
(->> (rp/query :colors {:file-id file-id})
(rx/map colors-fetched)))))
(defn colors-fetched
[colors]
(ptk/reify ::colors-fetched
ptk/UpdateEvent
(update [_ state]
(let [colors (d/index-by :id colors)]
(assoc state :workspace-colors colors)))))
;; --- Upload Image
(declare image-uploaded)
(def allowed-file-types #{"image/jpeg" "image/png" "image/webp"})
(def allowed-file-types #{"image/jpeg" "image/png" "image/webp" "image/svg+xml"})
(def max-file-size (* 5 1024 1024))
;; TODO: unify with create-images at main/data/images.cljs
@ -428,7 +448,25 @@
(ptk/reify ::image-created
ptk/UpdateEvent
(update [_ state]
(update state :workspace-images assoc (:id item) item))))
state)))
;; (update state :workspace-images assoc (:id item) item))))
;; --- Delete image
(defn delete-file-image
[file-id image-id]
(ptk/reify ::delete-file-image
ptk/UpdateEvent
(update [_ state]
(update state :workspace-images dissoc image-id))
ptk/WatchEvent
(watch [_ state stream]
(let [params {:file-id file-id
:image-id image-id}]
(rp/mutation :delete-file-image params)))))
;; --- Helpers

View file

@ -60,6 +60,9 @@
(def workspace-images
(l/derived :workspace-images st/state))
(def workspace-colors
(l/derived :workspace-colors st/state))
(def workspace-users
(l/derived :workspace-users st/state))

View file

@ -25,11 +25,15 @@
(let [open? (gobj/get props "show")
options (gobj/get props "options")
is-selectable (gobj/get props "selectable")
selected (gobj/get props "selected")]
selected (gobj/get props "selected")
top (gobj/get props "top")
left (gobj/get props "left")]
(when open?
[:> dropdown' props
[:div.context-menu {:class (classnames :is-open open?
:is-selectable is-selectable)}
:is-selectable is-selectable)
:style {:top top
:left left}}
[:ul.context-menu-items
(for [[action-name action-handler] options]
[:li.context-menu-item {:class (classnames :is-selected (and selected (= action-name selected)))

View file

@ -79,6 +79,7 @@
(def picker (icon-xref :picker))
(def pin (icon-xref :pin))
(def play (icon-xref :play))
(def plus (icon-xref :plus))
(def radius (icon-xref :radius))
(def recent (icon-xref :recent))
(def redo (icon-xref :redo))

View file

@ -106,8 +106,8 @@
(mf/use-effect
(mf/deps project-id file-id)
(fn []
(st/emit! (dw/initialize project-id file-id))
#(st/emit! (dw/finalize project-id file-id))))
(st/emit! (dw/initialize-file project-id file-id))
#(st/emit! (dw/finalize-file project-id file-id))))
(hooks/use-shortcuts dw/shortcuts)

View file

@ -96,10 +96,15 @@
:class (when (contains? layout :layers) "selected")
:on-click #(st/emit! (dw/toggle-layout-flags :sitemap :layers))}
i/layers]
;; [:li.tooltip.tooltip-right
;; {:alt (t locale "workspace.toolbar.libraries")
;; :class (when (contains? layout :libraries) "selected")
;; :on-click #(st/emit! (dw/toggle-layout-flags :libraries))}
;; i/icon-set]
[:li.tooltip.tooltip-right
{:alt (t locale "workspace.toolbar.libraries")
:class (when (contains? layout :libraries) "selected")
:on-click #(st/emit! (dw/toggle-layout-flags :libraries))}
{:alt (t locale "workspace.toolbar.assets")
:class (when (contains? layout :assets) "selected")
:on-click #(st/emit! (dw/toggle-layout-flags :assets))}
i/icon-set]
[:li.tooltip.tooltip-right
{:alt "History"}

View file

@ -15,7 +15,8 @@
[uxbox.main.ui.workspace.sidebar.layers :refer [layers-toolbox]]
[uxbox.main.ui.workspace.sidebar.options :refer [options-toolbox]]
[uxbox.main.ui.workspace.sidebar.sitemap :refer [sitemap-toolbox]]
[uxbox.main.ui.workspace.sidebar.libraries :refer [libraries-toolbox]]))
[uxbox.main.ui.workspace.sidebar.libraries :refer [libraries-toolbox]]
[uxbox.main.ui.workspace.sidebar.assets :refer [assets-toolbox]]))
;; --- Left Sidebar (Component)
@ -34,7 +35,9 @@
(when (contains? layout :layers)
[:& layers-toolbox {:page page}])
(when (contains? layout :libraries)
[:& libraries-toolbox])]])
[:& libraries-toolbox])
(when (contains? layout :assets)
[:& assets-toolbox])]])
;; --- Right Sidebar (Component)

View file

@ -0,0 +1,355 @@
;; 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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.workspace.sidebar.assets
(:require
[okulary.core :as l]
[cuerdas.core :as str]
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.common.pages :as cp]
[uxbox.common.geom.shapes :as geom]
[uxbox.common.geom.point :as gpt]
[uxbox.main.ui.icons :as i]
[uxbox.main.data.workspace :as dw]
[uxbox.main.data.images :as di]
[uxbox.main.data.colors :as dcol]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.keyboard :as kbd]
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.util.dom :as dom]
[uxbox.util.dom.dnd :as dnd]
[uxbox.util.timers :as timers]
[uxbox.common.uuid :as uuid]
[uxbox.util.i18n :as i18n :refer [tr]]
[uxbox.util.data :refer [classnames]]
[uxbox.main.data.library :as dlib]
[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))))
(mf/defc modal-edit-color
[{:keys [color-value on-accept on-cancel] :as ctx}]
(let [state (mf/use-state {:current-color color-value})]
(letfn [(accept [event]
(dom/prevent-default event)
(modal/hide!)
(when on-accept (on-accept (:current-color @state))))
(cancel [event]
(dom/prevent-default event)
(modal/hide!)
(when on-cancel (on-cancel)))]
[:div.modal-create-color
[:h3.modal-create-color-title (tr "modal.create-color.new-color")]
[:& colorpicker {:value (:current-color @state)
:colors (into-array @most-used-colors)
:disable-opacity true
:on-change #(swap! state assoc :current-color %)}]
[:input.btn-primary {:type "button"
:value (tr "ds.button.save")
:on-click accept}]
[:a.close {:href "#" :on-click cancel} i/close]])))
(mf/defc graphics-box
[{:keys [library-id images] :as props}]
(let [state (mf/use-state {:menu-open false
:top nil
:left nil
:image-id nil})
file-input (mf/use-ref nil)
add-graphic
#(dom/click (mf/ref-val file-input))
delete-graphic
#(st/emit! (dw/delete-file-image library-id (:image-id @state)))
on-files-selected
(fn [files]
(st/emit! (di/create-images library-id files)))
on-context-menu
(fn [image-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
:image-id image-id))))
on-drag-start
(fn [uri]
(fn [event]
(dnd/set-data! event "text/uri-list" uri)
(dnd/set-allowed-effect! event "move")))]
[:div.asset-group
[:div.group-title
(tr "workspace.assets.graphics")
[:span (str "\u00A0(") (count images) ")"] ;; Unicode 00A0 is non-breaking space
[:div.group-button {:on-click add-graphic}
i/plus
[:& file-uploader {:accept "image/jpeg,image/png,image/webp,image/svg+xml"
:multi true
:input-ref file-input
:on-selected on-files-selected}]]]
[:div.group-grid
(for [image (sort-by :name images)]
[:div.grid-cell {:key (:id image)
:draggable true
:on-context-menu (on-context-menu (:id image))
:on-drag-start (on-drag-start (:uri image))}
[:img {:src (:thumb-uri image)
:draggable false}] ;; Also need to add css pointer-events: none
[:div.cell-name (:name image)]])
[:& 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 library-id] :as props}]
(let [workspace-local @refs/workspace-local
color-for-rename (:color-for-rename workspace-local)
edit-input-ref (mf/use-ref)
state (mf/use-state {:menu-open false
:top nil
:left nil
:editing (= color-for-rename (:id color))})
rename-color
(fn [name]
(st/emit! (dcol/rename-color library-id (:id color) name)))
edit-color
(fn [value opacity]
(st/emit! (dcol/update-color library-id (:id color) value)))
delete-color
(fn []
(st/emit! (dcol/delete-color library-id (:id color))))
rename-color-clicked
(fn [event]
(dom/prevent-default event)
(swap! state assoc :editing true))
input-blur
(fn [event]
(let [target (dom/event->target event)
name (dom/get-value target)]
(rename-color name)
(st/emit! dcol/clear-color-for-rename)
(swap! state assoc :editing false)))
input-key-down
(fn [event]
(when (kbd/esc? event)
(st/emit! dcol/clear-color-for-rename)
(swap! state assoc :editing false))
(when (kbd/enter? event)
(input-blur event)))
edit-color-clicked
(fn [event]
(modal/show! modal-edit-color
{:color-value (:content color)
:on-accept edit-color}))
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)))]
(mf/use-effect
(mf/deps (:editing @state))
#(when (:editing @state)
(let [edit-input (mf/ref-val edit-input-ref)]
(dom/select-text! edit-input))
nil))
[:div.group-list-item {:on-context-menu on-context-menu}
[:div.color-block {:style {:background-color (:content color)}}]
(if (:editing @state)
[:input.element-name
{:type "text"
:ref edit-input-ref
:on-blur input-blur
:on-key-down input-key-down
:auto-focus true
:default-value (:name color "")}]
[:div.name-block
{:on-double-click rename-color-clicked}
(: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]]}]]))
(mf/defc colors-box
[{:keys [library-id colors] :as props}]
(let [add-color
(fn [value opacity]
(st/emit! (dcol/create-color library-id value)))
add-color-clicked
(fn [event]
(modal/show! modal-edit-color
{:color-value "#406280"
:on-accept add-color}))]
[:div.asset-group
[: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]]
[:div.group-list
(for [color (sort-by :name colors)]
[:& color-item {:key (:id color)
:color color
:library-id library-id}])]]))
(mf/defc library-toolbox
[{:keys [library-id
images
colors
initial-open?
search-term
box-filter] :as props}]
(let [open? (mf/use-state initial-open?)
toggle-open #(swap! open? not)]
[: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 @open?
(let [show-graphics (and (or (= box-filter :all) (= box-filter :graphics))
(or (> (count images) 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 {:library-id library-id :images images}])
(when show-colors
[:& colors-box {:library-id library-id :colors colors}])
(when (and (not show-graphics) (not show-colors))
[:div.asset-group
[:div.group-title (tr "workspace.assets.not-found")]])]))]))
(mf/defc assets-toolbox
[]
(let [team-id (-> refs/workspace-project mf/deref :team-id)
file-id (-> refs/workspace-file mf/deref :id)
file-images (mf/deref refs/workspace-images)
file-colors (mf/deref refs/workspace-colors)
state (mf/use-state {:search-term ""
:box-filter :all})
filtered-images (filter #(matches-search (:name %) (:search-term @state))
(vals file-images))
filtered-colors (filter #(or (matches-search (:name %) (:search-term @state))
(matches-search (:content %) (:search-term @state)))
(vals file-colors))
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 ""))
on-box-filter-change (fn [event]
(let [value (-> (dom/get-target event)
(dom/get-value)
(d/read-string))]
(swap! state assoc :box-filter value)))]
(mf/use-effect
(mf/deps file-id)
#(when file-id
(st/emit! (dw/fetch-images file-id))
(st/emit! (dw/fetch-colors file-id))))
[:div.assets-bar
[:div.tool-window
[:div.tool-window-content
[:div.assets-bar-title (tr "workspace.assets.assets")]
[:div.search-block
[:input.search-input
{:placeholder (tr "workspace.assets.search")
: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.close
{:on-click on-search-clear-click}
i/close])]
[:select.input-select {:value (:box-filter @state)
: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")]]
]]
[:& library-toolbox {:library-id file-id
:images filtered-images
:colors filtered-colors
:initial-open? true
:search-term (:search-term @state)
:box-filter (:box-filter @state)}]]))

View file

@ -1,96 +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/.
;;
;; This Source Code Form is "Incompatible With Secondary Licenses", as
;; defined by the Mozilla Public License, v. 2.0.
;;
;; Copyright (c) 2020 UXBOX Labs SL
(ns uxbox.main.ui.workspace.sidebar.icons
(:require
#_[uxbox.main.ui.dashboard.icons :as icons]
[rumext.alpha :as mf]
[uxbox.common.data :as d]
[uxbox.main.data.icons :as di]
[uxbox.main.data.workspace :as dw]
[uxbox.main.refs :as refs]
[uxbox.main.store :as st]
[uxbox.main.ui.icons :as i]
[uxbox.main.ui.shapes.icon :as icon]
[uxbox.util.data :refer [read-string]]
[uxbox.util.dom :as dom]
[uxbox.util.i18n :as i18n :refer [t]]
[uxbox.util.router :as r]))
(mf/defc icons-collections
[{:keys [collections value on-change] :as props}]
[:div.figures-catalog
;; extract component: set selector
[:select.input-select.small
{:on-change (fn [event]
(let [val (-> (dom/get-target event)
(dom/get-value))]
(on-change (d/read-string val))))
:value (pr-str value)}
[:option {:value (pr-str nil)} "Storage"]
(for [coll collections]
[:option {:key (str "icon-coll" (:id coll))
:value (pr-str (:id coll))}
(:name coll)])]])
(mf/defc icons-list
[{:keys [collection-id] :as props}]
(let [icons [] #_(mf/deref icons/icons-iref) ;; TODO: Fix this
on-select
(fn [event data]
(st/emit! (dw/select-for-drawing :icon data)))]
#_(mf/use-effect
{:fn #(st/emit! (di/fetch-icons collection-id))
:deps (mf/deps collection-id)})
(for [icon icons
:let [selected? (= nil #_(:drawing local) icon)]]
[:div.figure-btn {:key (str (:id icon))
:class (when selected? "selected")
:on-click #(on-select % icon)
}
[:& icon/icon-svg {:shape icon}]])))
;; --- Icons (Component)
(mf/defc icons-toolbox
[props]
(let [locale (i18n/use-locale)
selected (mf/use-state nil)
local (mf/deref refs/workspace-local)
collections (vals [] #_(mf/deref icons/collections-iref)) ;; TODO: FIX THIS
collection (first collections)
on-close
(fn [event]
(st/emit! (dw/toggle-layout-flags :icons)))
on-change
(fn [val]
(st/emit! (dw/select-for-drawing nil))
(reset! selected val))]
#_(mf/use-effect
{:fn #(st/emit! di/fetch-collections)})
[:div#form-figures.tool-window
[:div.tool-window-bar
[:div.tool-window-icon i/icon-set]
[:span (t locale "workspace.sidebar.icons")]
[:div.tool-window-close {:on-click on-close} i/close]]
[:div.tool-window-content
[:& icons-collections {:collections collections
:value @selected
:on-change on-change
}]
[:& icons-list {:collection-id @selected}]]]))

View file

@ -113,7 +113,7 @@
(if (= section :icons)
[:svg {:view-box (->> item :metadata :view-box (str/join " "))
:width (-> item :metadata :width)
:height (-> item :metadat :height)
:height (-> item :metadata :height)
:dangerouslySetInnerHTML {:__html (:content item)}}]
[:img {:draggable false
:src (:thumb-uri item)}])

View file

@ -58,7 +58,10 @@
(set-data! e "uxbox/data" data))
([e data-type data]
(let [dt (.-dataTransfer e)]
(.setData dt data-type (t/encode data))
(if (or (str/starts-with? data-type "application")
(str/starts-with? data-type "uxbox"))
(.setData dt data-type (t/encode data))
(.setData dt data-type data))
e)))
(defn set-drag-image!

View file

@ -1,54 +1,68 @@
{:icons
[{:name "Material Design (Action)"
:path "./icons/material-action"
:regex #"^.*_48px\.svg$"}
[
;; Icons
{:name "Material Design (Action)"
:images {:path "./icons/material-action"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Alert)"
:path "./icons/material-alert"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-alert"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Av)"
:path "./icons/material-av"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-av"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Content)"
:path "./icons/material-content"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-content"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Device)"
:path "./icons/material-device"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-device"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Editor)"
:path "./icons/material-editor"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-editor"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (File)"
:path "./icons/material-file"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-file"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Hardware)"
:path "./icons/material-hardware"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-hardware"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Image)"
:path "./icons/material-image"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-image"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Maps)"
:path "./icons/material-maps"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-maps"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Navigation)"
:path "./icons/material-navigation"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-navigation"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Notification)"
:path "./icons/material-notification"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-notification"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Places)"
:path "./icons/material-places"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-places"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Social)"
:path "./icons/material-social"
:regex #"^.*_48px\.svg$"}
:images {:path "./icons/material-social"
:regex #"^.*_48px\.svg$"}}
{:name "Material Design (Toggle)"
:path "./icons/material-toggle"
:regex #"^.*_48px\.svg$"}
]
:images {:path "./icons/material-toggle"
:regex #"^.*_48px\.svg$"}}
:colors
[{:name "Flat design"
:id #uuid "00000000-0000-0000-0000-00000000001"
;; Images
{:name "Unsplash"
:images {:path "./images/unsplash"
:regex #"^.*\.jpg$"}
:colors ["Chateau Green" "#419860"
"Toast" "#987567"
"Confetti" "#EAC75B"
"Thunderbird" "#C23D1F"
"Coffee Bean" "#331113"
"Lavender Magenta" "#EF88DF"
"Persian Rose" "#F536A6"
"Royal Blue" "#5C4AEE"
"Biscay" "#202362"
"Olivine" "#98C277"]}
;; Colors
{:name "Flat design"
:colors ["turquoise-50" "#e8f8f5"
"turquoise-100" "#d1f2eb"
"turquoise-200" "#a3e4d7"
@ -250,9 +264,7 @@
"asbestos-800" "#515a5a"
"asbestos-900" "#424949"]}
{:name "Material design"
:id #uuid "00000000-0000-0000-0000-000000000020"
:colors ["red-50" "#ffebee"
"red-100" "#ffcdd2"
"red-200" "#ef9a9a"
@ -509,5 +521,4 @@
"blue-grey-900" "#263238"
"white" "#ffffff"
"black" "#000000"]}
]}
]

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB