From 726cdb9a278241dc0c017cb0d6e2e5fb3f082af7 Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Thu, 10 Sep 2020 14:06:46 +0200
Subject: [PATCH] :tada: Picker shortcut

---
 frontend/deps.edn                             |   2 +-
 frontend/src/app/main.cljs                    |   8 +-
 frontend/src/app/main/data/colors.cljs        |  66 ++++++++-
 frontend/src/app/main/data/workspace.cljs     |   5 +-
 frontend/src/app/main/refs.cljs               |   3 +
 frontend/src/app/main/ui/confirm.cljs         |   2 +
 frontend/src/app/main/ui/dashboard/grid.cljs  |   7 +-
 .../src/app/main/ui/dashboard/libraries.cljs  |   1 -
 .../src/app/main/ui/dashboard/project.cljs    |   5 +-
 .../app/main/ui/dashboard/recent_files.cljs   |   2 -
 .../src/app/main/ui/dashboard/sidebar.cljs    |   2 -
 frontend/src/app/main/ui/modal.cljs           | 129 +++++++++++++-----
 .../app/main/ui/settings/change_email.cljs    |   2 +
 .../app/main/ui/settings/delete_account.cljs  |  35 ++---
 .../src/app/main/ui/settings/profile.cljs     |   6 +-
 .../app/main/ui/workspace/colorpicker.cljs    |  35 +++--
 .../src/app/main/ui/workspace/header.cljs     |   5 +-
 .../src/app/main/ui/workspace/libraries.cljs  |   2 +
 .../app/main/ui/workspace/sidebar/assets.cljs |   8 +-
 .../sidebar/options/rows/color_row.cljs       |  12 +-
 .../ui/workspace/sidebar/options/stroke.cljs  |   1 -
 .../main/ui/workspace/sidebar/sitemap.cljs    |   3 +-
 .../src/app/main/ui/workspace/viewport.cljs   |   5 +-
 23 files changed, 236 insertions(+), 110 deletions(-)

diff --git a/frontend/deps.edn b/frontend/deps.edn
index a97681bdb..9a9b7cd9f 100644
--- a/frontend/deps.edn
+++ b/frontend/deps.edn
@@ -18,7 +18,7 @@
   funcool/okulary {:mvn/version "2020.04.14-0"}
   funcool/potok {:mvn/version "2020.08.10-2"}
   funcool/promesa {:mvn/version "5.1.0"}
-  funcool/rumext {:mvn/version "2020.05.22-1"}
+  funcool/rumext {:mvn/version "2020.08.21-0"}
 
   lambdaisland/uri {:mvn/version "1.3.45"
                     :exclusions [org.clojure/data.json]}
diff --git a/frontend/src/app/main.cljs b/frontend/src/app/main.cljs
index 6759e6510..652ca96df 100644
--- a/frontend/src/app/main.cljs
+++ b/frontend/src/app/main.cljs
@@ -26,7 +26,13 @@
    [app.util.router :as rt]
    [app.util.object :as obj]
    [app.util.storage :refer [storage]]
-   [app.util.timers :as ts]))
+   [app.util.timers :as ts]
+
+   ;; MODALS
+   [app.main.ui.settings.delete-account]
+   [app.main.ui.settings.change-email]
+   [app.main.ui.confirm]
+   [app.main.ui.workspace.colorpicker]))
 
 (declare reinit)
 
diff --git a/frontend/src/app/main/data/colors.cljs b/frontend/src/app/main/data/colors.cljs
index a94efbe54..5babaff19 100644
--- a/frontend/src/app/main/data/colors.cljs
+++ b/frontend/src/app/main/data/colors.cljs
@@ -10,6 +10,7 @@
    [beicon.core :as rx]
    [clojure.set :as set]
    [potok.core :as ptk]
+   [app.main.streams :as ms]
    [app.common.data :as d]
    [app.common.spec :as us]
    [app.main.repo :as rp]
@@ -18,8 +19,9 @@
    [app.util.i18n :refer [tr]]
    [app.util.router :as rt]
    [app.util.time :as dt]
-   [app.common.uuid :as uuid]))
-
+   [app.common.uuid :as uuid]
+   [app.main.data.workspace.common :as dwc]
+   [app.main.data.workspace.texts :as dwt]))
 
 (declare create-color-result)
 
@@ -142,6 +144,8 @@
     ptk/UpdateEvent
     (update [_ state]
       (-> state
+          (update :workspace-local dissoc :picked-color-select)
+          (update :workspace-local dissoc :picked-shift?)
           (assoc-in [:workspace-local :picking-color?] false)))))
 
 (defn pick-color [rgba]
@@ -151,9 +155,61 @@
       (-> state
           (assoc-in [:workspace-local :picked-color] rgba)))))
 
-(defn pick-color-select [value]
-  (ptk/reify ::pick-color
+(defn pick-color-select [value shift?]
+  (ptk/reify ::pick-color-select
     ptk/UpdateEvent
     (update [_ state]
       (-> state
-          (assoc-in [:workspace-local :picked-color-select] value)))))
+          (assoc-in [:workspace-local :picked-color-select] value)
+          (assoc-in [:workspace-local :picked-shift?] shift?)))))
+
+
+(defn change-fill-selected [color]
+  (ptk/reify ::change-fill-selected
+    ptk/WatchEvent
+    (watch [_ state s]
+      (let [ids (get-in state [:workspace-local :selected])
+            objects (get-in state [:workspace-data :pages-index (:current-page-id state) :objects])
+            is-text? #(= :text (:type (get objects %)))
+            text-ids (filter is-text? ids)
+            shape-ids (filter (comp not is-text?) ids)
+            update-fn (fn [shape] (assoc shape :fill-color color))
+            editor (get-in state [:workspace-local :editor])
+            converted-attrs {:fill color}]
+        (rx/from (conj
+                  (map #(dwt/update-text-attrs {:id % :editor editor :attrs converted-attrs}) text-ids)
+                  (dwc/update-shapes shape-ids update-fn)))))))
+
+(defn change-stroke-selected [color]
+  (ptk/reify ::change-stroke-selected
+    ptk/WatchEvent
+    (watch [_ state s]
+      (let [ids (get-in state [:workspace-local :selected])
+            update-fn (fn [s]
+                        (cond-> s
+                          true
+                          (assoc :stroke-color color)
+
+                          (= (:stroke-style s) :none)
+                          (assoc :stroke-style "solid"
+                                 :stroke-width 1
+                                 :stroke-opacity 1)))]
+        (rx/of (dwc/update-shapes ids update-fn))))))
+
+(defn picker-for-selected-shape []
+  (let [handle-change-color (fn [color _ shift?]
+                              (st/emit!
+                               (if shift?
+                                 (change-stroke-selected color)
+                                 (change-fill-selected color)
+                                 )
+                               (fn [state] (update state :workspace-local dissoc :modal))))]
+    (ptk/reify ::start-picker
+      ptk/UpdateEvent
+      (update [_ state]
+        (-> state
+            (assoc-in [:workspace-local :picking-color?] true)
+            (assoc-in [:workspace-local :modal] {:id (random-uuid)
+                                                 :type :colorpicker
+                                                 :props {:on-change handle-change-color}
+                                                 :allow-click-outside true}))))))
diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs
index fb1acc624..502fdc619 100644
--- a/frontend/src/app/main/data/workspace.cljs
+++ b/frontend/src/app/main/data/workspace.cljs
@@ -27,6 +27,7 @@
    [app.main.data.workspace.selection :as dws]
    [app.main.data.workspace.texts :as dwtxt]
    [app.main.data.workspace.transforms :as dwt]
+   [app.main.data.colors :as dwl]
    [app.main.repo :as rp]
    [app.main.store :as st]
    [app.main.streams :as ms]
@@ -1546,5 +1547,7 @@
    "up" #(st/emit! (dwt/move-selected :up false))
    "down" #(st/emit! (dwt/move-selected :down false))
    "right" #(st/emit! (dwt/move-selected :right false))
-   "left" #(st/emit! (dwt/move-selected :left false))})
+   "left" #(st/emit! (dwt/move-selected :left false))
+
+   "i" #(st/emit! (dwl/picker-for-selected-shape ))})
 
diff --git a/frontend/src/app/main/refs.cljs b/frontend/src/app/main/refs.cljs
index 18d594f20..b61341b3b 100644
--- a/frontend/src/app/main/refs.cljs
+++ b/frontend/src/app/main/refs.cljs
@@ -89,6 +89,9 @@
 (def picked-color-select
   (l/derived :picked-color-select workspace-local))
 
+(def picked-shift?
+  (l/derived :picked-shift? workspace-local))
+
 (def workspace-layout
   (l/derived :workspace-layout st/state))
 
diff --git a/frontend/src/app/main/ui/confirm.cljs b/frontend/src/app/main/ui/confirm.cljs
index 2d15804e6..11c289197 100644
--- a/frontend/src/app/main/ui/confirm.cljs
+++ b/frontend/src/app/main/ui/confirm.cljs
@@ -15,6 +15,8 @@
    [app.util.dom :as dom]))
 
 (mf/defc confirm-dialog
+  {::mf/register modal/components
+   ::mf/register-as :confirm-dialog}
   [{:keys [message on-accept on-cancel hint cancel-text accept-text not-danger?] :as ctx}]
   (let [message (or message (tr "ds.confirm-title"))
         cancel-text (or cancel-text (tr "ds.confirm-cancel"))
diff --git a/frontend/src/app/main/ui/dashboard/grid.cljs b/frontend/src/app/main/ui/dashboard/grid.cljs
index f75766a75..348b6db64 100644
--- a/frontend/src/app/main/ui/dashboard/grid.cljs
+++ b/frontend/src/app/main/ui/dashboard/grid.cljs
@@ -15,7 +15,6 @@
    [app.main.fonts :as fonts]
    [app.main.store :as st]
    [app.main.ui.components.context-menu :refer [context-menu]]
-   [app.main.ui.confirm :refer [confirm-dialog]]
    [app.main.ui.icons :as i]
    [app.main.ui.keyboard :as kbd]
    [app.main.ui.modal :as modal]
@@ -73,7 +72,7 @@
          (mf/deps id)
          (fn [event]
            (dom/stop-propagation event)
-           (modal/show! confirm-dialog {:on-accept delete})))
+           (modal/show! :confirm-dialog {:on-accept delete})))
 
         on-navigate
         (mf/use-callback
@@ -89,7 +88,7 @@
          (mf/deps id)
          (fn [event]
            (dom/stop-propagation event)
-           (modal/show! confirm-dialog
+           (modal/show! :confirm-dialog
                         {:message (t locale "dashboard.grid.add-shared-message" (:name file))
                          :hint (t locale "dashboard.grid.add-shared-hint")
                          :accept-text (t locale "dashboard.grid.add-shared-accept")
@@ -108,7 +107,7 @@
          (mf/deps id)
          (fn [event]
            (dom/stop-propagation event)
-           (modal/show! confirm-dialog
+           (modal/show! :confirm-dialog
                         {:message (t locale "dashboard.grid.remove-shared-message" (:name file))
                          :hint (t locale "dashboard.grid.remove-shared-hint")
                          :accept-text (t locale "dashboard.grid.remove-shared-accept")
diff --git a/frontend/src/app/main/ui/dashboard/libraries.cljs b/frontend/src/app/main/ui/dashboard/libraries.cljs
index e44993768..56b00b35c 100644
--- a/frontend/src/app/main/ui/dashboard/libraries.cljs
+++ b/frontend/src/app/main/ui/dashboard/libraries.cljs
@@ -19,7 +19,6 @@
    [app.main.store :as st]
    [app.main.ui.modal :as modal]
    [app.main.ui.keyboard :as kbd]
-   [app.main.ui.confirm :refer [confirm-dialog]]
    [app.main.ui.components.context-menu :refer [context-menu]]
    [app.main.ui.dashboard.grid :refer [grid]]))
 
diff --git a/frontend/src/app/main/ui/dashboard/project.cljs b/frontend/src/app/main/ui/dashboard/project.cljs
index 323f7de17..464490ded 100644
--- a/frontend/src/app/main/ui/dashboard/project.cljs
+++ b/frontend/src/app/main/ui/dashboard/project.cljs
@@ -19,7 +19,6 @@
    [app.main.store :as st]
    [app.main.ui.modal :as modal]
    [app.main.ui.keyboard :as kbd]
-   [app.main.ui.confirm :refer [confirm-dialog]]
    [app.main.ui.components.context-menu :refer [context-menu]]
    [app.main.ui.dashboard.grid :refer [grid]]))
 
@@ -49,7 +48,7 @@
         delete-fn #(do
                      (st/emit! (dsh/delete-project project-id))
                      (st/emit! (rt/nav :dashboard-team {:team-id team-id})))
-        on-delete #(modal/show! confirm-dialog {:on-accept delete-fn})]
+        on-delete #(modal/show! :confirm-dialog {:on-accept delete-fn})]
     [:header.main-bar
      (if (:is-default project)
        [:h1.dashboard-title (t locale "dashboard.header.draft")]
@@ -83,4 +82,4 @@
     [:*
       [:& project-header {:team-id team-id :project-id project-id}]
       [:section.projects-page
-       [:& grid { :id project-id :files files :hide-new? true}]]]))
\ No newline at end of file
+       [:& grid { :id project-id :files files :hide-new? true}]]]))
diff --git a/frontend/src/app/main/ui/dashboard/recent_files.cljs b/frontend/src/app/main/ui/dashboard/recent_files.cljs
index 7f12b14cf..473e3a912 100644
--- a/frontend/src/app/main/ui/dashboard/recent_files.cljs
+++ b/frontend/src/app/main/ui/dashboard/recent_files.cljs
@@ -16,11 +16,9 @@
    [app.main.data.dashboard :as dsh]
    [app.main.refs :as refs]
    [app.main.store :as st]
