mirror of
https://github.com/penpot/penpot.git
synced 2025-08-04 07:28:33 +02:00
444 lines
11 KiB
Clojure
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))
|