(ns cruncher.utils.lib
  (:require [om.next :as om :refer-macros [defui]]
            [cljs.core.async :refer [chan close!]]
            [cruncher.data.pokemon :as pokemon]))

(defonce app-state
         (atom
           {:pokemon           []
            :user              {:view       :default
                                :logged-in? false}
            :error             {:message nil}
            :info              {:message nil}
            :app               {:loading?   false
                                :connected? nil}
            :progress          {:running   false
                                :to_delete 100
                                :deleted   0
                                :status    "ok"}
            :progress-running? false
            :player            {}
            :evolution-number  0
            :sort-asc              true}))

(declare get-pokemon-by-id)
(declare evolution-sum)
(declare reconciler)

;;;; React compatibility
(defonce counter (atom 0))

(defn get-unique-key
  "Return unique react-key."
  []
  (str "cruncher-unique-react-key-" (swap! counter inc)))

(defn merge-react-key
  "Get a unique key, create a small map with :react-key property and merge it with the given collection."
  [col]
  (merge {:react-key (get-unique-key)} col))

;;;; Reconciler action
(defmulti read (fn [env key params] key))

(defmethod read :default
  [{:keys [state] :as env} key params]
  (let [st @state]
    (if-let [[_ value] (find st key)]
      {:value value}
      {:value :not-found})))

(defmulti mutate om/dispatch)

(defmethod mutate 'update/pokemon
  [{:keys [state]} _ {:keys [pokemon]}]
  (let [named-pokemon (map (fn [pokemap] (merge pokemap {:name (:name (get-pokemon-by-id (:pokemon_id pokemap)))})) pokemon)]
    {:action (fn [] (swap! state update-in [:pokemon] (fn [] named-pokemon)))}))

(defmethod mutate 'update/evolution-amount
  [{:keys [state]} _ {:keys [pokemon]}]
  (let [pokes (get @app-state :pokemon)]
    {:action (fn [] (swap! state update-in [:evolution-number] (fn [] (evolution-sum pokes))))}))

(defmethod mutate 'update/player
  [{:keys [state]} _ {:keys [player]}]
  {:action (fn [] (swap! state update-in [:player] (fn [] player)))})

(defmethod mutate 'sort/pokemon
  [{:keys [state]} _ {:keys [key]}]
  {:action (fn [] (swap! state update-in [:pokemon] (fn [] (cond-> (sort-by (juxt key :individual_percentage :cp) (:pokemon @state))
                                                                  (:sort-asc @state) reverse))))})

(defmethod mutate 'sort/toggle-asc
  [{:keys [state]} _ {:keys []}]
  {:action (fn [] (swap! state update-in [:sort-asc] not))})

(defmethod mutate 'change/view
  [{:keys [state]} _ {:keys [view]}]
  {:action (fn [] (swap! state update-in [:user :view] (fn [] view)))})

(defmethod mutate 'app/loading
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:app :loading?] (fn [] status)))})

(defmethod mutate 'update/error
  [{:keys [state]} _ {:keys [message]}]
  {:action (fn [] (swap! state update-in [:error :message] (fn [] message)))})

(defmethod mutate 'update/info
  [{:keys [state]} _ {:keys [message]}]
  {:action (fn [] (swap! state update-in [:info :message] (fn [] message)))})

(defmethod mutate 'user/logged-in
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:user :logged-in?] (fn [] status)))})

(defmethod mutate 'user/remove-pokemon-cache
  [{:keys [state]} _]
  {:action (fn [] (swap! state update-in [:pokemon] (fn [] [])))})

(defmethod mutate 'status/progress
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:progress]
                         (fn [] {:status    (:status status)
                                 :to_delete (:to_delete status)
                                 :deleted   (:deleted status)})))})

(defmethod mutate 'status/niantic
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:connected?] (fn [] status)))})

(defmethod mutate 'toggle/progress
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:progress-running?] (fn [] status)))})

(defonce reconciler
         (om/reconciler
           {:state  app-state
            :parser (om/parser {:read read :mutate mutate})}))

;;;; Get stuff
(defn inventory-pokemon
  "Return all pokemon which are currently stored."
  []
  (get-in @app-state [:pokemon]))

(defn get-pokemon-by-id
  "Look up database to return complete pokemon by its id."
  [pokemon-id]
  (get pokemon/all pokemon-id))

(defn playerinfo
  "Return the stored Playerinformation"
  []
  (get-in @app-state [:player]))

(defn logged-in?
  "Return boolean if user is logged in or not."
  []
  (get-in @app-state [:user :logged-in?]))

(defn logged-in!
  "Set boolean if user is logged in or not. If no parameters are given, set logged-in to true."
  ([bool]
   (if (not bool) (om/transact! reconciler `[(user/remove-pokemon-cache {})]))
   (om/transact! reconciler `[(user/logged-in {:status ~bool})]))
  ([] (logged-in! true)))

;;;; About views
(defn current-view
  "Return current selected view."
  []
  (get-in @app-state [:user :view]))

(defn change-view!
  "Return current selected view."
  [key]
  (om/transact! reconciler `[(change/view {:view ~key})]))

(defn loading?
  "Return boolean if ajax request is still in process."
  []
  (get-in @app-state [:app :loading?]))

(defn loading!
  "Set boolean if ajax request is still in process. Defaults to true without parameters."
  ([bool]
   (om/transact! reconciler `[(app/loading {:status ~bool})]))
  ([] (loading! true)))

;;;; Error messages
(defn error?
  "Return boolean if there is an error message."
  []
  (let [message (get-in @app-state [:error :message])]
    (pos? (count message))))

(defn error!
  "Set error message."
  [message]
  (om/transact! reconciler `[(update/error {:message ~message})]))

(defn no-error!
  "Reset error message."
  []
  (error! nil))

(defn get-error
  "Return error message."
  []
  (get-in @app-state [:error :message]))

;;;; Info box
(defn info?
  "Return boolean if there is an error message."
  []
  (let [message (get-in @app-state [:info :message])]
    (pos? (count message))))

(defn info!
  "Set error message."
  [message]
  (om/transact! reconciler `[(update/info {:message ~message})]))

(defn no-info!
  "Reset error message."
  []
  (info! nil))

(defn get-info
  "Return error message."
  []
  (get-in @app-state [:info :message]))

;;;; Status information
(defn update-progress-status!
  "Receives a map containing information about the progress status, which are then stored in the app-state."
  [response]
  (om/transact! reconciler `[(status/progress {:status ~response})]))

(defn progress!
  "Toggle progress bar."
  [bool]
  (om/transact! reconciler `[(toggle/progress {:status ~bool})]))

(defn progress?
  "Return bool if a progress is running."
  []
  (get-in @app-state [:progress-running?]))

;;;; State transitions
(defn update-pokemon!
  "Update pokemon based on API response."
  [res]
  (om/transact! reconciler `[(update/pokemon {:pokemon ~res})]))

(defn sort-pokemon!
  "Sort complete list of pokemon by given key."
  [key]
  (om/transact! reconciler `[(sort/pokemon {:key ~key})])
  (om/transact! reconciler `[(sort/toggle-asc)]))

(defn update-player!
  "Update the playerdata."
  [res]
  (om/transact! reconciler `[(update/player {:player ~res})]))