-   [app.main.ui.confirm :refer [confirm-dialog]]
    [app.main.ui.dashboard.grid :refer [grid]]
    [app.main.ui.icons :as i]
    [app.main.ui.keyboard :as kbd]
-   [app.main.ui.modal :as modal]
    [app.util.dom :as dom]
    [app.util.i18n :as i18n :refer [t tr]]
    [app.util.router :as rt]
diff --git a/frontend/src/app/main/ui/dashboard/sidebar.cljs b/frontend/src/app/main/ui/dashboard/sidebar.cljs
index d2b334990..10d6b2e61 100644
--- a/frontend/src/app/main/ui/dashboard/sidebar.cljs
+++ b/frontend/src/app/main/ui/dashboard/sidebar.cljs
@@ -18,11 +18,9 @@
    [app.main.data.dashboard :as dsh]
    [app.main.refs :as refs]
    [app.main.store :as st]
-   [app.main.ui.confirm :refer [confirm-dialog]]
    [app.main.ui.dashboard.common :as common]
    [app.main.ui.icons :as i]
    [app.main.ui.keyboard :as kbd]
-   [app.main.ui.modal :as modal]
    [app.util.dom :as dom]
    [app.util.i18n :as i18n :refer [t tr]]
    [app.util.router :as rt]
