| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879 |
- (ns microtables-frontend.utils.coordinates
- (:require
- [microtables-frontend.db :as db]))
- (defn highest-col
- "Return the highest column (letter) for which there is a non-empty cell"
- [db]
- ; choose the "max" (alphabetical order) value among the longest keys
- (->> db
- :table-data
- keys
- (group-by #(.-length %))
- (apply max-key key)
- val
- (concat [db/min-max-col])
- (apply max)))
- (defn highest-row
- "Return the highest row (number) for which there is a non-empty cell"
- [db]
- ; get all the row keys from all the column objects (and flatten), then pick the max
- (->> db
- :table-data
- vals
- (map keys)
- flatten
- (concat [db/min-max-row])
- (apply max)))
- (defn- increment-letter-code [s]
- (let [l (last s)]
- (cond
- (empty? s) [65]
- (= l 90) (conj (increment-letter-code (subvec s 0 (dec (count s)))) 65)
- :else (conj (subvec s 0 (dec (count s))) (inc l)))))
- (defn next-letter [lc]
- (->> lc
- (mapv #(.charCodeAt % 0))
- increment-letter-code
- (map char)
- (apply str)))
- (def col-letters
- (iterate next-letter "A"))
- (defn prev-letter [lc]
- (last (take-while #(not= % lc) col-letters)))
- (defn- order-two-cols
- "Accepts two column names (letters) and returns them in order."
- [col1 col2]
- (cond
- (> (.-length col1) (.-length col2)) [col2 col1]
- (> (.-length col2) (.-length col1)) [col1 col2]
- (= (max col1 col2) col1) [col2 col1]
- :else [col1 col2]))
- (defn range->list
- "Converts two cells (accepting four coordinates) into a list of all the cells in the range between them (inclusively)."
- [col1 row1 col2 row2]
- (let [[start-col end-col] (order-two-cols col1 col2)
- start-row (min row1 row2)
- end-row (max row1 row2)]
- (for [col (take-while #(not= (next-letter end-col) %) (iterate next-letter start-col))
- row (range start-row (inc end-row))]
- {:col col :row row})))
- ; the order goes top to bottom, then left to right - that makes the most sense to me
- ; I don't know why a different order would be important, or even in what situation order is important at all
- (defn parse-range
- "Converts a range in \"A1:B2\" notation to a list of col/row cells: {:col \"A\" :row 1}, etc."
- [range-string]
- (let [col1 (second (re-find #"\(\s*([A-Z]+)" range-string))
- col2 (second (re-find #":\s*([A-Z]+)" range-string))
- row1 (js/parseInt (second (re-find #"([0-9]+)\s*:" range-string)))
- row2 (js/parseInt (second (re-find #"([0-9]+)\s*\)" range-string)))]
- (range->list col1 row1 col2 row2)))
|