(defn update-evolution-amount!
  "Update the amount of evolutions"
  [res]
  (om/transact! reconciler `[(update/evolution-amount {:pokemon ~res})]))

;;;; Conversions
(defn str->int
  "Convert String to Integer."
  [s]
  (let [converted (js/parseInt s)]
    (if-not (js/isNaN converted)
      converted
      s)))

(defn str->bool
  "Convert JS String to boolean."
  [str]
  (when (string? str)
    (= "true" str)))

;;;; Threading
(defn timeout
  "Set timeout for the current thread."
  [ms]
  (let [c (chan)]
    (js/setTimeout (fn [] (close! c)) ms)
    c))

;;;; Helpers
(defn pokemon-evolution
  [pokemon]
  (if (= (:name pokemon) "Eevee")
    (reduce #(str %1 " / " %2) [(:name (get-pokemon-by-id 134)) (:name (get-pokemon-by-id 135)) (:name (get-pokemon-by-id 136))])
    (:name (get-pokemon-by-id (first (:next-evolutions (get-pokemon-by-id (:pokemon_id pokemon))))))))

(defn calc-evolutions
  [pokemon]
  (let [requirement (:next-evolution-requirements (get-pokemon-by-id (:pokemon_id pokemon)))
        amount (if requirement (:amount requirement) 1000000000000) ;; More elegant way available?
        result (quot (:candy pokemon) amount)]
    result))

(defn evolution-sum
  [pokemon]
  (let [grouped-pokemon (group-by :name pokemon)
        unique-pokes-lists (vec (map second grouped-pokemon))
        unique-pokes (map first unique-pokes-lists)]
    (reduce + (map calc-evolutions (flatten unique-pokes)))))

(ns cruncher.utils.lib
  (:require [om.next :as om :refer-macros [defui]]
            [cljs.core.async :refer [chan close!]]
            [cruncher.data.pokemon :as pokemon]))

(defonce app-state
         (atom
           {:pokemon           []
            :user              {:view       :default
                                :logged-in? false}
            :error             {:message nil}
            :info              {:message nil}
            :app               {:loading?   false
                                :connected? nil}
            :progress          {:running   false
                                :to_delete 100
                                :deleted   0
                                :status    "ok"}
            :progress-running? false
            :player            {}
            :evolution-number  0
            :sort-asc          true}))

(declare get-pokemon-by-id)
(declare evolution-sum)
(declare reconciler)

;;;; React compatibility
(defonce counter (atom 0))

(defn get-unique-key
  "Return unique react-key."
  []
  (str "cruncher-unique-react-key-" (swap! counter inc)))

(defn merge-react-key
  "Get a unique key, create a small map with :react-key property and merge it with the given collection."
  [col]
  (merge {:react-key (get-unique-key)} col))

;;;; Reconciler action
(defmulti read (fn [env key params] key))

(defmethod read :default
  [{:keys [state] :as env} key params]
  (let [st @state]
    (if-let [[_ value] (find st key)]
      {:value value}
      {:value :not-found})))

(defmulti mutate om/dispatch)

(defmethod mutate 'update/pokemon
  [{:keys [state]} _ {:keys [pokemon]}]
  (let [named-pokemon (map (fn [pokemap] (merge pokemap {:name (:name (get-pokemon-by-id (:pokemon_id pokemap)))})) pokemon)]
    {:action (fn [] (swap! state update-in [:pokemon] (fn [] named-pokemon)))}))

(defmethod mutate 'update/evolution-amount
  [{:keys [state]} _ {:keys [pokemon]}]
  (let [pokes (get @app-state :pokemon)]
    {:action (fn [] (swap! state update-in [:evolution-number] (fn [] (evolution-sum pokes))))}))

(defmethod mutate 'update/player
  [{:keys [state]} _ {:keys [player]}]
  {:action (fn [] (swap! state update-in [:player] (fn [] player)))})

(defmethod mutate 'sort/pokemon
  [{:keys [state]} _ {:keys [key]}]
  {:action (fn []
             (swap! state update-in [:pokemon] (fn [] (cond-> (sort-by (juxt key :individual_percentage :cp) (:pokemon @state))
                                                              (:sort-asc @state) reverse)))
             (swap! state update-in [:sort-asc] not))})

(defmethod mutate 'change/view
  [{:keys [state]} _ {:keys [view]}]
  {:action (fn [] (swap! state update-in [:user :view] (fn [] view)))})

(defmethod mutate 'app/loading
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:app :loading?] (fn [] status)))})

(defmethod mutate 'update/error
  [{:keys [state]} _ {:keys [message]}]
  {:action (fn [] (swap! state update-in [:error :message] (fn [] message)))})

(defmethod mutate 'update/info
  [{:keys [state]} _ {:keys [message]}]
  {:action (fn [] (swap! state update-in [:info :message] (fn [] message)))})

(defmethod mutate 'user/logged-in
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:user :logged-in?] (fn [] status)))})

(defmethod mutate 'user/remove-pokemon-cache
  [{:keys [state]} _]
  {:action (fn [] (swap! state update-in [:pokemon] (fn [] [])))})

(defmethod mutate 'status/progress
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:progress]
                         (fn [] {:status    (:status status)
                                 :to_delete (:to_delete status)
                                 :deleted   (:deleted status)})))})

(defmethod mutate 'status/niantic
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:connected?] (fn [] status)))})

(defmethod mutate 'toggle/progress
  [{:keys [state]} _ {:keys [status]}]
  {:action (fn [] (swap! state update-in [:progress-running?] (fn [] status)))})

(defonce reconciler
         (om/reconciler
           {:state  app-state
            :parser (om/parser {:read read :mutate mutate})}))

;;;; Get stuff
(defn inventory-pokemon
  "Return all pokemon which are currently stored."
  []
  (get-in @app-state [:pokemon]))

(defn get-pokemon-by-id
  "Look up database to return complete pokemon by its id."
  [pokemon-id]
  (get pokemon/all pokemon-id))

(defn playerinfo
  "Return the stored Playerinformation"
  []
  (get-in @app-state [:player]))

(defn logged-in?
  "Return boolean if user is logged in or not."
  []
  (get-in @app-state [:user :logged-in?]))

(defn logged-in!
  "Set boolean if user is logged in or not. If no parameters are given, set logged-in to true."
  ([bool]
   (if (not bool) (om/transact! reconciler `[(user/remove-pokemon-cache {})]))
   (om/transact! reconciler `[(user/logged-in {:status ~bool})]))
  ([] (logged-in! true)))

;;;; About views
(defn current-view
  "Return current selected view."
  []
  (get-in @app-state [:user :view]))

(defn change-view!
  "Return current selected view."
  [key]
  (om/transact! reconciler `[(change/view {:view ~key})]))

(defn loading?
  "Return boolean if ajax request is still in process."
  []
  (get-in @app-state [:app :loading?]))