diff --git a/frontend/src/app/main/ui/modal.cljs b/frontend/src/app/main/ui/modal.cljs
index a382896af..7d6135377 100644
--- a/frontend/src/app/main/ui/modal.cljs
+++ b/frontend/src/app/main/ui/modal.cljs
@@ -1,69 +1,126 @@
+;; 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 app.main.ui.modal
   (:require
-   [cuerdas.core :as str]
+   [okulary.core :as l]
    [goog.events :as events]
    [rumext.alpha :as mf]
    [app.main.store :as st]
    [app.main.ui.keyboard :as k]
-   [app.util.data :refer [classnames]]
-   [app.util.dom :as dom])
+   [app.util.dom :as dom]
+   [app.main.refs :as refs]
+   [potok.core :as ptk])
   (:import goog.events.EventType))
 
-(defonce state (atom nil))
-(defonce can-click-outside (atom false))
+(defonce components (atom {}))
+
+(defn show-modal [id type props]
+  (ptk/reify ::show-modal
+    ptk/UpdateEvent
+    (update [_ state]
+      (-> state
+          (assoc-in [:workspace-local :modal] {:id id
+                                               :type type
+                                               :props props
+                                               :allow-click-outside false})))))
+
+(defn hide-modal []
+  (ptk/reify ::hide-modal
+    ptk/UpdateEvent
+    (update [_ state]
+      (-> state
+          (update :workspace-local dissoc :modal)))))
+
+(defn update-modal [options]
+  (ptk/reify ::hide-modal
+    ptk/UpdateEvent
+    (update [_ state]
+      (-> state
+          (update-in [:workspace-local :modal] merge options)))))
 
 (defn show!
-  [component props]
-  (reset! state {:component component :props props}))
+  ([type props]
+   (let [id    (random-uuid)]
+     (st/emit! (show-modal id type props)))))
+
+(defn allow-click-outside! []
+  (st/emit! (update-modal {:allow-click-outside true})))
+
+(defn disallow-click-outside! []
+  (st/emit! (update-modal {:allow-click-outside false})))
 
 (defn hide!
   []
-  (reset! state nil))
+  (st/emit! (hide-modal)))
 
 (defn- on-esc-clicked
   [event]
   (when (k/esc? event)
-    (reset! state nil)
+    (hide!)
     (dom/stop-propagation event)))
 
