mirror of
https://github.com/penpot/penpot.git
synced 2025-02-22 14:57:01 -05:00
🎉 Autocomplete color fields
This commit is contained in:
parent
9e39e53488
commit
c16434e608
10 changed files with 177 additions and 37 deletions
|
@ -5,7 +5,8 @@
|
|||
### :sparkles: New features
|
||||
|
||||
- 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)
|
||||
- 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)
|
||||
|
|
|
@ -498,7 +498,7 @@
|
|||
height: 20px;
|
||||
margin: 5px 0 0 0;
|
||||
padding: 0 $x-small;
|
||||
width: 64px;
|
||||
width: 84px;
|
||||
font-size: $fs13;
|
||||
|
||||
&:focus {
|
||||
|
|
105
frontend/src/app/main/ui/components/color_input.cljs
Normal file
105
frontend/src/app/main/ui/components/color_input.cljs
Normal 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])]]))
|
||||
|
|
@ -18,15 +18,18 @@
|
|||
(mf/defc numeric-input
|
||||
{::mf/wrap-props false
|
||||
::mf/forward-ref true}
|
||||
[props ref]
|
||||
[props external-ref]
|
||||
(let [value-str (obj/get props "value")
|
||||
min-val-str (obj/get props "min")
|
||||
max-val-str (obj/get props "max")
|
||||
wrap-value? (obj/get props "data-wrap")
|
||||
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 ref local-ref)
|
||||
ref (or external-ref local-ref)
|
||||
|
||||
value (d/parse-integer value-str)
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.context :as ctx]
|
||||
[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]]))
|
||||
|
||||
(defn color-picker-callback
|
||||
|
@ -38,13 +39,6 @@
|
|||
(handle-open)
|
||||
(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]
|
||||
(if (= opacity :multiple)
|
||||
""
|
||||
|
@ -92,13 +86,9 @@
|
|||
handle-close (fn [value opacity id file-id]
|
||||
(when on-close (on-close value opacity id file-id)))
|
||||
|
||||
handle-value-change (fn [event]
|
||||
(let [target (dom/get-target event)]
|
||||
(when (dom/valid? target)
|
||||
(-> target
|
||||
dom/get-value
|
||||
append-hash
|
||||
change-value))))
|
||||
handle-value-change (fn [new-value]
|
||||
(-> new-value
|
||||
change-value))
|
||||
|
||||
handle-opacity-change (fn [value]
|
||||
(change-opacity (/ value 100)))
|
||||
|
@ -155,13 +145,12 @@
|
|||
:else
|
||||
[:*
|
||||
[:div.color-info
|
||||
[:input {:value (if (uc/multiple? color)
|
||||
""
|
||||
(-> color :color remove-hash))
|
||||
:pattern "^[0-9a-fA-F]{0,6}$"
|
||||
:placeholder (tr "settings.multiple")
|
||||
:on-click select-all
|
||||
:on-change handle-value-change}]]
|
||||
[:> color-input {:value (if (uc/multiple? color)
|
||||
""
|
||||
(-> color :color uc/remove-hash))
|
||||
:placeholder (tr "settings.multiple")
|
||||
:on-click select-all
|
||||
:on-change handle-value-change}]]
|
||||
|
||||
(when (and (not disable-opacity)
|
||||
(not (:gradient color)))
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[app.common.math :as math]
|
||||
[app.util.object :as obj]
|
||||
[goog.color :as gcolor]))
|
||||
|
||||
(defn rgb->str
|
||||
|
@ -80,6 +81,34 @@
|
|||
[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]}]
|
||||
(let [parse-stop
|
||||
(fn [{:keys [offset color opacity]}]
|
||||
|
@ -122,5 +151,8 @@
|
|||
(let [result (gcolor/parse color-str)]
|
||||
(str (.-hex ^js result))))
|
||||
|
||||
(def color-names
|
||||
(obj/get-keys ^js gcolor/names))
|
||||
|
||||
(def empty-color
|
||||
(into {} (map #(vector % nil)) [:color :id :file-id :gradient :opacity]))
|
||||
|
|
|
@ -108,6 +108,15 @@
|
|||
[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!
|
||||
[node]
|
||||
(set! (.-value node) ""))
|
||||
|
|
|
@ -22,6 +22,10 @@
|
|||
(let [result (get obj k)]
|
||||
(if (undefined? result) default result))))
|
||||
|
||||
(defn get-keys
|
||||
[obj]
|
||||
(js/Object.keys ^js obj))
|
||||
|
||||
(defn get-in
|
||||
[obj keys]
|
||||
(loop [key (first keys)
|
||||
|
|
|
@ -443,6 +443,10 @@ msgstr "Something wrong has happened."
|
|||
msgid "errors.google-auth-not-enabled"
|
||||
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
|
||||
msgid "errors.ldap-disabled"
|
||||
msgstr "LDAP authentication is disabled."
|
||||
|
@ -2509,4 +2513,4 @@ msgid "workspace.updates.update"
|
|||
msgstr "Update"
|
||||
|
||||
msgid "workspace.viewport.click-to-close-path"
|
||||
msgstr "Click to close the path"
|
||||
msgstr "Click to close the path"
|
||||
|
|
|
@ -428,6 +428,10 @@ msgstr "Ha ocurrido algún error."
|
|||
msgid "errors.google-auth-not-enabled"
|
||||
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
|
||||
msgid "errors.ldap-disabled"
|
||||
msgstr "La autheticacion via LDAP esta deshabilitada."
|
||||
|
@ -2488,14 +2492,3 @@ msgstr "Actualizar"
|
|||
|
||||
msgid "workspace.viewport.click-to-close-path"
|
||||
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"
|
||||
|
|
Loading…
Add table
Reference in a new issue