0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-10 17:18:21 -05:00

Colors management (#24)

* Colors management

* Minor indentation fixes.

* Remove redundant naming.

* Add missing block comment annotations.

* Use consistently defrecord instead of reify.

* Remove useless mapcat usage and simplify the related code.

* Start using more optimistic updates on collection operations.

* Remove println.

* Remove ^:const metadata.

* Remove neested let.

* Replace when with if on sablono templates.
This commit is contained in:
Jesús Espino 2016-05-14 20:50:06 +02:00 committed by Andrey Antukh
parent 4c193d6026
commit 3f4ed6faa5
15 changed files with 456 additions and 182 deletions

View file

@ -23,6 +23,23 @@
h2 {
text-align: center;
width: 100%;
.edit {
padding: 5px 10px;
background: $color-white;
border: none;
height: 100%;
}
.close {
padding: 5px 10px;
background: $color-white;
cursor: pointer;
svg {
transform: rotate(45deg);
fill: $color-gray;
height: 20px;
width: 20px;
}
}
}
.edition {

158
src/uxbox/data/colors.cljs Normal file
View file

@ -0,0 +1,158 @@
;; 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>
(ns uxbox.data.colors
(:require [clojure.set :as set]
[beicon.core :as rx]
[uuid.core :as uuid]
[uxbox.rstore :as rs]
[uxbox.state.colors :as stc]
[uxbox.repo :as rp]))
;; --- Collections Fetched
(defrecord CollectionFetched [items]
rs/UpdateEvent
(-apply-update [_ state]
(reduce stc/assoc-collection state items)))
(defn collections-fetched
[items]
(CollectionFetched. items))
;; --- Fetch Collections
(defrecord FetchCollections []
rs/WatchEvent
(-apply-watch [_ state s]
(->> (rp/req :fetch/color-collections)
(rx/map :payload)
(rx/map collections-fetched))))
(defn fetch-collections
[]
(FetchCollections.))
;; --- Collection Created
(defrecord CollectionCreated [item]
rs/UpdateEvent
(-apply-update [_ state]
(-> state
(stc/assoc-collection item)
(assoc-in [:dashboard :collection-id] (:id item))
(assoc-in [:dashboard :collection-type] :own))))
(defn collection-created
[item]
(CollectionCreated. item))
;; --- Create Collection
(defrecord CreateCollection []
rs/WatchEvent
(-apply-watch [_ state s]
(let [coll {:name "Unnamed collection"
:id (uuid/random)}]
(->> (rp/req :create/color-collection coll)
(rx/map :payload)
(rx/map collection-created)))))
(defn create-collection
[]
(CreateCollection.))
;; --- Collection Updated
(defrecord CollectionUpdated [item]
rs/UpdateEvent
(-apply-update [_ state]
(stc/assoc-collection state item)))
(defn collection-updated
[item]
(CollectionUpdated. item))
;; --- Update Collection
(defrecord UpdateCollection [id]
rs/WatchEvent
(-apply-watch [_ state s]
(let [item (get-in state [:colors-by-id id])]
(->> (rp/req :update/color-collection item)
(rx/map :payload)
(rx/map collection-updated)))))
(defn update-collection
[id]
(UpdateCollection. id))
;; --- Rename Collection
(defrecord RenameCollection [id name]
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:colors-by-id id :name] name))
rs/WatchEvent
(-apply-watch [_ state s]
(rx/of (update-collection id))))
(defn rename-collection
[item name]
(RenameCollection. item name))
;; --- Delete Collection
(defrecord DeleteCollection [id]
rs/UpdateEvent
(-apply-update [_ state]
(stc/dissoc-collection state id))
rs/WatchEvent
(-apply-watch [_ state s]
(->> (rp/req :delete/color-collection id)
(rx/ignore))))
(defn delete-collection
[id]
(DeleteCollection. id))
;; --- Replace Color
(defrecord ReplaceColor [id from to]
rs/UpdateEvent
(-apply-update [_ state]
(let [replacer #(-> (disj % from) (conj to))]
(update-in state [:colors-by-id id :data] (fnil replacer #{}))))
rs/WatchEvent
(-apply-watch [_ state s]
(rx/of (update-collection id))))
(defn replace-color
"Add or replace color in a collection."
[{:keys [id from to] :as params}]
(ReplaceColor. id from to))
;; --- Remove Color
(defrecord RemoveColors [id colors]
rs/UpdateEvent
(-apply-update [_ state]
(update-in state [:colors-by-id id :data]
#(set/difference % colors)))
rs/WatchEvent
(-apply-watch [_ state s]
(rx/of (update-collection id))))
(defn remove-colors
"Remove color in a collection."
[id colors]
(RemoveColors. id colors))

View file

@ -14,6 +14,7 @@
[uxbox.schema :as sc]
[uxbox.repo :as rp]
[uxbox.data.projects :as dp]
[uxbox.data.colors :as dc]
[uxbox.util.data :refer (deep-merge)]))
;; --- Events
@ -35,13 +36,16 @@
rs/WatchEvent
(-apply-watch [_ state s]
(let [projects (seq (:projects-by-id state))]
(let [projects (seq (:projects-by-id state))
color-collections (seq (:colors-by-id state))]
(rx/merge
;; Load projects if needed
(if projects
(rx/empty)
(rx/of (dp/fetch-projects)))
(rx/of (dc/fetch-collections))
(when (:loader state)
(if projects
(rx/of #(assoc % :loader false))
@ -54,28 +58,6 @@
[section]
(InitializeDashboard. section))
(defn set-project-ordering
[order]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:dashboard :project-order] order))))
(defn set-project-filtering
[term]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:dashboard :project-filter] term))))
(defn clear-project-filtering
[]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:dashboard :project-filter] ""))))
(defn set-collection-type
[type]
{:pre [(contains? #{:builtin :own} type)]}
@ -97,58 +79,3 @@
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:dashboard :collection-id] id))))
(defn mk-color-collection
[]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(let [id (uuid/random)
coll {:name "Unnamed collection"
:id id :colors #{}}]
(-> state
(assoc-in [:colors-by-id id] coll)
(assoc-in [:dashboard :collection-id] id)
(assoc-in [:dashboard :collection-type] :own))))))
(defn rename-color-collection
[id name]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:colors-by-id id :name] name))))
(defn delete-color-collection
[id]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(let [state (update state :colors-by-id dissoc id)
colls (sort-by :id (vals (:colors-by-id state)))]
(assoc-in state [:dashboard :collection-id] (:id (first colls)))))))
(defn replace-color
"Add or replace color in a collection."
[{:keys [id from to] :as params}]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(if-let [colors (get-in state [:colors-by-id id :colors])]
(as-> colors $
(disj $ from)
(conj $ to)
(assoc-in state [:colors-by-id id :colors] $))
state))))
(defn remove-color
"Remove color in a collection."
[{:keys [id color] :as params}]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(if-let [colors (get-in state [:colors-by-id id :colors])]
(as-> colors $
(disj $ color)
(assoc-in state [:colors-by-id id :colors] $))
state))))

View file

@ -149,3 +149,23 @@
projs
(filter #(contains-term? (:name %) term) projs)))
(defn set-project-ordering
[order]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:dashboard :project-order] order))))
(defn set-project-filtering
[term]
(reify
rs/WatchEvent
(-apply-watch [_ state s]
(assoc-in state [:dashboard :project-filter] term))))
(defn clear-project-filtering
[]
(reify
rs/UpdateEvent
(-apply-update [_ state]
(assoc-in state [:dashboard :project-filter] ""))))

View file

@ -11,34 +11,34 @@
[{:name "Generic 1"
:id 1
:builtin true
:colors #{"#00f9ff"
"#009fff"
"#0078ff"
"#005eff"
"#0900ff"
"#7502f1"
"#ffe705"
"#00ffab"
"#f52105"}}
:data #{"#00f9ff"
"#009fff"
"#0078ff"
"#005eff"
"#0900ff"
"#7502f1"
"#ffe705"
"#00ffab"
"#f52105"}}
{:name "Generic 2"
:id 2
:builtin true
:colors #{"#20f9ff"
"#209fff"
"#2078ff"
"#205eff"
"#2900ff"
"#3502f1"
"#3fe705"
"#30ffab"
"#352105"
"#209f20"
"#207820"
"#205e20"
"#290020"
"#350220"
"#3fe720"
"#30ff20"
"#352120"
"#352140"}}])
:data #{"#20f9ff"
"#209fff"
"#2078ff"
"#205eff"
"#2900ff"
"#3502f1"
"#3fe705"
"#30ffab"
"#352105"
"#209f20"
"#207820"
"#205e20"
"#290020"
"#350220"
"#3fe720"
"#30ff20"
"#352120"
"#352140"}}])