-(defn- on-click
-  [event wrapper-ref]
+(defn- on-pop-state
+  [event]
+  (dom/prevent-default event)
+  (dom/stop-propagation event)
+  (hide!)
+  (.forward js/history))
+
+(defn- on-parent-clicked
+  [event parent-ref]
+  (let [parent (mf/ref-val parent-ref)
+        current (dom/get-target event)]
+    (when (and (dom/equals? (.-firstElementChild ^js parent) current)
+               (= (.-className ^js current) "modal-overlay"))
+      (dom/stop-propagation event)
+      (dom/prevent-default event)
+      (hide!))))
+
+(defn- on-click-outside
+  [event wrapper-ref allow-click-outside]
   (let [wrapper (mf/ref-val wrapper-ref)
         current (dom/get-target event)]
 
-    (when (and (not @can-click-outside) (not (.contains wrapper current)))
+    (when (and wrapper (not allow-click-outside) (not (.contains wrapper current)))
       (dom/stop-propagation event)
       (dom/prevent-default event)
-      (reset! state nil))))
+      (hide!))))
 
 (mf/defc modal-wrapper
-  [{:keys [component props]}]
+  {::mf/wrap-props false
+   ::mf/wrap [mf/memo]}
+  [props]
+  (let [data        (unchecked-get props "data")
+        wrapper-ref (mf/use-ref nil)
+        handle-click-outside
+        (fn [event]
+          (on-click-outside event wrapper-ref (:allow-click-outside data)))]
 
-  (let [wrapper-ref (mf/use-ref nil)]
-    (mf/use-effect
+    (mf/use-layout-effect
      (fn []
-       (let [key (events/listen js/document EventType.KEYDOWN on-esc-clicked)]
-         #(events/unlistenByKey key))))
+       (let [keys [(events/listen js/document EventType.KEYDOWN  on-esc-clicked)
+                   (events/listen js/window   EventType.POPSTATE on-pop-state)
+                   (events/listen js/document EventType.CLICK    handle-click-outside)]]
+         #(for [key keys]
+            (events/unlistenByKey key)))))
+    [:div.modal-wrapper {:ref wrapper-ref}
+     (mf/element
+      (get @components (:type data))
+      (:props data))]))
 
-    (mf/use-effect
-     (fn []
-       (let [key (events/listen js/document EventType.CLICK #(on-click % wrapper-ref))]
-         #(events/unlistenByKey key))))
 
-    [:div.modal-wrapper
-     {:ref wrapper-ref}
-     [:& component props]]))
+(def modal-ref
+  (l/derived :modal refs/workspace-local))
 
 (mf/defc modal
   []
-  (when-let [{:keys [component props]} (mf/deref state)]
-    [:& modal-wrapper {:component component
-                       :props props
-                       :key (random-uuid)}]))
-
-
-(defn allow-click-outside! []
-  (reset! can-click-outside true))
-
-(defn disallow-click-outside! []
-  (reset! can-click-outside false))
+  (let [modal (mf/deref modal-ref)]
+    (when modal [:& modal-wrapper {:data modal
+                                   :key (:id modal)}])))
diff --git a/frontend/src/app/main/ui/settings/change_email.cljs b/frontend/src/app/main/ui/settings/change_email.cljs
index 9af293667..bd51ed6e3 100644
--- a/frontend/src/app/main/ui/settings/change_email.cljs
+++ b/frontend/src/app/main/ui/settings/change_email.cljs
@@ -98,6 +98,8 @@
     (t locale "settings.close-modal-label")]])
 
 (mf/defc change-email-modal
+  {::mf/register modal/components
+   ::mf/register-as :change-email}
   [props]
   (let [locale (mf/deref i18n/locale)
         profile (mf/deref refs/profile)]
diff --git a/frontend/src/app/main/ui/settings/delete_account.cljs b/frontend/src/app/main/ui/settings/delete_account.cljs
index ca5854b36..7c79a8c0f 100644
--- a/frontend/src/app/main/ui/settings/delete_account.cljs
+++ b/frontend/src/app/main/ui/settings/delete_account.cljs
@@ -20,24 +20,27 @@
    [app.util.i18n :as i18n :refer [tr t]]))
 
 (mf/defc delete-account-modal
+  {::mf/register modal/components
+   ::mf/register-as :delete-account}
   [props]
   (let [locale (mf/deref i18n/locale)]
-    [:section.generic-modal.change-email-modal
-     [:span.close {:on-click #(modal/hide!)} i/close]
+    [:div.modal-overlay
+     [:section.generic-modal.change-email-modal
+      [:span.close {:on-click #(modal/hide!)} i/close]
 
-     [:section.modal-content.generic-form
-      [:h2 (t locale "settings.delete-account-title")]
+      [:section.modal-content.generic-form
+       [:h2 (t locale "settings.delete-account-title")]
 
-      [:& msgs/inline-banner
-       {:type :warning
-        :content (t locale "settings.delete-account-info")}]
+       [:& msgs/inline-banner
+        {:type :warning
+         :content (t locale "settings.delete-account-info")}]
 
-      [:div.button-row
-       [:button.btn-warning.btn-large
-        {:on-click #(do
-                      (modal/hide!)
-                      (st/emit! da/request-account-deletion))}
-        (t locale "settings.yes-delete-my-account")]
-       [:button.btn-secondary.btn-large
-        {:on-click #(modal/hide!)}
-        (t locale "settings.cancel-and-keep-my-account")]]]]))
+       [:div.button-row
+        [:button.btn-warning.btn-large
+         {:on-click #(do
+                       (modal/hide!)
+                       (st/emit! da/request-account-deletion))}
+         (t locale "settings.yes-delete-my-account")]
+        [:button.btn-secondary.btn-large
+         {:on-click #(modal/hide!)}
+         (t locale "settings.cancel-and-keep-my-account")]]]]]))
diff --git a/frontend/src/app/main/ui/settings/profile.cljs b/frontend/src/app/main/ui/settings/profile.cljs
index 2e2f0a23a..bcee7b8c1 100644
--- a/frontend/src/app/main/ui/settings/profile.cljs
+++ b/frontend/src/app/main/ui/settings/profile.cljs
@@ -21,8 +21,6 @@
    [app.main.ui.icons :as i]
    [app.main.ui.messages :as msgs]
    [app.main.ui.modal :as modal]
-   [app.main.ui.settings.change-email :refer [change-email-modal]]
-   [app.main.ui.settings.delete-account :refer [delete-account-modal]]
    [app.util.dom :as dom]
    [app.util.forms :as fm]
    [app.util.i18n :as i18n :refer [tr t]]))
@@ -71,7 +69,7 @@
      (cond
        (nil? (:pending-email prof))
        [:div.change-email
-        [:a {:on-click #(modal/show! change-email-modal {})}
+        [:a {:on-click #(modal/show! :change-email {})}
          (t locale "settings.change-email-label")]]
 
        (not= (:pending-email prof) (:email prof))
@@ -92,7 +90,7 @@
 
      [:div.links
       [:div.link-item
-       [:a {:on-click #(modal/show! delete-account-modal {})}
+       [:a {:on-click #(modal/show! :delete-account {})}
         (t locale "settings.remove-account-label")]]]]))
 
 ;; --- Profile Photo Form
diff --git a/frontend/src/app/main/ui/workspace/colorpicker.cljs b/frontend/src/app/main/ui/workspace/colorpicker.cljs
index 306ad4144..a331731f2 100644
--- a/frontend/src/app/main/ui/workspace/colorpicker.cljs
+++ b/frontend/src/app/main/ui/workspace/colorpicker.cljs
@@ -19,6 +19,7 @@
    [app.common.uuid :refer [uuid]]
    [app.main.data.workspace.libraries :as dwl]
    [app.main.data.colors :as dwc]
+   #_[app.main.ui.modal :as modal]
    [app.main.ui.modal :as modal]
    [okulary.core :as l]
    [app.main.refs :as refs]
@@ -105,6 +106,7 @@
         picking-color? (mf/deref refs/picking-color?)
         picked-color (mf/deref refs/picked-color)
         picked-color-select (mf/deref refs/picked-color-select)
+        picked-shift? (mf/deref refs/picked-shift?)
 
         locale    (mf/deref i18n/locale)
 
@@ -158,8 +160,10 @@
 
     ;; When closing the modal we update the recent-color list
     (mf/use-effect
-     (fn [] #(st/emit! (dwc/stop-picker)
-                       (dwl/add-recent-color @value-ref))))
+     (fn [] (fn []
+              (st/emit! (dwc/stop-picker))
+              (when @value-ref
+                  (st/emit! (dwl/add-recent-color @value-ref))))))
 
     (mf/use-effect
      (mf/deps picking-color? picked-color)
@@ -172,12 +176,12 @@
                        :h h :s s :v v
                        :hex hex)
                 (when picked-color-select
-                  (on-change hex (:alpha @current-color)))))))
+                  (on-change hex (:alpha @current-color) picked-shift?))))))
 
     (mf/use-effect
      (mf/deps picking-color? picked-color-select)
-     (fn [] (when picking-color?
-              (on-change (:hex @current-color) (:alpha @current-color)))))
+     (fn [] (when (and picking-color? picked-color-select)
+              (on-change (:hex @current-color) (:alpha @current-color) picked-shift?))))
 
     [:div.colorpicker {:ref ref-picker}
      [:div.top-actions
@@ -350,13 +354,24 @@
   )
 
 (mf/defc colorpicker-modal
+  {::mf/register modal/components
+   ::mf/register-as :colorpicker}
   [{:keys [x y default value opacity page on-change disable-opacity position on-accept] :as props}]
   (let [position (or position :left)
-        style (case position
-                :left {:left (str (- x 270) "px")
-                       :top (str (- y 50) "px")}
-                :right {:left (str (+ x 24) "px")
-                        :top (str (- y 50) "px")})]
+        style (cond
+                (or (nil? x) (nil? y))
+                {:left "auto"
+                 :right "16rem"
+                 :top "4rem"}
+
+                (= position :left)
+                {:left (str (- x 270) "px")
+                 :top (str (- y 50) "px")}
+
+                :else
+                {:left (str (+ x 24) "px")
+                 :top (str (- y 50) "px")})
+        ]
     [:div.colorpicker-tooltip
       {:style (clj->js style)}
       [:& colorpicker {:value (or value default)
diff --git a/frontend/src/app/main/ui/workspace/header.cljs b/frontend/src/app/main/ui/workspace/header.cljs
index 18ae1d448..0260e1413 100644
--- a/frontend/src/app/main/ui/workspace/header.cljs
+++ b/frontend/src/app/main/ui/workspace/header.cljs
@@ -18,7 +18,6 @@
    [app.main.store :as st]
    [app.main.ui.components.dropdown :refer [dropdown]]
    [app.main.ui.modal :as modal]
-   [app.main.ui.confirm :refer [confirm-dialog]]
    [app.main.ui.workspace.presence :as presence]
    [app.util.i18n :as i18n :refer [t]]
    [app.util.data :refer [classnames]]
@@ -63,7 +62,7 @@
 
         add-shared-fn #(st/emit! nil (dw/set-file-shared (:id file) true))
         on-add-shared
-        #(modal/show! confirm-dialog
+        #(modal/show! :confirm-dialog
                         {:message (t locale "dashboard.grid.add-shared-message" (:name file))
                          :hint (t locale "dashboard.grid.add-shared-hint")
                          :accept-text (t locale "dashboard.grid.add-shared-accept")
@@ -72,7 +71,7 @@
 
         remove-shared-fn #(st/emit! nil (dw/set-file-shared (:id file) false))
         on-remove-shared
-        #(modal/show! confirm-dialog
+        #(modal/show! :confirm-dialog
                         {:message (t locale "dashboard.grid.remove-shared-message" (:name file))
                          :hint (t locale "dashboard.grid.remove-shared-hint")
                          :accept-text (t locale "dashboard.grid.remove-shared-accept")
diff --git a/frontend/src/app/main/ui/workspace/libraries.cljs b/frontend/src/app/main/ui/workspace/libraries.cljs
index 626b4e296..e288651d6 100644
--- a/frontend/src/app/main/ui/workspace/libraries.cljs
+++ b/frontend/src/app/main/ui/workspace/libraries.cljs
@@ -118,6 +118,8 @@
 
 
 (mf/defc libraries-dialog
+  {::mf/register modal/components
+   ::mf/register-as :libraries-dialog}
   [{:keys [] :as ctx}]
   (let [selected-tab (mf/use-state :libraries)
 
diff --git a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
index f5eab6e80..74e87251a 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/assets.cljs
@@ -27,8 +27,6 @@
    [app.main.ui.keyboard :as kbd]
    [app.main.ui.modal :as modal]
    [app.main.ui.shapes.icon :as icon]
-   [app.main.ui.workspace.libraries :refer [libraries-dialog]]
-   [app.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
    [app.util.data :refer [matches-search]]
    [app.util.dom :as dom]
    [app.util.dom.dnd :as dnd]
@@ -170,7 +168,7 @@
 
         edit-color-clicked
         (fn [event]
-          (modal/show! colorpicker-modal
+          (modal/show! :colorpicker
                        {:x (.-clientX event)
                         :y (.-clientY event)
                         :on-accept edit-color
@@ -235,7 +233,7 @@
         (mf/use-callback
          (mf/deps file-id)
          (fn [event]
-           (modal/show! colorpicker-modal
+           (modal/show! :colorpicker
                         {:x (.-clientX event)
                          :y (.-clientY event)
                          :on-accept add-color
@@ -372,7 +370,7 @@
        [:div.tool-window-content
         [:div.assets-bar-title
          (t locale "workspace.assets.assets")
-         [:div.libraries-button {:on-click #(modal/show! libraries-dialog {})}
+         [:div.libraries-button {:on-click #(modal/show! :libraries-dialog {})}
           i/libraries
           (t locale "workspace.assets.libraries")]]
 
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs
index 8f74455d2..d5f62839d 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/rows/color_row.cljs
@@ -15,7 +15,6 @@
    [app.util.data :refer [classnames]]
    [app.util.i18n :as i18n :refer [tr]]
    [app.main.ui.modal :as modal]
-   [app.main.ui.workspace.colorpicker :refer [colorpicker-modal]]
    [app.common.data :as d]
    [app.main.refs :as refs]))
 
@@ -30,7 +29,7 @@
                  :value (:value color)
                  :opacity (:opacity color)
                  :disable-opacity disable-opacity}]
-      (modal/show! colorpicker-modal props))))
+      (modal/show! :colorpicker props))))
 
 (defn value-to-background [value]
   (if (= value :multiple) "transparent" value))
@@ -142,12 +141,5 @@
                             :on-click select-all
                             :on-change handle-opacity-change
                             :min "0"
-                            :max "100"}]])
-
-     #_[:input.slidebar {:type "range"
-                       :min "0"
-                       :max "100"
-                       :value (-> opacity opacity->string)
-                       :step "1"
-                       :on-change handle-opacity-change}]]))
+                            :max "100"}]])]))
 
