♻️ Extract token parsing

This commit is contained in:
Eva Marco
2026-02-19 09:58:29 +01:00
parent 192ba3bbd0
commit 25f24f0e8e
3 changed files with 123 additions and 107 deletions

View File

@@ -14,13 +14,13 @@
[app.main.ui.context :as muc]
[app.main.ui.ds.buttons.icon-button :refer [icon-button*]]
[app.main.ui.ds.controls.input :as ds]
[app.main.ui.ds.controls.select :refer [get-option]]
[app.main.ui.ds.controls.shared.options-dropdown :refer [options-dropdown*]]
[app.main.ui.ds.controls.utilities.utils :as csu]
[app.main.ui.ds.foundations.assets.icon :as i]
[app.main.ui.forms :as fc]
[app.main.ui.workspace.tokens.management.forms.controls.floating :refer [use-floating-dropdown]]
[app.main.ui.workspace.tokens.management.forms.controls.navigation :refer [use-navigation]]
[app.main.ui.workspace.tokens.management.forms.controls.token-parsing :as tp]
[app.util.dom :as dom]
[app.util.forms :as fm]
[app.util.i18n :refer [tr]]
@@ -29,61 +29,6 @@
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn extract-partial-token
[value cursor]
(let [text-before (subs value 0 cursor)
last-open (str/last-index-of text-before "{")
last-close (str/last-index-of text-before "}")]
(when (and last-open (or (nil? last-close) (> last-open last-close)))
{:start last-open
:partial (subs text-before (inc last-open))})))
(defn replace-active-token
[value cursor new-name]
(let [before (subs value 0 cursor)
last-open (str/last-index-of before "{")
last-close (str/last-index-of before "}")]
(if (and last-open
(or (nil? last-close)
(> last-open last-close)))
(let [after-start (subs value last-open)
close-pos (str/index-of after-start "}")
end (if close-pos
(+ last-open close-pos 1)
cursor)]
(str (subs value 0 last-open)
"{" new-name "}"
(subs value end)))
(str (subs value 0 cursor)
"{" new-name "}"
(subs value cursor)))))
(defn active-token [value input-node]
(let [cursor (.-selectionStart input-node)]
(extract-partial-token value cursor)))
(defn remove-self-token [filtered-options current-token]
(let [group (:type current-token)
current-id (:id current-token)
filtered-options (deref filtered-options)]
(update filtered-options group
(fn [options]
(remove #(= (:id %) current-id) options)))))
(defn- select-option-by-id
[id options-ref input-node value]
(let [cursor (.-selectionStart input-node)
options (mf/ref-val options-ref)
options (if (delay? options) @options options)
option (get-option options id)
name (:name option)
final-val (replace-active-token value cursor name)]
final-val))
(defn- resolve-value
[tokens prev-token token-name value]
(let [valid-token-name?
@@ -150,7 +95,7 @@
visible-options
(mf/with-memo [filtered-tokens-by-type token]
(if token
(remove-self-token filtered-tokens-by-type token)
(tp/remove-self-token filtered-tokens-by-type token)
filtered-tokens-by-type))
dropdown-options
@@ -168,12 +113,12 @@
toggle-dropdown
(mf/use-fn
(mf/deps)
(mf/deps is-open)
(fn [event]
(dom/prevent-default event)
(swap! is-open* not)
(let [input-node (mf/ref-val ref)]
(dom/focus! input-node))
(swap! is-open* not)))
(dom/focus! input-node))))
resolve-stream
(mf/with-memo [token]
@@ -186,7 +131,7 @@
(mf/deps value resolve-stream name)
(fn [id]
(let [input-node (mf/ref-val ref)
final-val (select-option-by-id id options-ref input-node value)]
final-val (tp/select-option-by-id id options-ref input-node value)]
(fm/on-input-change form name final-val true)
(rx/push! resolve-stream final-val)
(reset! filter-term* "")
@@ -207,7 +152,7 @@
(fn [event]
(let [node (dom/get-target event)
value (dom/get-input-value node)
token (active-token value node)]
token (tp/active-token value node)]
(fm/on-input-change form name value)
(rx/push! resolve-stream value)
@@ -227,7 +172,7 @@
(let [input-node (mf/ref-val ref)
node (dom/get-current-target event)
id (dom/get-data node "id")
final-val (select-option-by-id id options-ref input-node value)]
final-val (tp/select-option-by-id id options-ref input-node value)]
(fm/on-input-change form name final-val true)
(rx/push! resolve-stream final-val)
@@ -278,7 +223,7 @@
props)
floating (use-floating-dropdown is-open wrapper-ref dropdown-ref)]
{:keys [style ready?]} (use-floating-dropdown is-open wrapper-ref dropdown-ref)]
(mf/with-effect [resolve-stream tokens token name token-name]
(let [subs (->> resolve-stream
@@ -304,41 +249,42 @@
(mf/with-effect [dropdown-options]
(mf/set-ref-val! options-ref dropdown-options))
(mf/with-effect [is-open* ref nodes-ref]
(mf/with-effect [is-open* ref wrapper-ref]
(when is-open
(let [handler (fn [event]
(let [input-node (mf/ref-val ref)
(let [wrapper-node (mf/ref-val wrapper-ref)
dropdown-node (mf/ref-val dropdown-ref)
target (dom/get-target event)]
(when (and input-node dropdown-node
(not (dom/child? target input-node))
(when (and wrapper-node dropdown-node
(not (dom/child? target wrapper-node))
(not (dom/child? target dropdown-node)))
(reset! is-open* false))))]
(.addEventListener js/document "mousedown" handler)
(fn []
(.removeEventListener js/document "mousedown" handler)))))
(.removeEventListener js/document "mousedown" handler)))))
[:div {:ref wrapper-ref}
[:> ds/input* props]
(when ^boolean is-open
(let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)]
(mf/portal
(mf/html
[:> options-dropdown* {:on-click on-option-click
:class (stl/css :dropdown)
:style {:visibility (if (:ready? floating) "visible" "hidden")
:left (get-in floating [:style :left])
:top (get-in floating [:style :top])
:width (get-in floating [:style :width])}
:id listbox-id
:options options
:focused focused-id
:selected nil
:align :right
:empty-to-end empty-to-end
:wrapper-ref dropdown-ref
:ref set-option-ref}])
(dom/get-body))))]))
[:div {:ref wrapper-ref}
[:> ds/input* props]
(when ^boolean is-open
(let [options (if (delay? dropdown-options) @dropdown-options dropdown-options)]
(mf/portal
(mf/html
[:> options-dropdown* {:on-click on-option-click
:class (stl/css :dropdown)
:style {:visibility (if ready? "visible" "hidden")
:left (:left style)
:top (or (:top style) "unset")
:bottom (or (:bottom style) "unset")
:width (:width style)}
:id listbox-id
:options options
:focused focused-id
:selected nil
:align :right
:empty-to-end empty-to-end
:wrapper-ref dropdown-ref
:ref set-option-ref}])
(dom/get-body))))]))

View File

@@ -10,8 +10,10 @@
[rumext.v2 :as mf]))
(defn use-floating-dropdown [is-open wrapper-ref dropdown-ref]
(let [pos* (mf/use-state nil)
ready* (mf/use-state false)
(let [position* (mf/use-state nil)
position (deref position*)
ready* (mf/use-state false)
ready (deref ready*)
calculate-position
(fn [node]
(let [combobox-rect (dom/get-bounding-rect node)
@@ -24,22 +26,23 @@
windows-height (-> (dom/get-window-size)
(:height))
space-below (- windows-height (:bottom combobox-rect))
space-above (:top combobox-rect)
space-below (- windows-height (:bottom combobox-rect))
place-top? (< space-below dropdown-height)
open-up? (and dropdown-height
(> dropdown-height space-below))
position (if place-top?
{:top (str (- space-above dropdown-height -14) "px")
:left (str (:left combobox-rect) "px")
:width (str (:width combobox-rect) "px")
:placement :top}
{:top (str (+ (:bottom combobox-rect) 4) "px")
:left (str (:left combobox-rect) "px")
:width (str (:width combobox-rect) "px")
:placement :bottom})]
position (if open-up?
{:bottom (str (- windows-height (:top combobox-rect) 12) "px")
:left (str (:left combobox-rect) "px")
:width (str (:width combobox-rect) "px")
:placement :top}
{:top (str (+ (:bottom combobox-rect) 4) "px")
:left (str (:left combobox-rect) "px")
:width (str (:width combobox-rect) "px")
:placement :bottom})]
(reset! ready* true)
(reset! pos* position)))]
(reset! position* position)))]
(mf/with-effect [is-open dropdown-ref wrapper-ref]
(when is-open
@@ -63,5 +66,6 @@
(.removeEventListener js/window "resize" handler)
(.removeEventListener js/window "scroll" handler true)))))
{:style @pos*
:ready? @ready*}))
{:style position
:ready? ready
:recalculate calculate-position}))

