Skip to content

Commit

Permalink
fix(selection): Selection "freeze" (#1273)
Browse files Browse the repository at this point in the history
* Selected items is a set.

* Fixed selection logic with set theory.

![Science For The Win](https://media.giphy.com/media/VVgRNcBKp64NO/giphy.gif)

* `cljstyle`
  • Loading branch information
neotyk authored Jun 2, 2021
1 parent 1b78701 commit b66bfa8
Show file tree
Hide file tree
Showing 6 changed files with 127 additions and 75 deletions.
2 changes: 1 addition & 1 deletion src/cljs/athens/db.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
:right-sidebar/width 32
:mouse-down false
:daily-notes/items []
:selected/items []
:selected/items #{}
:theme/dark false
:graph-conf default-graph-conf})

Expand Down
15 changes: 10 additions & 5 deletions src/cljs/athens/events.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -300,26 +300,31 @@
(reg-event-db
:selected/add-item
(fn [db [_ uid]]
(update db :selected/items conj uid)))
(update db :selected/items (fnil conj #{}) uid)))


(reg-event-db
:selected/remove-item
(fn [db [_ uid]]
(let [items (:selected/items db)]
(assoc db :selected/items (filterv #(not= % uid) items)))))
(update db :selected/items disj uid)))


(reg-event-db
:selected/remove-items
(fn [db [_ uids]]
(update db :selected/items #(apply disj %1 %2) uids)))


(reg-event-db
:selected/add-items
(fn [db [_ uids]]
(update db :selected/items concat uids)))
(update db :selected/items #(apply conj %1 %2) uids)))


(reg-event-db
:selected/clear-items
(fn [db _]
(assoc db :selected/items [])))
(assoc db :selected/items #{})))


(defn select-up
Expand Down
12 changes: 12 additions & 0 deletions src/cljs/athens/util.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,18 @@
uid))


(defn get-dataset-children-uids
[el]
(let [block (when el (.. el (closest ".block-container")))
children-uids (when block
(let [dom-children-uids ^String (.-childrenuids (.-dataset block))]
(when-not (string/blank? dom-children-uids)
(-> dom-children-uids
(string/split #",")
set))))]
children-uids))


(defn get-caret-position
[target]
(let [selectionEnd (.. target -selectionEnd)]
Expand Down
109 changes: 68 additions & 41 deletions src/cljs/athens/views/blocks/content.cljs
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
(ns athens.views.blocks.content
(:require
[athens.db :as db]
[athens.electron :as electron]
[athens.events :as events]
[athens.parse-renderer :refer [parse-and-render]]
[athens.style :as style]
[athens.util :as util]
[athens.config :as config]
[athens.db :as db]
[athens.electron :as electron]
[athens.parse-renderer :refer [parse-and-render]]
[athens.style :as style]
[athens.util :as util]
[athens.views.blocks.textarea-keydown :as textarea-keydown]
[garden.selectors :as selectors]
[goog.events :as goog-events]
[komponentit.autosize :as autosize]
[re-frame.core :as rf]
[stylefy.core :as stylefy])
[clojure.set :as set]
[garden.selectors :as selectors]
[goog.events :as goog-events]
[komponentit.autosize :as autosize]
[re-frame.core :as rf]
[stylefy.core :as stylefy])
(:import
(goog.events
EventType)))
Expand Down Expand Up @@ -187,33 +188,54 @@
• 3
Because of this bug, add additional exit cases to prevent stack overflow."
[e source-uid target-uid]
(let [target (.. e -target)
page (or (.. target (closest ".node-page")) (.. target (closest ".block-page")))
blocks (->> (.. page (querySelectorAll ".block-container"))
array-seq
vec)
uids (map util/get-dataset-uid blocks)
start-idx (first (keep-indexed (fn [i uid] (when (= uid source-uid) i)) uids))
end-idx (first (keep-indexed (fn [i uid] (when (= uid target-uid) i)) uids))]
(when (and start-idx end-idx)
(let [up? (> start-idx end-idx)
delta (js/Math.abs (- start-idx end-idx))
select-fn (if up? events/select-up events/select-down)
start-uid (nth uids start-idx)
end-uid (nth uids end-idx)
new-items (loop [new-items [source-uid]
prev-items []]
(cond
(= prev-items new-items) new-items
(> (count new-items) delta) new-items
(nil? new-items) []
(or (and (= (first new-items) start-uid)
(= (last new-items) end-uid))
(and (= (last new-items) start-uid)
(= (first new-items) end-uid))) new-items
:else (recur (select-fn new-items)
new-items)))]
(rf/dispatch [:selected/add-items new-items])))))
(let [target (.. e -target)
page (or (.. target (closest ".node-page"))
(.. target (closest ".block-page")))
blocks (->> (.. page (querySelectorAll ".block-container"))
array-seq
vec)
uids (map util/get-dataset-uid blocks)
uids->children-uids (->> (zipmap uids
(map util/get-dataset-children-uids blocks))
(remove #(-> % second empty?))
(into {}))
indexed-uids (map-indexed vector uids)
start-index (->> indexed-uids
(filter (fn [[_idx uid]]
(= source-uid uid)))
ffirst)
end-index (->> indexed-uids
(filter (fn [[_idx uid]]
(= target-uid uid)))
ffirst)
selected-uids @(rf/subscribe [:selected/items])
candidate-uids (->> indexed-uids
(filter (fn [[idx _uid]]
(<= (min start-index end-index)
idx
(max start-index end-index))))
(map second)
(into (or selected-uids #{})))
descendants-uids (loop [descendants #{}
ancestors-uids candidate-uids]
(if (seq ancestors-uids)
(let [ancestors-children (->> ancestors-uids
(mapcat #(get uids->children-uids %))
(into #{}))]
(recur (apply conj descendants ancestors-children)
ancestors-children))
descendants))
to-remove-uids (set/intersection selected-uids descendants-uids)
selection-new-uids (set/difference candidate-uids descendants-uids)]
(when config/debug?
(js/console.debug (str "selection: " (pr-str selected-uids)
", candidates: " (pr-str candidate-uids)
", descendants: " (pr-str descendants-uids)
", rm: " (pr-str to-remove-uids)
", add: " (pr-str selection-new-uids))))
(when (and start-index end-index)
(rf/dispatch [:selected/remove-items to-remove-uids])
(rf/dispatch [:selected/add-items selection-new-uids]))))


;; Event Handlers
Expand Down Expand Up @@ -279,9 +301,14 @@
"If shift key is held when user clicks across multiple blocks, select the blocks."
[e target-uid _state]
(let [[target-uid _] (db/uid-and-embed-id target-uid)
source-uid @(rf/subscribe [:editing/uid])]
(when (and source-uid target-uid (not= source-uid target-uid) (.. e -shiftKey))
(find-selected-items e source-uid target-uid))))
source-uid @(rf/subscribe [:editing/uid])
shift? (.-shiftKey e)]
(if (and shift?
source-uid
target-uid
(not= source-uid target-uid))
(find-selected-items e source-uid target-uid)
(rf/dispatch [:selected/clear-items]))))


(defn global-mouseup
Expand Down
58 changes: 33 additions & 25 deletions src/cljs/athens/views/blocks/core.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,8 @@
(let [{:keys [linked-ref initial-open linked-ref-uid parent-uids]} linked-ref-data
state (r/atom {:string/local nil
:string/previous nil
:search/type nil ; one of #{:page :block :slash :hashtag}
;; one of #{:page :block :slash :hashtag}
:search/type nil
:search/results nil
:search/query nil
:search/index nil
Expand All @@ -208,19 +209,24 @@
:context-menu/y nil
:context-menu/show false
:caret-position nil
:show-editable-dom false
:linked-ref/open (or (false? linked-ref) initial-open)})]
:show-editable-dom false
:linked-ref/open (or (false? linked-ref) initial-open)})]

(fn [block linked-ref-data opts]
(let [{:block/keys [uid string open children _refs]} block
uid-sanitized-block (s/transform
(specter-recursive-path #(contains? % :block/uid))
(fn [{:block/keys [original-uid uid] :as block}]
(assoc block :block/uid (or original-uid uid)))
block)
{:search/keys [] :keys [dragging]} @state
is-editing @(rf/subscribe [:editing/is-editing uid])
is-selected @(rf/subscribe [:selected/is-selected uid])]
(let [{:block/keys [uid
string
open
children
_refs]} block
children-uids (set (map :block/uid children))
uid-sanitized-block (s/transform
(specter-recursive-path #(contains? % :block/uid))
(fn [{:block/keys [original-uid uid] :as block}]
(assoc block :block/uid (or original-uid uid)))
block)
{:keys [dragging]} @state
is-editing @(rf/subscribe [:editing/is-editing uid])
is-selected @(rf/subscribe [:selected/is-selected uid])]

;; (prn uid is-selected)

Expand All @@ -231,23 +237,25 @@
(swap! state assoc :string/previous string :string/local string))

[:div
{:class ["block-container"
(when (and dragging (not is-selected)) "dragging")
(when is-editing "is-editing")
(when is-selected "is-selected")
(when (and (seq children) open) "show-tree-indicator")
(when (and (false? initial-open) (= uid linked-ref-uid)) "is-linked-ref")]
:data-uid uid
{:class ["block-container"
(when (and dragging (not is-selected)) "dragging")
(when is-editing "is-editing")
(when is-selected "is-selected")
(when (and (seq children) open) "show-tree-indicator")
(when (and (false? initial-open) (= uid linked-ref-uid)) "is-linked-ref")]
:data-uid uid
;; need to know children for selection resolution
:data-childrenuids children-uids
;; :show-editable-dom allows us to render the editing elements (like the textarea)
;; even when not editing this block. When true, clicking the block content will pass
;; the clicks down to the underlying textarea. The textarea is expensive to render,
;; so we avoid rendering it when it's not needed.
:on-mouse-enter #(swap! state assoc :show-editable-dom true)
:on-mouse-leave #(swap! state assoc :show-editable-dom false)
:on-click (fn [e] (doall (.. e stopPropagation) (rf/dispatch [:editing/uid uid])))
:on-drag-over (fn [e] (block-drag-over e block state))
:on-drag-leave (fn [e] (block-drag-leave e block state))
:on-drop (fn [e] (block-drop e block state))}
:on-mouse-enter #(swap! state assoc :show-editable-dom true)
:on-mouse-leave #(swap! state assoc :show-editable-dom false)
:on-click (fn [e] (doall (.. e stopPropagation) (rf/dispatch [:editing/uid uid])))
:on-drag-over (fn [e] (block-drag-over e block state))
:on-drag-leave (fn [e] (block-drag-leave e block state))
:on-drop (fn [e] (block-drop e block state))}

[presence/presence-popover-info uid {:inline? true}]

Expand Down
6 changes: 3 additions & 3 deletions src/cljs/athens/views/devtool.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,9 @@
(eval-box!)))


; Only run the listener in dev mode, not in prod. The listener slows things
; down a lot. For example it makes the enter key take ~300ms rather than ~100ms,
; according to the Chrome devtools flamegraph.
;; Only run the listener in dev mode, not in prod. The listener slows things
;; down a lot. For example it makes the enter key take ~300ms rather than ~100ms,
;; according to the Chrome devtools flamegraph.
(when config/debug?
(d/listen! dsdb :devtool/open listener))

Expand Down

0 comments on commit b66bfa8

Please sign in to comment.