├── README.md ├── .gitignore ├── resources └── public │ ├── favicon.ico │ ├── like.svg │ ├── index.html │ ├── logo.svg │ └── hammer.min.js ├── dev.cljs.edn ├── prod.cljs.edn ├── deps.edn └── src └── hp ├── specs.cljc ├── core.clj └── core.cljs /README.md: -------------------------------------------------------------------------------- 1 | Tinder UX mobile web app for [happypaw.ua](https://happypaw.ua) 2 | http://happy-paw.surge.sh/ 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cpcache 2 | .idea 3 | resources/public/out 4 | *.iml 5 | .nrepl-port 6 | resources/public/main.js 7 | -------------------------------------------------------------------------------- /resources/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/roman01la/happy-paw/master/resources/public/favicon.ico -------------------------------------------------------------------------------- /dev.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main hp.core 2 | :output-dir "resources/public/out" 3 | :output-to "resources/public/main.js"} 4 | -------------------------------------------------------------------------------- /prod.cljs.edn: -------------------------------------------------------------------------------- 1 | {:main hp.core 2 | :output-dir "resources/public/out" 3 | :output-to "resources/public/main.js" 4 | :infer-externs true 5 | :optimizations :advanced 6 | :fn-invoke-direct true} 7 | -------------------------------------------------------------------------------- /resources/public/like.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /deps.edn: -------------------------------------------------------------------------------- 1 | {:deps {clj-http {:mvn/version "3.10.0"} 2 | enlive {:mvn/version "1.1.6"} 3 | uix.core {:git/url "https://github.com/roman01la/uix.git" 4 | :deps/root "core" 5 | :sha "e63bcc3f9e19037633b56b8751d2976c036a955e"} 6 | uix.dom {:git/url "https://github.com/roman01la/uix.git" 7 | :deps/root "dom" 8 | :sha "e63bcc3f9e19037633b56b8751d2976c036a955e"} 9 | com.bhauman/figwheel-main {:mvn/version "0.2.3"}} 10 | :paths ["src" "resources"]} 11 | -------------------------------------------------------------------------------- /src/hp/specs.cljc: -------------------------------------------------------------------------------- 1 | (ns hp.specs 2 | (:require [clojure.spec.alpha :as s])) 3 | 4 | (s/def :filter/Type 5 | #{2}) 6 | 7 | (s/def :filter/City 8 | #{36, 55, 58, 59, 74, 83, 85, 90, 106, 131, 146, 154, 168, 178, 181, 183, 184, 185, 186, 187, 188, 191, 192, 196, 209, 265, 286, 303, 312, 317, 319, 331, 343, 359, 368, 378, 379, 384, 394, 400, 415, 432, 444, 452}) 9 | 10 | (s/def :filter/Name string?) 11 | 12 | (s/def :filter/Age 13 | #{1 2 3 4 5 6 7 8 9 10 11}) 14 | 15 | (s/def :filter/Size 16 | #{5 6 7 8}) 17 | 18 | (s/def :filter/Gender 19 | #{:male :female}) 20 | 21 | (s/def :filter/Sterile 22 | #{:sterile :nosterile}) 23 | 24 | (s/def :filter/Uniq string?) 25 | 26 | (s/def :filter/AnimalId string?) 27 | 28 | (s/def :filter/Host 29 | #{-1, 45, 48, 55, 57, 58, 59, 60, 61, 63, 64, 65, 66, 67, 69, 70, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116}) 30 | 31 | (s/def :filter/Labels 32 | #{:not-sheltered :sheltered :ill}) 33 | -------------------------------------------------------------------------------- /resources/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | HappyPaw 6 | 7 | 8 | 58 | 59 | 60 | 61 |
62 | 63 |
64 | 65 | 66 | 67 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/hp/core.clj: -------------------------------------------------------------------------------- 1 | (ns hp.core 2 | (:require [clj-http.client :as http] 3 | [net.cgrand.enlive-html :as html] 4 | [clojure.spec.alpha :as s] 5 | [clojure.string :as str] 6 | [hp.specs :as hp.specs])) 7 | 8 | (s/check-asserts true) 9 | 10 | (s/def :req/filter 11 | (s/keys :req-un [:filter/Type :filter/City] 12 | :opt-un [:filter/Name 13 | :filter/Age :filter/Size :filter/Gender 14 | :filter/Sterile :filter/Uniq :filter/AnimalId 15 | :filter/Host :filter/Labels])) 16 | 17 | (s/def :req/per-page pos-int?) 18 | (s/def :req/page pos-int?) 19 | 20 | (s/def :req-url/opts 21 | (s/keys :req-un [:req/filter :req/per-page :req/page])) 22 | 23 | (def BASE_URL "https://happypaw.ua") 24 | (def USER_AGENT "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/79.0.3945.88 Safari/537.36") 25 | 26 | (defn make-dog-url [{:keys [page per-page] :as opts}] 27 | {:pre [(s/assert :req-url/opts opts)]} 28 | (let [filters (->> (:filter opts) 29 | (filter (comp some? val)) 30 | (map #(str "filter[" (name (key %)) "]=" (val %))) 31 | (str/join "&") 32 | (str "?"))] 33 | (str BASE_URL "/ru/shelter/dog" 34 | filters 35 | "&per_page=" per-page 36 | "&page=" page))) 37 | 38 | (defn find-all-dogs [] 39 | (-> (make-dog-url {:page 1 40 | :per-page 500 41 | :filter {:Type 2 42 | :City 36}}) 43 | (http/get {:headers {:User-Agent USER_AGENT} :as :stream}) 44 | :body)) 45 | 46 | (defn fetch-details [id] 47 | (->> (http/get (str BASE_URL "/ru/shelter/card/" id) {:headers {:User-Agent USER_AGENT} :as :stream}) 48 | :body)) 49 | 50 | (defn string->number [s] 51 | (->> s 52 | str/trim 53 | (re-find #"[0-9]+") 54 | read-string)) 55 | 56 | (defn parse-results [h] 57 | (let [names (->> (html/select h [:.list-results :.list-item-title]) 58 | (map (comp str/trim first :content))) 59 | images (->> (html/select h [:.list-results :.list-item-image-src]) 60 | (map (comp #(str BASE_URL %) :src :attrs))) 61 | descriptions (->> (html/select h [:.list-results :.list-item-text]) 62 | (map (comp str/trim first :content))) 63 | ids (->> (html/select h [:.list-results :.list-item-id]) 64 | (map (comp string->number first :content))) 65 | fields [[:pet/name names] 66 | [:pet/image images] 67 | [:pet/description descriptions] 68 | [:pet/id ids]]] 69 | (->> (apply interleave (map second fields)) 70 | (partition (count fields)) 71 | (map #(zipmap (map first fields) %))))) 72 | 73 | (defn parse-details [h] 74 | (let [images (->> (html/select h [:.animal :.list-item-image-src]) 75 | (map (comp #(str BASE_URL %) :data-full :attrs))) 76 | details (->> (html/select h [:.animal-traits :.animal-trait]) 77 | (map (comp str/trim last :content)) 78 | (filter (comp not str/blank?)) 79 | (zipmap [:pet/age :pet/gender :pet/height :pet/steril 80 | :owner/name :owner/city :owner/phone-number :owner/email]))] 81 | (assoc details :pet/images images))) 82 | 83 | (def cache (atom {:dogs/index nil})) 84 | 85 | (defn find-all-dogs-with-cache [] 86 | (if-some [dogs (:dogs/index @cache)] 87 | dogs 88 | (->> (find-all-dogs) 89 | html/html-resource 90 | parse-results 91 | (reduce #(assoc! %1 (:pet/id %2) %2) (transient {})) 92 | persistent! 93 | (swap! cache assoc :dogs/index) 94 | :dogs/index))) 95 | 96 | (defn fetch-parsed-details [id] 97 | (->> (fetch-details id) 98 | html/html-resource 99 | parse-details)) 100 | 101 | (defn update-db! [] 102 | (let [dogs (find-all-dogs-with-cache)] 103 | (->> (keys dogs) 104 | (reduce #(update %1 %2 merge (fetch-parsed-details %2)) dogs) 105 | (swap! cache assoc :dogs/index)))) 106 | 107 | (defn persist-cache [] 108 | (when-some [dogs (:dogs/index @cache)] 109 | (spit "cache.edn" (str dogs)))) 110 | 111 | (defn hydrate-cache [] 112 | (->> (slurp "cache.edn") 113 | read-string 114 | (swap! cache assoc :dogs/index))) 115 | 116 | (comment 117 | (hydrate-cache) 118 | (update-db!) 119 | (persist-cache)) 120 | 121 | -------------------------------------------------------------------------------- /src/hp/core.cljs: -------------------------------------------------------------------------------- 1 | (ns ^:figwheel-hooks hp.core 2 | (:require [uix.core.alpha :as uix.core] 3 | [uix.dom.alpha :as uix.dom] 4 | [cljs.tools.reader :as reader] 5 | [hp.specs :as hp.specs])) 6 | 7 | (set! *warn-on-infer* true) 8 | 9 | (def cities 10 | (->> [[36,"Киев"],[55,"Винница"],[58,"Казатин"],[59,"Калиновка"],[74,"Луцк"],[83,"Днепропетровск"],[85,"Кривой Рог"],[90,"Павлоград"],[106,"Донецк"],[131,"Житомир"],[146,"Ужгород"],[154,"Запорожье"],[168,"Ивано-Франковск"],[178,"Белая Церковь"],[181,"Борисполь"],[183,"Бровары"],[184,"Буча"],[185,"Васильков"],[186,"Вишнёвое"],[187,"Вышгород"],[188,"Ирпень"],[191,"Обухов"],[192,"Переяслав-Хмельницкий"],[196,"Славутич"],[209,"Кропивницкий"],[265,"Львов"],[286,"Николаев"],[303,"Одесса"],[312,"Кременчуг"],[317,"Миргород"],[319,"Полтава"],[331,"Ровно"],[343,"Сумы"],[359,"Тернополь"],[368,"Волчанск"],[378,"Харьков"],[379,"Чугуев"],[384,"Новая Каховка"],[394,"Каменец-Подольский"],[400,"Хмельницкий"],[415,"Черкассы"],[432,"Чернигов"],[444,"Черновцы"],[452,"Макаров"]] 11 | (into (sorted-map)))) 12 | 13 | 14 | (defonce db (atom {:dogs []})) 15 | 16 | (defn fetch-cache [] 17 | (-> (js/fetch "/cache.edn") 18 | (.then #(.text ^js %)) 19 | (.then #(reader/read-string %)))) 20 | 21 | (defn app-top-bar [{:keys [city on-change-city]}] 22 | [:div {:style {:height 64 23 | :max-height 64 24 | :min-height 64 25 | :width "100%" 26 | :background "#fff" 27 | :box-shadow "0 2px 8px rgba(0, 0, 0, 0.07)" 28 | :display :flex 29 | :align-items :center 30 | :justify-content :center}} 31 | [:a {:href "https://happypaw.ua/ua"} 32 | [:img {:src "logo.svg" 33 | :style {:height 40}}]] 34 | #_ 35 | [:select {:value city 36 | :on-change #(on-change-city (.. % -target -value)) 37 | :style {:font-size 16}} 38 | (for [[code name] cities] 39 | ^{:key code} 40 | [:option {:value code} name])]]) 41 | 42 | (defn app-bottom-bar [] 43 | [:div {:style {:height 64 44 | :max-height 64 45 | :min-height 64 46 | :width "100%" 47 | :display :flex 48 | :justify-content :center}} 49 | [:button {:style {:border :none 50 | :padding 0 51 | :display :block 52 | :box-shadow "0 1px 4px rgba(0,0,0,0.2)" 53 | :border-radius "50%" 54 | :width 44 55 | :height 44 56 | :background-color "#f93824" 57 | :background-position "10px 11px" 58 | :background-repeat :no-repeat 59 | :background-image "url(like.svg)"}}]]) 60 | 61 | (defn pet-view [{:keys [pet on-prev on-next iidx change-iidx]}] 62 | (let [{:pet/keys [name gender age image images]} pet 63 | imgs (uix.core/state {}) 64 | ref (uix.core/ref) 65 | img-loaded? (contains? @imgs (nth images iidx))] 66 | (uix.core/effect! 67 | (fn [] 68 | (-> (js/Hammer. @ref) 69 | (.on "swipe" (fn [^js e] 70 | (cond 71 | (and (< (.-deltaX e) -100) (< (.-deltaY e) 50)) 72 | (on-next) 73 | 74 | (and (> (.-deltaX e) 100) (< (.-deltaY e) 50)) 75 | (on-prev)))))) 76 | []) 77 | (uix.core/effect! 78 | (fn [] 79 | (let [rw (.-clientWidth ^js @ref) 80 | rh (.-clientHeight ^js @ref)] 81 | (doseq [url images] 82 | (let [img (js/Image.)] 83 | (set! (.-onload img) 84 | #(let [w (.-naturalWidth img) 85 | h (.-naturalHeight img) 86 | ratio (Math/floor (/ w rw)) 87 | sw (/ w ratio) 88 | sh (/ h ratio)] 89 | (swap! imgs assoc url [sw sh]))) 90 | (set! (.-src img) url))))) 91 | [images]) 92 | [:div {:ref ref 93 | :style {:flex 1 94 | :display :flex 95 | :flex-direction :column 96 | :border-radius 7 97 | :overflow :hidden 98 | :box-shadow "0 0 8px rgba(0, 0, 0, 0.07)" 99 | :background-image (when img-loaded? 100 | (str "url(" (nth images iidx) ")")) 101 | :background-size :cover 102 | :color "#fff" 103 | :text-shadow "0 2px 4px rgba(0,0,0,0.3)"}} 104 | [:div {:style {:padding "4px 4px 0 4px" 105 | :display :flex}} 106 | (when (> (count images) 1) 107 | (for [idx (range (count images))] 108 | ^{:key (nth images idx)} 109 | [:div {:style {:margin "0 1px" 110 | :flex 1 111 | :background (if (== iidx idx) 112 | "#fff" 113 | "rgba(255,255,255,0.6)") 114 | :height 4 115 | :border-radius 2}}]))] 116 | [:div {:style {:flex 1 117 | :display :flex}} 118 | [:div {:style {:flex 1} 119 | :on-click (when (pos? iidx) 120 | #(change-iidx dec))}] 121 | [:div {:style {:flex 1} 122 | :on-click (when (< iidx (dec (count images))) 123 | #(change-iidx inc))}]] 124 | [:div {:style {:display :flex 125 | :align-items :center 126 | :justify-self :flex-end 127 | :padding 8 128 | :background "rgba(0,0,0,0.3)"}} 129 | [:div {:style {:font-size "24px" 130 | :font-weight 700}} 131 | name] 132 | [:div {:style {:font-size "16px" 133 | :margin-left 24 134 | :font-weight 600}} 135 | (str gender ", " age)]]])) 136 | 137 | (defn app-main [{:keys [pets]}] 138 | (let [idx (uix.core/state 0) 139 | iidx (uix.core/state 0) 140 | change-idx #(do (reset! iidx 0) 141 | (reset! idx %))] 142 | [:div {:style {:flex 1 143 | :display :flex 144 | :flex-direction :column 145 | :padding 16}} 146 | [pet-view {:pet (nth pets @idx) 147 | :iidx @iidx 148 | :change-iidx #(swap! iidx %) 149 | :on-prev #(when (pos? @idx) 150 | (change-idx (dec @idx))) 151 | :on-next #(when (< @idx (count pets)) 152 | (change-idx (inc @idx)))}]])) 153 | 154 | 155 | (defn app [st] 156 | (let [city (uix.core/state 36)] 157 | [:div {:style {:display :flex 158 | :flex-direction :column 159 | :background "#fff" 160 | :height "100%"}} 161 | [app-top-bar {:city @city 162 | :on-change-city #(reset! city %)}] 163 | [app-main {:pets (:dogs st)}] 164 | [app-bottom-bar]])) 165 | 166 | (defn ^:after-load render [] 167 | (uix.dom/render [app @db] js/root)) 168 | 169 | (defn ^:export init [] 170 | (-> (fetch-cache) 171 | (.then #(swap! db assoc :dogs (vec (vals %))))) 172 | (add-watch db ::watch #(render))) 173 | -------------------------------------------------------------------------------- /resources/public/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 12 | 14 | 32 | 33 | 35 | 45 | 47 | 53 | 55 | 56 | 59 | 62 | 65 | 68 | 71 | 74 | 77 | 86 | 87 | 89 | 90 | 98 | 99 | 101 | 104 | 105 | 107 | 109 | 112 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /resources/public/hammer.min.js: -------------------------------------------------------------------------------- 1 | /*! Hammer.JS - v2.0.8 - 2016-04-23 2 | * http://hammerjs.github.io/ 3 | * 4 | * Copyright (c) 2016 Jorik Tangelder; 5 | * Licensed under the MIT license */ 6 | !function(a,b,c,d){"use strict";function e(a,b,c){return setTimeout(j(a,c),b)}function f(a,b,c){return Array.isArray(a)?(g(a,c[b],c),!0):!1}function g(a,b,c){var e;if(a)if(a.forEach)a.forEach(b,c);else if(a.length!==d)for(e=0;e\s*\(/gm,"{anonymous}()@"):"Unknown Stack Trace",f=a.console&&(a.console.warn||a.console.log);return f&&f.call(a.console,e,d),b.apply(this,arguments)}}function i(a,b,c){var d,e=b.prototype;d=a.prototype=Object.create(e),d.constructor=a,d._super=e,c&&la(d,c)}function j(a,b){return function(){return a.apply(b,arguments)}}function k(a,b){return typeof a==oa?a.apply(b?b[0]||d:d,b):a}function l(a,b){return a===d?b:a}function m(a,b,c){g(q(b),function(b){a.addEventListener(b,c,!1)})}function n(a,b,c){g(q(b),function(b){a.removeEventListener(b,c,!1)})}function o(a,b){for(;a;){if(a==b)return!0;a=a.parentNode}return!1}function p(a,b){return a.indexOf(b)>-1}function q(a){return a.trim().split(/\s+/g)}function r(a,b,c){if(a.indexOf&&!c)return a.indexOf(b);for(var d=0;dc[b]}):d.sort()),d}function u(a,b){for(var c,e,f=b[0].toUpperCase()+b.slice(1),g=0;g1&&!c.firstMultiple?c.firstMultiple=D(b):1===e&&(c.firstMultiple=!1);var f=c.firstInput,g=c.firstMultiple,h=g?g.center:f.center,i=b.center=E(d);b.timeStamp=ra(),b.deltaTime=b.timeStamp-f.timeStamp,b.angle=I(h,i),b.distance=H(h,i),B(c,b),b.offsetDirection=G(b.deltaX,b.deltaY);var j=F(b.deltaTime,b.deltaX,b.deltaY);b.overallVelocityX=j.x,b.overallVelocityY=j.y,b.overallVelocity=qa(j.x)>qa(j.y)?j.x:j.y,b.scale=g?K(g.pointers,d):1,b.rotation=g?J(g.pointers,d):0,b.maxPointers=c.prevInput?b.pointers.length>c.prevInput.maxPointers?b.pointers.length:c.prevInput.maxPointers:b.pointers.length,C(c,b);var k=a.element;o(b.srcEvent.target,k)&&(k=b.srcEvent.target),b.target=k}function B(a,b){var c=b.center,d=a.offsetDelta||{},e=a.prevDelta||{},f=a.prevInput||{};b.eventType!==Ea&&f.eventType!==Ga||(e=a.prevDelta={x:f.deltaX||0,y:f.deltaY||0},d=a.offsetDelta={x:c.x,y:c.y}),b.deltaX=e.x+(c.x-d.x),b.deltaY=e.y+(c.y-d.y)}function C(a,b){var c,e,f,g,h=a.lastInterval||b,i=b.timeStamp-h.timeStamp;if(b.eventType!=Ha&&(i>Da||h.velocity===d)){var j=b.deltaX-h.deltaX,k=b.deltaY-h.deltaY,l=F(i,j,k);e=l.x,f=l.y,c=qa(l.x)>qa(l.y)?l.x:l.y,g=G(j,k),a.lastInterval=b}else c=h.velocity,e=h.velocityX,f=h.velocityY,g=h.direction;b.velocity=c,b.velocityX=e,b.velocityY=f,b.direction=g}function D(a){for(var b=[],c=0;ce;)c+=a[e].clientX,d+=a[e].clientY,e++;return{x:pa(c/b),y:pa(d/b)}}function F(a,b,c){return{x:b/a||0,y:c/a||0}}function G(a,b){return a===b?Ia:qa(a)>=qa(b)?0>a?Ja:Ka:0>b?La:Ma}function H(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return Math.sqrt(d*d+e*e)}function I(a,b,c){c||(c=Qa);var d=b[c[0]]-a[c[0]],e=b[c[1]]-a[c[1]];return 180*Math.atan2(e,d)/Math.PI}function J(a,b){return I(b[1],b[0],Ra)+I(a[1],a[0],Ra)}function K(a,b){return H(b[0],b[1],Ra)/H(a[0],a[1],Ra)}function L(){this.evEl=Ta,this.evWin=Ua,this.pressed=!1,x.apply(this,arguments)}function M(){this.evEl=Xa,this.evWin=Ya,x.apply(this,arguments),this.store=this.manager.session.pointerEvents=[]}function N(){this.evTarget=$a,this.evWin=_a,this.started=!1,x.apply(this,arguments)}function O(a,b){var c=s(a.touches),d=s(a.changedTouches);return b&(Ga|Ha)&&(c=t(c.concat(d),"identifier",!0)),[c,d]}function P(){this.evTarget=bb,this.targetIds={},x.apply(this,arguments)}function Q(a,b){var c=s(a.touches),d=this.targetIds;if(b&(Ea|Fa)&&1===c.length)return d[c[0].identifier]=!0,[c,c];var e,f,g=s(a.changedTouches),h=[],i=this.target;if(f=c.filter(function(a){return o(a.target,i)}),b===Ea)for(e=0;e-1&&d.splice(a,1)};setTimeout(e,cb)}}function U(a){for(var b=a.srcEvent.clientX,c=a.srcEvent.clientY,d=0;d=f&&db>=g)return!0}return!1}function V(a,b){this.manager=a,this.set(b)}function W(a){if(p(a,jb))return jb;var b=p(a,kb),c=p(a,lb);return b&&c?jb:b||c?b?kb:lb:p(a,ib)?ib:hb}function X(){if(!fb)return!1;var b={},c=a.CSS&&a.CSS.supports;return["auto","manipulation","pan-y","pan-x","pan-x pan-y","none"].forEach(function(d){b[d]=c?a.CSS.supports("touch-action",d):!0}),b}function Y(a){this.options=la({},this.defaults,a||{}),this.id=v(),this.manager=null,this.options.enable=l(this.options.enable,!0),this.state=nb,this.simultaneous={},this.requireFail=[]}function Z(a){return a&sb?"cancel":a&qb?"end":a&pb?"move":a&ob?"start":""}function $(a){return a==Ma?"down":a==La?"up":a==Ja?"left":a==Ka?"right":""}function _(a,b){var c=b.manager;return c?c.get(a):a}function aa(){Y.apply(this,arguments)}function ba(){aa.apply(this,arguments),this.pX=null,this.pY=null}function ca(){aa.apply(this,arguments)}function da(){Y.apply(this,arguments),this._timer=null,this._input=null}function ea(){aa.apply(this,arguments)}function fa(){aa.apply(this,arguments)}function ga(){Y.apply(this,arguments),this.pTime=!1,this.pCenter=!1,this._timer=null,this._input=null,this.count=0}function ha(a,b){return b=b||{},b.recognizers=l(b.recognizers,ha.defaults.preset),new ia(a,b)}function ia(a,b){this.options=la({},ha.defaults,b||{}),this.options.inputTarget=this.options.inputTarget||a,this.handlers={},this.session={},this.recognizers=[],this.oldCssProps={},this.element=a,this.input=y(this),this.touchAction=new V(this,this.options.touchAction),ja(this,!0),g(this.options.recognizers,function(a){var b=this.add(new a[0](a[1]));a[2]&&b.recognizeWith(a[2]),a[3]&&b.requireFailure(a[3])},this)}function ja(a,b){var c=a.element;if(c.style){var d;g(a.options.cssProps,function(e,f){d=u(c.style,f),b?(a.oldCssProps[d]=c.style[d],c.style[d]=e):c.style[d]=a.oldCssProps[d]||""}),b||(a.oldCssProps={})}}function ka(a,c){var d=b.createEvent("Event");d.initEvent(a,!0,!0),d.gesture=c,c.target.dispatchEvent(d)}var la,ma=["","webkit","Moz","MS","ms","o"],na=b.createElement("div"),oa="function",pa=Math.round,qa=Math.abs,ra=Date.now;la="function"!=typeof Object.assign?function(a){if(a===d||null===a)throw new TypeError("Cannot convert undefined or null to object");for(var b=Object(a),c=1;ch&&(b.push(a),h=b.length-1):e&(Ga|Ha)&&(c=!0),0>h||(b[h]=a,this.callback(this.manager,e,{pointers:b,changedPointers:[a],pointerType:f,srcEvent:a}),c&&b.splice(h,1))}});var Za={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},$a="touchstart",_a="touchstart touchmove touchend touchcancel";i(N,x,{handler:function(a){var b=Za[a.type];if(b===Ea&&(this.started=!0),this.started){var c=O.call(this,a,b);b&(Ga|Ha)&&c[0].length-c[1].length===0&&(this.started=!1),this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}}});var ab={touchstart:Ea,touchmove:Fa,touchend:Ga,touchcancel:Ha},bb="touchstart touchmove touchend touchcancel";i(P,x,{handler:function(a){var b=ab[a.type],c=Q.call(this,a,b);c&&this.callback(this.manager,b,{pointers:c[0],changedPointers:c[1],pointerType:za,srcEvent:a})}});var cb=2500,db=25;i(R,x,{handler:function(a,b,c){var d=c.pointerType==za,e=c.pointerType==Ba;if(!(e&&c.sourceCapabilities&&c.sourceCapabilities.firesTouchEvents)){if(d)S.call(this,b,c);else if(e&&U.call(this,c))return;this.callback(a,b,c)}},destroy:function(){this.touch.destroy(),this.mouse.destroy()}});var eb=u(na.style,"touchAction"),fb=eb!==d,gb="compute",hb="auto",ib="manipulation",jb="none",kb="pan-x",lb="pan-y",mb=X();V.prototype={set:function(a){a==gb&&(a=this.compute()),fb&&this.manager.element.style&&mb[a]&&(this.manager.element.style[eb]=a),this.actions=a.toLowerCase().trim()},update:function(){this.set(this.manager.options.touchAction)},compute:function(){var a=[];return g(this.manager.recognizers,function(b){k(b.options.enable,[b])&&(a=a.concat(b.getTouchAction()))}),W(a.join(" "))},preventDefaults:function(a){var b=a.srcEvent,c=a.offsetDirection;if(this.manager.session.prevented)return void b.preventDefault();var d=this.actions,e=p(d,jb)&&!mb[jb],f=p(d,lb)&&!mb[lb],g=p(d,kb)&&!mb[kb];if(e){var h=1===a.pointers.length,i=a.distance<2,j=a.deltaTime<250;if(h&&i&&j)return}return g&&f?void 0:e||f&&c&Na||g&&c&Oa?this.preventSrc(b):void 0},preventSrc:function(a){this.manager.session.prevented=!0,a.preventDefault()}};var nb=1,ob=2,pb=4,qb=8,rb=qb,sb=16,tb=32;Y.prototype={defaults:{},set:function(a){return la(this.options,a),this.manager&&this.manager.touchAction.update(),this},recognizeWith:function(a){if(f(a,"recognizeWith",this))return this;var b=this.simultaneous;return a=_(a,this),b[a.id]||(b[a.id]=a,a.recognizeWith(this)),this},dropRecognizeWith:function(a){return f(a,"dropRecognizeWith",this)?this:(a=_(a,this),delete this.simultaneous[a.id],this)},requireFailure:function(a){if(f(a,"requireFailure",this))return this;var b=this.requireFail;return a=_(a,this),-1===r(b,a)&&(b.push(a),a.requireFailure(this)),this},dropRequireFailure:function(a){if(f(a,"dropRequireFailure",this))return this;a=_(a,this);var b=r(this.requireFail,a);return b>-1&&this.requireFail.splice(b,1),this},hasRequireFailures:function(){return this.requireFail.length>0},canRecognizeWith:function(a){return!!this.simultaneous[a.id]},emit:function(a){function b(b){c.manager.emit(b,a)}var c=this,d=this.state;qb>d&&b(c.options.event+Z(d)),b(c.options.event),a.additionalEvent&&b(a.additionalEvent),d>=qb&&b(c.options.event+Z(d))},tryEmit:function(a){return this.canEmit()?this.emit(a):void(this.state=tb)},canEmit:function(){for(var a=0;af?Ja:Ka,c=f!=this.pX,d=Math.abs(a.deltaX)):(e=0===g?Ia:0>g?La:Ma,c=g!=this.pY,d=Math.abs(a.deltaY))),a.direction=e,c&&d>b.threshold&&e&b.direction},attrTest:function(a){return aa.prototype.attrTest.call(this,a)&&(this.state&ob||!(this.state&ob)&&this.directionTest(a))},emit:function(a){this.pX=a.deltaX,this.pY=a.deltaY;var b=$(a.direction);b&&(a.additionalEvent=this.options.event+b),this._super.emit.call(this,a)}}),i(ca,aa,{defaults:{event:"pinch",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.scale-1)>this.options.threshold||this.state&ob)},emit:function(a){if(1!==a.scale){var b=a.scale<1?"in":"out";a.additionalEvent=this.options.event+b}this._super.emit.call(this,a)}}),i(da,Y,{defaults:{event:"press",pointers:1,time:251,threshold:9},getTouchAction:function(){return[hb]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distanceb.time;if(this._input=a,!d||!c||a.eventType&(Ga|Ha)&&!f)this.reset();else if(a.eventType&Ea)this.reset(),this._timer=e(function(){this.state=rb,this.tryEmit()},b.time,this);else if(a.eventType&Ga)return rb;return tb},reset:function(){clearTimeout(this._timer)},emit:function(a){this.state===rb&&(a&&a.eventType&Ga?this.manager.emit(this.options.event+"up",a):(this._input.timeStamp=ra(),this.manager.emit(this.options.event,this._input)))}}),i(ea,aa,{defaults:{event:"rotate",threshold:0,pointers:2},getTouchAction:function(){return[jb]},attrTest:function(a){return this._super.attrTest.call(this,a)&&(Math.abs(a.rotation)>this.options.threshold||this.state&ob)}}),i(fa,aa,{defaults:{event:"swipe",threshold:10,velocity:.3,direction:Na|Oa,pointers:1},getTouchAction:function(){return ba.prototype.getTouchAction.call(this)},attrTest:function(a){var b,c=this.options.direction;return c&(Na|Oa)?b=a.overallVelocity:c&Na?b=a.overallVelocityX:c&Oa&&(b=a.overallVelocityY),this._super.attrTest.call(this,a)&&c&a.offsetDirection&&a.distance>this.options.threshold&&a.maxPointers==this.options.pointers&&qa(b)>this.options.velocity&&a.eventType&Ga},emit:function(a){var b=$(a.offsetDirection);b&&this.manager.emit(this.options.event+b,a),this.manager.emit(this.options.event,a)}}),i(ga,Y,{defaults:{event:"tap",pointers:1,taps:1,interval:300,time:250,threshold:9,posThreshold:10},getTouchAction:function(){return[ib]},process:function(a){var b=this.options,c=a.pointers.length===b.pointers,d=a.distance