From 03c9d9c8f1c930342e59add86d7974557cd4f20d Mon Sep 17 00:00:00 2001 From: Andrey Antukh Date: Thu, 8 Aug 2019 16:27:37 +0200 Subject: [PATCH] :recycle: Refactor drag-and-drop on workspace sidebars (now using react-dnd). --- .../styles/main/partials/sidebar-layers.scss | 4 + .../styles/main/partials/sidebar-sitemap.scss | 14 +- frontend/src/uxbox/main/data/pages.cljs | 133 +++----- frontend/src/uxbox/main/data/workspace.cljs | 15 + frontend/src/uxbox/main/store.cljs | 6 +- .../src/uxbox/main/ui/dashboard/projects.cljs | 5 +- .../src/uxbox/main/ui/workspace/sidebar.cljs | 22 +- .../main/ui/workspace/sidebar/layers.cljs | 284 ++++++------------ .../main/ui/workspace/sidebar/sitemap.cljs | 181 +++++------ frontend/src/uxbox/util/data.cljs | 3 +- frontend/src/uxbox/worker.cljs | 5 +- 11 files changed, 250 insertions(+), 422 deletions(-) diff --git a/frontend/resources/styles/main/partials/sidebar-layers.scss b/frontend/resources/styles/main/partials/sidebar-layers.scss index 3e539c106..065147f67 100644 --- a/frontend/resources/styles/main/partials/sidebar-layers.scss +++ b/frontend/resources/styles/main/partials/sidebar-layers.scss @@ -102,6 +102,10 @@ flex-direction: column; width: 100%; + &.dragging-TODO { + background-color: #eee; + } + &.open { ul { diff --git a/frontend/resources/styles/main/partials/sidebar-sitemap.scss b/frontend/resources/styles/main/partials/sidebar-sitemap.scss index 33db17c05..2d1ecf5b2 100644 --- a/frontend/resources/styles/main/partials/sidebar-sitemap.scss +++ b/frontend/resources/styles/main/partials/sidebar-sitemap.scss @@ -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; - } - } } diff --git a/frontend/src/uxbox/main/data/pages.cljs b/frontend/src/uxbox/main/data/pages.cljs index cc017ea88..26e123fc0 100644 --- a/frontend/src/uxbox/main/data/pages.cljs +++ b/frontend/src/uxbox/main/data/pages.cljs @@ -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 ;; diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 2e48351c0..0192d1b77 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -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 diff --git a/frontend/src/uxbox/main/store.cljs b/frontend/src/uxbox/main/store.cljs index 5209add1a..7b7b90998 100644 --- a/frontend/src/uxbox/main/store.cljs +++ b/frontend/src/uxbox/main/store.cljs @@ -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 diff --git a/frontend/src/uxbox/main/ui/dashboard/projects.cljs b/frontend/src/uxbox/main/ui/dashboard/projects.cljs index c484319f4..d9ea84bfb 100644 --- a/frontend/src/uxbox/main/ui/dashboard/projects.cljs +++ b/frontend/src/uxbox/main/ui/dashboard/projects.cljs @@ -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" diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs index 0aec4f3de..df5693e17 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar.cljs @@ -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) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs index 4db402ac5..52bb3bdb4 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/layers.cljs @@ -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 ;; Copyright (c) 2015-2016 Juan de la Cruz +;; Copyright (c) 2015-2019 Andrey Antukh (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}]])) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs index 1b8fcee7e..0b98a1ea4 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/sitemap.cljs @@ -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}]]])) diff --git a/frontend/src/uxbox/util/data.cljs b/frontend/src/uxbox/util/data.cljs index 0f4b9e660..b1b3367a1 100644 --- a/frontend/src/uxbox/util/data.cljs +++ b/frontend/src/uxbox/util/data.cljs @@ -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 -;; Copyright (c) 2015-2016 Juan de la Cruz +;; Copyright (c) 2015-2019 Andrey Antukh (ns uxbox.util.data "A collection of data transformation utils." diff --git a/frontend/src/uxbox/worker.cljs b/frontend/src/uxbox/worker.cljs index edb10c925..72a965cc8 100644 --- a/frontend/src/uxbox/worker.cljs +++ b/frontend/src/uxbox/worker.cljs @@ -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))