(ns outliner.model.core.tree) (defn index-of [coll x] #?(:cljs (.indexOf (to-array coll) x) :clj (.indexOf ^java.util.List coll x))) (defn get-node [doc id] (get-in doc [:nodes id])) (defn get-parent-id [doc id] (:parent (get-node doc id))) (defn get-siblings-path [doc id] (if-let [parent-id (get-parent-id doc id)] [:nodes parent-id :children] [:root])) (defn get-siblings [doc id] (get-in doc (get-siblings-path doc id))) (defn detach-node [doc id] (update-in doc (get-siblings-path doc id) (fn [sib] (vec (filter #(not= % id) sib))))) (defn delete-node-recursive [doc id] (let [node (get-in doc [:nodes id]) children (:children node) doc (reduce delete-node-recursive doc children)] (update doc :nodes dissoc id))) (defn insert-into-siblings [siblings id target-id position] (let [siblings (vec (filter #(not= % id) siblings))] (case position :before (let [idx (index-of siblings target-id)] (if (neg? idx) (vec (cons id siblings)) (vec (concat (take idx siblings) [id] (drop idx siblings))))) :after (let [idx (index-of siblings target-id)] (if (neg? idx) (vec (cons id siblings)) (vec (concat (take (inc idx) siblings) [id] (drop (inc idx) siblings))))) :first-child (vec (cons id siblings)) :last-child (conj (vec siblings) id)))) (defn attach-node ([doc id target-id position] (attach-node doc id target-id position true)) ([doc id target-id position force-expand?] (let [path (case position (:before :after) (get-siblings-path doc target-id) (:first-child :last-child) [:nodes target-id :children]) parent-id (case position (:before :after) (get-parent-id doc target-id) (:first-child :last-child) target-id)] (-> doc (update-in path insert-into-siblings id target-id position) (assoc-in [:nodes id :parent] parent-id) (cond-> (and force-expand? (#{:first-child :last-child} position)) (assoc-in [:nodes target-id :expanded?] true)))))) (defn insert-node ([doc node] (insert-node doc node true)) ([doc node force-expand?] (let [id (:id node) parent-id (:parent node) doc (update-in doc [:nodes id] merge {:children [] :parent nil} node)] (if parent-id (attach-node doc id parent-id :last-child force-expand?) (update doc :root insert-into-siblings id nil :last-child))))) (defn find-last-descendant [doc id] (let [node (get-node doc id) children (:children node) expanded? (:expanded? node true)] (if (and expanded? (seq children)) (find-last-descendant doc (last children)) id))) (defn find-prev-id ([doc id] (find-prev-id doc id nil)) ([doc id zoom-id] (if (= id zoom-id) nil (let [siblings (get-siblings doc id) index (index-of siblings id)] (if (and index (> index 0)) (find-last-descendant doc (nth siblings (dec index))) (get-parent-id doc id)))))) (defn find-next-id ([doc id] (find-next-id doc id nil)) ([doc id zoom-id] (let [node (get-node doc id) children (:children node) expanded? (or (= id zoom-id) (:expanded? node true))] (if (and expanded? (seq children)) (first children) (loop [curr-id id] (if (= curr-id zoom-id) nil (let [siblings (get-siblings doc curr-id) index (index-of siblings curr-id)] (if (and (>= index 0) (< index (dec (count siblings)))) (nth siblings (inc index)) (let [parent-id (get-parent-id doc curr-id)] (if (and parent-id (not= parent-id zoom-id)) (recur parent-id) nil)))))))))) (defn is-ancestor? [doc potential-ancestor-id id] (loop [curr-id id] (if-let [parent-id (get-parent-id doc curr-id)] (cond (= parent-id potential-ancestor-id) true :else (recur parent-id)) false))) (defn get-path [doc id] (loop [curr-id id path []] (if-let [node (get-node doc curr-id)] (recur (:parent node) (conj path node)) (reverse path))))