0
Fork 0
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:
Andrey Antukh 2019-08-08 16:27:37 +02:00
parent dbf754880e
commit 03c9d9c8f1
11 changed files with 250 additions and 422 deletions

View file

@ -102,6 +102,10 @@
flex-direction: column;
width: 100%;
&.dragging-TODO {
background-color: #eee;
}
&.open {
ul {

View file

@ -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;
}
}
}

View file

@ -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
;;

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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)

View file

@ -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}]]))

View file

@ -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}]]]))

View file

@ -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."

View file

@ -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))