View File

@@ -0,0 +1,66 @@
;; 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) KALEIDOS INC
(ns app.main.ui.workspace.tokens.management.forms.controls.token-parsing
(:require
[app.main.ui.ds.controls.select :refer [get-option]]
[cuerdas.core :as str]
[rumext.v2 :as mf]))
(defn extract-partial-token
[value cursor]
(let [text-before (subs value 0 cursor)
last-open (str/last-index-of text-before "{")
last-close (str/last-index-of text-before "}")]
(when (and last-open (or (nil? last-close) (> last-open last-close)))
{:start last-open
:partial (subs text-before (inc last-open))})))
(defn replace-active-token
[value cursor new-name]
(let [before (subs value 0 cursor)
last-open (str/last-index-of before "{")
last-close (str/last-index-of before "}")]
(if (and last-open
(or (nil? last-close)
(> last-open last-close)))
(let [after-start (subs value last-open)
close-pos (str/index-of after-start "}")
end (if close-pos
(+ last-open close-pos 1)
cursor)]
(str (subs value 0 last-open)
"{" new-name "}"
(subs value end)))
(str (subs value 0 cursor)
"{" new-name "}"
(subs value cursor)))))
(defn active-token [value input-node]
(let [cursor (.-selectionStart input-node)]
(extract-partial-token value cursor)))
(defn remove-self-token [filtered-options current-token]
(let [group (:type current-token)
current-id (:id current-token)
filtered-options (deref filtered-options)]
(update filtered-options group
(fn [options]
(remove #(= (:id %) current-id) options)))))
(defn select-option-by-id
[id options-ref input-node value]
(let [cursor (.-selectionStart input-node)
options (mf/ref-val options-ref)
options (if (delay? options) @options options)
option (get-option options id)
name (:name option)
final-val (replace-active-token value cursor name)]
final-val))