View file

@ -12,6 +12,9 @@
"ds.num-projects" ["No projects"
"%s project"
"%s projects"]
"ds.num-colors" ["No colors"
"%s color"
"%s colors"]
"ds.project-ordering" "Sort by"
"ds.project-ordering.by-name" "name"
"ds.project-ordering.by-last-update" "last update"

View file

@ -12,6 +12,7 @@
[uxbox.repo.users]
[uxbox.repo.projects]
[uxbox.repo.pages]
[uxbox.repo.colors]
[httpurr.status :as status]
[beicon.core :as rx]))

View file

@ -0,0 +1,60 @@
;; This Source Code Form is subject to the terms of the Mozilla Public
;; License, v. 2.0. If a copy of the MPL was not distributed with this
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
;;
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.repo.colors
"A main interface for access to remote resources."
(:require [beicon.core :as rx]
[uxbox.repo.core :refer (request url send!)]
[uxbox.state :as ust]
[uxbox.util.transit :as t]))
(defn- decode-color-collection
[{:keys [data] :as coll}]
(merge coll
(when data {:data (t/decode data)})))
(defn- decode-payload
[{:keys [payload] :as rsp}]
(if (sequential? payload)
(assoc rsp :payload (mapv decode-color-collection payload))
(assoc rsp :payload (decode-color-collection payload))))
(defmethod request :fetch/color-collection
[_ id]
(let [params {:url (str url "/library/color-collections/" id)
:method :get}]
(->> (send! params)
(rx/map decode-payload))))
(defmethod request :fetch/color-collections
[_]
(let [params {:url (str url "/library/color-collections")
:method :get}]
(->> (send! params)
(rx/map decode-payload))))
(defmethod request :delete/color-collection
[_ id]
(let [url (str url "/library/color-collections/" id)]
(send! {:url url :method :delete})))
(defmethod request :create/color-collection
[_ {:keys [data] :as body}]
(let [body (assoc body :data (t/encode data))
params {:url (str url "/library/color-collections")
:method :post
:body body}]
(->> (send! params)
(rx/map decode-payload))))
(defmethod request :update/color-collection
[_ {:keys [id data] :as body}]
(let [body (assoc body :data (t/encode data))
params {:url (str url "/library/color-collections/" id)
:method :put
:body body}]
(->> (send! params)
(rx/map decode-payload))))

View file

@ -0,0 +1,22 @@
(ns uxbox.state.colors
"A collection of functions for manage dashboard data insinde the state.")
(defn assoc-collection
"A reduce function for assoc the color collection
to the state map."
[state coll]
(let [id (:id coll)]
(assoc-in state [:colors-by-id id] coll)))
(defn dissoc-collection
"A reduce function for dissoc the color collection
to the state map."
[state id]
(update state :colors-by-id dissoc id))
(defn select-first-collection
"A reduce function for select the first color collection
to the state map."
[state]
(let [colls (sort-by :id (vals (:colors-by-id state)))]
(assoc-in state [:dashboard :collection-id] (:id (first colls)))))

View file

