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:
parent
9e39e53488
commit
c16434e608
10 changed files with 177 additions and 37 deletions
|
@ -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)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
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/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)
|
||||||
|
|
||||||
|
|
|
@ -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)))
|
||||||
|
|
|
@ -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]))
|
||||||
|
|
|
@ -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) ""))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue