From 8c8b5887d6a3ff4fdaaa726b98c69a9a8c8bb5b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s=20Moya?= Date: Thu, 23 Jul 2020 17:11:36 +0200 Subject: [PATCH] :tada: Manage file images as assets --- backend/scripts/psql.sh | 2 + .../src/uxbox/services/mutations/files.clj | 28 +++ backend/src/uxbox/services/queries/files.clj | 3 +- frontend/resources/locales.json | 100 ++++++++++- frontend/resources/styles/main-default.scss | 1 + .../styles/main/partials/sidebar-assets.scss | 143 +++++++++++++++ .../styles/main/partials/sidebar.scss | 2 +- frontend/src/uxbox/main/data/workspace.cljs | 13 +- .../main/data/workspace/persistence.cljs | 17 ++ .../main/ui/components/context_menu.cljs | 8 +- frontend/src/uxbox/main/ui/icons.cljs | 1 + .../uxbox/main/ui/workspace/left_toolbar.cljs | 11 +- .../src/uxbox/main/ui/workspace/sidebar.cljs | 7 +- .../main/ui/workspace/sidebar/assets.cljs | 164 ++++++++++++++++++ 14 files changed, 482 insertions(+), 18 deletions(-) create mode 100755 backend/scripts/psql.sh create mode 100644 frontend/resources/styles/main/partials/sidebar-assets.scss create mode 100644 frontend/src/uxbox/main/ui/workspace/sidebar/assets.cljs diff --git a/backend/scripts/psql.sh b/backend/scripts/psql.sh new file mode 100755 index 000000000..5edf959d8 --- /dev/null +++ b/backend/scripts/psql.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +PGPASSWORD=$UXBOX_DATABASE_PASSWORD psql $UXBOX_DATABASE_URI -U $UXBOX_DATABASE_USERNAME diff --git a/backend/src/uxbox/services/mutations/files.clj b/backend/src/uxbox/services/mutations/files.clj index d128d46f6..272427d80 100644 --- a/backend/src/uxbox/services/mutations/files.clj +++ b/backend/src/uxbox/services/mutations/files.clj @@ -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 @@ -189,6 +190,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) diff --git a/backend/src/uxbox/services/queries/files.clj b/backend/src/uxbox/services/queries/files.clj index 66ca104f2..40c7a74a7 100644 --- a/backend/src/uxbox/services/queries/files.clj +++ b/backend/src/uxbox/services/queries/files.clj @@ -169,7 +169,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}] diff --git a/frontend/resources/locales.json b/frontend/resources/locales.json index ebc7095b7..f8633e264 100644 --- a/frontend/resources/locales.json +++ b/frontend/resources/locales.json @@ -1358,6 +1358,87 @@ "es" : "Alinear arriba" } }, + "workspace.assets.assets" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:144" ], + "translations" : { + "en" : "Assets", + "fr" : "", + "ru" : "", + "es" : "Recursos" + } + }, + "workspace.assets.box-filter-all" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:154" ], + "translations" : { + "en" : "All assets", + "fr" : "", + "ru" : "", + "es" : "Todos" + } + }, + "workspace.assets.box-filter-colors" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:156" ], + "translations" : { + "en" : "Colors", + "fr" : "", + "ru" : "", + "es" : "Colores" + } + }, + "workspace.assets.box-filter-graphics" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:155" ], + "translations" : { + "en" : "Graphics", + "fr" : "", + "ru" : "", + "es" : "Gráficos" + } + }, + "workspace.assets.colors" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:91" ], + "translations" : { + "en" : "Colors", + "fr" : "", + "ru" : "", + "es" : "Colores" + } + }, + "workspace.assets.delete" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:83" ], + "translations" : { + "en" : "Delete", + "fr" : "", + "ru" : "", + "es" : "Borrar" + } + }, + "workspace.assets.file-library" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:104" ], + "translations" : { + "en" : "File library", + "fr" : "", + "ru" : "", + "es" : "Bilioteca del archivo" + } + }, + "workspace.assets.graphics" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:68" ], + "translations" : { + "en" : "Graphics", + "fr" : "", + "ru" : "", + "es" : "Gráficos" + } + }, + "workspace.assets.search" : { + "used-in" : [ "src/uxbox/main/ui/workspace/sidebar/assets.cljs:147" ], + "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 +2260,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 +2277,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 +2296,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 +2332,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" ], diff --git a/frontend/resources/styles/main-default.scss b/frontend/resources/styles/main-default.scss index 122715b6a..e613cbd47 100644 --- a/frontend/resources/styles/main-default.scss +++ b/frontend/resources/styles/main-default.scss @@ -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'; diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss new file mode 100644 index 000000000..7624e8aef --- /dev/null +++ b/frontend/resources/styles/main/partials/sidebar-assets.scss @@ -0,0 +1,143 @@ +// 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 +// Copyright (c) 2015-2016 Juan de la Cruz + +.assets-bar { + display: flex; + flex-direction: column; + + .assets-bar-title { + color: $color-gray-10; + font-size: $fs14; + margin: $small $small 0 $small; + } + + .search-input { + background-color: $color-gray-50; + border: 1px solid $color-gray-30; + color: $color-gray-10; + font-size: $fs12; + margin: $small $small 0 $small; + padding: $x-small; + + &:focus { + color: lighten($color-gray-10, 8%); + border-color: $color-primary !important; + } + + &:hover { + border-color: $color-gray-20; + } + + } + + .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; + + & img { + max-height: 100%; + max-width: 100%; + height: auto; + width: auto; + } + } + + .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; + } + } + + .context-menu { + position: absolute; + top: 10px; + left: 10px; + } + } +} diff --git a/frontend/resources/styles/main/partials/sidebar.scss b/frontend/resources/styles/main/partials/sidebar.scss index 27d80a3d7..3bd89a0f9 100644 --- a/frontend/resources/styles/main/partials/sidebar.scss +++ b/frontend/resources/styles/main/partials/sidebar.scss @@ -25,7 +25,7 @@ $width-settings-bar: 15rem; } .settings-bar-inside { - align-items: center; + align-items: flex-start; display: grid; grid-template-columns: 100%; diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 7e39d7139..176d8e00e 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -63,6 +63,7 @@ :sitemap-pages :layers :libraries + :assets :document-history :colorpalette :element-options @@ -74,9 +75,10 @@ (s/def ::layout-flags (s/coll-of ::layout-flag)) (def default-layout - #{:sitemap - :sitemap-pages - :layers + #{;; :sitemap + ;; :sitemap-pages + ;; :layers + :assets :element-options :rules :display-grid @@ -305,7 +307,8 @@ left-sidebar? (not (empty? (keep layout [:layers :sitemap :document-history - :libraries]))) + :libraries + :assets]))) right-sidebar? (not (empty? (keep layout [:element-options])))] (update-in state [:workspace-local] assoc :left-sidebar? left-sidebar? @@ -1434,8 +1437,10 @@ ;; 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 rename-page dwp/rename-page) (def delete-page dwp/delete-page) (def create-empty-page dwp/create-empty-page) diff --git a/frontend/src/uxbox/main/data/workspace/persistence.cljs b/frontend/src/uxbox/main/data/workspace/persistence.cljs index 73470f82c..39a6e60b8 100644 --- a/frontend/src/uxbox/main/data/workspace/persistence.cljs +++ b/frontend/src/uxbox/main/data/workspace/persistence.cljs @@ -430,6 +430,23 @@ (update [_ 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 (defn purge-page diff --git a/frontend/src/uxbox/main/ui/components/context_menu.cljs b/frontend/src/uxbox/main/ui/components/context_menu.cljs index df6fa4f69..e07788b15 100644 --- a/frontend/src/uxbox/main/ui/components/context_menu.cljs +++ b/frontend/src/uxbox/main/ui/components/context_menu.cljs @@ -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))) diff --git a/frontend/src/uxbox/main/ui/icons.cljs b/frontend/src/uxbox/main/ui/icons.cljs index e2a12b1d9..731ba3bbe 100644 --- a/frontend/src/uxbox/main/ui/icons.cljs +++ b/frontend/src/uxbox/main/ui/icons.cljs @@ -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)) diff --git a/frontend/src/uxbox/main/ui/workspace/left_toolbar.cljs b/frontend/src/uxbox/main/ui/workspace/left_toolbar.cljs index d2aaaa355..a97a059d4 100644 --- a/frontend/src/uxbox/main/ui/workspace/left_toolbar.cljs +++ b/frontend/src/uxbox/main/ui/workspace/left_toolbar.cljs @@ -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"} diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs index f78465bea..7734c749a 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs @@ -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) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/assets.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/assets.cljs new file mode 100644 index 000000000..bcbef1d51 --- /dev/null +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/assets.cljs @@ -0,0 +1,164 @@ +;; This Source Code Form is subject to the terms of the Mozilla Public +;; License, v. 2.0. If a copy of the MPL was not distributed with this +;; file, You can obtain one at http://mozilla.org/MPL/2.0/. +;; +;; 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.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.ui.components.tab-container :refer [tab-container tab-element]] + [uxbox.main.data.library :as dlib] + [uxbox.main.ui.components.context-menu :refer [context-menu]])) + +(defn matches-search + [name search-term] + (if (empty? search-term) + true + (let [st (str/trim (str/lower search-term)) + nm (str/trim (str/lower name))] + (str/includes? nm st)))) + +(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}) + + add-graphic #(println "añadir gráfico") + + delete-graphic + #(st/emit! (dw/delete-file-image library-id (:image-id @state))) + + 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))))] + + [: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]] + [:div.group-grid + (for [image (sort-by :name images)] + [:div.grid-cell {:key (:id image) + :on-context-menu (on-context-menu (:id image))} + [:img {:src (:thumb-uri image)}] + [: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 colors-box + [{:keys [colors] :as props}] + (let [add-color #(println "añadir color")] + [:div.asset-group + [:div.group-title + (tr "workspace.assets.colors") + [:div.group-button {:on-click add-color} i/plus]]])) + +(mf/defc library-toolbox + [{:keys [library-id images 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? + [:div.tool-window-content + (when (or (= box-filter :all) (= box-filter :graphics)) + [:& graphics-box {:library-id library-id :images images}]) + (when (or (= box-filter :all) (= box-filter :colors)) + [:& colors-box {:colors {}}])])])) + +(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) + + state (mf/use-state {:search-term "" + :box-filter :all}) + + filtered-images (filter #(matches-search (:name %) (:search-term @state)) + (vals file-images)) + + on-search-term-change (fn [event] + (let [value (-> (dom/get-target event) + (dom/get-value))] + (swap! state assoc :search-term value))) + + 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)))) + + [:div.assets-bar + + [:div.tool-window + [:div.tool-window-content + [:div.assets-bar-title (tr "workspace.assets.assets")] + + [:input.search-input + {:placeholder (tr "workspace.assets.search") + :type "text" + :value (:search-term @state) + :on-change on-search-term-change}] + + [: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 + :initial-open? true + :search-term (:search-term @state) + :box-filter (:box-filter @state)}]])) +