@ -16,6 +16,7 @@
[uxbox.schema :as sc]
[uxbox.library :as library]
[uxbox.data.dashboard :as dd]
[uxbox.data.colors :as dc]
[uxbox.data.lightbox :as udl]
[uxbox.ui.icons :as i]
[uxbox.ui.forms :as form]
@ -23,17 +24,18 @@
[uxbox.ui.colorpicker :refer (colorpicker)]
[uxbox.ui.mixins :as mx]
[uxbox.ui.dashboard.header :refer (header)]
[uxbox.ui.keyboard :as k]
[uxbox.util.dom :as dom]
[uxbox.util.lens :as ul]
[uxbox.util.color :refer (hex->rgb)]))
;; --- Lenses
(def ^:const ^:private dashboard-l
(def ^:private dashboard-l
(-> (l/key :dashboard)
(l/focus-atom st/state)))
(def ^:const ^:private collections-by-id-l
(def ^:private collections-by-id-l
(-> (comp (l/key :colors-by-id)
(ul/merge library/+color-collections-by-id+))
(l/focus-atom st/state)))
@ -47,31 +49,48 @@
(defn page-title-render
[own coll]
(letfn [(on-title-edited [e]
(let [content (dom/event->inner-text e)
collid (:id coll)]
(rs/emit! (dd/rename-color-collection collid content))))
(on-delete [e]
(rs/emit! (dd/delete-color-collection (:id coll))))]
(let [dashboard (rum/react dashboard-l)
own? (:builtin coll false)]
(let [local (:rum/local own)
dashboard (rum/react dashboard-l)
own? (:builtin coll false)]
(letfn [(on-title-save [e]
(rs/emit! (dc/rename-collection (:id coll) (:coll-name @local)))
(swap! local assoc :edit false))
(on-title-edited [e]
(cond
(k/esc? e) (swap! local assoc :edit false)
(k/enter? e) (on-title-save e)
:else (let [content (dom/event->inner-text e)]
(swap! local assoc :coll-name content))))
(on-title-edit [e]
(swap! local assoc :edit true :coll-name (:name coll)))
(on-delete [e]
(rs/emit! (dc/delete-collection (:id coll))))]
(html
[:div.dashboard-title {}
[:h2 {}
[:span (tr "ds.library-title")]
[:span {:content-editable ""
:on-key-up on-title-edited}
(:name coll)]]
(if (:edit @local)
[:div.dashboard-title-field
[:span.edit
{:content-editable ""
:on-key-up on-title-edited}
(:name coll)]
[:span.close
{:on-click #(swap! local assoc :edit false)}
i/close]]
[:span.dashboard-title-field
(:name coll)])]
(if (and (not own?) coll)
[:div.edition {}
#_[:span i/pencil]
[:div.edition
(if (:edit @local)
[:span {:on-click on-title-save} i/save]
[:span {:on-click on-title-edit} i/pencil])
[:span {:on-click on-delete} i/trash]])]))))
(def ^:const ^:private page-title
(def ^:private page-title
(mx/component
{:render page-title-render
:name "page-title"
:mixins [mx/static rum/reactive]}))
:mixins [(rum/local {}) mx/static rum/reactive]}))
;; --- Nav
@ -97,13 +116,13 @@
:on-click #(rs/emit! (dd/set-collection-type :own))}
"YOUR LIBRARIES"]]
[:ul.library-elements
(when own?
(if own?
[:li
[:a.btn-primary
{:on-click #(rs/emit! (dd/mk-color-collection))}
{:on-click #(rs/emit! (dc/create-collection))}
"+ New library"]])
(for [props collections
:let [num (count (:colors props))]]
:let [num (count (:data props))]]
[:li {:key (str (:id props))
:on-click #(rs/emit! (dd/set-collection (:id props)))
:class-name (when (= (:id props) collid) "current")}
@ -111,7 +130,7 @@
[:span.element-subtitle
(tr "ds.num-elements" (t/c num))]])]]])))
(def ^:const ^:private nav
(def ^:private nav
(mx/component
{:render nav-render
:name "nav"
@ -121,42 +140,82 @@
(defn grid-render
[own]
(let [dashboard (rum/react dashboard-l)
(let [local (:rum/local own)
dashboard (rum/react dashboard-l)
coll-type (:collection-type dashboard)
coll-id (:collection-id dashboard)
own? (= coll-type :own)
coll (rum/react (focus-collection coll-id))
edit-cb #(udl/open! :color-form {:coll coll :color %})
remove-cb #(rs/emit! (dd/remove-color {:id (:id coll) :color %}))]
toggle-color-check (fn [color]
(swap! local update :selected #(if (% color) (disj % color) (conj % color))))
delete-selected #(rs/emit! (dc/remove-colors (:id coll) (:selected @local)))]
(when coll
(html
[:section.dashboard-grid.library
(page-title coll)
[:div.dashboard-grid-content
[:div.dashboard-grid-row
(when own?
(if own?
[:div.grid-item.small-item.add-project
{:on-click #(udl/open! :color-form {:coll coll})}
[:span "+ New color"]])
(for [color (remove nil? (:colors coll))
(for [color (remove nil? (:data coll))
:let [color-rgb (hex->rgb color)]]
[:div.grid-item.small-item.project-th {:key color}
[:div.grid-item.small-item.project-th
{:key color :on-click #(when (k/shift? %) (toggle-color-check color))}
[:span.color-swatch {:style {:background-color color}}]
[:div.input-checkbox.check-primary
[:input {:type "checkbox"
:id color
:on-click #(toggle-color-check color)
:checked ((:selected @local) color)}]
[:label {:for color}]]
[:span.color-data color]
[:span.color-data (apply str "RGB " (interpose ", " color-rgb))]
(if own?
[:div.project-th-actions
[:div.project-th-icon.edit
{:on-click #(edit-cb color)} i/pencil]
[:div.project-th-icon.delete
{:on-click #(remove-cb color)}
i/trash]])])]]]))))
[:span.color-data (apply str "RGB " (interpose ", " color-rgb))]])]]
(def ^:const ^:private grid
(when (not (empty? (:selected @local)))
;; MULTISELECT OPTIONS BAR
[:div.multiselect-bar
(if own?
[:div.multiselect-nav
[:span.move-item.tooltip.tooltip-top
{:alt "Move to"}
i/organize]
[:span.delete.tooltip.tooltip-top
{:alt "Delete" :on-click delete-selected}
i/trash]]
[:div.multiselect-nav
[:span.move-item.tooltip.tooltip-top
{:alt "Copy to"}
i/organize]])])]))))
(def ^:private grid
(mx/component
{:render grid-render
:name "grid"
:mixins [mx/static rum/reactive]}))
:mixins [(rum/local {:selected #{}})
mx/static
rum/reactive]}))
;; --- Menu
(defn menu-render
[]
(let [dashboard (rum/react dashboard-l)
coll-id (:collection-id dashboard)
coll (rum/react (focus-collection coll-id))
ccount (count (:data coll)) ]
(html
[:section.dashboard-bar.library-gap
[:div.dashboard-info
[:span.dashboard-colors (tr "ds.num-colors" (t/c ccount))]]])))
(def menu
(mx/component
{:render menu-render
:name "menu"
:mixins [rum/reactive mx/static]}))
;; --- Colors Page
@ -167,6 +226,7 @@
(header)
[:section.dashboard-content
(nav)
(menu)
(grid)]]))
(defn colors-page-will-mount
@ -192,40 +252,36 @@
(defn- color-lightbox-render
[own {:keys [coll color]}]
(html
[:p "TODO"]))
;; (let [local (:rum/local own)]
;; (letfn [(submit [e]
;; (if-let [errors (sc/validate +color-form-schema+ @local)]
;; (swap! local assoc :errors errors)
;; (let [params {:id (:id coll) :from color
;; :to (:hex @local)}]
;; (rs/emit! (dd/replace-color params))
;; (lightbox/close!))))
;; (on-change [e]
;; (let [value (str/trim (dom/event->value e))]
;; (swap! local assoc :hex value)))]
;; (html
;; [:div.lightbox-body
;; [:h3 "New color"]
;; [:form
;; [:div.row-flex
;; [:input#color-hex.input-text
;; {:placeholder "#"
;; :class (form/error-class local :hex)
;; :on-change on-change
;; :value (or (:hex @local) color "")
;; :type "text"}]]
;; [:div.row-flex.center.color-picker-default
;; (colorpicker
;; :value (or (:hex @local) color "#00ccff")
;; :on-change #(swap! local assoc :hex %))]
(let [local (:rum/local own)]
(letfn [(submit [e]
(let [params {:id (:id coll) :from color :to (:hex @local)}]
(rs/emit! (dc/replace-color params))
(udl/close!)))
(on-change [e]
(let [value (str/trim (dom/event->value e))]
(swap! local assoc :hex value)))]
(html
[:div.lightbox-body
[:h3 "New color"]
[:form
[:div.row-flex
[:input#color-hex.input-text
{:placeholder "#"
:class (form/error-class local :hex)
:on-change on-change
:value (or (:hex @local) color "")
:type "text"}]]
[:div.row-flex.center.color-picker-default
(colorpicker
:value (or (:hex @local) color "#00ccff")
:on-change #(swap! local assoc :hex %))]
;; [:input#project-btn.btn-primary
;; {:value "+ Add color"
;; :on-click submit
;; :type "button"}]]
;; [:a.close {:on-click #(lightbox/close!)}
;; i/close]]))))
[:input#project-btn.btn-primary
{:value "+ Add color"
:on-click submit
:type "button"}]]
[:a.close {:on-click #(udl/close!)}
i/close]])))))
(def color-lightbox
(mx/component

View file

@ -79,7 +79,7 @@
(defn sort-widget-render
[]
(let [ordering (rum/react project-ordering-l)
on-change #(rs/emit! (dd/set-project-ordering
on-change #(rs/emit! (dp/set-project-ordering
(keyword (.-value (.-target %)))))]
(html
[:div
@ -109,10 +109,10 @@
(letfn [(on-term-change [event]
(-> (dom/get-target event)
(dom/get-value)
(dd/set-project-filtering)
(dp/set-project-filtering)
(rs/emit!)))
(on-clear [event]
(rs/emit! (dd/clear-project-filtering)))]
(rs/emit! (dp/clear-project-filtering)))]
(html
[:form.dashboard-search
[:input.input-text

View file

@ -668,3 +668,15 @@
{:id "loader-line"
:d
"M134.482 157.147v25l518.57.008.002-25-518.572-.008z"}]]]))
(def save
(html
[:svg
{:viewBox "0 0 48 48"
:height "48"
:width "48"
:id "save"}
[:path
{:style {:stroke nil}
:d
"M34 6h-24c-2.21 0-4 1.79-4 4v28c0 2.21 1.79 4 4 4h28c2.21 0 4-1.79 4-4v-24l-8-8zm-10 32c-3.31 0-6-2.69-6-6s2.69-6 6-6 6 2.69 6 6-2.69 6-6 6zm6-20h-20v-8h20v8z"}]]))

View file

@ -14,7 +14,6 @@
[uxbox.ui.icons :as i]
[uxbox.ui.mixins :as mx]
[uxbox.util.dom :as dom]
[uxbox.data.dashboard :as dd]
[uxbox.ui.settings.profile :as profile]
[uxbox.ui.settings.password :as password]
[uxbox.ui.settings.notifications :as notifications]

View file

@ -14,7 +14,6 @@
[uxbox.ui.icons :as i]
[uxbox.ui.mixins :as mx]
[uxbox.util.dom :as dom]
[uxbox.data.dashboard :as dd]
[uxbox.ui.settings.header :refer (header)]))
(defn notifications-page-render

View file

@ -77,7 +77,7 @@
[:div.btn-palette.create i/close]]]
[:span.left-arrow i/arrow-slide]
[:div.color-palette-content
(for [hex-color (:colors collection)
(for [hex-color (:data collection)
:let [rgb-vec (hex->rgb hex-color)
rgb-color (apply str "" (interpose ", " rgb-vec))]]
[:div.color-cell {:key (str hex-color)