mirror of
https://github.com/penpot/penpot.git
synced 2025-04-02 10:01:34 -05: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;
|
||||
width: 100%;
|
||||
|
||||
&.dragging-TODO {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
&.open {
|
||||
|
||||
ul {
|
||||
|
|
|
@ -269,18 +269,10 @@
|
|||
|
||||
}
|
||||
|
||||
&.drag-top {
|
||||
border-top: 40px solid $soft-ui-border !important;
|
||||
&.dragging {
|
||||
// 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]
|
||||
(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
|
||||
|
||||
(deftype PagesFetched [id pages]
|
||||
|
@ -174,13 +153,15 @@
|
|||
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [get-order #(get-in % [:metadata :order])
|
||||
pages (sort-by get-order pages)
|
||||
page-ids (into [] (map :id) pages)]
|
||||
(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-packed-page $ pages)))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(rx/of (update-project-main-page id))))
|
||||
(reduce assoc-packed-page $ pages)))))
|
||||
|
||||
(defn pages-fetched
|
||||
[id pages]
|
||||
|
@ -295,7 +276,7 @@
|
|||
(->> (rp/req :update/page page)
|
||||
(rx/map :payload)
|
||||
(rx/do #(when (fn? on-success)
|
||||
(ts/schedule 0 on-success)))
|
||||
(ts/schedule-on-idle on-success)))
|
||||
(rx/map page-persisted)))))))
|
||||
|
||||
(defn persist-page?
|
||||
|
@ -335,34 +316,16 @@
|
|||
;; that does not sends the heavyweiht `:data` attribute
|
||||
;; and only serves for update other page data.
|
||||
|
||||
(deftype PersistMetadata [id]
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page (get-in state [:pages id])]
|
||||
(->> (rp/req :update/page-metadata page)
|
||||
(rx/map :payload)
|
||||
(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))
|
||||
(reify
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page (get-in state [:pages id])]
|
||||
(->> (rp/req :update/page-metadata page)
|
||||
(rx/map :payload)
|
||||
(rx/map metadata-persisted))))))
|
||||
|
||||
;; --- Update Page
|
||||
|
||||
|
@ -396,49 +359,37 @@
|
|||
;; page order numbers after a user sorting
|
||||
;; 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
|
||||
[id]
|
||||
{:pre [(uuid? id)]}
|
||||
(ReorderPages. id))
|
||||
[project-id]
|
||||
{:pre [(uuid? project-id)]}
|
||||
(reify
|
||||
ptk/UpdateEvent
|
||||
(update [this state]
|
||||
(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))))
|
||||
|
||||
;; --- Update Order
|
||||
;;
|
||||
;; A specialized event for update order
|
||||
;; attribute on the page metadata
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [page-ids (get-in state [:projects project-id :pages])]
|
||||
(->> (rx/from-coll page-ids)
|
||||
(rx/map persist-metadata))))))
|
||||
|
||||
(deftype UpdateOrder [id order]
|
||||
ptk/UpdateEvent
|
||||
(update [this state]
|
||||
(assoc-in state [:pages id :metadata :order] order))
|
||||
;; --- Move Page (Ordering)
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [project (get-in state [:pages id :project])]
|
||||
(rx/of (reorder-pages project)))))
|
||||
|
||||
(defn update-order
|
||||
[id order]
|
||||
{:pre [(uuid? id) (number? order)]}
|
||||
(UpdateOrder. id order))
|
||||
(defn move-page
|
||||
[{: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
|
||||
;;
|
||||
|
|
|
@ -517,6 +517,21 @@
|
|||
[]
|
||||
(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
|
||||
|
||||
(def ^:private canvas-coords
|
||||
|
|
|
@ -26,9 +26,11 @@
|
|||
|
||||
(defn emit!
|
||||
([event]
|
||||
(ptk/emit! store event))
|
||||
(ptk/emit! store event)
|
||||
nil)
|
||||
([event & events]
|
||||
(apply ptk/emit! store (cons event events))))
|
||||
(apply ptk/emit! store (cons event events))
|
||||
nil))
|
||||
|
||||
(def initial-state
|
||||
{:dashboard {:project-order :name
|
||||
|
|
|
@ -118,7 +118,7 @@
|
|||
(mf/defc grid-item-thumbnail
|
||||
[{:keys [project] :as props}]
|
||||
(let [url (mf/use-state nil)]
|
||||
(mf/use-effect
|
||||
#_(mf/use-effect
|
||||
{:deps #js [(:page-id project)]
|
||||
:init (fn []
|
||||
(when-let [page-id (:page-id project)]
|
||||
|
@ -160,7 +160,8 @@
|
|||
(dom/prevent-default %)
|
||||
(swap! local assoc :edition true))]
|
||||
[: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
|
||||
(if (:edition @local)
|
||||
[:input.element-name {:type "text"
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
[uxbox.main.ui.workspace.sidebar.icons :refer [icons-toolbox]]
|
||||
[uxbox.main.ui.workspace.sidebar.layers :refer [layers-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)
|
||||
|
||||
|
@ -21,14 +22,17 @@
|
|||
[{:keys [wst page] :as props}]
|
||||
(let [{:keys [flags selected]} wst]
|
||||
[:aside#settings-bar.settings-bar.settings-bar-left
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
[:& sitemap-toolbox {:page page}])
|
||||
#_(when (contains? flags :document-history)
|
||||
(history-toolbox page-id))
|
||||
(when (contains? flags :layers)
|
||||
[:& layers-toolbox {:page page
|
||||
:selected selected}])]]))
|
||||
[:> rdnd/provider {:backend rdnd/html5}
|
||||
[:div.settings-bar-inside
|
||||
(when (contains? flags :sitemap)
|
||||
[:& sitemap-toolbox {:project-id (:project page)
|
||||
:current-page-id (:id page)
|
||||
:page page}])
|
||||
#_(when (contains? flags :document-history)
|
||||
(history-toolbox page-id))
|
||||
(when (contains? flags :layers)
|
||||
[:& layers-toolbox {:page page
|
||||
:selected selected}])]]]))
|
||||
|
||||
;; --- Right Sidebar (Component)
|
||||
|
||||
|
|
|
@ -2,76 +2,26 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.main.ui.workspace.sidebar.layers
|
||||
(:require
|
||||
[cuerdas.core :as str]
|
||||
[goog.events :as events]
|
||||
[lentes.core :as l]
|
||||
[potok.core :as ptk]
|
||||
[rumext.alpha :as mf]
|
||||
[rumext.core :as mx]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
[uxbox.main.data.shapes :as uds]
|
||||
[uxbox.main.data.workspace :as udw]
|
||||
[uxbox.main.refs :as refs]
|
||||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.keyboard :as kbd]
|
||||
[uxbox.main.ui.shapes.icon :as icon]
|
||||
[uxbox.util.data :refer (read-string classnames)]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.dom.dnd :as dnd]
|
||||
[uxbox.util.timers :as tm]
|
||||
[uxbox.util.router :as r])
|
||||
(:import goog.events.EventType))
|
||||
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.dom :as dom]))
|
||||
|
||||
;; --- 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
|
||||
[item]
|
||||
(case (:type item)
|
||||
|
@ -85,7 +35,7 @@
|
|||
:group i/folder
|
||||
nil))
|
||||
|
||||
;; --- Shape Name (Component)
|
||||
;; --- Layer Name
|
||||
|
||||
(mf/defc layer-name
|
||||
[{:keys [shape] :as props}]
|
||||
|
@ -117,162 +67,108 @@
|
|||
{:on-double-click on-click}
|
||||
(:name shape "")])))
|
||||
|
||||
;; --- Layer Simple (Component)
|
||||
;; --- Layer Item
|
||||
|
||||
(mf/defc layer-item
|
||||
[{:keys [shape selected] :as props}]
|
||||
(let [local (mf/use-state {})
|
||||
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)
|
||||
(let [id (dnd/get-data event)
|
||||
over (:over @local)]
|
||||
(case (:over @local)
|
||||
:top (st/emit! (uds/drop-shape id (:id shape) :before))
|
||||
:bottom (st/emit! (uds/drop-shape id (:id shape) :after)))
|
||||
(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}
|
||||
[{:keys [shape selected index] :as props}]
|
||||
(letfn [(toggle-blocking [event]
|
||||
(dom/stop-propagation event)
|
||||
(let [id (:id shape)
|
||||
blocked? (:blocked shape)]
|
||||
(if blocked?
|
||||
(st/emit! (uds/unblock-shape id))
|
||||
(st/emit! (uds/block-shape id)))))
|
||||
|
||||
(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.toggle-element
|
||||
{:class (when-not (:hidden shape) "selected")
|
||||
:on-click toggle-visibility}
|
||||
[:div.toggle-element {:class (when-not (:hidden shape) "selected")
|
||||
:on-click toggle-visibility}
|
||||
i/eye]
|
||||
[:div.block-element
|
||||
{:class (when (:blocked shape) "selected")
|
||||
:on-click toggle-blocking}
|
||||
[:div.block-element {:class (when (:blocked shape) "selected")
|
||||
:on-click toggle-blocking}
|
||||
i/lock]]
|
||||
[:div.element-icon (element-icon shape)]
|
||||
[:& layer-name {:shape shape}]]])))
|
||||
|
||||
;; --- Layer Group (Component)
|
||||
|
||||
;; --- 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)
|
||||
;; --- Layers List
|
||||
|
||||
(def ^:private shapes-iref
|
||||
(-> (l/key :shapes)
|
||||
(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
|
||||
[{:keys [page selected] :as props}]
|
||||
(let [shapes (mf/deref shapes-iref)
|
||||
on-click #(st/emit! (udw/toggle-flag :layers))]
|
||||
(let [on-click #(st/emit! (udw/toggle-flag :layers))]
|
||||
[:div#layers.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/layers]
|
||||
[:span "Layers"]
|
||||
[:div.tool-window-close {:on-click on-click} i/close]]
|
||||
[:div.tool-window-content
|
||||
[:ul.element-list
|
||||
(for [id (:shapes page)]
|
||||
[:& layer-item {:shape (get shapes id)
|
||||
:selected selected
|
||||
:key id}])]]
|
||||
[:& layers-tools {:selected selected
|
||||
:shapes shapes}]]))
|
||||
|
||||
[:& layers-list {:shapes (:shapes page)
|
||||
:selected selected}]]))
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
[cuerdas.core :as str]
|
||||
[lentes.core :as l]
|
||||
[rumext.alpha :as mf]
|
||||
[rumext.util :as mfu]
|
||||
[uxbox.builtins.icons :as i]
|
||||
[uxbox.main.data.lightbox :as udl]
|
||||
[uxbox.main.data.pages :as udp]
|
||||
|
@ -19,80 +20,46 @@
|
|||
[uxbox.main.store :as st]
|
||||
[uxbox.main.ui.lightbox :as lbx]
|
||||
[uxbox.main.ui.workspace.sidebar.sitemap-pageform]
|
||||
[uxbox.main.ui.workspace.sortable :refer [use-sortable]]
|
||||
[uxbox.util.data :refer [classnames]]
|
||||
[uxbox.util.dom :as dom]
|
||||
[uxbox.util.dom.dnd :as dnd]
|
||||
[uxbox.util.i18n :refer (tr)]
|
||||
[uxbox.util.router :as r]))
|
||||
[uxbox.util.router :as rt]))
|
||||
|
||||
;; --- Page Item
|
||||
|
||||
(mf/defc page-item
|
||||
[{:keys [page 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]
|
||||
(udl/open! :page-form {:page page}))
|
||||
[{:keys [page index deletable? selected?] :as props}]
|
||||
(letfn [(on-edit [event]
|
||||
(udl/open! :page-form {:page page}))
|
||||
(delete []
|
||||
(let [next #(st/emit! (dp/go-to (:project page)))]
|
||||
(st/emit! (udp/delete-page (:id page) next))))
|
||||
|
||||
(on-navigate [event]
|
||||
(st/emit! (dp/go-to (:project page) (:id page))))
|
||||
|
||||
(delete []
|
||||
(let [next #(st/emit! (dp/go-to (:project page)))]
|
||||
(st/emit! (udp/delete-page (:id page) next))))
|
||||
|
||||
(on-delete [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(udl/open! :confirm {:on-accept delete}))
|
||||
|
||||
(on-drag-start [event]
|
||||
(let [target (dom/event->target event)]
|
||||
(dnd/set-allowed-effect! event "move")
|
||||
(dnd/set-data! event (:id page))
|
||||
(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 (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}
|
||||
(on-delete [event]
|
||||
(dom/prevent-default event)
|
||||
(dom/stop-propagation event)
|
||||
(udl/open! :confirm {:on-accept delete}))
|
||||
(on-drop [item monitor]
|
||||
(st/emit! (udp/reorder-pages (:project page))))
|
||||
(on-hover [item monitor]
|
||||
(st/emit! (udp/move-page {:project-id (:project-id item)
|
||||
:page-id (:page-id item)
|
||||
:index index})))]
|
||||
(let [[dprops ref] (use-sortable {:type "page-item"
|
||||
:data {:page-id (:id page)
|
||||
:project-id (:project page)
|
||||
:index index}
|
||||
:on-hover on-hover
|
||||
:on-drop on-drop})]
|
||||
[:li {:ref ref :class (classnames :selected selected?)}
|
||||
[:div.element-list-body
|
||||
{:class body-classes
|
||||
:style {:opacity (if (:dragging @local)
|
||||
"0.5"
|
||||
"1")}
|
||||
:on-click on-navigate
|
||||
{:class (classnames :selected selected?
|
||||
:dragging (:dragging? dprops))
|
||||
:on-click #(st/emit! (rt/nav :workspace/page {:project (:project page)
|
||||
:page (:id page)}))
|
||||
: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}
|
||||
|
||||
[:div.page-icon i/page]
|
||||
|
@ -102,51 +69,47 @@
|
|||
(when deletable?
|
||||
[:a {:on-click on-delete} i/trash])]]])))
|
||||
|
||||
;; TODO: refactor this to not use global refs
|
||||
;; --- Pages List
|
||||
|
||||
(defn- pages-selector
|
||||
[project-id]
|
||||
(let [get-order #(get-in % [:metadata :order])]
|
||||
(fn [state]
|
||||
;; NOTE: this function will be executed on every state change
|
||||
;; 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)))))
|
||||
(defn- make-pages-iref
|
||||
[{:keys [id pages] :as project}]
|
||||
(letfn [(selector [state]
|
||||
(into [] (map #(get-in state [:pages %])) pages))]
|
||||
(-> (l/lens selector)
|
||||
(l/derive st/state))))
|
||||
|
||||
(mf/def sitemap-toolbox
|
||||
:mixins [mf/memo mf/reactive]
|
||||
(mf/defc pages-list
|
||||
[{:keys [project current-page-id] :as props}]
|
||||
(let [pages-iref (mf/use-memo {:deps #js [project]
|
||||
:init #(make-pages-iref project)})
|
||||
pages (mf/deref pages-iref)
|
||||
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)}])]))
|
||||
|
||||
: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))))
|
||||
;; --- Sitemap Toolbox
|
||||
|
||||
:render
|
||||
(fn [own {:keys [page] :as props}]
|
||||
(let [project (mf/react (::project-ref own))
|
||||
pages (mf/react (::pages-ref own))
|
||||
create #(udl/open! :page-form {:page {:project (:id project)}})
|
||||
close #(st/emit! (dw/toggle-flag :sitemap))
|
||||
deletable? (> (count pages) 1)]
|
||||
[:div.sitemap.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/project-tree]
|
||||
[:span (tr "ds.sitemap")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
[:div.project-title
|
||||
[:span (:name project)]
|
||||
[:div.add-page {:on-click create} i/close]]
|
||||
[:ul.element-list
|
||||
(for [item pages]
|
||||
(let [selected? (= (:id item) (:id page))]
|
||||
[:& page-item {:page item
|
||||
:deletable? deletable?
|
||||
:selected? selected?
|
||||
:key (:id item)}]))]]])))
|
||||
(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.tool-window-bar
|
||||
[:div.tool-window-icon i/project-tree]
|
||||
[:span (tr "ds.sitemap")]
|
||||
[:div.tool-window-close {:on-click close} i/close]]
|
||||
[:div.tool-window-content
|
||||
[:div.project-title
|
||||
[:span (:name project)]
|
||||
[:div.add-page {:on-click create} i/close]]
|
||||
[:& pages-list {:project project
|
||||
:current-page-id current-page-id}]]]))
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
;; 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>
|
||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.util.data
|
||||
"A collection of data transformation utils."
|
||||
|
|
|
@ -18,8 +18,9 @@
|
|||
|
||||
(defn- on-message
|
||||
[event]
|
||||
(let [message (t/decode (.-data event))]
|
||||
(impl/handler message)))
|
||||
(when (nil? (.-source event))
|
||||
(let [message (t/decode (.-data event))]
|
||||
(impl/handler message))))
|
||||
|
||||
(defonce _
|
||||
(.addEventListener js/self "message" on-message))
|
||||
|
|
Loading…
Add table
Reference in a new issue