diff --git a/frontend/src/app/main/ui/workspace/sidebar/options/stroke.cljs b/frontend/src/app/main/ui/workspace/sidebar/options/stroke.cljs
index 08fffd164..4f2029d8f 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/options/stroke.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/options/stroke.cljs
@@ -16,7 +16,6 @@
    [app.main.data.workspace.common :as dwc]
    [app.main.store :as st]
    [app.main.ui.icons :as i]
-   [app.main.ui.modal :as modal]
    [app.main.ui.workspace.sidebar.options.rows.color-row :refer [color-row]]
    [app.util.data :refer [classnames]]
    [app.util.dom :as dom]
diff --git a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs
index ddf0cd411..9015f4ec3 100644
--- a/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs
+++ b/frontend/src/app/main/ui/workspace/sidebar/sitemap.cljs
@@ -13,7 +13,6 @@
    [app.main.data.workspace :as dw]
    [app.main.refs :as refs]
    [app.main.store :as st]
-   [app.main.ui.confirm :refer [confirm-dialog]]
    [app.main.ui.hooks :as hooks]
    [app.main.ui.icons :as i]
    [app.main.ui.keyboard :as kbd]
@@ -34,7 +33,7 @@
         id          (:id page)
 
         delete-fn   (mf/use-callback (mf/deps id) #(st/emit! (dw/delete-page id)))
-        on-delete   (mf/use-callback (mf/deps id) #(modal/show! confirm-dialog {:on-accept delete-fn}))
+        on-delete   (mf/use-callback (mf/deps id) #(modal/show! :confirm-dialog {:on-accept delete-fn}))
         navigate-fn (mf/use-callback (mf/deps id) #(st/emit! (dw/go-to-page id)))
 
         on-double-click
diff --git a/frontend/src/app/main/ui/workspace/viewport.cljs b/frontend/src/app/main/ui/workspace/viewport.cljs
index 1e104eae3..16bec9417 100644
--- a/frontend/src/app/main/ui/workspace/viewport.cljs
+++ b/frontend/src/app/main/ui/workspace/viewport.cljs
@@ -450,14 +450,13 @@
         (fn [event]
           (dom/prevent-default event)
           (dom/stop-propagation event)
-          (st/emit! (dwc/pick-color-select true)))
+          (st/emit! (dwc/pick-color-select true (kbd/shift? event))))
 
         on-mouse-up-picker
         (fn [event]
           (dom/prevent-default event)
           (dom/stop-propagation event)
-          (st/emit! (dwc/pick-color-select false)
-                    (dwc/stop-picker))
+          (st/emit! (dwc/stop-picker))
           (modal/disallow-click-outside!))]
 
     (mf/use-layout-effect