penpot/frontend/src/uxbox/main/data/icons.cljs
2016-11-27 21:53:12 +01:00

444 lines
11 KiB
Clojure

;; 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) 2016 Andrey Antukh <niwi@niwi.nz>
(ns uxbox.main.data.icons
(:require [cuerdas.core :as str]
[beicon.core :as rx]
[uxbox.util.data :refer (jscoll->vec)]
[uxbox.util.uuid :as uuid]
[potok.core :as ptk]
[uxbox.util.router :as r]
[uxbox.util.dom :as dom]
[uxbox.util.files :as files]
[uxbox.store :as st]
[uxbox.main.repo :as rp]))
;; --- Initialize
(declare fetch-icons)
(declare fetch-collections)
(declare collections-fetched?)
(defrecord Initialize [type id]
ptk/UpdateEvent
(update [_ state]
(let [type (or type :own)
data {:type type :id id :selected #{}}]
(-> state
(assoc-in [:dashboard :icons] data)
(assoc-in [:dashboard :section] :dashboard/icons))))
ptk/WatchEvent
(watch [_ state s]
(rx/merge (rx/of (fetch-collections))
(rx/of (fetch-icons id)))))
(defn initialize
[type id]
(Initialize. type id))
;; --- Select a Collection
(defrecord SelectCollection [type id]
ptk/WatchEvent
(watch [_ state stream]
(rx/of (r/navigate :dashboard/icons
{:type type :id id}))))
(defn select-collection
([type]
(select-collection type nil))
([type id]
{:pre [(keyword? type)]}
(SelectCollection. type id)))
;; --- Collections Fetched
(defrecord CollectionsFetched [items]
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state {:keys [id user] :as item}]
(let [type (if (uuid/zero? (:user item)) :builtin :own)
item (assoc item :type type)]
(assoc-in state [:icons-collections id] item)))
state
items)))
(defn collections-fetched
[items]
(CollectionsFetched. items))
;; --- Fetch Collections
(defrecord FetchCollections []
ptk/WatchEvent
(watch [_ state s]
(->> (rp/req :fetch/icon-collections)
(rx/map :payload)
(rx/map collections-fetched))))
(defn fetch-collections
[]
(FetchCollections.))
;; --- Collection Created
(defrecord CollectionCreated [item]
ptk/UpdateEvent
(update [_ state]
(let [{:keys [id] :as item} (assoc item :type :own)]
(update state :icons-collections assoc id item)))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (select-collection :own (:id item)))))
(defn collection-created
[item]
(CollectionCreated. item))
;; --- Create Collection
(defrecord CreateCollection []
ptk/WatchEvent
(watch [_ state s]
(let [name (str "Unnamed Collection (" (gensym "c") ")")
coll {:name name}]
(->> (rp/req :create/icon-collection coll)
(rx/map :payload)
(rx/map collection-created)))))
(defn create-collection
[]
(CreateCollection.))
(defn collections-fetched?
[v]
(instance? CollectionsFetched v))
;; --- Collection Updated
(defrecord CollectionUpdated [item]
ptk/UpdateEvent
(update [_ state]
(update-in state [:icons-collections (:id item)] merge item)))
(defn collection-updated
[item]
(CollectionUpdated. item))
;; --- Update Collection
(defrecord UpdateCollection [id]
ptk/WatchEvent
(watch [_ state s]
(let [item (get-in state [:icons-collections id])]
(->> (rp/req :update/icon-collection item)
(rx/map :payload)
(rx/map collection-updated)))))
(defn update-collection
[id]
(UpdateCollection. id))
;; --- Rename Collection
(defrecord RenameCollection [id name]
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:icons-collections id :name] name))
ptk/WatchEvent
(watch [_ state s]
(rx/of (update-collection id))))
(defn rename-collection
[id name]
(RenameCollection. id name))
;; --- Delete Collection
(defrecord DeleteCollection [id]
ptk/UpdateEvent
(update [_ state]
(update state :icons-collections dissoc id))
ptk/WatchEvent
(watch [_ state s]
(let [type (get-in state [:dashboard :icons :type])]
(->> (rp/req :delete/icon-collection id)
(rx/map #(select-collection type))))))
(defn delete-collection
[id]
(DeleteCollection. id))
;; --- Icon Created
(defrecord IconCreated [item]
ptk/UpdateEvent
(update [_ state]
(let [{:keys [id] :as item} (assoc item :type :icon)]
(update state :icons assoc id item))))
(defn icon-created
[item]
(IconCreated. item))
;; --- Create Icon
(defn- parse-svg
[data]
{:pre [(string? data)]}
(let [valid-tags #{"defs" "path" "circle" "rect" "metadata" "g"
"radialGradient" "stop"}
div (dom/create-element "div")
gc (dom/create-element "div")
g (dom/create-element "http://www.w3.org/2000/svg" "g")
_ (dom/set-html! div data)
svg (dom/query div "svg")]
(loop [child (dom/get-first-child svg)]
(if child
(let [tagname (dom/get-tag-name child)]
(if (contains? valid-tags tagname)
(dom/append-child! g child)
(dom/append-child! gc child))
(recur (dom/get-first-child svg)))
(let [width (.. svg -width -baseVal -value)
header (.. svg -height -baseVal -value)
view-box [(.. svg -viewBox -baseVal -x)
(.. svg -viewBox -baseVal -y)
(.. svg -viewBox -baseVal -width)
(.. svg -viewBox -baseVal -height)]
props {:width width
:mimetype "image/svg+xml"
:height header
:view-box view-box}]
[(dom/get-outer-html g) props])))))
(defrecord CreateIcons [id files]
ptk/WatchEvent
(watch [_ state s]
(letfn [(parse [file]
(->> (files/read-as-text file)
(rx/map parse-svg)))
(allowed? [file]
(= (.-type file) "image/svg+xml"))
(prepare [[content metadata]]
{:collection id
:content content
:name (str "Icon " (gensym "i"))
:metadata metadata})]
(->> (rx/from-coll (jscoll->vec files))
(rx/filter allowed?)
(rx/flat-map parse)
(rx/map prepare)
(rx/flat-map #(rp/req :create/icon %))
(rx/map :payload)
(rx/map icon-created)))))
(defn create-icons
[id files]
{:pre [(or (uuid? id) (nil? id))]}
(CreateIcons. id files))
;; --- Icon Persisted
(defrecord IconPersisted [id data]
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:icons id] data)))
(defn icon-persisted
[{:keys [id] :as data}]
{:pre [(map? data)]}
(IconPersisted. id data))
;; --- Persist Icon
(defrecord PersistIcon [id]
ptk/WatchEvent
(watch [_ state stream]
(let [icon (get-in state [:icons id])]
(->> (rp/req :update/icon icon)
(rx/map :payload)
(rx/map icon-persisted)))))
(defn persist-icon
[id]
{:pre [(uuid? id)]}
(PersistIcon. id))
;; --- Icons Fetched
(defrecord IconsFetched [items]
ptk/UpdateEvent
(update [_ state]
(reduce (fn [state {:keys [id] :as icon}]
(let [icon (assoc icon :type :icon)]
(assoc-in state [:icons id] icon)))
state
items)))
(defn icons-fetched
[items]
(IconsFetched. items))
;; --- Load Icons
(defrecord FetchIcons [id]
ptk/WatchEvent
(watch [_ state s]
(let [params {:coll id}]
(->> (rp/req :fetch/icons params)
(rx/map :payload)
(rx/map icons-fetched)))))
(defn fetch-icons
[id]
{:pre [(or (uuid? id) (nil? id))]}
(FetchIcons. id))
;; --- Delete Icons
(defrecord DeleteIcon [id]
ptk/UpdateEvent
(update [_ state]
(-> state
(update :icons dissoc id)
(update-in [:dashboard :icons :selected] disj id)))
ptk/WatchEvent
(watch [_ state s]
(->> (rp/req :delete/icon id)
(rx/ignore))))
(defn delete-icon
[id]
{:pre [(uuid? id)]}
(DeleteIcon. id))
;; --- Rename Icon
(defrecord RenameIcon [id name]
ptk/UpdateEvent
(update [_ state]
(assoc-in state [:icons id :name] name))
ptk/WatchEvent
(watch [_ state stream]
(rx/of (persist-icon id))))
(defn rename-icon
[id name]
{:pre [(uuid? id) (string? name)]}
(RenameIcon. id name))
;; --- Select icon
(defrecord SelectIcon [id]
ptk/UpdateEvent
(update [_ state]
(update-in state [:dashboard :icons :selected] conj id)))
(defrecord DeselectIcon [id]
ptk/UpdateEvent
(update [_ state]
(update-in state [:dashboard :icons :selected] disj id)))
(defrecord ToggleIconSelection [id]
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:dashboard :icons :selected])]
(rx/of
(if (selected id)
(DeselectIcon. id)
(SelectIcon. id))))))
(defn deselect-icon
[id]
{:pre [(uuid? id)]}
(DeselectIcon. id))
(defn toggle-icon-selection
[id]
(ToggleIconSelection. id))
;; --- Copy Selected Icon
(defrecord CopySelected [id]
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:dashboard :icons :selected])]
(rx/merge
(->> (rx/from-coll selected)
(rx/map #(get-in state [:icons %]))
(rx/map #(dissoc % :id))
(rx/map #(assoc % :collection id))
(rx/flat-map #(rp/req :create/icon %))
(rx/map :payload)
(rx/map icon-created))
(->> (rx/from-coll selected)
(rx/map deselect-icon))))))
(defn copy-selected
[id]
{:pre [(or (uuid? id) (nil? id))]}
(CopySelected. id))
;; --- Move Selected Icon
(defrecord MoveSelected [id]
ptk/UpdateEvent
(update [_ state]
(let [selected (get-in state [:dashboard :icons :selected])]
(reduce (fn [state icon]
(assoc-in state [:icons icon :collection] id))
state
selected)))
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:dashboard :icons :selected])]
(rx/merge
(->> (rx/from-coll selected)
(rx/map persist-icon))
(->> (rx/from-coll selected)
(rx/map deselect-icon))))))
(defn move-selected
[id]
{:pre [(or (uuid? id) (nil? id))]}
(MoveSelected. id))
;; --- Delete Selected
(defrecord DeleteSelected []
ptk/WatchEvent
(watch [_ state stream]
(let [selected (get-in state [:dashboard :icons :selected])]
(->> (rx/from-coll selected)
(rx/map delete-icon)))))
(defn delete-selected
[]
(DeleteSelected.))
;; --- Update Opts (Filtering & Ordering)
(defrecord UpdateOpts [order filter edition]
ptk/UpdateEvent
(update [_ state]
(update-in state [:dashboard :icons] merge
{:edition edition}
(when order {:order order})
(when filter {:filter filter}))))
(defn update-opts
[& {:keys [order filter edition]
:or {edition false}
:as opts}]
(UpdateOpts. order filter edition))