(defn loading!
  "Set boolean if ajax request is still in process. Defaults to true without parameters."
  ([bool]
   (om/transact! reconciler `[(app/loading {:status ~bool})]))
  ([] (loading! true)))

;;;; Error messages
(defn error?
  "Return boolean if there is an error message."
  []
  (let [message (get-in @app-state [:error :message])]
    (pos? (count message))))

(defn error!
  "Set error message."
  [message]
  (om/transact! reconciler `[(update/error {:message ~message})]))

(defn no-error!
  "Reset error message."
  []
  (error! nil))

(defn get-error
  "Return error message."
  []
  (get-in @app-state [:error :message]))

;;;; Info box
(defn info?
  "Return boolean if there is an error message."
  []
  (let [message (get-in @app-state [:info :message])]
    (pos? (count message))))

(defn info!
  "Set error message."
  [message]
  (om/transact! reconciler `[(update/info {:message ~message})]))

(defn no-info!
  "Reset error message."
  []
  (info! nil))

(defn get-info
  "Return error message."
  []
  (get-in @app-state [:info :message]))

;;;; Status information
(defn update-progress-status!
  "Receives a map containing information about the progress status, which are then stored in the app-state."
  [response]
  (om/transact! reconciler `[(status/progress {:status ~response})]))

(defn progress!
  "Toggle progress bar."
  [bool]
  (om/transact! reconciler `[(toggle/progress {:status ~bool})]))

(defn progress?
  "Return bool if a progress is running."
  []
  (get-in @app-state [:progress-running?]))

;;;; State transitions
(defn update-pokemon!
  "Update pokemon based on API response."
  [res]
  (om/transact! reconciler `[(update/pokemon {:pokemon ~res})]))

(defn sort-pokemon!
  "Sort complete list of pokemon by given key."
  [key]
  (om/transact! reconciler `[(sort/pokemon {:key ~key})]))

(defn update-player!
  "Update the playerdata."
  [res]
  (om/transact! reconciler `[(update/player {:player ~res})]))

(defn update-evolution-amount!
  "Update the amount of evolutions"
  [res]
  (om/transact! reconciler `[(update/evolution-amount {:pokemon ~res})]))

;;;; Conversions
(defn str->int
  "Convert String to Integer."
  [s]
  (let [converted (js/parseInt s)]
    (if-not (js/isNaN converted)
      converted
      s)))

(defn str->bool
  "Convert JS String to boolean."
  [str]
  (when (string? str)
    (= "true" str)))

;;;; Threading
(defn timeout
  "Set timeout for the current thread."
  [ms]
  (let [c (chan)]
    (js/setTimeout (fn [] (close! c)) ms)
    c))

;;;; Helpers
(defn pokemon-evolution
  [pokemon]
  (if (= (:name pokemon) "Eevee")
    (reduce #(str %1 " / " %2) [(:name (get-pokemon-by-id 134)) (:name (get-pokemon-by-id 135)) (:name (get-pokemon-by-id 136))])
    (:name (get-pokemon-by-id (first (:next-evolutions (get-pokemon-by-id (:pokemon_id pokemon))))))))

(defn calc-evolutions
  [pokemon]
  (let [requirement (:next-evolution-requirements (get-pokemon-by-id (:pokemon_id pokemon)))
        amount (if requirement (:amount requirement) 1000000000000) ;; More elegant way available?
        result (quot (:candy pokemon) amount)]
    result))

(defn evolution-sum
  [pokemon]
  (let [grouped-pokemon (group-by :name pokemon)
        unique-pokes-lists (vec (map second grouped-pokemon))
        unique-pokes (map first unique-pokes-lists)]
    (reduce + (map calc-evolutions (flatten unique-pokes)))))

Simplify reverse sort

Simplify reverse sort


(ns migrations.044-setup-humanizers-table
  (:require [clojure.java.jdbc :as j]
            [config.migrate-config :as config]
            [config.mdb-migrate-helper :as h]
            [cmr.metadata-db.data.oracle.concepts]
            [cmr.metadata-db.data.concepts :as c]
            [cheshire.core :as json]
            [cmr.common-app.test.sample-humanizer :as sh]))

(def ^:private humanizers-column-sql
  "id NUMBER,
  concept_id VARCHAR(255) NOT NULL,
  native_id VARCHAR(1030) NOT NULL,
  metadata BLOB NOT NULL,
  format VARCHAR(255) NOT NULL,
  revision_id INTEGER DEFAULT 1 NOT NULL,
  revision_date TIMESTAMP WITH TIME ZONE DEFAULT SYSTIMESTAMP NOT NULL,
  deleted INTEGER DEFAULT 0 NOT NULL,
  user_id VARCHAR(30),
  transaction_id INTEGER DEFAULT 0 NOT NULL")

(def ^:private humanizers-constraint-sql
  (str "CONSTRAINT humanizers_pk PRIMARY KEY (id), "
       ;; Unique constraint on native id and revision id
       "CONSTRAINT humanizers_con_rev UNIQUE (native_id, revision_id)
       USING INDEX (create unique index humanizers_ucr_i ON cmr_humanizers (native_id, revision_id)), "

       ;; Unique constraint on concept id and revision id
       "CONSTRAINT humanizers_cid_rev UNIQUE (concept_id, revision_id)
       USING INDEX (create unique index humanizers_cri ON cmr_humanizers (concept_id, revision_id))"))

(defn- create-humanizers-table
  []
  (h/sql (format "CREATE TABLE METADATA_DB.cmr_humanizers (%s, %s)" humanizers-column-sql humanizers-constraint-sql)))

(defn- create-humanizers-indices
  []
  (h/sql "CREATE INDEX humanizers_crdi ON cmr_humanizers (concept_id, revision_id, deleted)"))

(defn- create-humanizers-sequence
  []
  (h/sql "CREATE SEQUENCE cmr_humanizers_seq"))

(defn- insert-humanizer
  []
  (let [provider {:provider-id "CMR"
                  :short-name "CMR"
                  :system-level? true
                  :cmr-only true
                  :small false}
        concept {:concept-type :humanizer
                 :native-id "humanizer"
                 :metadata (json/generate-string sh/sample-humanizers)
                 :user-id "migration"
                 :format "application/json"
                 :provider-id "CMR"
                 :concept-id "H12345-CMR"
                 :revision-id 1
                 :deleted false}]
    (c/save-concept (config/db) provider concept)))

(defn up
  "Migrates the database up to version 44."
  []
  (println "migrations.044-setup-humanizers-table up...")
  (create-humanizers-table)
  (create-humanizers-indices)
  (create-humanizers-sequence)
  (insert-humanizer))

(defn down
  "Migrates the database down from version 44."
  []
  (println "migrations.044-setup-humanizers-table down...")
  (h/sql "DELETE cmr_humanizers")
  (h/sql "DROP SEQUENCE METADATA_DB.cmr_humanizers_seq")
  (h/sql "DROP TABLE METADATA_DB.cmr_humanizers"))

(ns migrations.044-setup-humanizers-table
  (:require [clojure.java.jdbc :as j]
            [config.migrate-config :as config]
            [config.mdb-migrate-helper :as h]))

(def ^:private humanizers-column-sql
  "id NUMBER,
  concept_id VARCHAR(255) NOT NULL,
  native_id VARCHAR(1030) NOT NULL,
  metadata BLOB NOT NULL,
  format VARCHAR(255) NOT NULL,
  revision_id INTEGER DEFAULT 1 NOT NULL,
  revision_date TIMESTAMP WITH TIME ZONE DEFAULT SYSTIMESTAMP NOT NULL,
  deleted INTEGER DEFAULT 0 NOT NULL,
  user_id VARCHAR(30),
  transaction_id INTEGER DEFAULT 0 NOT NULL")

(def ^:private humanizers-constraint-sql
  (str "CONSTRAINT humanizers_pk PRIMARY KEY (id), "
       ;; Unique constraint on native id and revision id
       "CONSTRAINT humanizers_con_rev UNIQUE (native_id, revision_id)
       USING INDEX (create unique index humanizers_ucr_i ON cmr_humanizers (native_id, revision_id)), "

       ;; Unique constraint on concept id and revision id
       "CONSTRAINT humanizers_cid_rev UNIQUE (concept_id, revision_id)
       USING INDEX (create unique index humanizers_cri ON cmr_humanizers (concept_id, revision_id))"))

(defn- create-humanizers-table
  []
  (h/sql (format "CREATE TABLE METADATA_DB.cmr_humanizers (%s, %s)" humanizers-column-sql humanizers-constraint-sql)))

(defn- create-humanizers-indices
  []
  (h/sql "CREATE INDEX humanizers_crdi ON cmr_humanizers (concept_id, revision_id, deleted)"))

(defn- create-humanizers-sequence
  []
  (h/sql "CREATE SEQUENCE cmr_humanizers_seq"))

(defn up
  "Migrates the database up to version 44."
  []
  (println "migrations.044-setup-humanizers-table up...")
  (create-humanizers-table)
  (create-humanizers-indices)
  (create-humanizers-sequence))

(defn down
  "Migrates the database down from version 44."
  []
  (println "migrations.044-setup-humanizers-table down...")
  (h/sql "DROP SEQUENCE METADATA_DB.cmr_humanizers_seq")
  (h/sql "DROP TABLE METADATA_DB.cmr_humanizers"))

Revert "CMR-3260: Updated migration to insert the initial humanizer concept."

This reverts commit dd2d72b62daf4de3d679300af0f70ee870b67d19.


;; 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) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>

(ns uxbox.main.ui.workspace.sidebar.layers
  (:require [lentes.core :as l]
            [cuerdas.core :as str]
            [goog.events :as events]
            [uxbox.util.router :as r]
            [uxbox.util.rstore :as rs]
            [uxbox.main.state :as st]
            [uxbox.main.library :as library]
            [uxbox.util.data :refer (read-string classnames)]
            [uxbox.main.data.workspace :as udw]
            [uxbox.main.data.shapes :as uds]
            [uxbox.main.ui.shapes.icon :as icon]
            [uxbox.main.ui.workspace.base :as wb]
            [uxbox.main.ui.icons :as i]
            [uxbox.main.ui.keyboard :as kbd]
            [uxbox.util.mixins :as mx :include-macros true]
            [uxbox.util.dom.dnd :as dnd]
            [uxbox.util.dom :as dom])
  (:import goog.events.EventType))

;; --- Helpers

(defn- focus-page
  [id]
  (-> (l/in [:pages-by-id id])
      (l/derive st/state)))

(defn- select-shape
  [selected item event]
  (dom/prevent-default event)
  (let [id (:id item)]
    (cond
      (or (:blocked item)
          (:hidden item))
      nil

      (.-ctrlKey event)
      (rs/emit! (uds/select-shape id))

      (> (count selected) 1)
      (rs/emit! (uds/deselect-all)
                (uds/select-shape id))

      (contains? selected id)
      (rs/emit! (uds/select-shape id))

      :else
      (rs/emit! (uds/deselect-all)
                (uds/select-shape id)))))

(defn- toggle-visibility
  [selected item event]
  (dom/stop-propagation event)
  (let [id (:id item)
        hidden? (:hidden item)]
    (if hidden?
      (rs/emit! (uds/show-shape id))
      (rs/emit! (uds/hide-shape id)))
    (when (contains? selected id)
      (rs/emit! (uds/select-shape id)))))

(defn- toggle-blocking
  [item event]
  (dom/stop-propagation event)
  (let [id (:id item)
        blocked? (:blocked item)]
    (if blocked?
      (rs/emit! (uds/unblock-shape id))
      (rs/emit! (uds/block-shape id)))))

(defn- element-icon
  [item]
  (case (:type item)
    :icon (icon/icon-svg item)
    :line i/line
    :circle i/circle
    :path i/curve
    :rect i/box
    :text i/text
    :group i/folder))

(defn- get-hover-position
  [event group?]
  (let [target (.-currentTarget event)
        brect (.getBoundingClientRect target)
        width (.-offsetHeight target)
        y (- (.-clientY event) (.-top brect))
        part (/ (* 30 width) 100)]
    (if group?
      (cond
        (> part y) :top
        (< (- width part) y) :bottom
        :else :middle)
      (if (>= y (/ width 2))
        :bottom
        :top))))

;; --- Shape Name (Component)

(mx/defcs shape-name
  "A generic component that displays the shape name
  if it is available and allows inline edition of it."
  {:mixins [mx/static (mx/local)]}
  [own shape]
  (let [local (:rum/local own)]
    (letfn [(on-blur [event]
              (let [target (dom/event->target event)
                    parent (.-parentNode target)
                    data {:id (:id shape)
                          :name (dom/get-value target)}]
                (set! (.-draggable parent) true)
                (rs/emit! (uds/update-shape data))
                (swap! local assoc :edition false)))
            (on-key-down [event]
              (js/console.log event)
              (when (kbd/enter? event)
                (on-blur event)))
            (on-click [event]
              (dom/stop-propagation event)
              (dom/prevent-default event)
              (let [parent (.-parentNode (.-target event))]
                (set! (.-draggable parent) false))
              (swap! local assoc :edition true))]
      (if (:edition @local)
        [:input.element-name
         {:type "text"
          :on-blur on-blur
          :on-key-down on-key-down
          :auto-focus true
          :default-value (:name shape "")}]
        [:span.element-name
         {:on-click on-click}
         (:name shape "")]))))

;; --- Layer Simple (Component)

(mx/defcs layer-simple
  {:mixins [mx/static (mx/local)]}
  [own item selected]
  (let [selected? (contains? selected (:id item))
        select #(select-shape selected item %)
        toggle-visibility #(toggle-visibility selected item %)
        toggle-blocking #(toggle-blocking item %)
        local (:rum/local own)
        classes (classnames
                 :selected selected?
                 :drag-active (:dragging @local)
                 :drag-top (= :top (:over @local))
                 :drag-bottom (= :bottom (:over @local))
                 :drag-inside (= :middle (:over @local)))]
    (letfn [(on-drag-start [event]
              (let [target (dom/event->target event)]
                (dnd/set-allowed-effect! event "move")
                (dnd/set-data! event (:id item))
                (dnd/set-image! event target 50 10)
                (swap! local assoc :dragging true)))
            (on-drag-end [event]
              (swap! local assoc :dragging false :over nil))
            (on-drop [event]
              (dom/stop-propagation event)
              (let [id (dnd/get-data event)
                    over (:over @local)]
                (case (:over @local)
                  :top (rs/emit! (uds/drop-shape id (:id item) :before))
                  :bottom (rs/emit! (uds/drop-shape id (:id item) :after)))
                (swap! local assoc :dragging false :over nil)))
            (on-drag-over [event]
              (dom/prevent-default event)
              (dnd/set-drop-effect! event "move")
              (let [over (get-hover-position event false)]
                (swap! local assoc :over over)))
            (on-drag-enter [event]
              (swap! local assoc :over true))
            (on-drag-leave [event]
              (swap! local assoc :over false))]
      [:li {:key (str (:id item))
            :class (when selected? "selected")}
       [:div.element-list-body
        {:class classes
         :style {:opacity (if (:dragging @local)
                            "0.5"
                            "1")}
         :on-click select
         :on-drag-start on-drag-start
         :on-drag-enter on-drag-enter
         :on-drag-leave on-drag-leave
         :on-drag-over on-drag-over
         :on-drag-end on-drag-end
         :on-drop on-drop
         :draggable true}

        [:div.element-actions
         [:div.toggle-element
          {:class (when-not (:hidden item) "selected")
           :on-click toggle-visibility}
          i/eye]
         [:div.block-element
          {:class (when (:blocked item) "selected")
           :on-click toggle-blocking}
          i/lock]]
        [:div.element-icon (element-icon item)]
        (shape-name item)]])))

;; --- Layer Group (Component)

(mx/defcs layer-group
  {:mixins [mx/static mx/reactive (mx/local)]}
  [own {:keys [id] :as item} selected]
  (let [local (:rum/local own)
        selected? (contains? selected (:id item))
        collapsed? (:collapsed item true)
        shapes-map (mx/react wb/shapes-by-id-ref)
        classes (classnames
                 :selected selected?
                 :drag-top (= :top (:over @local))
                 :drag-bottom (= :bottom (:over @local))
                 :drag-inside (= :middle (:over @local)))
        select #(select-shape selected item %)
        toggle-visibility #(toggle-visibility selected item %)
        toggle-blocking #(toggle-blocking item %)]
    (letfn [(toggle-collapse [event]
              (dom/stop-propagation event)
              (if (:collapsed item)
                (rs/emit! (uds/uncollapse-shape id))
                (rs/emit! (uds/collapse-shape id))))
            (toggle-locking [event]
              (dom/stop-propagation event)
              (if (:locked item)
                (rs/emit! (uds/unlock-shape id))
                (rs/emit! (uds/lock-shape id))))
            (on-drag-start [event]
              (let [target (dom/event->target event)]
                (dnd/set-allowed-effect! event "move")
                (dnd/set-data! event (:id item))
                (swap! local assoc :dragging true)))
            (on-drag-end [event]
              (swap! local assoc :dragging false :over nil))
            (on-drop [event]
              (dom/stop-propagation event)
              (let [coming-id (dnd/get-data event)
                    over (:over @local)]
                (case (:over @local)
                  :top (rs/emit! (uds/drop-shape coming-id id :before))
                  :bottom (rs/emit! (uds/drop-shape coming-id id :after))
                  :middle (rs/emit! (uds/drop-shape coming-id id :inside)))
                (swap! local assoc :dragging false :over nil)))
            (on-drag-over [event]
              (dom/prevent-default event)
              (dnd/set-drop-effect! event "move")
              (let [over (get-hover-position event true)]
                (swap! local assoc :over over)))
            (on-drag-enter [event]
              (swap! local assoc :over true))
            (on-drag-leave [event]
              (swap! local assoc :over false))]
      [:li.group {:class (when-not collapsed? "open")}
       [:div.element-list-body
        {:class classes
         :draggable true
         :on-drag-start on-drag-start
         :on-drag-enter on-drag-enter
         :on-drag-leave on-drag-leave
         :on-drag-over on-drag-over
         :on-drag-end on-drag-end
         :on-drop on-drop
         :on-click select}
        [:div.element-actions
         [:div.toggle-element
          {:class (when-not (:hidden item) "selected")
           :on-click toggle-visibility}
          i/eye]
         [:div.block-element
          {:class (when (:blocked item) "selected")
           :on-click toggle-blocking}
          i/lock]
         [:div.chain-element
          {:class (when (:locked item) "selected")
           :on-click toggle-locking}
          i/chain]]
        [:div.element-icon i/folder]
        (shape-name item)
        [:span.toggle-content
         {:on-click toggle-collapse
          :class (when-not collapsed? "inverse")}
         i/arrow-slide]]
       (if-not collapsed?
         [:ul
          (for [shape (map #(get shapes-map %) (:items item))
                :let [key (str (:id shape))]]
            (if (= (:type shape) :group)
              (-> (layer-group shape selected)
                  (mx/with-key key))
              (-> (layer-simple shape selected)
                  (mx/with-key key))))])])))

;; --- Layers Toolbox (Component)

(mx/defc layers-toolbox
  {:mixins [mx/reactive]}
  []
  (let [workspace (mx/react wb/workspace-ref)
        selected (:selected workspace)
        shapes-map (mx/react wb/shapes-by-id-ref)
        page (mx/react (focus-page (:page workspace)))
        close #(rs/emit! (udw/toggle-flag :layers))
        duplicate #(rs/emit! (uds/duplicate-selected))
        group #(rs/emit! (uds/group-selected))
        degroup #(rs/emit! (uds/degroup-selected))
        delete #(rs/emit! (uds/delete-selected))
        dragel (volatile! nil)]
    [:div#layers.tool-window
     [:div.tool-window-bar
      [:div.tool-window-icon i/layers]
      [:span "Layers"]
      [:div.tool-window-close {:on-click close} i/close]]
     [:div.tool-window-content
      [:ul.element-list {}
       (for [shape (map #(get shapes-map %) (:shapes page))
             :let [key (str (:id shape))]]
         (if (= (:type shape) :group)
           (-> (layer-group shape selected)
               (mx/with-key key))
           (-> (layer-simple shape selected)
               (mx/with-key key))))]]
     [:div.layers-tools
      [:ul.layers-tools-content
       [:li.clone-layer {:on-click duplicate} i/copy]
       [:li.group-layer {:on-click group} i/folder]
       [:li.degroup-layer {:on-click degroup} i/ungroup]
       [:li.delete-layer {:on-click delete} i/trash]]]]))

;; 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) 2015-2016 Andrey Antukh <niwi@niwi.nz>
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>

(ns uxbox.main.ui.workspace.sidebar.layers
  (:require [lentes.core :as l]
            [cuerdas.core :as str]
            [goog.events :as events]
            [uxbox.util.router :as r]
            [uxbox.util.rstore :as rs]
            [uxbox.main.state :as st]
            [uxbox.main.library :as library]
            [uxbox.util.data :refer (read-string classnames)]
            [uxbox.main.data.workspace :as udw]
            [uxbox.main.data.shapes :as uds]
            [uxbox.main.ui.shapes.icon :as icon]
            [uxbox.main.ui.workspace.base :as wb]
            [uxbox.main.ui.icons :as i]
            [uxbox.main.ui.keyboard :as kbd]
            [uxbox.util.mixins :as mx :include-macros true]
            [uxbox.util.dom.dnd :as dnd]
            [uxbox.util.dom :as dom])
  (:import goog.events.EventType))

;; --- Helpers

(defn- focus-page
  [id]
  (-> (l/in [:pages-by-id id])
      (l/derive st/state)))

(defn- select-shape
  [selected item event]
  (dom/prevent-default event)
  (let [id (:id item)]
    (cond
      (or (:blocked item)
          (:hidden item))
      nil

      (.-ctrlKey event)
      (rs/emit! (uds/select-shape id))

      (> (count selected) 1)
      (rs/emit! (uds/deselect-all)
                (uds/select-shape id))

      (contains? selected id)
      (rs/emit! (uds/select-shape id))

      :else
      (rs/emit! (uds/deselect-all)
                (uds/select-shape id)))))

(defn- toggle-visibility
  [selected item event]
  (dom/stop-propagation event)
  (let [id (:id item)
        hidden? (:hidden item)]
    (if hidden?
      (rs/emit! (uds/show-shape id))
      (rs/emit! (uds/hide-shape id)))
    (when (contains? selected id)
      (rs/emit! (uds/select-shape id)))))

(defn- toggle-blocking
  [item event]
  (dom/stop-propagation event)
  (let [id (:id item)
        blocked? (:blocked item)]
    (if blocked?
      (rs/emit! (uds/unblock-shape id))
      (rs/emit! (uds/block-shape id)))))

(defn- element-icon
  [item]
  (case (:type item)
    :icon (icon/icon-svg item)
    :icon-raw (icon/icon-raw-svg item)
    :line i/line
    :circle i/circle
    :path i/curve
    :rect i/box
    :text i/text
    :group i/folder))

(defn- get-hover-position
  [event group?]
  (let [target (.-currentTarget event)
        brect (.getBoundingClientRect target)
        width (.-offsetHeight target)
        y (- (.-clientY event) (.-top brect))
        part (/ (* 30 width) 100)]
    (if group?
      (cond
        (> part y) :top
        (< (- width part) y) :bottom
        :else :middle)
      (if (>= y (/ width 2))
        :bottom
        :top))))

;; --- Shape Name (Component)

(mx/defcs shape-name
  "A generic component that displays the shape name
  if it is available and allows inline edition of it."
  {:mixins [mx/static (mx/local)]}
  [own shape]
  (let [local (:rum/local own)]
    (letfn [(on-blur [event]
              (let [target (dom/event->target event)
                    parent (.-parentNode target)
                    data {:id (:id shape)
                          :name (dom/get-value target)}]
                (set! (.-draggable parent) true)
                (rs/emit! (uds/update-shape data))
                (swap! local assoc :edition false)))
            (on-key-down [event]
              (js/console.log event)
              (when (kbd/enter? event)
                (on-blur event)))
            (on-click [event]
              (dom/stop-propagation event)
              (dom/prevent-default event)
              (let [parent (.-parentNode (.-target event))]
                (set! (.-draggable parent) false))
              (swap! local assoc :edition true))]
      (if (:edition @local)
        [:input.element-name
         {:type "text"
          :on-blur on-blur
          :on-key-down on-key-down
          :auto-focus true
          :default-value (:name shape "")}]
        [:span.element-name
         {:on-click on-click}
         (:name shape "")]))))

;; --- Layer Simple (Component)

(mx/defcs layer-simple
  {:mixins [mx/static (mx/local)]}
  [own item selected]
  (let [selected? (contains? selected (:id item))
        select #(select-shape selected item %)
        toggle-visibility #(toggle-visibility selected item %)
        toggle-blocking #(toggle-blocking item %)
        local (:rum/local own)
        classes (classnames
                 :selected selected?
                 :drag-active (:dragging @local)
                 :drag-top (= :top (:over @local))
                 :drag-bottom (= :bottom (:over @local))
                 :drag-inside (= :middle (:over @local)))]
    (letfn [(on-drag-start [event]
              (let [target (dom/event->target event)]
                (dnd/set-allowed-effect! event "move")
                (dnd/set-data! event (:id item))
                (dnd/set-image! event target 50 10)
                (swap! local assoc :dragging true)))
            (on-drag-end [event]
              (swap! local assoc :dragging false :over nil))
            (on-drop [event]
              (dom/stop-propagation event)
              (let [id (dnd/get-data event)
                    over (:over @local)]
                (case (:over @local)
                  :top (rs/emit! (uds/drop-shape id (:id item) :before))
                  :bottom (rs/emit! (uds/drop-shape id (:id item) :after)))
                (swap! local assoc :dragging false :over nil)))
            (on-drag-over [event]
              (dom/prevent-default event)
              (dnd/set-drop-effect! event "move")
              (let [over (get-hover-position event false)]
                (swap! local assoc :over over)))
            (on-drag-enter [event]
              (swap! local assoc :over true))
            (on-drag-leave [event]
              (swap! local assoc :over false))]
      [:li {:key (str (:id item))
            :class (when selected? "selected")}
       [:div.element-list-body
        {:class classes
         :style {:opacity (if (:dragging @local)
                            "0.5"
                            "1")}
         :on-click select
         :on-drag-start on-drag-start
         :on-drag-enter on-drag-enter
         :on-drag-leave on-drag-leave
         :on-drag-over on-drag-over
         :on-drag-end on-drag-end
         :on-drop on-drop
         :draggable true}

        [:div.element-actions
         [:div.toggle-element
          {:class (when-not (:hidden item) "selected")
           :on-click toggle-visibility}
          i/eye]
         [:div.block-element
          {:class (when (:blocked item) "selected")
           :on-click toggle-blocking}
          i/lock]]
        [:div.element-icon (element-icon item)]
        (shape-name item)]])))

;; --- Layer Group (Component)

(mx/defcs layer-group
  {:mixins [mx/static mx/reactive (mx/local)]}
  [own {:keys [id] :as item} selected]
  (let [local (:rum/local own)
        selected? (contains? selected (:id item))
        collapsed? (:collapsed item true)
        shapes-map (mx/react wb/shapes-by-id-ref)
        classes (classnames
                 :selected selected?
                 :drag-top (= :top (:over @local))
                 :drag-bottom (= :bottom (:over @local))
                 :drag-inside (= :middle (:over @local)))
        select #(select-shape selected item %)
        toggle-visibility #(toggle-visibility selected item %)
        toggle-blocking #(toggle-blocking item %)]
    (letfn [(toggle-collapse [event]
              (dom/stop-propagation event)
              (if (:collapsed item)
                (rs/emit! (uds/uncollapse-shape id))
                (rs/emit! (uds/collapse-shape id))))
            (toggle-locking [event]
              (dom/stop-propagation event)
              (if (:locked item)
                (rs/emit! (uds/unlock-shape id))
                (rs/emit! (uds/lock-shape id))))
            (on-drag-start [event]
              (let [target (dom/event->target event)]
                (dnd/set-allowed-effect! event "move")
                (dnd/set-data! event (:id item))
                (swap! local assoc :dragging true)))
            (on-drag-end [event]
              (swap! local assoc :dragging false :over nil))
            (on-drop [event]
              (dom/stop-propagation event)
              (let [coming-id (dnd/get-data event)
                    over (:over @local)]
                (case (:over @local)
                  :top (rs/emit! (uds/drop-shape coming-id id :before))
                  :bottom (rs/emit! (uds/drop-shape coming-id id :after))
                  :middle (rs/emit! (uds/drop-shape coming-id id :inside)))
                (swap! local assoc :dragging false :over nil)))
            (on-drag-over [event]
              (dom/prevent-default event)
              (dnd/set-drop-effect! event "move")
              (let [over (get-hover-position event true)]
                (swap! local assoc :over over)))
            (on-drag-enter [event]
              (swap! local assoc :over true))
            (on-drag-leave [event]
              (swap! local assoc :over false))]
      [:li.group {:class (when-not collapsed? "open")}
       [:div.element-list-body
        {:class classes
         :draggable true
         :on-drag-start on-drag-start
         :on-drag-enter on-drag-enter
         :on-drag-leave on-drag-leave
         :on-drag-over on-drag-over
         :on-drag-end on-drag-end
         :on-drop on-drop
         :on-click select}
        [:div.element-actions
         [:div.toggle-element
          {:class (when-not (:hidden item) "selected")
           :on-click toggle-visibility}
          i/eye]
         [:div.block-element
          {:class (when (:blocked item) "selected")
           :on-click toggle-blocking}
          i/lock]
         [:div.chain-element
          {:class (when (:locked item) "selected")
           :on-click toggle-locking}
          i/chain]]
        [:div.element-icon i/folder]
        (shape-name item)
        [:span.toggle-content
         {:on-click toggle-collapse
          :class (when-not collapsed? "inverse")}
         i/arrow-slide]]
       (if-not collapsed?
         [:ul
          (for [shape (map #(get shapes-map %) (:items item))
                :let [key (str (:id shape))]]
            (if (= (:type shape) :group)
              (-> (layer-group shape selected)
                  (mx/with-key key))
              (-> (layer-simple shape selected)
                  (mx/with-key key))))])])))

;; --- Layers Toolbox (Component)

(mx/defc layers-toolbox
  {:mixins [mx/reactive]}
  []
  (let [workspace (mx/react wb/workspace-ref)
        selected (:selected workspace)
        shapes-map (mx/react wb/shapes-by-id-ref)
        page (mx/react (focus-page (:page workspace)))
        close #(rs/emit! (udw/toggle-flag :layers))
        duplicate #(rs/emit! (uds/duplicate-selected))
        group #(rs/emit! (uds/group-selected))
        degroup #(rs/emit! (uds/degroup-selected))
        delete #(rs/emit! (uds/delete-selected))
        dragel (volatile! nil)]
    [:div#layers.tool-window
     [:div.tool-window-bar
      [:div.tool-window-icon i/layers]
      [:span "Layers"]
      [:div.tool-window-close {:on-click close} i/close]]
     [:div.tool-window-content
      [:ul.element-list {}
       (for [shape (map #(get shapes-map %) (:shapes page))
             :let [key (str (:id shape))]]
         (if (= (:type shape) :group)
           (-> (layer-group shape selected)
               (mx/with-key key))
           (-> (layer-simple shape selected)
               (mx/with-key key))))]]
     [:div.layers-tools
      [:ul.layers-tools-content
       [:li.clone-layer {:on-click duplicate} i/copy]
       [:li.group-layer {:on-click group} i/folder]
       [:li.degroup-layer {:on-click degroup} i/ungroup]
       [:li.delete-layer {:on-click delete} i/trash]]]]))

Add support for icon-raw to layers sidebar.


(ns chat.client.views.helpers
  (:require-macros [cljs.core.async.macros :refer [go]])
  (:require [cljs.core.async :refer [<! put! chan alts! timeout]]
            [cljs-time.format :as f]
            [cljs-time.core :as t]
            [chat.client.store :as store]
            [goog.style :as gstyle]))

; TODO: clojure 1.8 should implement this:
(defn starts-with? [s prefix]
  ; not using .startsWith because it's only supported in ES6
  (= 0 (.indexOf s prefix)))

; TODO: clojure 1.8 should implement this:
(defn ends-with? [s suffix]
  (let [pos (- (count s) (count suffix))
        idx (.indexOf s suffix)]
    (and (not= -1 idx) (= idx pos))))

(defn decimal->color [decimal]
 (str "hsl(" (int (* 360 (mod decimal 1))) ",71%,35%)") )

(defn string->color [string]
  (-> string
      char-array
      (->> (map int)
           (apply +))
      (/ 213)
      decimal->color))

(defn id->color [uuid]
  (-> uuid
      str
      (.substring 33 36)
      (js/parseInt 16)
      (/ 4096)
      ; at this point, we have a decimal between 0 and 1
      ; approximately evenly distributed
      decimal->color))

(defn format-date
  "Turn a Date object into a nicely formatted string"
  [datetime]
  (let [datetime (t/to-default-time-zone datetime)
        now (t/to-default-time-zone (t/now))
        format (cond
                 (= (f/unparse (f/formatter "yyyydM") now)
                    (f/unparse (f/formatter "yyyydM") datetime))
                 "h:mm A"

                 (= (t/year now) (t/year datetime))
                 "h:mm A MMM d"

                 :else
                 "h:mm A MMM d yyyy")]
    (f/unparse (f/formatter format) datetime)))

(defn debounce
  "Given the input channel source and a debouncing time of msecs, return a new
  channel that will forward the latest event from source at most every msecs
  milliseconds"
  [source msecs]
  (let [out (chan)]
    (go
      (loop [state ::init
             lastv nil
             chans [source]]
        (let [[_ threshold] chans]
          (let [[v sc] (alts! chans)]
            (condp = sc
              source (recur ::debouncing v
                            (case state
                              ::init (conj chans (timeout msecs))
                              ::debouncing (conj (pop chans) (timeout msecs))))
              threshold (do (when lastv
                              (put! out lastv))
                            (recur ::init nil (pop chans))))))))
    out))

(defn location
  [e]
  [(.-clientX e) (.-clientY e)])

(defn element-offset
  [elt]
  (let [offset (gstyle/getPageOffset elt)]
    [(.-x offset) (.-y offset)]))

(defn get-style
  [elt prop]
  (cond
    (.-currentStyle elt)
    (aget (.-currentStyle elt) prop)

    (.-getComputedStyle js/window)
    (.. js/document -defaultView
        (getComputedStyle elt nil)
        (getPropertyValue prop))))

(ns chat.client.views.helpers
  (:require-macros [cljs.core.async.macros :refer [go]])
  (:require [cljs.core.async :refer [<! put! chan alts! timeout]]
            [cljs-time.format :as f]
            [cljs-time.core :as t]
            [chat.client.store :as store]
            [goog.style :as gstyle]))

; TODO: clojure 1.8 should implement this:
(defn starts-with? [s prefix]
  ; not using .startsWith because it's only supported in ES6
  (= 0 (.indexOf s prefix)))

; TODO: clojure 1.8 should implement this:
(defn ends-with? [s suffix]
  (let [pos (- (count s) (count suffix))
        idx (.indexOf s suffix)]
    (and (not= -1 idx) (= idx pos))))

(defn ->color [input]
  (str "hsl(" (mod (Math/abs (hash input)) 360) ",71%,35%)"))

(defn id->color [uuid]
  (->color uuid))

(defn format-date
  "Turn a Date object into a nicely formatted string"
  [datetime]
  (let [datetime (t/to-default-time-zone datetime)
        now (t/to-default-time-zone (t/now))
        format (cond
                 (= (f/unparse (f/formatter "yyyydM") now)
                    (f/unparse (f/formatter "yyyydM") datetime))
                 "h:mm A"

                 (= (t/year now) (t/year datetime))
                 "h:mm A MMM d"

                 :else
                 "h:mm A MMM d yyyy")]
    (f/unparse (f/formatter format) datetime)))

(defn debounce
  "Given the input channel source and a debouncing time of msecs, return a new
  channel that will forward the latest event from source at most every msecs
  milliseconds"
  [source msecs]
  (let [out (chan)]
    (go
      (loop [state ::init
             lastv nil
             chans [source]]
        (let [[_ threshold] chans]
          (let [[v sc] (alts! chans)]
            (condp = sc
              source (recur ::debouncing v
                            (case state
                              ::init (conj chans (timeout msecs))
                              ::debouncing (conj (pop chans) (timeout msecs))))
              threshold (do (when lastv
                              (put! out lastv))
                            (recur ::init nil (pop chans))))))))
    out))

(defn location
  [e]
  [(.-clientX e) (.-clientY e)])

(defn element-offset
  [elt]
  (let [offset (gstyle/getPageOffset elt)]
    [(.-x offset) (.-y offset)]))

(defn get-style
  [elt prop]
  (cond
    (.-currentStyle elt)
    (aget (.-currentStyle elt) prop)

    (.-getComputedStyle js/window)
    (.. js/document -defaultView
        (getComputedStyle elt nil)
        (getPropertyValue prop))))

Replace random color generation with more generic algorithm


(ns kixi.hecuba.api.measurements
  (:require
   [bidi.bidi :as bidi]
   [clojure.string :as string]
   [clojure.tools.logging :as log]
   [kixi.hecuba.data.validate :as v]
   [kixi.hecuba.data.misc :as misc]
   [kixi.hecuba.protocols :as hecuba]
   [kixi.hecuba.queue :as q]
   [kixi.hecuba.security :as sec]
   [kixi.hecuba.webutil :as util]
   [clj-time.coerce :as tc]
   [clj-time.format :as tf]
   [clj-time.core :as t]
   [clj-time.periodic :as tp]
   [kixi.hecuba.webutil :refer (decode-body authorized? uuid stringify-values sha1-regex)]
   [liberator.core :refer (defresource)]
   [liberator.representation :refer (ring-response)]))

(defn parse-value
  "AMON API specifies that when value is not present, error must be returned and vice versa."
  [measurement]
  (let [value (:value measurement)]
    (if-not (empty? value)
      (-> measurement
          (update-in [:value] read-string)
          (dissoc :error))
      (dissoc measurement :value))))

(def formatter (tf/formatter "yyyy-MM-dd'T'HH:mm:ssZ"))
(defn to-db-format [date]
  (tf/parse formatter date))
(defn db-to-iso [s]
  (let [date (misc/to-timestamp s)]
    (tf/unparse formatter (tc/from-date date))))

(defn time-range
  "Return a lazy sequence of DateTime's from start to end, incremented
  by 'step' units of time."
  [start end step]
  (let [inf-range (tp/periodic-seq start step)
        below-end? (fn [t] (t/within? (t/interval start end)
                                         t))]
    (take-while below-end? inf-range)))

(defn retrieve-measurements
  "Iterate over a sequence of months and concatanate measurements retrieved from the database."
  [querier start-date end-date device-id reading-type]
  (let [range  (time-range start-date end-date (t/months 1))
        months (map #(util/get-month-partition-key (tc/to-date %)) range)
        where  [[= :device-id device-id]
                [= :type reading-type]
                [>= :timestamp (tc/to-date start-date)]
                [<= :timestamp (tc/to-date end-date)]]]
    (mapcat (fn [month] (hecuba/items querier :measurement (conj where [= :month month]))) months)))

(defn measurements-slice-handle-ok [querier ctx]
  (let [request                (:request ctx)
        {:keys [route-params
                query-string]} request
        {:keys [device-id
                reading-type]} route-params
        decoded-params         (util/decode-query-params query-string)
        start-date             (to-db-format (string/replace (get decoded-params "startDate") "%20" " "))
        end-date               (to-db-format (string/replace (get decoded-params "endDate") "%20" " "))
        measurements           (retrieve-measurements querier start-date end-date device-id reading-type)]
    (util/downcast-to-json {:measurements (->> measurements
                                               (map (fn [m]
                                                      (-> m
                                                          parse-value
                                                          (update-in [:timestamp] db-to-iso)
                                                          (dissoc :month :metadata :device-id)
                                                          util/camelify))))})))

(defn index-post! [commander querier queue ctx]
  (let [request      (:request ctx)
        route-params (:route-params request)
        device-id    (:device-id route-params)
        topic        (get-in queue ["measurements"])
        measurements (:measurements (decode-body request))
        type         (get (first  measurements) "type")]
    (if (and device-id type (not (empty? (first (hecuba/items querier :sensor [[= :device-id device-id] [= :type type]])))))
      (do
        (doseq [measurement measurements]
          (let [t  (util/db-timestamp (get measurement "timestamp"))
                m  (stringify-values measurement)
                m2 {:device-id device-id
                    :type      type
                    :timestamp t
                    :value     (get m "value")
                    :error     (get m "error")
                    :month     (util/get-month-partition-key t)
                    :metadata  "{}"}]
            (->> m2
                 (v/validate commander querier)
                 (hecuba/upsert! commander :measurement))
            (q/put-on-queue topic m2)))
        {:response {:status 202 :body "Accepted"}})
      {:response {:status 400 :body "Provide valid deviceId and type."}})))

(defn index-handle-ok [querier ctx]
  (let [request (:request ctx)
        route-params (:route-params request)
        device-id (:device-id route-params)
        where [[= :device-id device-id]]
        measurements (hecuba/items querier :measurement where)]
    (util/downcast-to-json {:measurements (->> measurements
                                               (map #(-> %
                                                         parse-value
                                                         (update-in [:timestamp] db-to-iso)
                                                         (dissoc :metadata :device-id :month)
                                                         util/camelify)))})))

(defn index-handle-created [ctx]
  (ring-response (:response ctx)))

(defn measurements-by-reading-handle-ok [querier ctx]
  (let [{:keys [request]} ctx
        {:keys [route-params]} request
        {:keys [device-id sensor-type timestamp]} route-params
        measurement (first (hecuba/items querier :measurement [[= :device-id device-id] [= :type sensor-type] [= :timestamp timestamp]]))]
    (util/render-item request measurement)))

(defresource measurements-slice [{:keys [commander querier]} handlers]
  :allowed-methods #{:get}
  :available-media-types #{"application/json"}
  :known-content-type? #{"application/json"}
  :authorized? (authorized? querier :measurement)
  :handle-ok (partial measurements-slice-handle-ok querier))

(defresource index [{:keys [commander querier]} queue handlers]
  :allowed-methods #{:post :get}
  :available-media-types #{"application/json"}
  :known-content-type? #{"application/json"}
  :authorized? (authorized? querier :measurement)
  :post! (partial index-post! commander querier queue)
  :handle-ok (partial index-handle-ok querier)
  :handle-created index-handle-created)

(defresource measurements-by-reading [{:keys [commander querier]} handlers]
  :allowed-methods #{:get}
  :available-media-types #{"application/json"}
  :authorized? (authorized? querier :measurement)
  :handle-ok (partial measurements-by-reading-handle-ok querier))

(ns kixi.hecuba.api.measurements
  (:require
   [bidi.bidi :as bidi]
   [clojure.string :as string]
   [clojure.tools.logging :as log]
   [kixi.hecuba.data.validate :as v]
   [kixi.hecuba.data.misc :as misc]
   [kixi.hecuba.protocols :as hecuba]
   [kixi.hecuba.queue :as q]
   [kixi.hecuba.security :as sec]
   [kixi.hecuba.webutil :as util]
   [clj-time.coerce :as tc]
   [clj-time.format :as tf]
   [clj-time.core :as t]
   [clj-time.periodic :as tp]
   [kixi.hecuba.webutil :refer (decode-body authorized? uuid stringify-values sha1-regex)]
   [liberator.core :refer (defresource)]
   [liberator.representation :refer (ring-response)]))

(defn parse-value
  "AMON API specifies that when value is not present, error must be returned and vice versa."
  [measurement]
  (let [value (:value measurement)]
    (if-not (empty? value)
      (-> measurement
          (update-in [:value] read-string)
          (dissoc :error))
      (dissoc measurement :value))))

(def formatter (tf/formatter "yyyy-MM-dd'T'HH:mm:ssZ"))
(defn to-db-format [date]
  (tf/parse formatter date))
(defn db-to-iso [s]
  (let [date (misc/to-timestamp s)]
    (tf/unparse formatter (tc/from-date date))))

(defn time-range
  "Return a lazy sequence of DateTime's from start to end, incremented
  by 'step' units of time."
  [start end step]
  (let [start-date (t/first-day-of-the-month start)
        end-date   (t/last-day-of-the-month end)
        inf-range  (tp/periodic-seq start step)
        below-end? (fn [t] (t/within? (t/interval start end-date)
                                         t))]
    (take-while below-end? inf-range)))

(defn retrieve-measurements
  "Iterate over a sequence of months and concatanate measurements retrieved from the database."
  [querier start-date end-date device-id reading-type]
  (let [range  (time-range start-date end-date (t/months 1))
        months (map #(util/get-month-partition-key (tc/to-date %)) range)
        where  [[= :device-id device-id]
                [= :type reading-type]
                [>= :timestamp (tc/to-date start-date)]
                [<= :timestamp (tc/to-date end-date)]]]
    (mapcat (fn [month] (hecuba/items querier :measurement (conj where [= :month month]))) months)))

(defn measurements-slice-handle-ok [querier ctx]
  (let [request                (:request ctx)
        {:keys [route-params
                query-string]} request
        {:keys [device-id
                reading-type]} route-params
        decoded-params         (util/decode-query-params query-string)
        start-date             (to-db-format (string/replace (get decoded-params "startDate") "%20" " "))
        end-date               (to-db-format (string/replace (get decoded-params "endDate") "%20" " "))
        measurements           (retrieve-measurements querier start-date end-date device-id reading-type)]
    (util/downcast-to-json {:measurements (->> measurements
                                               (map (fn [m]
                                                      (-> m
                                                          parse-value
                                                          (update-in [:timestamp] db-to-iso)
                                                          (dissoc :month :metadata :device-id)
                                                          util/camelify))))})))

(defn index-post! [commander querier queue ctx]
  (let [request      (:request ctx)
        route-params (:route-params request)
        device-id    (:device-id route-params)
        topic        (get-in queue ["measurements"])
        measurements (:measurements (decode-body request))
        type         (get (first  measurements) "type")]
    (if (and device-id type (not (empty? (first (hecuba/items querier :sensor [[= :device-id device-id] [= :type type]])))))
      (do
        (doseq [measurement measurements]
          (let [t  (util/db-timestamp (get measurement "timestamp"))
                m  (stringify-values measurement)
                m2 {:device-id device-id
                    :type      type
                    :timestamp t
                    :value     (get m "value")
                    :error     (get m "error")
                    :month     (util/get-month-partition-key t)
                    :metadata  "{}"}]
            (->> m2
                 (v/validate commander querier)
                 (hecuba/upsert! commander :measurement))
            (q/put-on-queue topic m2)))
        {:response {:status 202 :body "Accepted"}})
      {:response {:status 400 :body "Provide valid deviceId and type."}})))

(defn index-handle-ok [querier ctx]
  (let [request (:request ctx)
        route-params (:route-params request)
        device-id (:device-id route-params)
        where [[= :device-id device-id]]
        measurements (hecuba/items querier :measurement where)]
    (util/downcast-to-json {:measurements (->> measurements
                                               (map #(-> %
                                                         parse-value
                                                         (update-in [:timestamp] db-to-iso)
                                                         (dissoc :metadata :device-id :month)
                                                         util/camelify)))})))

(defn index-handle-created [ctx]
  (ring-response (:response ctx)))

(defn measurements-by-reading-handle-ok [querier ctx]
  (let [{:keys [request]} ctx
        {:keys [route-params]} request
        {:keys [device-id sensor-type timestamp]} route-params
        measurement (first (hecuba/items querier :measurement [[= :device-id device-id] [= :type sensor-type] [= :timestamp timestamp]]))]
    (util/render-item request measurement)))

(defresource measurements-slice [{:keys [commander querier]} handlers]
  :allowed-methods #{:get}
  :available-media-types #{"application/json"}
  :known-content-type? #{"application/json"}
  :authorized? (authorized? querier :measurement)
  :handle-ok (partial measurements-slice-handle-ok querier))

(defresource index [{:keys [commander querier]} queue handlers]
  :allowed-methods #{:post :get}
  :available-media-types #{"application/json"}
  :known-content-type? #{"application/json"}
  :authorized? (authorized? querier :measurement)
  :post! (partial index-post! commander querier queue)
  :handle-ok (partial index-handle-ok querier)
  :handle-created index-handle-created)

(defresource measurements-by-reading [{:keys [commander querier]} handlers]
  :allowed-methods #{:get}
  :available-media-types #{"application/json"}
  :authorized? (authorized? querier :measurement)
  :handle-ok (partial measurements-by-reading-handle-ok querier))

Fix missing last month in GET.

Fix missing last month in GET.


(ns scrabble-score-test
  (:require [clojure.test :refer [deftest is]]
            scrabble-score))

(deftest lower-case-letter
  (is (= 1 (scrabble-score/score-letter "a"))))

(deftest upper-case-letter
  (is (= 1 (scrabble-score/score-letter "A"))))

(deftest two-letter-word
  (is (= 2 (scrabble-score/score-word "at"))))

(deftest bigger-word-1
  (is (= 6 (scrabble-score/score-word "street"))))

(deftest bigger-word-2
  (is (= 22 (scrabble-score/score-word "quirky"))))

(deftest all-upper-case-word
  (is (= 20 (scrabble-score/score-word "MULTIBILLIONAIRE"))))

(ns scrabble-score-test
  (:require [clojure.test :refer [deftest is]]
            scrabble-score))

(deftest lower-case-letter
  (is (= 1 (scrabble-score/score-letter "a"))))

(deftest upper-case-letter
  (is (= 1 (scrabble-score/score-letter "A"))))

(deftest two-letter-word
  (is (= 2 (scrabble-score/score-word "at"))))

(deftest bigger-word-1
  (is (= 6 (scrabble-score/score-word "street"))))

(deftest bigger-word-2
  (is (= 22 (scrabble-score/score-word "quirky"))))

(deftest all-upper-case-word
  (is (= 41 (scrabble-score/score-word "OXYPHENBUTAZONE"))))

Update scrabble word oxyphenbutazone

Update scrabble word oxyphenbutazone


(ns ctia.entity.investigation
  (:require [ctia.domain.entities :refer [default-realize-fn]]
            [ctia.store :refer :all]
            [ctia.http.routes
             [common :refer [BaseEntityFilterParams PagingParams SourcableEntityFilterParams]]
             [crud :refer [entity-crud-routes]]]
            [ctia.schemas
             [utils :as csu]
             [core :refer [def-stored-schema
                           CTIAEntity]]
             [sorting :as sorting]]
            [ctia.schemas.graphql
             [sorting :as graphql-sorting]
             [flanders :as flanders]
             [helpers :as g]
             [pagination :as pagination]]
            [ctia.stores.es
             [mapping :as em]
             [store :refer [def-es-store]]]
            [ctim.schemas.common :refer [IdentitySpecification]]
            [ctim.schemas.investigation :as inv]
            [flanders
             [schema :as f-schema]
             [spec :as f-spec]
             [utils :as fu]]
            [schema-tools.core :as st]
            [schema.core :as s]
            [ctia.schemas.graphql.ownership :as go]))

(s/defschema Investigation
  (st/merge (f-schema/->schema inv/Investigation)
            CTIAEntity
            {(s/required-key :actions) (s/either s/Str {s/Keyword s/Any})
             (s/required-key :object_ids) [s/Str]
             ;; The value of this field should look like "type:value".
             (s/required-key :investigated_observables [s/Str])
             (s/required-key :targets [(f-schema/->schema IdentitySpecification)])
             s/Keyword s/Any}))

(f-spec/->spec inv/Investigation "investigation")

(s/defschema PartialInvestigation
  (st/merge (f-schema/->schema (fu/optionalize-all inv/Investigation))
            CTIAEntity
            {s/Keyword s/Any}))

(s/defschema PartialInvestigationList
  [PartialInvestigation])

(s/defschema NewInvestigation
  (st/merge (f-schema/->schema inv/NewInvestigation)
            CTIAEntity
            {s/Keyword s/Any}))

(f-spec/->spec inv/NewInvestigation "new-investigation")

(def-stored-schema StoredInvestigation Investigation)

(s/defschema PartialStoredInvestigation
  (csu/optional-keys-schema StoredInvestigation))

(def realize-investigation
  (default-realize-fn "investigation" NewInvestigation StoredInvestigation))

(def snapshot-action-fields-mapping
  {:object_ids {:type "text"
                :analyzer "text_analyzer"
                :search_quote_analyzer "text_analyzer"
                :search_analyzer "search_analyzer"
                :include_in_all false}
   :targets {:type "nested"
             :include_in_all false}
   :investigated_observables {:type "text"
                              :analyzer "text_analyzer"
                              :search_quote_analyzer "text_analyzer"
                              :search_analyzer "search_analyzer"
                              :include_in_all false}})

(def investigation-mapping
  {"investigation"
   {:dynamic false
    :properties
    (merge
     em/base-entity-mapping
     em/describable-entity-mapping
     em/sourcable-entity-mapping
     em/stored-entity-mapping
     snapshot-action-fields-mapping)}})

(def-es-store InvestigationStore :investigation
  StoredInvestigation
  PartialStoredInvestigation)

(def investigation-fields
  (concat sorting/default-entity-sort-fields
          sorting/describable-entity-sort-fields
          sorting/sourcable-entity-sort-fields))

(def investigation-sort-fields
  (apply s/enum investigation-fields))

(def investigation-select-fields
  (apply s/enum (concat investigation-fields
                        [:description
                         :type
                         :search-txt
                         :short_description
                         :created_at])))

(s/defschema InvestigationFieldsParam
  {(s/optional-key :fields) [investigation-select-fields]})

(s/defschema InvestigationSearchParams
  (st/merge
   PagingParams
   BaseEntityFilterParams
   SourcableEntityFilterParams
   InvestigationFieldsParam
   {:query s/Str}
   {s/Keyword s/Any}))

(def InvestigationGetParams InvestigationFieldsParam)

(s/defschema InvestigationsByExternalIdQueryParams
  (st/merge
   InvestigationFieldsParam
   PagingParams))

(def InvestigationType
  (let [{:keys [fields name description]}
        (flanders/->graphql
         (fu/optionalize-all inv/Investigation)
         {})]
    (g/new-object
     name
     description
     []
     (merge
      fields go/graphql-ownership-fields))))

(def investigation-order-arg
  (graphql-sorting/order-by-arg
   "InvestigationOrder"
   "investigations"
   (into {}
         (map (juxt graphql-sorting/sorting-kw->enum-name name)
              investigation-fields))))

(def InvestigationConnectionType
  (pagination/new-connection InvestigationType))

(def investigation-routes
  (entity-crud-routes
   {:entity :investigation
    :new-schema NewInvestigation
    :entity-schema Investigation
    :get-schema PartialInvestigation
    :get-params InvestigationGetParams
    :list-schema PartialInvestigationList
    :search-schema PartialInvestigationList
    :external-id-q-params InvestigationsByExternalIdQueryParams
    :search-q-params InvestigationSearchParams
    :new-spec :new-investigation/map
    :realize-fn realize-investigation
    :get-capabilities :read-investigation
    :post-capabilities :create-investigation
    :put-capabilities :create-investigation
    :delete-capabilities :delete-investigation
    :search-capabilities :search-investigation
    :external-id-capabilities :read-investigation}))

(def capabilities
  #{:read-investigation
    :list-investigations
    :create-investigation
    :search-investigation
    :delete-investigation})

(def investigation-entity
  {:route-context "/investigation"
   :tags ["Investigation"]
   :entity :investigation
   :plural :investigations
   :new-spec :new-investigation/map
   :schema Investigation
   :partial-schema PartialInvestigation
   :partial-list-schema PartialInvestigationList
   :new-schema NewInvestigation
   :stored-schema StoredInvestigation
   :partial-stored-schema PartialStoredInvestigation
   :realize-fn realize-investigation
   :es-store ->InvestigationStore
   :es-mapping investigation-mapping
   :routes investigation-routes
   :capabilities capabilities})

(ns ctia.entity.investigation
  (:require [ctia.domain.entities :refer [default-realize-fn]]
            [ctia.store :refer :all]
            [ctia.http.routes
             [common :refer [BaseEntityFilterParams PagingParams SourcableEntityFilterParams]]
             [crud :refer [entity-crud-routes]]]
            [ctia.schemas
             [utils :as csu]
             [core :refer [def-stored-schema
                           CTIAEntity]]
             [sorting :as sorting]]
            [ctia.schemas.graphql
             [sorting :as graphql-sorting]
             [flanders :as flanders]
             [helpers :as g]
             [pagination :as pagination]]
            [ctia.stores.es
             [mapping :as em]
             [store :refer [def-es-store]]]
            [ctim.schemas.common :refer [IdentitySpecification]]
            [ctim.schemas.investigation :as inv]
            [flanders
             [schema :as f-schema]
             [spec :as f-spec]
             [utils :as fu]]
            [schema-tools.core :as st]
            [schema.core :as s]
            [ctia.schemas.graphql.ownership :as go]))

(s/defschema Investigation
  (st/merge (f-schema/->schema inv/Investigation)
            CTIAEntity
            {(s/required-key :actions) (s/either s/Str {s/Keyword s/Any})
             (s/required-key :object_ids) [s/Str]
             ;; The value of this field should look like "type:value".
             (s/required-key :investigated_observables) [s/Str]
             (s/required-key :targets) [(f-schema/->schema IdentitySpecification)]
             s/Keyword s/Any}))

(f-spec/->spec inv/Investigation "investigation")

(s/defschema PartialInvestigation
  (st/merge (f-schema/->schema (fu/optionalize-all inv/Investigation))
            CTIAEntity
            {s/Keyword s/Any}))

(s/defschema PartialInvestigationList
  [PartialInvestigation])

(s/defschema NewInvestigation
  (st/merge (f-schema/->schema inv/NewInvestigation)
            CTIAEntity
            {s/Keyword s/Any}))

(f-spec/->spec inv/NewInvestigation "new-investigation")

(def-stored-schema StoredInvestigation Investigation)

(s/defschema PartialStoredInvestigation
  (csu/optional-keys-schema StoredInvestigation))

(def realize-investigation
  (default-realize-fn "investigation" NewInvestigation StoredInvestigation))

(def snapshot-action-fields-mapping
  {:object_ids {:type "text"
                :analyzer "text_analyzer"
                :search_quote_analyzer "text_analyzer"
                :search_analyzer "search_analyzer"
                :include_in_all false}
   :targets {:type "nested"
             :include_in_all false}
   :investigated_observables {:type "text"
                              :analyzer "text_analyzer"
                              :search_quote_analyzer "text_analyzer"
                              :search_analyzer "search_analyzer"
                              :include_in_all false}})

(def investigation-mapping
  {"investigation"
   {:dynamic false
    :properties
    (merge
     em/base-entity-mapping
     em/describable-entity-mapping
     em/sourcable-entity-mapping
     em/stored-entity-mapping
     snapshot-action-fields-mapping)}})

(def-es-store InvestigationStore :investigation
  StoredInvestigation
  PartialStoredInvestigation)

(def investigation-fields
  (concat sorting/default-entity-sort-fields
          sorting/describable-entity-sort-fields
          sorting/sourcable-entity-sort-fields))

(def investigation-sort-fields
  (apply s/enum investigation-fields))

(def investigation-select-fields
  (apply s/enum (concat investigation-fields
                        [:description
                         :type
                         :search-txt
                         :short_description
                         :created_at])))

(s/defschema InvestigationFieldsParam
  {(s/optional-key :fields) [investigation-select-fields]})

(s/defschema InvestigationSearchParams
  (st/merge
   PagingParams
   BaseEntityFilterParams
   SourcableEntityFilterParams
   InvestigationFieldsParam
   {:query s/Str}
   {s/Keyword s/Any}))

(def InvestigationGetParams InvestigationFieldsParam)

(s/defschema InvestigationsByExternalIdQueryParams
  (st/merge
   InvestigationFieldsParam
   PagingParams))

(def InvestigationType
  (let [{:keys [fields name description]}
        (flanders/->graphql
         (fu/optionalize-all inv/Investigation)
         {})]
    (g/new-object
     name
     description
     []
     (merge
      fields go/graphql-ownership-fields))))

(def investigation-order-arg
  (graphql-sorting/order-by-arg
   "InvestigationOrder"
   "investigations"
   (into {}
         (map (juxt graphql-sorting/sorting-kw->enum-name name)
              investigation-fields))))

(def InvestigationConnectionType
  (pagination/new-connection InvestigationType))

(def investigation-routes
  (entity-crud-routes
   {:entity :investigation
    :new-schema NewInvestigation
    :entity-schema Investigation
    :get-schema PartialInvestigation
    :get-params InvestigationGetParams
    :list-schema PartialInvestigationList
    :search-schema PartialInvestigationList
    :external-id-q-params InvestigationsByExternalIdQueryParams
    :search-q-params InvestigationSearchParams
    :new-spec :new-investigation/map
    :realize-fn realize-investigation
    :get-capabilities :read-investigation
    :post-capabilities :create-investigation
    :put-capabilities :create-investigation
    :delete-capabilities :delete-investigation
    :search-capabilities :search-investigation
    :external-id-capabilities :read-investigation}))

(def capabilities
  #{:read-investigation
    :list-investigations
    :create-investigation
    :search-investigation
    :delete-investigation})

(def investigation-entity
  {:route-context "/investigation"
   :tags ["Investigation"]
   :entity :investigation
   :plural :investigations
   :new-spec :new-investigation/map
   :schema Investigation
   :partial-schema PartialInvestigation
   :partial-list-schema PartialInvestigationList
   :new-schema NewInvestigation
   :stored-schema StoredInvestigation
   :partial-stored-schema PartialStoredInvestigation
   :realize-fn realize-investigation
   :es-store ->InvestigationStore
   :es-mapping investigation-mapping
   :routes investigation-routes
   :capabilities capabilities})

Fix s/require-key syntax

Fix s/require-key syntax