0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-22 23:06:08 -05:00

🎉 Autocomplete color fields

This commit is contained in:
Andrés Moya 2021-04-21 17:20:38 +02:00 committed by Alonso Torres
parent 9e39e53488
commit c16434e608
10 changed files with 177 additions and 37 deletions

View file

@ -5,7 +5,8 @@
### :sparkles: New features ### :sparkles: New features
- Add integration with gitpod.io (an online IDE) [#807](https://github.com/penpot/penpot/pull/807) - Add integration with gitpod.io (an online IDE) [#807](https://github.com/penpot/penpot/pull/807)
- Allow basic math operations in inputs [1383](https://tree.taiga.io/project/penpot/us/1383) - Allow basic math operations in inputs [Taiga 1383](https://tree.taiga.io/project/penpot/us/1383)
- Autocomplete color names in hex inputs [Taiga 1596](https://tree.taiga.io/project/penpot/us/1596)
- Allow to group assets (components and graphics) [Taiga #1289](https://tree.taiga.io/project/penpot/us/1289) - Allow to group assets (components and graphics) [Taiga #1289](https://tree.taiga.io/project/penpot/us/1289)
- Internal: refactor of http client, replace internal xhr usage with more modern Fetch API. - Internal: refactor of http client, replace internal xhr usage with more modern Fetch API.
- New features for paths: snap points on edition, add/remove nodes, merge/join/split nodes. [Taiga #907](https://tree.taiga.io/project/penpot/us/907) - New features for paths: snap points on edition, add/remove nodes, merge/join/split nodes. [Taiga #907](https://tree.taiga.io/project/penpot/us/907)

View file

@ -498,7 +498,7 @@
height: 20px; height: 20px;
margin: 5px 0 0 0; margin: 5px 0 0 0;
padding: 0 $x-small; padding: 0 $x-small;
width: 64px; width: 84px;
font-size: $fs13; font-size: $fs13;
&:focus { &:focus {

View file

@ -0,0 +1,105 @@
;; 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) UXBOX Labs SL
(ns app.main.ui.components.color-input
(:require
[app.common.data :as d]
[app.common.math :as math]
[app.common.spec :as us]
[app.common.uuid :as uuid]
[app.util.color :as uc]
[app.util.dom :as dom]
[app.util.keyboard :as kbd]
[app.util.object :as obj]
[app.util.simple-math :as sm]
[app.util.i18n :as i18n :refer [tr]]
[rumext.alpha :as mf]))
(mf/defc color-input
{::mf/wrap-props false
::mf/forward-ref true}
[props external-ref]
(let [value (obj/get props "value")
on-change (obj/get props "onChange")
;; We need a ref pointing to the input dom element, but the user
;; of this component may provide one (that is forwarded here).
;; So we use the external ref if provided, and the local one if not.
local-ref (mf/use-ref)
ref (or external-ref local-ref)
parse-value
(mf/use-callback
(mf/deps ref)
(fn []
(let [input-node (mf/ref-val ref)]
(try
(let [new-value (-> (dom/get-value input-node)
(uc/expand-hex)
(uc/parse-color)
(uc/prepend-hash))]
(dom/set-validity! input-node "")
new-value)
(catch :default _
(dom/set-validity! input-node (tr "errors.invalid-color"))
nil)))))
update-input
(mf/use-callback
(mf/deps ref)
(fn [new-value]
(let [input-node (mf/ref-val ref)]
(dom/set-value! input-node (uc/remove-hash new-value)))))
apply-value
(mf/use-callback
(mf/deps on-change update-input)
(fn [new-value]
(when new-value
(when on-change
(on-change new-value))
(update-input new-value))))
handle-key-down
(mf/use-callback
(mf/deps apply-value update-input)
(fn [event]
(let [enter? (kbd/enter? event)
esc? (kbd/esc? event)]
(when enter?
(dom/prevent-default event)
(let [new-value (parse-value)]
(apply-value new-value)))
(when esc?
(dom/prevent-default event)
(update-input value)))))
handle-blur
(mf/use-callback
(mf/deps parse-value apply-value update-input)
(fn [event]
(let [new-value (parse-value)]
(if new-value
(apply-value new-value)
(update-input value)))))
list-id (str "colors-" (uuid/next))
props (-> props
(obj/without ["value" "onChange"])
(obj/set! "type" "text")
(obj/set! "ref" ref)
(obj/set! "list" list-id)
(obj/set! "defaultValue" value)
(obj/set! "onKeyDown" handle-key-down)
(obj/set! "onBlur" handle-blur))]
[:*
[:> :input props]
[:datalist {:id list-id}
(for [color-name uc/color-names]
[:option color-name])]]))

View file

@ -18,15 +18,18 @@
(mf/defc numeric-input (mf/defc numeric-input
{::mf/wrap-props false {::mf/wrap-props false
::mf/forward-ref true} ::mf/forward-ref true}
[props ref] [props external-ref]
(let [value-str (obj/get props "value") (let [value-str (obj/get props "value")
min-val-str (obj/get props "min") min-val-str (obj/get props "min")
max-val-str (obj/get props "max") max-val-str (obj/get props "max")
wrap-value? (obj/get props "data-wrap") wrap-value? (obj/get props "data-wrap")
on-change (obj/get props "onChange") on-change (obj/get props "onChange")
;; We need a ref pointing to the input dom element, but the user
;; of this component may provide one (that is forwarded here).
;; So we use the external ref if provided, and the local one if not.
local-ref (mf/use-ref) local-ref (mf/use-ref)
ref (or ref local-ref) ref (or external-ref local-ref)
value (d/parse-integer value-str) value (d/parse-integer value-str)

View file

@ -21,6 +21,7 @@
[app.main.ui.icons :as i] [app.main.ui.icons :as i]
[app.main.ui.context :as ctx] [app.main.ui.context :as ctx]
[app.main.ui.components.color-bullet :as cb] [app.main.ui.components.color-bullet :as cb]
[app.main.ui.components.color-input :refer [color-input]]
[app.main.ui.components.numeric-input :refer [numeric-input]])) [app.main.ui.components.numeric-input :refer [numeric-input]]))
(defn color-picker-callback (defn color-picker-callback
@ -38,13 +39,6 @@
(handle-open) (handle-open)
(modal/show! :colorpicker props)))) (modal/show! :colorpicker props))))
(defn remove-hash [value]
(if (or (nil? value) (= value :multiple)) "" (subs value 1)))
(defn append-hash [value]
(str "#" value))
(defn opacity->string [opacity] (defn opacity->string [opacity]
(if (= opacity :multiple) (if (= opacity :multiple)
"" ""
@ -92,13 +86,9 @@
handle-close (fn [value opacity id file-id] handle-close (fn [value opacity id file-id]
(when on-close (on-close value opacity id file-id))) (when on-close (on-close value opacity id file-id)))
handle-value-change (fn [event] handle-value-change (fn [new-value]
(let [target (dom/get-target event)] (-> new-value
(when (dom/valid? target) change-value))
(-> target
dom/get-value
append-hash
change-value))))
handle-opacity-change (fn [value] handle-opacity-change (fn [value]
(change-opacity (/ value 100))) (change-opacity (/ value 100)))
@ -155,13 +145,12 @@
:else :else
[:* [:*
[:div.color-info [:div.color-info
[:input {:value (if (uc/multiple? color) [:> color-input {:value (if (uc/multiple? color)
"" ""
(-> color :color remove-hash)) (-> color :color uc/remove-hash))
:pattern "^[0-9a-fA-F]{0,6}$" :placeholder (tr "settings.multiple")
:placeholder (tr "settings.multiple") :on-click select-all
:on-click select-all :on-change handle-value-change}]]
:on-change handle-value-change}]]
(when (and (not disable-opacity) (when (and (not disable-opacity)
(not (:gradient color))) (not (:gradient color)))

View file

@ -9,6 +9,7 @@
(:require (:require
[cuerdas.core :as str] [cuerdas.core :as str]
[app.common.math :as math] [app.common.math :as math]
[app.util.object :as obj]
[goog.color :as gcolor])) [goog.color :as gcolor]))
(defn rgb->str (defn rgb->str
@ -80,6 +81,34 @@
[hsv] [hsv]
(hex->hsl (hsv->hex hsv))) (hex->hsl (hsv->hex hsv)))
(defn expand-hex
[v]
(cond
(re-matches #"^[0-9A-Fa-f]$" v)
(str v v v v v v)
(re-matches #"^[0-9A-Fa-f]{2}$" v)
(str v v v)
(re-matches #"^[0-9A-Fa-f]{3}$" v)
(let [a (nth v 0)
b (nth v 1)
c (nth v 2)]
(str a a b b c c))
:default
v))
(defn prepend-hash
[color]
(gcolor/prependHashIfNecessaryHelper color))
(defn remove-hash
[color]
(if (str/starts-with? color "#")
(subs color 1)
color))
(defn gradient->css [{:keys [type stops]}] (defn gradient->css [{:keys [type stops]}]
(let [parse-stop (let [parse-stop
(fn [{:keys [offset color opacity]}] (fn [{:keys [offset color opacity]}]
@ -122,5 +151,8 @@
(let [result (gcolor/parse color-str)] (let [result (gcolor/parse color-str)]
(str (.-hex ^js result)))) (str (.-hex ^js result))))
(def color-names
(obj/get-keys ^js gcolor/names))
(def empty-color (def empty-color
(into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity])) (into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity]))

View file

@ -108,6 +108,15 @@
[node] [node]
(.-valid (.-validity node))) (.-valid (.-validity node)))
(defn set-validity!
"Manually set the validity status of a node that
is a form input. If the state is an empty string,
the input will be valid. If not, the string will
be set as the error message."
[node status]
(.setCustomValidity node status)
(.reportValidity node))
(defn clean-value! (defn clean-value!
[node] [node]
(set! (.-value node) "")) (set! (.-value node) ""))

View file

@ -22,6 +22,10 @@
(let [result (get obj k)] (let [result (get obj k)]
(if (undefined? result) default result)))) (if (undefined? result) default result))))
(defn get-keys
[obj]
(js/Object.keys ^js obj))
(defn get-in (defn get-in
[obj keys] [obj keys]
(loop [key (first keys) (loop [key (first keys)

View file

@ -443,6 +443,10 @@ msgstr "Something wrong has happened."
msgid "errors.google-auth-not-enabled" msgid "errors.google-auth-not-enabled"
msgstr "Authentication with google disabled on backend" msgstr "Authentication with google disabled on backend"
#: src/app/main/ui/components/color_input.cljs
msgid "errors.invalid-color"
msgstr "Invalid color"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "errors.ldap-disabled" msgid "errors.ldap-disabled"
msgstr "LDAP authentication is disabled." msgstr "LDAP authentication is disabled."
@ -2509,4 +2513,4 @@ msgid "workspace.updates.update"
msgstr "Update" msgstr "Update"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Click to close the path" msgstr "Click to close the path"

View file

@ -428,6 +428,10 @@ msgstr "Ha ocurrido algún error."
msgid "errors.google-auth-not-enabled" msgid "errors.google-auth-not-enabled"
msgstr "Autenticación con google esta dehabilitada en el servidor" msgstr "Autenticación con google esta dehabilitada en el servidor"
#: src/app/main/ui/components/color_input.cljs
msgid "errors.invalid-color"
msgstr "Color inválido"
#: src/app/main/ui/auth/login.cljs #: src/app/main/ui/auth/login.cljs
msgid "errors.ldap-disabled" msgid "errors.ldap-disabled"
msgstr "La autheticacion via LDAP esta deshabilitada." msgstr "La autheticacion via LDAP esta deshabilitada."
@ -2488,14 +2492,3 @@ msgstr "Actualizar"
msgid "workspace.viewport.click-to-close-path" msgid "workspace.viewport.click-to-close-path"
msgstr "Pulsar para cerrar la ruta" msgstr "Pulsar para cerrar la ruta"
#: src/app/main/ui/auth/register.cljs
#, fuzzy
msgid "auth.check-your-email"
msgstr ""
"Comprueba tu email y haz click en el link de verificación para comenzar a "
"usar Penpot."
#: src/app/main/ui/auth/register.cljs
msgid "auth.verification-email-sent"
msgstr "Hemos enviado un email de verificación a"