mirror of
https://github.com/penpot/penpot.git
synced 2025-05-12 20:06:37 +02:00
♻️ Refactor drag-and-drop on workspace sidebars (now using react-dnd).
This commit is contained in:
parent
dbf754880e
commit
03c9d9c8f1
11 changed files with 250 additions and 422 deletions
|
@ -102,6 +102,10 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
|
&.dragging-TODO {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
&.open {
|
&.open {
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
|
|
|
@ -269,18 +269,10 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&.drag-top {
|
&.dragging {
|
||||||
border-top: 40px solid $soft-ui-border !important;
|
// TODO: revisit this
|
||||||
|
background-color: #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.drag-bottom {
|
|
||||||
border-bottom: 40px solid $soft-ui-border !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.drag-inside {
|
|
||||||
border: 2px solid $main-ui-color !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -145,27 +145,6 @@
|
||||||
[state id]
|
[state id]
|
||||||
(update state :packed-pages dissoc id))
|
(update state :packed-pages dissoc id))
|
||||||
|
|
||||||
;; --- Update Project main page
|
|
||||||
;;
|
|
||||||
;; A event that handles the project main page
|
|
||||||
;; update based on the user selected page ordering.
|
|
||||||
|
|
||||||
(deftype UpdateProjectMainPage [id]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [_ state]
|
|
||||||
(let [get-order #(get-in % [:metadata :order])
|
|
||||||
page-id (->> (vals (:pages state))
|
|
||||||
(filter #(= id (:project %)))
|
|
||||||
(sort-by get-order)
|
|
||||||
(map :id)
|
|
||||||
(first))]
|
|
||||||
(assoc-in state [:projects id :page-id] page-id))))
|
|
||||||
|
|
||||||
(defn update-project-main-page
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(UpdateProjectMainPage. id))
|
|
||||||
|
|
||||||
;; --- Pages Fetched
|
;; --- Pages Fetched
|
||||||
|
|
||||||
(deftype PagesFetched [id pages]
|
(deftype PagesFetched [id pages]
|
||||||
|
@ -174,13 +153,15 @@
|
||||||
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
|
(let [get-order #(get-in % [:metadata :order])
|
||||||
|
pages (sort-by get-order pages)
|
||||||
|
page-ids (into [] (map :id) pages)]
|
||||||
(as-> state $
|
(as-> state $
|
||||||
|
(assoc-in $ [:projects id :pages] page-ids)
|
||||||
|
;; TODO: this is a workaround
|
||||||
|
(assoc-in $ [:projects id :page-id] (first page-ids))
|
||||||
(reduce assoc-page $ pages)
|
(reduce assoc-page $ pages)
|
||||||
(reduce assoc-packed-page $ pages)))
|
(reduce assoc-packed-page $ pages)))))
|
||||||
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(rx/of (update-project-main-page id))))
|
|
||||||
|
|
||||||
(defn pages-fetched
|
(defn pages-fetched
|
||||||
[id pages]
|
[id pages]
|
||||||
|
@ -295,7 +276,7 @@
|
||||||
(->> (rp/req :update/page page)
|
(->> (rp/req :update/page page)
|
||||||
(rx/map :payload)
|
(rx/map :payload)
|
||||||
(rx/do #(when (fn? on-success)
|
(rx/do #(when (fn? on-success)
|
||||||
(ts/schedule 0 on-success)))
|
(ts/schedule-on-idle on-success)))
|
||||||
(rx/map page-persisted)))))))
|
(rx/map page-persisted)))))))
|
||||||
|
|
||||||
(defn persist-page?
|
(defn persist-page?
|
||||||
|
@ -335,34 +316,16 @@
|
||||||
;; that does not sends the heavyweiht `:data` attribute
|
;; that does not sends the heavyweiht `:data` attribute
|
||||||
;; and only serves for update other page data.
|
;; and only serves for update other page data.
|
||||||
|
|
||||||
(deftype PersistMetadata [id]
|
(defn persist-metadata
|
||||||
|
[id]
|
||||||
|
{:pre [(uuid? id)]}
|
||||||
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [page (get-in state [:pages id])]
|
(let [page (get-in state [:pages id])]
|
||||||
(->> (rp/req :update/page-metadata page)
|
(->> (rp/req :update/page-metadata page)
|
||||||
(rx/map :payload)
|
(rx/map :payload)
|
||||||
(rx/map metadata-persisted)))))
|
(rx/map metadata-persisted))))))
|
||||||
|
|
||||||
(defn persist-metadata
|
|
||||||
[id]
|
|
||||||
{:pre [(uuid? id)]}
|
|
||||||
(PersistMetadata. id))
|
|
||||||
|
|
||||||
(deftype PersistPagesMetadata [project-id]
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(let [xform (comp
|
|
||||||
(map second)
|
|
||||||
(filter #(= project-id (:project %)))
|
|
||||||
(map :id))]
|
|
||||||
(->> (sequence xform (:pages state))
|
|
||||||
(rx/from-coll)
|
|
||||||
(rx/map persist-metadata)))))
|
|
||||||
|
|
||||||
(defn persist-pages-metadata
|
|
||||||
[project-id]
|
|
||||||
{:pre [(uuid? project-id)]}
|
|
||||||
(PersistPagesMetadata. project-id))
|
|
||||||
|
|
||||||
;; --- Update Page
|
;; --- Update Page
|
||||||
|
|
||||||
|
@ -396,49 +359,37 @@
|
||||||
;; page order numbers after a user sorting
|
;; page order numbers after a user sorting
|
||||||
;; operation for a concrete project.
|
;; operation for a concrete project.
|
||||||
|
|
||||||
(deftype ReorderPages [project-id]
|
|
||||||
ptk/UpdateEvent
|
|
||||||
(update [this state]
|
|
||||||
(let [get-order #(get-in % [:metadata :order])
|
|
||||||
pages (->> (vals (:pages state))
|
|
||||||
(filter #(= project-id (:project %)))
|
|
||||||
(sort-by get-order)
|
|
||||||
(map :id)
|
|
||||||
(map-indexed vector))]
|
|
||||||
(reduce (fn [state [order id]]
|
|
||||||
(assoc-in state [:pages id :metadata :order] (* order 10)))
|
|
||||||
state
|
|
||||||
pages)))
|
|
||||||
|
|
||||||
ptk/WatchEvent
|
|
||||||
(watch [_ state stream]
|
|
||||||
(rx/of (update-project-main-page project-id)
|
|
||||||
(persist-pages-metadata project-id))))
|
|
||||||
|
|
||||||
(defn reorder-pages
|
(defn reorder-pages
|
||||||
[id]
|
[project-id]
|
||||||
{:pre [(uuid? id)]}
|
{:pre [(uuid? project-id)]}
|
||||||
(ReorderPages. id))
|
(reify
|
||||||
|
|
||||||
;; --- Update Order
|
|
||||||
;;
|
|
||||||
;; A specialized event for update order
|
|
||||||
;; attribute on the page metadata
|
|
||||||
|
|
||||||
(deftype UpdateOrder [id order]
|
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [this state]
|
(update [this state]
|
||||||
(assoc-in state [:pages id :metadata :order] order))
|
(let [page-ids (get-in state [:projects project-id :pages])]
|
||||||
|
(reduce (fn [state [index id]]
|
||||||
|
(assoc-in state [:pages id :metadata :order] index))
|
||||||
|
;; TODO: this is workaround
|
||||||
|
(assoc-in state [:projects project-id :page-id] (first page-ids))
|
||||||
|
(map-indexed vector page-ids))))
|
||||||
|
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state stream]
|
(watch [_ state stream]
|
||||||
(let [project (get-in state [:pages id :project])]
|
(let [page-ids (get-in state [:projects project-id :pages])]
|
||||||
(rx/of (reorder-pages project)))))
|
(->> (rx/from-coll page-ids)
|
||||||
|
(rx/map persist-metadata))))))
|
||||||
|
|
||||||
(defn update-order
|
;; --- Move Page (Ordering)
|
||||||
[id order]
|
|
||||||
{:pre [(uuid? id) (number? order)]}
|
(defn move-page
|
||||||
(UpdateOrder. id order))
|
[{:keys [page-id project-id index] :as params}]
|
||||||
|
(reify
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [pages (get-in state [:projects project-id :pages])
|
||||||
|
pages (into [] (remove #(= % page-id)) pages)
|
||||||
|
[before after] (split-at index pages)
|
||||||
|
pages (vec (concat before [page-id] after))]
|
||||||
|
(assoc-in state [:projects project-id :pages] pages)))))
|
||||||
|
|
||||||
;; --- Persist Page Form
|
;; --- Persist Page Form
|
||||||
;;
|
;;
|
||||||
|
|
|
@ -517,6 +517,21 @@
|
||||||
[]
|
[]
|
||||||
(DeleteSelected.))
|
(DeleteSelected.))
|
||||||
|
|
||||||
|
;; --- Change Shape Order (Ordering)
|
||||||
|
|
||||||
|
(defn change-shape-order
|
||||||
|
[{:keys [id index] :as params}]
|
||||||
|
{:pre [(uuid? id) (number? index)]}
|
||||||
|
(reify
|
||||||
|
ptk/UpdateEvent
|
||||||
|
(update [_ state]
|
||||||
|
(let [page-id (get-in state [:shapes id :page])
|
||||||
|
shapes (get-in state [:pages page-id :shapes])
|
||||||
|
shapes (into [] (remove #(= % id)) shapes)
|
||||||
|
[before after] (split-at index shapes)
|
||||||
|
shapes (vec (concat before [id] after))]
|
||||||
|
(assoc-in state [:pages page-id :shapes] shapes)))))
|
||||||
|
|
||||||
;; --- Shape Transformations
|
;; --- Shape Transformations
|
||||||
|
|
||||||
(def ^:private canvas-coords
|
(def ^:private canvas-coords
|
||||||
|
|
|
@ -26,9 +26,11 @@
|
||||||
|
|
||||||
(defn emit!
|
(defn emit!
|
||||||
([event]
|
([event]
|
||||||
(ptk/emit! store event))
|
(ptk/emit! store event)
|
||||||
|
nil)
|
||||||
([event & events]
|
([event & events]
|
||||||
(apply ptk/emit! store (cons event events))))
|
(apply ptk/emit! store (cons event events))
|
||||||
|
nil))
|
||||||
|
|
||||||
(def initial-state
|
(def initial-state
|
||||||
{:dashboard {:project-order :name
|
{:dashboard {:project-order :name
|
||||||
|
|
|
@ -118,7 +118,7 @@
|
||||||
(mf/defc grid-item-thumbnail
|
(mf/defc grid-item-thumbnail
|
||||||
[{:keys [project] :as props}]
|
[{:keys [project] :as props}]
|
||||||
(let [url (mf/use-state nil)]
|
(let [url (mf/use-state nil)]
|
||||||
(mf/use-effect
|
#_(mf/use-effect
|
||||||
{:deps #js [(:page-id project)]
|
{:deps #js [(:page-id project)]
|
||||||
:init (fn []
|
:init (fn []
|
||||||
(when-let [page-id (:page-id project)]
|
(when-let [page-id (:page-id project)]
|
||||||
|
@ -160,7 +160,8 @@
|
||||||
(dom/prevent-default %)
|
(dom/prevent-default %)
|
||||||
(swap! local assoc :edition true))]
|
(swap! local assoc :edition true))]
|
||||||
[:div.grid-item.project-th {:on-click on-navigate}
|
[:div.grid-item.project-th {:on-click on-navigate}
|
||||||
[:& grid-item-thumbnail {:project project :key (select-keys project [:id :page-id])}]
|
[:& grid-item-thumbnail {:project project
|
||||||
|
:key (select-keys project [:id :page-id])}]
|
||||||
[:div.item-info
|
[:div.item-info
|
||||||
(if (:edition @local)
|
(if (:edition @local)
|
||||||
[:input.element-name {:type "text"
|
[:input.element-name {:type "text"
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
[uxbox.main.ui.workspace.sidebar.icons :refer [icons-toolbox]]
|
[uxbox.main.ui.workspace.sidebar.icons :refer [icons-toolbox]]
|
||||||
[uxbox.main.ui.workspace.sidebar.layers :refer [layers-toolbox]]
|
[uxbox.main.ui.workspace.sidebar.layers :refer [layers-toolbox]]
|
||||||
[uxbox.main.ui.workspace.sidebar.options :refer [options-toolbox]]
|
[uxbox.main.ui.workspace.sidebar.options :refer [options-toolbox]]
|
||||||
[uxbox.main.ui.workspace.sidebar.sitemap :refer [sitemap-toolbox]]))
|
[uxbox.main.ui.workspace.sidebar.sitemap :refer [sitemap-toolbox]]
|
||||||
|
[uxbox.util.rdnd :as rdnd]))
|
||||||
|
|
||||||
;; --- Left Sidebar (Component)
|
;; --- Left Sidebar (Component)
|
||||||
|
|
||||||
|
@ -21,14 +22,17 @@
|
||||||
[{:keys [wst page] :as props}]
|
[{:keys [wst page] :as props}]
|
||||||
(let [{:keys [flags selected]} wst]
|
(let [{:keys [flags selected]} wst]
|
||||||
[:aside#settings-bar.settings-bar.settings-bar-left
|
[:aside#settings-bar.settings-bar.settings-bar-left
|
||||||
|
[:> rdnd/provider {:backend rdnd/html5}
|
||||||
[:div.settings-bar-inside
|
[:div.settings-bar-inside
|
||||||
(when (contains? flags :sitemap)
|
(when (contains? flags :sitemap)
|
||||||
[:& sitemap-toolbox {:page page}])
|
[:& sitemap-toolbox {:project-id (:project page)
|
||||||
|
:current-page-id (:id page)
|
||||||
|
:page page}])
|
||||||
#_(when (contains? flags :document-history)
|
#_(when (contains? flags :document-history)
|
||||||
(history-toolbox page-id))
|
(history-toolbox page-id))
|
||||||
(when (contains? flags :layers)
|
(when (contains? flags :layers)
|
||||||
[:& layers-toolbox {:page page
|
[:& layers-toolbox {:page page
|
||||||
:selected selected}])]]))
|
:selected selected}])]]]))
|
||||||
|
|
||||||
;; --- Right Sidebar (Component)
|
;; --- Right Sidebar (Component)
|
||||||
|
|
||||||
|
|
|
@ -2,76 +2,26 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; 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/.
|
;; 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>
|
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
||||||
|
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
|
|
||||||
(ns uxbox.main.ui.workspace.sidebar.layers
|
(ns uxbox.main.ui.workspace.sidebar.layers
|
||||||
(:require
|
(:require
|
||||||
[cuerdas.core :as str]
|
|
||||||
[goog.events :as events]
|
|
||||||
[lentes.core :as l]
|
[lentes.core :as l]
|
||||||
[potok.core :as ptk]
|
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
[rumext.core :as mx]
|
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
|
[uxbox.main.data.pages :as udp]
|
||||||
[uxbox.main.data.shapes :as uds]
|
[uxbox.main.data.shapes :as uds]
|
||||||
[uxbox.main.data.workspace :as udw]
|
[uxbox.main.data.workspace :as udw]
|
||||||
[uxbox.main.refs :as refs]
|
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.keyboard :as kbd]
|
[uxbox.main.ui.keyboard :as kbd]
|
||||||
[uxbox.main.ui.shapes.icon :as icon]
|
[uxbox.main.ui.shapes.icon :as icon]
|
||||||
[uxbox.util.data :refer (read-string classnames)]
|
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.data :refer [classnames]]
|
||||||
[uxbox.util.dom.dnd :as dnd]
|
[uxbox.util.dom :as dom]))
|
||||||
[uxbox.util.timers :as tm]
|
|
||||||
[uxbox.util.router :as r])
|
|
||||||
(:import goog.events.EventType))
|
|
||||||
|
|
||||||
;; --- Helpers
|
;; --- Helpers
|
||||||
|
|
||||||
(defn- select-shape
|
|
||||||
[selected item event]
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(let [id (:id item)]
|
|
||||||
(cond
|
|
||||||
(or (:blocked item)
|
|
||||||
(:hidden item))
|
|
||||||
nil
|
|
||||||
|
|
||||||
(.-ctrlKey event)
|
|
||||||
(st/emit! (udw/select-shape id))
|
|
||||||
|
|
||||||
(> (count selected) 1)
|
|
||||||
(st/emit! (udw/deselect-all)
|
|
||||||
(udw/select-shape id))
|
|
||||||
|
|
||||||
(contains? selected id)
|
|
||||||
(st/emit! (udw/select-shape id))
|
|
||||||
|
|
||||||
:else
|
|
||||||
(st/emit! (udw/deselect-all)
|
|
||||||
(udw/select-shape id)))))
|
|
||||||
|
|
||||||
(defn- toggle-visibility
|
|
||||||
[selected item event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(let [id (:id item)
|
|
||||||
hidden? (:hidden item)]
|
|
||||||
(if hidden?
|
|
||||||
(st/emit! (uds/show-shape id))
|
|
||||||
(st/emit! (uds/hide-shape id)))
|
|
||||||
(when (contains? selected id)
|
|
||||||
(st/emit! (udw/select-shape id)))))
|
|
||||||
|
|
||||||
(defn- toggle-blocking
|
|
||||||
[item event]
|
|
||||||
(dom/stop-propagation event)
|
|
||||||
(let [id (:id item)
|
|
||||||
blocked? (:blocked item)]
|
|
||||||
(if blocked?
|
|
||||||
(st/emit! (uds/unblock-shape id))
|
|
||||||
(st/emit! (uds/block-shape id)))))
|
|
||||||
|
|
||||||
(defn- element-icon
|
(defn- element-icon
|
||||||
[item]
|
[item]
|
||||||
(case (:type item)
|
(case (:type item)
|
||||||
|
@ -85,7 +35,7 @@
|
||||||
:group i/folder
|
:group i/folder
|
||||||
nil))
|
nil))
|
||||||
|
|
||||||
;; --- Shape Name (Component)
|
;; --- Layer Name
|
||||||
|
|
||||||
(mf/defc layer-name
|
(mf/defc layer-name
|
||||||
[{:keys [shape] :as props}]
|
[{:keys [shape] :as props}]
|
||||||
|
@ -117,162 +67,108 @@
|
||||||
{:on-double-click on-click}
|
{:on-double-click on-click}
|
||||||
(:name shape "")])))
|
(:name shape "")])))
|
||||||
|
|
||||||
;; --- Layer Simple (Component)
|
;; --- Layer Item
|
||||||
|
|
||||||
(mf/defc layer-item
|
(mf/defc layer-item
|
||||||
[{:keys [shape selected] :as props}]
|
[{:keys [shape selected index] :as props}]
|
||||||
(let [local (mf/use-state {})
|
(letfn [(toggle-blocking [event]
|
||||||
selected? (contains? selected (:id shape))
|
|
||||||
select #(select-shape selected shape %)
|
|
||||||
toggle-visibility #(toggle-visibility selected shape %)
|
|
||||||
toggle-blocking #(toggle-blocking shape %)
|
|
||||||
li-classes (classnames
|
|
||||||
:selected selected?
|
|
||||||
:hide (:dragging @local))
|
|
||||||
body-classes (classnames
|
|
||||||
:selected selected?
|
|
||||||
:drag-active (:dragging @local)
|
|
||||||
:drag-top (= :top (:over @local))
|
|
||||||
:drag-bottom (= :bottom (:over @local))
|
|
||||||
:drag-inside (= :middle (:over @local)))]
|
|
||||||
;; TODO: consider using http://react-dnd.github.io/react-dnd/docs/overview
|
|
||||||
(letfn [(on-drag-start [event]
|
|
||||||
(let [target (dom/event->target event)]
|
|
||||||
(dnd/set-allowed-effect! event "move")
|
|
||||||
(dnd/set-data! event (:id shape))
|
|
||||||
(dnd/set-image! event target 50 10)
|
|
||||||
(tm/schedule #(swap! local assoc :dragging true))))
|
|
||||||
(on-drag-end [event]
|
|
||||||
(swap! local assoc :dragging false :over nil))
|
|
||||||
(on-drop [event]
|
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(let [id (dnd/get-data event)
|
(let [id (:id shape)
|
||||||
over (:over @local)]
|
blocked? (:blocked shape)]
|
||||||
(case (:over @local)
|
(if blocked?
|
||||||
:top (st/emit! (uds/drop-shape id (:id shape) :before))
|
(st/emit! (uds/unblock-shape id))
|
||||||
:bottom (st/emit! (uds/drop-shape id (:id shape) :after)))
|
(st/emit! (uds/block-shape id)))))
|
||||||
(swap! local assoc :dragging false :over nil)))
|
|
||||||
(on-drag-over [event]
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(dnd/set-drop-effect! event "move")
|
|
||||||
(let [over (dnd/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 {:class li-classes}
|
|
||||||
[:div.element-list-body
|
|
||||||
{:class body-classes
|
|
||||||
:style {:opacity (if (:dragging @local)
|
|
||||||
"0.5"
|
|
||||||
"1")}
|
|
||||||
:on-click select
|
|
||||||
:on-double-click #(dom/stop-propagation %)
|
|
||||||
: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}
|
|
||||||
|
|
||||||
|
(toggle-visibility [event]
|
||||||
|
(dom/stop-propagation event)
|
||||||
|
(let [id (:id shape)
|
||||||
|
hidden? (:hidden shape)]
|
||||||
|
(if hidden?
|
||||||
|
(st/emit! (uds/show-shape id))
|
||||||
|
(st/emit! (uds/hide-shape id)))
|
||||||
|
(when (contains? selected id)
|
||||||
|
(st/emit! (udw/select-shape id)))))
|
||||||
|
|
||||||
|
(select-shape [event]
|
||||||
|
(dom/prevent-default event)
|
||||||
|
(let [id (:id shape)]
|
||||||
|
(cond
|
||||||
|
(or (:blocked shape)
|
||||||
|
(:hidden shape))
|
||||||
|
nil
|
||||||
|
|
||||||
|
(.-ctrlKey event)
|
||||||
|
(st/emit! (udw/select-shape id))
|
||||||
|
|
||||||
|
(> (count selected) 1)
|
||||||
|
(st/emit! (udw/deselect-all)
|
||||||
|
(udw/select-shape id))
|
||||||
|
|
||||||
|
(contains? selected id)
|
||||||
|
(st/emit! (udw/select-shape id))
|
||||||
|
|
||||||
|
:else
|
||||||
|
(st/emit! (udw/deselect-all)
|
||||||
|
(udw/select-shape id)))))
|
||||||
|
|
||||||
|
(on-drop [item monitor]
|
||||||
|
(st/emit! (udp/persist-page (:page shape))))
|
||||||
|
|
||||||
|
(on-hover [item monitor]
|
||||||
|
(st/emit! (udw/change-shape-order {:id (:shape-id item)
|
||||||
|
:index index})))]
|
||||||
|
(let [selected? (contains? selected (:id shape))
|
||||||
|
[dprops dnd-ref] (use-sortable
|
||||||
|
{:type "layer-item"
|
||||||
|
:data {:shape-id (:id shape)
|
||||||
|
:page-id (:page shape)
|
||||||
|
:index index}
|
||||||
|
:on-hover on-hover
|
||||||
|
:on-drop on-drop})]
|
||||||
|
[:li {:ref dnd-ref
|
||||||
|
:class (classnames
|
||||||
|
:selected selected?
|
||||||
|
:dragging-TODO (:dragging? dprops))}
|
||||||
|
[:div.element-list-body {:class (classnames :selected selected?)
|
||||||
|
:on-click select-shape
|
||||||
|
:on-double-click #(dom/stop-propagation %)
|
||||||
|
:draggable true}
|
||||||
[:div.element-actions
|
[:div.element-actions
|
||||||
[:div.toggle-element
|
[:div.toggle-element {:class (when-not (:hidden shape) "selected")
|
||||||
{:class (when-not (:hidden shape) "selected")
|
|
||||||
:on-click toggle-visibility}
|
:on-click toggle-visibility}
|
||||||
i/eye]
|
i/eye]
|
||||||
[:div.block-element
|
[:div.block-element {:class (when (:blocked shape) "selected")
|
||||||
{:class (when (:blocked shape) "selected")
|
|
||||||
:on-click toggle-blocking}
|
:on-click toggle-blocking}
|
||||||
i/lock]]
|
i/lock]]
|
||||||
[:div.element-icon (element-icon shape)]
|
[:div.element-icon (element-icon shape)]
|
||||||
[:& layer-name {:shape shape}]]])))
|
[:& layer-name {:shape shape}]]])))
|
||||||
|
|
||||||
;; --- Layer Group (Component)
|
;; --- Layers List
|
||||||
|
|
||||||
;; --- Layers Tools (Buttons Component)
|
|
||||||
|
|
||||||
;; (defn- allow-grouping?
|
|
||||||
;; "Check if the current situation allows grouping
|
|
||||||
;; of the currently selected shapes."
|
|
||||||
;; [selected shapes-map]
|
|
||||||
;; (let [xform (comp (map shapes-map)
|
|
||||||
;; (map :group))
|
|
||||||
;; groups (into #{} xform selected)]
|
|
||||||
;; (= 1 (count groups))))
|
|
||||||
|
|
||||||
;; (defn- allow-ungrouping?
|
|
||||||
;; "Check if the current situation allows ungrouping
|
|
||||||
;; of the currently selected shapes."
|
|
||||||
;; [selected shapes-map]
|
|
||||||
;; (let [shapes (into #{} (map shapes-map) selected)
|
|
||||||
;; groups (into #{} (map :group) shapes)]
|
|
||||||
;; (or (and (= 1 (count shapes))
|
|
||||||
;; (= :group (:type (first shapes))))
|
|
||||||
;; (and (= 1 (count groups))
|
|
||||||
;; (not (nil? (first groups)))))))
|
|
||||||
|
|
||||||
(mf/defc layers-tools
|
|
||||||
"Layers widget options buttons."
|
|
||||||
[{:keys [selected shapes] :as props}]
|
|
||||||
#_(let [duplicate #(st/emit! (uds/duplicate-selected))
|
|
||||||
group #(st/emit! (uds/group-selected))
|
|
||||||
ungroup #(st/emit! (uds/ungroup-selected))
|
|
||||||
delete #(st/emit! (udw/delete-selected))
|
|
||||||
|
|
||||||
;; allow-grouping? (allow-grouping? selected shapes)
|
|
||||||
;; allow-ungrouping? (allow-ungrouping? selected shapes)
|
|
||||||
;; NOTE: the grouping functionallity will be removed/replaced
|
|
||||||
;; with elements.
|
|
||||||
allow-ungrouping? false
|
|
||||||
allow-grouping? false
|
|
||||||
allow-duplicate? (= 1 (count selected))
|
|
||||||
allow-deletion? (pos? (count selected))]
|
|
||||||
[:div.layers-tools
|
|
||||||
[:ul.layers-tools-content
|
|
||||||
[:li.clone-layer.tooltip.tooltip-top
|
|
||||||
{:alt "Duplicate"
|
|
||||||
:class (when-not allow-duplicate? "disable")
|
|
||||||
:on-click duplicate}
|
|
||||||
i/copy]
|
|
||||||
[:li.group-layer.tooltip.tooltip-top
|
|
||||||
{:alt "Group"
|
|
||||||
:class (when-not allow-grouping? "disable")
|
|
||||||
:on-click group}
|
|
||||||
i/folder]
|
|
||||||
[:li.degroup-layer.tooltip.tooltip-top
|
|
||||||
{:alt "Ungroup"
|
|
||||||
:class (when-not allow-ungrouping? "disable")
|
|
||||||
:on-click ungroup}
|
|
||||||
i/ungroup]
|
|
||||||
[:li.delete-layer.tooltip.tooltip-top
|
|
||||||
{:alt "Delete"
|
|
||||||
:class (when-not allow-deletion? "disable")
|
|
||||||
:on-click delete}
|
|
||||||
i/trash]]]))
|
|
||||||
|
|
||||||
;; --- Layers Toolbox (Component)
|
|
||||||
|
|
||||||
(def ^:private shapes-iref
|
(def ^:private shapes-iref
|
||||||
(-> (l/key :shapes)
|
(-> (l/key :shapes)
|
||||||
(l/derive st/state)))
|
(l/derive st/state)))
|
||||||
|
|
||||||
|
(mf/defc layers-list
|
||||||
|
[{:keys [shapes selected] :as props}]
|
||||||
|
(let [shapes-map (mf/deref shapes-iref)]
|
||||||
|
[:div.tool-window-content
|
||||||
|
[:ul.element-list
|
||||||
|
(for [[index id] (map-indexed vector shapes)]
|
||||||
|
[:& layer-item {:shape (get shapes-map id)
|
||||||
|
:selected selected
|
||||||
|
:index index
|
||||||
|
:key id}])]]))
|
||||||
|
|
||||||
|
;; --- Layers Toolbox
|
||||||
|
|
||||||
(mf/defc layers-toolbox
|
(mf/defc layers-toolbox
|
||||||
[{:keys [page selected] :as props}]
|
[{:keys [page selected] :as props}]
|
||||||
(let [shapes (mf/deref shapes-iref)
|
(let [on-click #(st/emit! (udw/toggle-flag :layers))]
|
||||||
on-click #(st/emit! (udw/toggle-flag :layers))]
|
|
||||||
[:div#layers.tool-window
|
[:div#layers.tool-window
|
||||||
[:div.tool-window-bar
|
[:div.tool-window-bar
|
||||||
[:div.tool-window-icon i/layers]
|
[:div.tool-window-icon i/layers]
|
||||||
[:span "Layers"]
|
[:span "Layers"]
|
||||||
[:div.tool-window-close {:on-click on-click} i/close]]
|
[:div.tool-window-close {:on-click on-click} i/close]]
|
||||||
[:div.tool-window-content
|
[:& layers-list {:shapes (:shapes page)
|
||||||
[:ul.element-list
|
:selected selected}]]))
|
||||||
(for [id (:shapes page)]
|
|
||||||
[:& layer-item {:shape (get shapes id)
|
|
||||||
:selected selected
|
|
||||||
:key id}])]]
|
|
||||||
[:& layers-tools {:selected selected
|
|
||||||
:shapes shapes}]]))
|
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[lentes.core :as l]
|
[lentes.core :as l]
|
||||||
[rumext.alpha :as mf]
|
[rumext.alpha :as mf]
|
||||||
|
[rumext.util :as mfu]
|
||||||
[uxbox.builtins.icons :as i]
|
[uxbox.builtins.icons :as i]
|
||||||
[uxbox.main.data.lightbox :as udl]
|
[uxbox.main.data.lightbox :as udl]
|
||||||
[uxbox.main.data.pages :as udp]
|
[uxbox.main.data.pages :as udp]
|
||||||
|
@ -19,30 +20,19 @@
|
||||||
[uxbox.main.store :as st]
|
[uxbox.main.store :as st]
|
||||||
[uxbox.main.ui.lightbox :as lbx]
|
[uxbox.main.ui.lightbox :as lbx]
|
||||||
[uxbox.main.ui.workspace.sidebar.sitemap-pageform]
|
[uxbox.main.ui.workspace.sidebar.sitemap-pageform]
|
||||||
|
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
|
||||||
[uxbox.util.data :refer [classnames]]
|
[uxbox.util.data :refer [classnames]]
|
||||||
[uxbox.util.dom :as dom]
|
[uxbox.util.dom :as dom]
|
||||||
[uxbox.util.dom.dnd :as dnd]
|
[uxbox.util.dom.dnd :as dnd]
|
||||||
[uxbox.util.i18n :refer (tr)]
|
[uxbox.util.i18n :refer (tr)]
|
||||||
[uxbox.util.router :as r]))
|
[uxbox.util.router :as rt]))
|
||||||
|
|
||||||
|
;; --- Page Item
|
||||||
|
|
||||||
(mf/defc page-item
|
(mf/defc page-item
|
||||||
[{:keys [page deletable? selected?] :as props}]
|
[{:keys [page index deletable? selected?] :as props}]
|
||||||
(let [local (mf/use-state {})
|
|
||||||
body-classes (classnames
|
|
||||||
:selected selected?
|
|
||||||
:drag-active (:dragging @local)
|
|
||||||
:drag-top (= :top (:over @local))
|
|
||||||
:drag-bottom (= :bottom (:over @local))
|
|
||||||
:drag-inside (= :middle (:over @local)))
|
|
||||||
li-classes (classnames
|
|
||||||
:selected selected?
|
|
||||||
:hide (:dragging @local))]
|
|
||||||
(letfn [(on-edit [event]
|
(letfn [(on-edit [event]
|
||||||
(udl/open! :page-form {:page page}))
|
(udl/open! :page-form {:page page}))
|
||||||
|
|
||||||
(on-navigate [event]
|
|
||||||
(st/emit! (dp/go-to (:project page) (:id page))))
|
|
||||||
|
|
||||||
(delete []
|
(delete []
|
||||||
(let [next #(st/emit! (dp/go-to (:project page)))]
|
(let [next #(st/emit! (dp/go-to (:project page)))]
|
||||||
(st/emit! (udp/delete-page (:id page) next))))
|
(st/emit! (udp/delete-page (:id page) next))))
|
||||||
|
@ -51,48 +41,25 @@
|
||||||
(dom/prevent-default event)
|
(dom/prevent-default event)
|
||||||
(dom/stop-propagation event)
|
(dom/stop-propagation event)
|
||||||
(udl/open! :confirm {:on-accept delete}))
|
(udl/open! :confirm {:on-accept delete}))
|
||||||
|
(on-drop [item monitor]
|
||||||
(on-drag-start [event]
|
(st/emit! (udp/reorder-pages (:project page))))
|
||||||
(let [target (dom/event->target event)]
|
(on-hover [item monitor]
|
||||||
(dnd/set-allowed-effect! event "move")
|
(st/emit! (udp/move-page {:project-id (:project-id item)
|
||||||
(dnd/set-data! event (:id page))
|
:page-id (:page-id item)
|
||||||
(dnd/set-image! event target 50 10)
|
:index index})))]
|
||||||
(swap! local assoc :dragging true)))
|
(let [[dprops ref] (use-sortable {:type "page-item"
|
||||||
(on-drag-end [event]
|
:data {:page-id (:id page)
|
||||||
(swap! local assoc :dragging false :over nil))
|
:project-id (:project page)
|
||||||
(on-drop [event]
|
:index index}
|
||||||
(dom/stop-propagation event)
|
:on-hover on-hover
|
||||||
(let [id (dnd/get-data event)
|
:on-drop on-drop})]
|
||||||
over (:over @local)]
|
[:li {:ref ref :class (classnames :selected selected?)}
|
||||||
(case (:over @local)
|
|
||||||
:top (let [new-order (dec (get-in page [:metadata :order]))]
|
|
||||||
(st/emit! (udp/update-order id new-order)))
|
|
||||||
:bottom (let [new-order (inc (get-in page [:metadata :order]))]
|
|
||||||
(st/emit! (udp/update-order id new-order))))
|
|
||||||
(swap! local assoc :dragging false :over nil)))
|
|
||||||
(on-drag-over [event]
|
|
||||||
(dom/prevent-default event)
|
|
||||||
(dnd/set-drop-effect! event "move")
|
|
||||||
(let [over (dnd/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 {:class li-classes}
|
|
||||||
[:div.element-list-body
|
[:div.element-list-body
|
||||||
{:class body-classes
|
{:class (classnames :selected selected?
|
||||||
:style {:opacity (if (:dragging @local)
|
:dragging (:dragging? dprops))
|
||||||
"0.5"
|
:on-click #(st/emit! (rt/nav :workspace/page {:project (:project page)
|
||||||
"1")}
|
:page (:id page)}))
|
||||||
:on-click on-navigate
|
|
||||||
:on-double-click #(dom/stop-propagation %)
|
:on-double-click #(dom/stop-propagation %)
|
||||||
: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}
|
:draggable true}
|
||||||
|
|
||||||
[:div.page-icon i/page]
|
[:div.page-icon i/page]
|
||||||
|
@ -102,38 +69,39 @@
|
||||||
(when deletable?
|
(when deletable?
|
||||||
[:a {:on-click on-delete} i/trash])]]])))
|
[:a {:on-click on-delete} i/trash])]]])))
|
||||||
|
|
||||||
;; TODO: refactor this to not use global refs
|
;; --- Pages List
|
||||||
|
|
||||||
(defn- pages-selector
|
(defn- make-pages-iref
|
||||||
[project-id]
|
[{:keys [id pages] :as project}]
|
||||||
(let [get-order #(get-in % [:metadata :order])]
|
(letfn [(selector [state]
|
||||||
(fn [state]
|
(into [] (map #(get-in state [:pages %])) pages))]
|
||||||
;; NOTE: this function will be executed on every state change
|
(-> (l/lens selector)
|
||||||
;; when we are on workspace page, that is ok but we need to
|
|
||||||
;; think in a better approach (maybe materialize the result
|
|
||||||
;; after pages fetching...)
|
|
||||||
(->> (vals (:pages state))
|
|
||||||
(filter #(= project-id (:project %)))
|
|
||||||
(sort-by get-order)))))
|
|
||||||
|
|
||||||
(mf/def sitemap-toolbox
|
|
||||||
:mixins [mf/memo mf/reactive]
|
|
||||||
|
|
||||||
:init
|
|
||||||
(fn [own {:keys [page] :as props}]
|
|
||||||
(assoc own
|
|
||||||
::project-ref (-> (l/in [:projects (:project page)])
|
|
||||||
(l/derive st/state))
|
|
||||||
::pages-ref (-> (l/lens (pages-selector (:project page)))
|
|
||||||
(l/derive st/state))))
|
(l/derive st/state))))
|
||||||
|
|
||||||
:render
|
(mf/defc pages-list
|
||||||
(fn [own {:keys [page] :as props}]
|
[{:keys [project current-page-id] :as props}]
|
||||||
(let [project (mf/react (::project-ref own))
|
(let [pages-iref (mf/use-memo {:deps #js [project]
|
||||||
pages (mf/react (::pages-ref own))
|
:init #(make-pages-iref project)})
|
||||||
create #(udl/open! :page-form {:page {:project (:id project)}})
|
pages (mf/deref pages-iref)
|
||||||
close #(st/emit! (dw/toggle-flag :sitemap))
|
|
||||||
deletable? (> (count pages) 1)]
|
deletable? (> (count pages) 1)]
|
||||||
|
[:ul.element-list
|
||||||
|
(for [[index item] (map-indexed vector pages)]
|
||||||
|
[:& page-item {:page item
|
||||||
|
:index index
|
||||||
|
:deletable? deletable?
|
||||||
|
:selected? (= (:id item) current-page-id)
|
||||||
|
:key (:id item)}])]))
|
||||||
|
|
||||||
|
;; --- Sitemap Toolbox
|
||||||
|
|
||||||
|
(mf/defc sitemap-toolbox
|
||||||
|
[{:keys [project-id current-page-id] :as props}]
|
||||||
|
(let [project-iref (mf/use-memo {:deps #js [project-id]
|
||||||
|
:init #(-> (l/in [:projects project-id])
|
||||||
|
(l/derive st/state))})
|
||||||
|
project (mf/deref project-iref)
|
||||||
|
create #(udl/open! :page-form {:page {:project project-id}})
|
||||||
|
close #(st/emit! (dw/toggle-flag :sitemap))]
|
||||||
[:div.sitemap.tool-window
|
[:div.sitemap.tool-window
|
||||||
[:div.tool-window-bar
|
[:div.tool-window-bar
|
||||||
[:div.tool-window-icon i/project-tree]
|
[:div.tool-window-icon i/project-tree]
|
||||||
|
@ -143,10 +111,5 @@
|
||||||
[:div.project-title
|
[:div.project-title
|
||||||
[:span (:name project)]
|
[:span (:name project)]
|
||||||
[:div.add-page {:on-click create} i/close]]
|
[:div.add-page {:on-click create} i/close]]
|
||||||
[:ul.element-list
|
[:& pages-list {:project project
|
||||||
(for [item pages]
|
:current-page-id current-page-id}]]]))
|
||||||
(let [selected? (= (:id item) (:id page))]
|
|
||||||
[:& page-item {:page item
|
|
||||||
:deletable? deletable?
|
|
||||||
:selected? selected?
|
|
||||||
:key (:id item)}]))]]])))
|
|
||||||
|
|
|
@ -2,8 +2,7 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; 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/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2015-2016 Andrey Antukh <niwi@niwi.nz>
|
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
|
||||||
|
|
||||||
(ns uxbox.util.data
|
(ns uxbox.util.data
|
||||||
"A collection of data transformation utils."
|
"A collection of data transformation utils."
|
||||||
|
|
|
@ -18,8 +18,9 @@
|
||||||
|
|
||||||
(defn- on-message
|
(defn- on-message
|
||||||
[event]
|
[event]
|
||||||
|
(when (nil? (.-source event))
|
||||||
(let [message (t/decode (.-data event))]
|
(let [message (t/decode (.-data event))]
|
||||||
(impl/handler message)))
|
(impl/handler message))))
|
||||||
|
|
||||||
(defonce _
|
(defonce _
|
||||||
(.addEventListener js/self "message" on-message))
|
(.addEventListener js/self "message" on-message))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue