mirror of
https://github.com/penpot/penpot.git
synced 2025-05-17 20:16:11 +02:00
🎉 Pixel picker
This commit is contained in:
parent
8aad43883f
commit
f8b3baef3f
18 changed files with 494 additions and 199 deletions
4
frontend/resources/images/cursors/picker.svg
Normal file
4
frontend/resources/images/cursors/picker.svg
Normal file
|
@ -0,0 +1,4 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="24px" height="24px">
|
||||
<path fill="#fff" d="M.607 13.076v2.401l2.224.025 7.86-7.405s-.05-.885-.253-1.087c-.202-.202-2.25-1.744-2.25-1.744z"/>
|
||||
<path fill="#000" d="M.343 15.974a.514.514 0 01-.317-.321c-.023-.07-.026-.23-.026-1.43 0-1.468-.001-1.445.09-1.586.02-.032 1.703-1.724 3.74-3.759a596.805 596.805 0 003.7-3.716c0-.009-.367-.384-.816-.833a29.9 29.9 0 01-.817-.833c0-.01.474-.49 1.054-1.07l1.053-1.053.948.946.947.947 1.417-1.413C12.366.806 12.765.418 12.856.357c.238-.161.52-.28.792-.334.17-.034.586-.03.76.008.801.173 1.41.794 1.57 1.603.03.15.03.569 0 .718a2.227 2.227 0 01-.334.793c-.061.09-.45.49-1.496 1.54L12.734 6.1l.947.948.947.947-1.053 1.054c-.58.58-1.061 1.054-1.07 1.054-.01 0-.384-.368-.833-.817-.45-.45-.824-.817-.834-.817-.009 0-1.68 1.666-3.716 3.701a493.093 493.093 0 01-3.759 3.74c-.14.091-.117.09-1.59.089-1.187 0-1.366-.004-1.43-.027zm6.024-4.633a592.723 592.723 0 003.663-3.68c0-.02-1.67-1.69-1.69-1.69-.01 0-1.666 1.648-3.68 3.663L.996 13.297v.834c0 .627.005.839.02.854.015.014.227.02.854.02h.833l3.664-3.664z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
|
@ -1743,19 +1743,19 @@
|
|||
}
|
||||
},
|
||||
"workspace.libraries.colors.file-library" : {
|
||||
"used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:277", "src/app/main/ui/workspace/colorpalette.cljs:149" ],
|
||||
"used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:314", "src/app/main/ui/workspace/colorpalette.cljs:149" ],
|
||||
"translations" : {
|
||||
"en" : "File library"
|
||||
}
|
||||
},
|
||||
"workspace.libraries.colors.recent-colors" : {
|
||||
"used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:276", "src/app/main/ui/workspace/colorpalette.cljs:159" ],
|
||||
"used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:313", "src/app/main/ui/workspace/colorpalette.cljs:159" ],
|
||||
"translations" : {
|
||||
"en" : "Recent colors"
|
||||
}
|
||||
},
|
||||
"workspace.libraries.colors.save-color" : {
|
||||
"used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:312" ],
|
||||
"used-in" : [ "src/app/main/ui/workspace/colorpicker.cljs:349" ],
|
||||
"translations" : {
|
||||
"en" : "Save color"
|
||||
}
|
||||
|
|
|
@ -8,11 +8,54 @@
|
|||
.colorpicker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 13rem;
|
||||
padding: 0.5rem;
|
||||
background-color: $color-white;
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
|
||||
& > * {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.top-actions {
|
||||
display: flex;
|
||||
margin-bottom: 0.25rem;
|
||||
|
||||
.picker-btn {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&.active,
|
||||
&:hover svg {
|
||||
fill: $color-primary;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.picker-detail-wrapper {
|
||||
position: relative;
|
||||
|
||||
.center-circle {
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
border: 2px solid $color-white;
|
||||
border-radius: 8px;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-7px, -7px);
|
||||
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.25));
|
||||
}
|
||||
}
|
||||
#picker-detail {
|
||||
border: 1px solid $color-gray-10;
|
||||
}
|
||||
|
||||
.handler {
|
||||
position: absolute;
|
||||
width: 12px;
|
||||
|
@ -25,7 +68,6 @@
|
|||
background-color: rgba(var(--hue));
|
||||
position: relative;
|
||||
height: 6.75rem;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
|
||||
.handler {
|
||||
|
@ -137,6 +179,7 @@
|
|||
border-top: 1px solid $color-gray-10;
|
||||
padding-top: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
width: 200px;
|
||||
|
||||
select {
|
||||
background-image: url(/images/icons/arrow-down.svg);
|
||||
|
@ -162,7 +205,8 @@
|
|||
grid-template-columns: repeat(8, 1fr);
|
||||
justify-content: space-between;
|
||||
margin-right: -8px;
|
||||
overflow: scroll;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
max-height: 5.5rem;
|
||||
}
|
||||
|
||||
|
|
|
@ -129,3 +129,31 @@
|
|||
(-> state
|
||||
(update :workspace-layout conj :colorpalette)
|
||||
(assoc-in [:workspace-local :selected-palette] selected)))))
|
||||
|
||||
(defn start-picker []
|
||||
(ptk/reify ::start-picker
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :picking-color?] true)))))
|
||||
|
||||
(defn stop-picker []
|
||||
(ptk/reify ::stop-picker
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :picking-color?] false)))))
|
||||
|
||||
(defn pick-color [rgba]
|
||||
(ptk/reify ::pick-color
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :picked-color] rgba)))))
|
||||
|
||||
(defn pick-color-select [value]
|
||||
(ptk/reify ::pick-color
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:workspace-local :picked-color-select] value)))))
|
||||
|
|
22
frontend/src/app/main/data/fetch.cljs
Normal file
22
frontend/src/app/main/data/fetch.cljs
Normal file
|
@ -0,0 +1,22 @@
|
|||
;; 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.data.fetch
|
||||
(:require
|
||||
[promesa.core :as p]
|
||||
[app.util.object :as obj]))
|
||||
|
||||
(defn fetch-as-data-uri [url]
|
||||
(-> (js/fetch url)
|
||||
(p/then (fn [res] (.blob res)))
|
||||
(p/then (fn [blob]
|
||||
(let [reader (js/FileReader.)]
|
||||
(p/create (fn [resolve reject]
|
||||
(obj/set! reader "onload" #(resolve [url (.-result reader)]))
|
||||
(.readAsDataURL reader blob))))))))
|
|
@ -102,7 +102,10 @@
|
|||
:right-sidebar? true
|
||||
:color-for-rename nil
|
||||
:selected-palette :recent
|
||||
:selected-palette-size :big})
|
||||
:selected-palette-size :big
|
||||
:picking-color? false
|
||||
:picked-color nil
|
||||
:picked-color-select false})
|
||||
|
||||
(def initialize-layout
|
||||
(ptk/reify ::initialize-layout
|
||||
|
|
|
@ -114,6 +114,11 @@
|
|||
(unchecked-set node "type" "text/css")
|
||||
node))
|
||||
|
||||
(defn gfont-url [family variants]
|
||||
(let [base (str "https://fonts.googleapis.com/css?family=" family)
|
||||
variants (str/join "," (map :id variants))]
|
||||
(str base ":" variants "&display=block")))
|
||||
|
||||
(defmulti ^:private load-font :backend)
|
||||
|
||||
(defmethod load-font :builtin
|
||||
|
@ -126,10 +131,7 @@
|
|||
[{:keys [id family variants ::on-loaded] :as font}]
|
||||
(when (exists? js/window)
|
||||
(js/console.log "[debug:fonts]: loading google font" id)
|
||||
(let [base (str "https://fonts.googleapis.com/css?family=" family)
|
||||
variants (str/join "," (map :id variants))
|
||||
uri (str base ":" variants "&display=block")
|
||||
node (create-link-node uri)]
|
||||
(let [node (create-link-node (gfont-url family variants))]
|
||||
(.addEventListener node "load" (fn [event] (when (fn? on-loaded)
|
||||
(on-loaded id))))
|
||||
(.append (.-head js/document) node)
|
||||
|
|
|
@ -80,6 +80,14 @@
|
|||
(def current-hover
|
||||
(l/derived :hover workspace-local))
|
||||
|
||||
(def picking-color?
|
||||
(l/derived :picking-color? workspace-local))
|
||||
|
||||
(def picked-color
|
||||
(l/derived :picked-color workspace-local))
|
||||
|
||||
(def picked-color-select
|
||||
(l/derived :picked-color-select workspace-local))
|
||||
|
||||
(def workspace-layout
|
||||
(l/derived :workspace-layout st/state))
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
(def resize-ns (cursor-fn :resize-h 90))
|
||||
(def rotate (cursor-fn :rotate 90))
|
||||
(def text (cursor-ref :text))
|
||||
(def picker (cursor-ref :picker 0 0 24))
|
||||
|
||||
(mf/defc debug-preview
|
||||
{::mf/wrap-props false}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
(:import goog.events.EventType))
|
||||
|
||||
(defonce state (atom nil))
|
||||
(defonce can-click-outside (atom false))
|
||||
|
||||
(defn show!
|
||||
[component props]
|
||||
|
@ -25,13 +26,12 @@
|
|||
(reset! state nil)
|
||||
(dom/stop-propagation event)))
|
||||
|
||||
(defn- on-parent-clicked
|
||||
[event parent-ref]
|
||||
(let [parent (mf/ref-val parent-ref)
|
||||
(defn- on-click
|
||||
[event wrapper-ref]
|
||||
(let [wrapper (mf/ref-val wrapper-ref)
|
||||
current (dom/get-target event)]
|
||||
;; (js/console.log current (.-className ^js current))
|
||||
(when (and (dom/equals? (.-firstElementChild ^js parent) current)
|
||||
(str/includes? (.-className ^js current) "modal-overlay"))
|
||||
|
||||
(when (and (not @can-click-outside) (not (.contains wrapper current)))
|
||||
(dom/stop-propagation event)
|
||||
(dom/prevent-default event)
|
||||
(reset! state nil))))
|
||||
|
@ -39,15 +39,19 @@
|
|||
(mf/defc modal-wrapper
|
||||
[{:keys [component props]}]
|
||||
|
||||
(let [wrapper-ref (mf/use-ref nil)]
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(let [key (events/listen js/document EventType.KEYDOWN on-esc-clicked)]
|
||||
#(events/unlistenByKey %))))
|
||||
#(events/unlistenByKey key))))
|
||||
|
||||
(mf/use-effect
|
||||
(fn []
|
||||
(let [key (events/listen js/document EventType.CLICK #(on-click % wrapper-ref))]
|
||||
#(events/unlistenByKey key))))
|
||||
|
||||
(let [ref (mf/use-ref nil)]
|
||||
[:div.modal-wrapper
|
||||
{:ref ref
|
||||
:on-click #(on-parent-clicked % ref)}
|
||||
{:ref wrapper-ref}
|
||||
[:& component props]]))
|
||||
|
||||
(mf/defc modal
|
||||
|
@ -58,4 +62,8 @@
|
|||
:key (random-uuid)}]))
|
||||
|
||||
|
||||
(defn allow-click-outside! []
|
||||
(reset! can-click-outside true))
|
||||
|
||||
(defn disallow-click-outside! []
|
||||
(reset! can-click-outside false))
|
||||
|
|
|
@ -13,23 +13,43 @@
|
|||
[app.config :as cfg]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.main.ui.shapes.attrs :as attrs]
|
||||
[app.util.object :as obj]))
|
||||
[app.util.object :as obj]
|
||||
[app.main.data.fetch :as df]
|
||||
[promesa.core :as p]))
|
||||
|
||||
(mf/defc image-shape
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
|
||||
(let [shape (unchecked-get props "shape")
|
||||
{:keys [id x y width height rotation metadata]} shape
|
||||
transform (geom/transform-matrix shape)
|
||||
uri (cfg/resolve-media-path (:path metadata))
|
||||
data-uri (mf/use-state nil)]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps shape)
|
||||
(fn []
|
||||
(-> (df/fetch-as-data-uri uri)
|
||||
(p/then #(reset! data-uri (second %))))))
|
||||
|
||||
(let [transform (geom/transform-matrix shape)
|
||||
props (-> (attrs/extract-style-attrs shape)
|
||||
(obj/merge!
|
||||
#js {:x x
|
||||
:y y
|
||||
:transform transform
|
||||
:id (str "shape-" id)
|
||||
:preserveAspectRatio "none"
|
||||
:xlinkHref uri
|
||||
:width width
|
||||
:height height}))]
|
||||
[:> "image" props]))
|
||||
:height height
|
||||
:preserveAspectRatio "none"}))]
|
||||
(if (nil? @data-uri)
|
||||
[:> "rect" (obj/merge!
|
||||
props
|
||||
#js {:fill "#E8E9EA"
|
||||
:stroke "#000000"})]
|
||||
[:> "image" (obj/merge!
|
||||
props
|
||||
#js {:xlinkHref @data-uri})]))
|
||||
|
||||
|
||||
))
|
||||
|
|
|
@ -6,12 +6,16 @@
|
|||
|
||||
(ns app.main.ui.shapes.text
|
||||
(:require
|
||||
[promesa.core :as p]
|
||||
[cuerdas.core :as str]
|
||||
[rumext.alpha :as mf]
|
||||
[app.main.data.fetch :as df]
|
||||
[app.common.data :as d]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.main.fonts :as fonts]
|
||||
[app.util.object :as obj]))
|
||||
[app.util.object :as obj]
|
||||
[clojure.set :as set]))
|
||||
|
||||
;; --- Text Editor Rendering
|
||||
|
||||
|
@ -55,7 +59,8 @@
|
|||
base #js {:textDecoration text-decoration
|
||||
:color fill
|
||||
:opacity opacity
|
||||
:textTransform text-transform}]
|
||||
:textTransform text-transform
|
||||
:lineHeight "inherit"}]
|
||||
|
||||
(when (and (string? letter-spacing)
|
||||
(pos? (alength letter-spacing)))
|
||||
|
@ -83,10 +88,42 @@
|
|||
|
||||
base))
|
||||
|
||||
(defn get-all-fonts [node]
|
||||
(let [current-font (if (not (nil? (:font-id node)))
|
||||
#{(:font-id node)}
|
||||
#{})
|
||||
children-font (map get-all-fonts (:children node))]
|
||||
(reduce set/union (conj children-font current-font))))
|
||||
|
||||
|
||||
(defn fetch-font [font-id]
|
||||
(let [{:keys [family variants]} (get @fonts/fontsdb font-id)]
|
||||
(-> (js/fetch (fonts/gfont-url family variants))
|
||||
(p/then (fn [res] (.text res))))))
|
||||
|
||||
(defn embed-font [font-id]
|
||||
(p/let [font-text (fetch-font font-id)
|
||||
url-to-data (->> font-text
|
||||
(re-seq #"url\(([^)]+)\)")
|
||||
(map second)
|
||||
(map df/fetch-as-data-uri)
|
||||
(p/all))]
|
||||
(reduce (fn [text [url data]] (str/replace text url data)) font-text url-to-data)))
|
||||
|
||||
(defn- render-text-node
|
||||
([node] (render-text-node 0 node))
|
||||
([index {:keys [type text children] :as node}]
|
||||
(mf/html
|
||||
(let [embeded-fonts (mf/use-state nil)]
|
||||
(mf/use-effect
|
||||
(mf/deps node)
|
||||
(fn []
|
||||
(when (= type "root")
|
||||
(let [font-to-embed (get-all-fonts node)
|
||||
embeded (map embed-font font-to-embed)]
|
||||
(-> (p/all embeded)
|
||||
(p/then (fn [result] (reset! embeded-fonts (str/join "\n" result)))))))))
|
||||
|
||||
(if (string? text)
|
||||
(let [style (generate-text-styles (clj->js node))]
|
||||
[:span {:style style :key index} text])
|
||||
|
@ -94,11 +131,15 @@
|
|||
(case type
|
||||
"root"
|
||||
(let [style (generate-root-styles (clj->js node))]
|
||||
|
||||
[:div.root.rich-text
|
||||
{:key index
|
||||
:style style
|
||||
:xmlns "http://www.w3.org/1999/xhtml"}
|
||||
children])
|
||||
[:*
|
||||
(when (not (nil? @embeded-fonts))
|
||||
[:style @embeded-fonts])
|
||||
children]])
|
||||
|
||||
"paragraph-set"
|
||||
(let [style #js {:display "inline-block"
|
||||
|
@ -109,7 +150,7 @@
|
|||
(let [style (generate-paragraph-styles (clj->js node))]
|
||||
[:p {:key index :style style} children])
|
||||
|
||||
nil))))))
|
||||
nil)))))))
|
||||
|
||||
(mf/defc text-content
|
||||
{::mf/wrap-props false
|
||||
|
|
|
@ -102,6 +102,10 @@
|
|||
shared-libs (mf/deref refs/workspace-libraries)
|
||||
recent-colors (mf/deref refs/workspace-recent-colors)
|
||||
|
||||
picking-color? (mf/deref refs/picking-color?)
|
||||
picked-color (mf/deref refs/picked-color)
|
||||
picked-color-select (mf/deref refs/picked-color-select)
|
||||
|
||||
locale (mf/deref i18n/locale)
|
||||
|
||||
value-ref (mf/use-var value)
|
||||
|
@ -154,9 +158,41 @@
|
|||
|
||||
;; When closing the modal we update the recent-color list
|
||||
(mf/use-effect
|
||||
(fn [] #(st/emit! (dwl/add-recent-color @value-ref))))
|
||||
(fn [] #(st/emit! (dwc/stop-picker)
|
||||
(dwl/add-recent-color @value-ref))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps picking-color? picked-color)
|
||||
(fn [] (when picking-color?
|
||||
(let [[r g b] picked-color
|
||||
hex (uc/rgb->hex [r g b])
|
||||
[h s v] (uc/hex->hsv hex)]
|
||||
(swap! current-color assoc
|
||||
:r r :g g :b b
|
||||
:h h :s s :v v
|
||||
:hex hex)
|
||||
(when picked-color-select
|
||||
(on-change hex (:alpha @current-color)))))))
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps picking-color? picked-color-select)
|
||||
(fn [] (when picking-color?
|
||||
(on-change (:hex @current-color) (:alpha @current-color)))))
|
||||
|
||||
[:div.colorpicker {:ref ref-picker}
|
||||
[:div.top-actions
|
||||
[:button.picker-btn
|
||||
{:class (when picking-color? "active")
|
||||
:on-click (fn []
|
||||
(modal/allow-click-outside!)
|
||||
(st/emit! (dwc/start-picker)))}
|
||||
i/picker]]
|
||||
|
||||
(if picking-color?
|
||||
[:div.picker-detail-wrapper
|
||||
[:div.center-circle]
|
||||
[:canvas#picker-detail {:width 200
|
||||
:height 160}]]
|
||||
[:& value-selector {:hue (:h @current-color)
|
||||
:saturation (:s @current-color)
|
||||
:value (:v @current-color)
|
||||
|
@ -168,7 +204,8 @@
|
|||
:r r :g g :b b
|
||||
:s s :v v)
|
||||
(reset! value-ref hex)
|
||||
(on-change hex (:alpha @current-color))))}]
|
||||
(on-change hex (:alpha @current-color))))}])
|
||||
(when (not picking-color?)
|
||||
[:div.shade-selector
|
||||
[:div.color-bullet]
|
||||
[:& hue-selector {:hue (:h @current-color)
|
||||
|
@ -184,7 +221,7 @@
|
|||
[:& opacity-selector {:opacity (:alpha @current-color)
|
||||
:on-change (fn [alpha]
|
||||
(swap! current-color assoc :alpha alpha)
|
||||
(on-change (:hex @current-color) alpha))}]]
|
||||
(on-change (:hex @current-color) alpha))}]])
|
||||
|
||||
[:div.color-values
|
||||
[:input.hex-value {:id "hex-value"
|
||||
|
@ -320,12 +357,11 @@
|
|||
:top (str (- y 50) "px")}
|
||||
:right {:left (str (+ x 24) "px")
|
||||
:top (str (- y 50) "px")})]
|
||||
[:div.modal-overlay.transparent
|
||||
[:div.colorpicker-tooltip
|
||||
{:style (clj->js style)}
|
||||
[:& colorpicker {:value (or value default)
|
||||
:opacity (or opacity 1)
|
||||
:on-change on-change
|
||||
:on-accept on-accept
|
||||
:disable-opacity disable-opacity}]]]))
|
||||
:disable-opacity disable-opacity}]]))
|
||||
|
||||
|
|
|
@ -33,9 +33,10 @@
|
|||
|
||||
(defn draw-rule!
|
||||
[dctx {:keys [zoom size start count type] :or {count 200}}]
|
||||
(when start
|
||||
(let [txfm (- (* (- 0 start) zoom) 20)
|
||||
minv (mth/round start)
|
||||
maxv (mth/round (+ start (/ size zoom)))
|
||||
minv (max (mth/round start) -10000)
|
||||
maxv (min (mth/round (+ start (/ size zoom))) 10000)
|
||||
step (calculate-step-size zoom)]
|
||||
|
||||
(if (= type :horizontal)
|
||||
|
@ -75,7 +76,7 @@
|
|||
(do
|
||||
(.moveTo path 17 pos)
|
||||
(.lineTo path 20 pos))))
|
||||
(recur (inc i))))))))
|
||||
(recur (inc i)))))))))
|
||||
|
||||
|
||||
(mf/defc horizontal-rule
|
||||
|
|
|
@ -119,7 +119,8 @@
|
|||
base #js {:textDecoration text-decoration
|
||||
:color fill
|
||||
:opacity opacity
|
||||
:textTransform text-transform}]
|
||||
:textTransform text-transform
|
||||
:lineHeight "inherit"}]
|
||||
|
||||
(when (and (string? letter-spacing)
|
||||
(pos? (alength letter-spacing)))
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
(if (= value :multiple) "transparent" value))
|
||||
|
||||
(defn remove-hash [value]
|
||||
(if (= value :multiple) "" (subs value 1)))
|
||||
(if (or (nil? value) (= value :multiple)) "" (subs value 1)))
|
||||
|
||||
(defn append-hash [value]
|
||||
(str "#" value))
|
||||
|
|
|
@ -15,12 +15,15 @@
|
|||
[goog.events :as events]
|
||||
[potok.core :as ptk]
|
||||
[rumext.alpha :as mf]
|
||||
[promesa.core :as p]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.cursors :as cur]
|
||||
[app.main.ui.modal :as modal]
|
||||
[app.common.data :as d]
|
||||
[app.main.constants :as c]
|
||||
[app.main.data.workspace :as dw]
|
||||
[app.main.data.workspace.drawing :as dd]
|
||||
[app.main.data.colors :as dwc]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.streams :as ms]
|
||||
|
@ -168,15 +171,20 @@
|
|||
edition
|
||||
tooltip
|
||||
selected
|
||||
panning]} local
|
||||
panning
|
||||
picking-color?]} local
|
||||
|
||||
file (mf/deref refs/workspace-file)
|
||||
viewport-ref (mf/use-ref nil)
|
||||
canvas-ref (mf/use-ref nil)
|
||||
zoom-view-ref (mf/use-ref nil)
|
||||
last-position (mf/use-var nil)
|
||||
drawing (mf/deref refs/workspace-drawing)
|
||||
drawing-tool (:tool drawing)
|
||||
drawing-obj (:object drawing)
|
||||
|
||||
pick-color (mf/use-state [255 255 255 255])
|
||||
|
||||
zoom (or zoom 1)
|
||||
|
||||
on-mouse-down
|
||||
|
@ -410,7 +418,47 @@
|
|||
prnt (dom/get-parent node)]
|
||||
(st/emit! (dw/update-viewport-size (dom/get-client-size prnt)))))
|
||||
|
||||
options (mf/deref refs/workspace-page-options)]
|
||||
options (mf/deref refs/workspace-page-options)
|
||||
|
||||
on-mouse-move-picker
|
||||
(fn [event]
|
||||
(when-let [zoom-view-node (.getElementById js/document "picker-detail")]
|
||||
(let [{brx :left bry :top} (dom/get-bounding-rect (mf/ref-val viewport-ref))
|
||||
x (- (.-clientX event) brx)
|
||||
y (- (.-clientY event) bry)
|
||||
|
||||
zoom-context (.getContext zoom-view-node "2d")
|
||||
canvas-node (mf/ref-val canvas-ref)
|
||||
canvas-context (.getContext canvas-node "2d")
|
||||
pixel-data (.getImageData canvas-context x y 1 1)
|
||||
rgba (.-data pixel-data)
|
||||
r (obj/get rgba 0)
|
||||
g (obj/get rgba 1)
|
||||
b (obj/get rgba 2)
|
||||
a (obj/get rgba 3)
|
||||
|
||||
area-data (.getImageData canvas-context (- x 25) (- y 20) 50 40)]
|
||||
|
||||
(-> (js/createImageBitmap area-data)
|
||||
(p/then (fn [image]
|
||||
;; Draw area
|
||||
(obj/set! zoom-context "imageSmoothingEnabled" false)
|
||||
(.drawImage zoom-context image 0 0 200 160))))
|
||||
(st/emit! (dwc/pick-color [r g b a])))))
|
||||
|
||||
on-mouse-down-picker
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dwc/pick-color-select true)))
|
||||
|
||||
on-mouse-up-picker
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(st/emit! (dwc/pick-color-select false)
|
||||
(dwc/stop-picker))
|
||||
(modal/disallow-click-outside!))]
|
||||
|
||||
(mf/use-layout-effect
|
||||
(fn []
|
||||
|
@ -434,6 +482,36 @@
|
|||
|
||||
(mf/use-layout-effect (mf/deps layout) on-resize)
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps props)
|
||||
(fn []
|
||||
(when picking-color?
|
||||
(try
|
||||
(let [svg-node (mf/ref-val viewport-ref)
|
||||
canvas-node (mf/ref-val canvas-ref)
|
||||
canvas-context (.getContext canvas-node "2d")
|
||||
xml (.serializeToString (js/XMLSerializer.) svg-node)
|
||||
content (str "data:image/svg+xml;base64," (js/btoa xml))
|
||||
img (js/Image.)]
|
||||
(obj/set! img "onload"
|
||||
(fn []
|
||||
(.drawImage canvas-context img 0 0)))
|
||||
(obj/set! img "src" content))
|
||||
(catch :default e (.error js/console e))))))
|
||||
|
||||
[:*
|
||||
|
||||
(when picking-color?
|
||||
[:canvas {:ref canvas-ref
|
||||
:width (:width vport 0)
|
||||
:height (:height vport 0)
|
||||
:on-mouse-down on-mouse-down-picker
|
||||
:on-mouse-up on-mouse-up-picker
|
||||
:on-mouse-move on-mouse-move-picker
|
||||
:style {:position "absolute"
|
||||
:top 0
|
||||
:left 0
|
||||
:cursor cur/picker}}])
|
||||
[:svg.viewport
|
||||
{:preserveAspectRatio "xMidYMid meet"
|
||||
:width (:width vport 0)
|
||||
|
@ -502,5 +580,5 @@
|
|||
[:& presence/active-cursors {:page-id page-id}]
|
||||
[:& selection-rect {:data (:selrect local)}]
|
||||
(when (= options-mode :prototype)
|
||||
[:& interactions {:selected selected}])]))
|
||||
[:& interactions {:selected selected}])]]))
|
||||
|
||||
|
|
|
@ -51,9 +51,7 @@
|
|||
(defn hex->hsl [hex]
|
||||
(try
|
||||
(into [] (gcolor/hexToHsl hex))
|
||||
(catch :default e (do
|
||||
(.log js/console e)
|
||||
[0 0 0]))))
|
||||
(catch :default e [0 0 0])))
|
||||
|
||||
(defn hsl->rgb
|
||||
[[h s l]]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue