mirror of
https://github.com/penpot/penpot.git
synced 2025-06-06 00:41:38 +02:00
✨ Handsoff mode basic structure.
This commit is contained in:
parent
aaaf099a3f
commit
04f620ec00
24 changed files with 720 additions and 25 deletions
|
@ -51,7 +51,11 @@
|
|||
:page-id page-id
|
||||
:file-id file-id
|
||||
:interactions-mode :hide
|
||||
:show-interactions? false}))
|
||||
:show-interactions? false
|
||||
|
||||
:selected #{}
|
||||
:collapsed #{}
|
||||
:hover #{}}))
|
||||
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
|
@ -170,24 +174,25 @@
|
|||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [route (:route state)
|
||||
screen (-> route :data :name keyword)
|
||||
qparams (get-in route [:params :query])
|
||||
pparams (get-in route [:params :path])
|
||||
index (d/parse-integer (:index qparams))]
|
||||
(when (pos? index)
|
||||
(rx/of (rt/nav :viewer pparams (assoc qparams :index (dec index)))))))))
|
||||
(rx/of (rt/nav screen pparams (assoc qparams :index (dec index)))))))))
|
||||
|
||||
(def select-next-frame
|
||||
(ptk/reify ::select-prev-frame
|
||||
ptk/WatchEvent
|
||||
(watch [_ state stream]
|
||||
(let [route (:route state)
|
||||
screen (-> route :data :name keyword)
|
||||
qparams (get-in route [:params :query])
|
||||
pparams (get-in route [:params :path])
|
||||
index (d/parse-integer (:index qparams))
|
||||
|
||||
total (count (get-in state [:viewer-data :frames]))]
|
||||
(when (< index (dec total))
|
||||
(rx/of (rt/nav :viewer pparams (assoc qparams :index (inc index)))))))))
|
||||
(rx/of (rt/nav screen pparams (assoc qparams :index (inc index)))))))))
|
||||
|
||||
(defn set-interactions-mode
|
||||
[mode]
|
||||
|
@ -249,3 +254,36 @@
|
|||
"shift+2" #(st/emit! zoom-to-200)
|
||||
"left" #(st/emit! select-prev-frame)
|
||||
"right" #(st/emit! select-next-frame)})
|
||||
|
||||
|
||||
(defn deselect-all []
|
||||
(ptk/reify ::deselect-all
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(assoc-in state [:viewer-local :selected] #{}))))
|
||||
|
||||
(defn select-shape
|
||||
([id] (select-shape id false))
|
||||
([id toggle?]
|
||||
(ptk/reify ::select-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(-> state
|
||||
(assoc-in [:viewer-local :selected] #{id}))))))
|
||||
|
||||
;; TODO
|
||||
(defn collapse-all []
|
||||
(ptk/reify ::collapse-all))
|
||||
|
||||
(defn toggle-collapse [id]
|
||||
(ptk/reify ::toggle-collapse
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(let [toggled? (contains? (get-in state [:viewer-local :collapsed]) id)]
|
||||
(update-in state [:viewer-local :collapsed] (if toggled? disj conj) id)))))
|
||||
|
||||
(defn hover-shape [id hover?]
|
||||
(ptk/reify ::hover-shape
|
||||
ptk/UpdateEvent
|
||||
(update [_ state]
|
||||
(update-in state [:viewer-local :hover] (if hover? conj disj) id))))
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
[app.main.ui.settings :as settings]
|
||||
[app.main.ui.static :refer [not-found-page not-authorized-page]]
|
||||
[app.main.ui.viewer :refer [viewer-page]]
|
||||
[app.main.ui.viewer.handoff :refer [handoff]]
|
||||
[app.main.ui.workspace :as workspace]
|
||||
[app.util.i18n :as i18n :refer [tr t]]
|
||||
[app.util.timers :as ts]
|
||||
|
@ -53,6 +54,7 @@
|
|||
["/options" :settings-options]]
|
||||
|
||||
["/view/:file-id/:page-id" :viewer]
|
||||
["/handoff/:file-id/:page-id" :handoff]
|
||||
["/not-found" :not-found]
|
||||
["/not-authorized" :not-authorized]
|
||||
|
||||
|
@ -127,6 +129,14 @@
|
|||
:index index
|
||||
:token token}])
|
||||
|
||||
:handoff
|
||||
(let [index (d/parse-integer (get-in route [:params :query :index]))
|
||||
file-id (uuid (get-in route [:params :path :file-id]))
|
||||
page-id (uuid (get-in route [:params :path :page-id]))]
|
||||
[:& handoff {:page-id page-id
|
||||
:file-id file-id
|
||||
:index index}])
|
||||
|
||||
:render-object
|
||||
(do
|
||||
(let [file-id (uuid (get-in route [:params :path :file-id]))
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
(def align-middle (icon-xref :align-middle))
|
||||
(def align-top (icon-xref :align-top))
|
||||
(def alignment (icon-xref :alignment))
|
||||
(def arrow (icon-xref :arrow))
|
||||
#_(def arrow (icon-xref :arrow))
|
||||
(def arrow-down (icon-xref :arrow-down))
|
||||
(def arrow-end (icon-xref :arrow-end))
|
||||
(def arrow-slide (icon-xref :arrow-slide))
|
||||
|
@ -91,7 +91,7 @@
|
|||
(def rotate (icon-xref :rotate))
|
||||
(def ruler (icon-xref :ruler))
|
||||
(def ruler-tool (icon-xref :ruler-tool))
|
||||
(def save (icon-xref :save))
|
||||
#_(def save (icon-xref :save))
|
||||
(def search (icon-xref :search))
|
||||
(def shape-halign-center (icon-xref :shape-halign-center))
|
||||
(def shape-halign-left (icon-xref :shape-halign-left))
|
||||
|
@ -128,6 +128,7 @@
|
|||
(def picker-ramp (icon-xref :picker-ramp))
|
||||
(def checkbox-checked (icon-xref :checkbox-checked))
|
||||
(def checkbox-unchecked (icon-xref :checkbox-unchecked))
|
||||
(def code (icon-xref :code))
|
||||
|
||||
(def loader-pencil
|
||||
(mf/html
|
||||
|
@ -149,9 +150,10 @@
|
|||
(mf/defc debug-icons-preview
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
[:section.debug-icons-preview
|
||||
[:section.debug-icons-preview {:style {:background-color "black"}}
|
||||
(for [[key val] (sort-by first (ns-publics 'app.main.ui.icons))]
|
||||
(when (not= key 'debug-icons-preview)
|
||||
[:div.icon-item {:key key}
|
||||
[:div.icon-item {:key key
|
||||
:style {:fill "white"}}
|
||||
(deref val)
|
||||
[:span (pr-str key)]]))])
|
||||
|
|
|
@ -92,10 +92,12 @@
|
|||
:toggle-fullscreen toggle-fullscreen
|
||||
:fullscreen? fullscreen?
|
||||
:local local
|
||||
:index index}]
|
||||
:index index
|
||||
:screen :viewer}]
|
||||
[:div.viewer-content {:on-click on-click}
|
||||
(when (:show-thumbnails local)
|
||||
[:& thumbnails-panel {:index index
|
||||
[:& thumbnails-panel {:screen :viewer
|
||||
:index index
|
||||
:data data}])
|
||||
[:& main-panel {:data data
|
||||
:local local
|
||||
|
|
121
frontend/src/app/main/ui/viewer/handoff.cljs
Normal file
121
frontend/src/app/main/ui/viewer/handoff.cljs
Normal file
|
@ -0,0 +1,121 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.viewer.handoff
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[beicon.core :as rx]
|
||||
[goog.events :as events]
|
||||
[okulary.core :as l]
|
||||
[app.common.exceptions :as ex]
|
||||
[app.util.data :refer [classnames]]
|
||||
[app.util.dom :as dom]
|
||||
[app.util.i18n :as i18n :refer [t tr]]
|
||||
[app.main.data.viewer :as dv]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.ui.components.dropdown :refer [dropdown]]
|
||||
[app.main.ui.hooks :as hooks]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.viewer.header :refer [header]]
|
||||
[app.main.ui.viewer.thumbnails :refer [thumbnails-panel]]
|
||||
|
||||
[app.main.ui.viewer.handoff.render :refer [render-frame-svg]]
|
||||
[app.main.ui.viewer.handoff.layers-sidebar :refer [layers-sidebar]]
|
||||
[app.main.ui.viewer.handoff.attributes-sidebar :refer [attributes-sidebar]])
|
||||
(:import goog.events.EventType))
|
||||
|
||||
(defn handle-select-frame [frame]
|
||||
#(do (dom/prevent-default %)
|
||||
(dom/stop-propagation %)
|
||||
(st/emit! (dv/select-shape (:id frame)))))
|
||||
|
||||
(mf/defc render-panel
|
||||
[{:keys [data local index]}]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
frames (:frames data [])
|
||||
objects (:objects data)
|
||||
frame (get frames index)]
|
||||
[:section.viewer-preview
|
||||
(cond
|
||||
(empty? frames)
|
||||
[:section.empty-state
|
||||
[:span (t locale "viewer.empty-state")]]
|
||||
|
||||
(nil? frame)
|
||||
[:section.empty-state
|
||||
[:span (t locale "viewer.frame-not-found")]]
|
||||
|
||||
:else
|
||||
[:*
|
||||
[:& layers-sidebar {:frame frame}]
|
||||
[:div.handoff-svg-wrapper {:on-click (handle-select-frame frame)}
|
||||
[:& render-frame-svg {:frame-id (:id frame)
|
||||
:zoom (:zoom local)
|
||||
:objects objects}]]
|
||||
[:& attributes-sidebar]])]))
|
||||
|
||||
(mf/defc handoff-content
|
||||
[{:keys [data local index] :as props}]
|
||||
(let [container (mf/use-ref)
|
||||
|
||||
[toggle-fullscreen fullscreen?] (hooks/use-fullscreen container)
|
||||
|
||||
on-mouse-wheel
|
||||
(fn [event]
|
||||
(when (kbd/ctrl? event)
|
||||
(dom/prevent-default event)
|
||||
(let [event (.getBrowserEvent ^js event)]
|
||||
(if (pos? (.-deltaY ^js event))
|
||||
(st/emit! dv/decrease-zoom)
|
||||
(st/emit! dv/increase-zoom)))))
|
||||
|
||||
on-mount
|
||||
(fn []
|
||||
;; bind with passive=false to allow the event to be cancelled
|
||||
;; https://stackoverflow.com/a/57582286/3219895
|
||||
(let [key1 (events/listen goog/global EventType.WHEEL
|
||||
on-mouse-wheel #js {"passive" false})]
|
||||
(fn []
|
||||
(events/unlistenByKey key1))))]
|
||||
|
||||
(mf/use-effect on-mount)
|
||||
(hooks/use-shortcuts dv/shortcuts)
|
||||
|
||||
[:div.handoff-layout {:class (classnames :fullscreen fullscreen?)
|
||||
:ref container}
|
||||
[:& header {:data data
|
||||
:toggle-fullscreen toggle-fullscreen
|
||||
:fullscreen? fullscreen?
|
||||
:local local
|
||||
:index index
|
||||
:screen :handoff}]
|
||||
[:div.viewer-content
|
||||
(when (:show-thumbnails local)
|
||||
[:& thumbnails-panel {:index index
|
||||
:data data
|
||||
:screen :handoff}])
|
||||
[:& render-panel {:data data
|
||||
:local local
|
||||
:index index}]]]))
|
||||
|
||||
(mf/defc handoff
|
||||
[{:keys [file-id page-id index] :as props}]
|
||||
(mf/use-effect
|
||||
(mf/deps file-id page-id)
|
||||
(fn []
|
||||
(st/emit! (dv/initialize props))))
|
||||
|
||||
(let [data (mf/deref refs/viewer-data)
|
||||
local (mf/deref refs/viewer-local)]
|
||||
(when data
|
||||
[:& handoff-content {:index index
|
||||
:local local
|
||||
:data data}])))
|
|
@ -0,0 +1,38 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.viewer.handoff.attributes-sidebar
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.components.tab-container :refer [tab-container tab-element]]))
|
||||
|
||||
(mf/defc info-panel []
|
||||
[:div.element-options])
|
||||
|
||||
(mf/defc code-panel []
|
||||
[:div.element-options])
|
||||
|
||||
(mf/defc attributes-sidebar []
|
||||
(let [section (mf/use-state :info #_:code)]
|
||||
[:aside.settings-bar.settings-bar-right
|
||||
[:div.settings-bar-inside
|
||||
[:div.tool-window
|
||||
[:div.tool-window-bar.big
|
||||
[:span.tool-window-bar-icon i/text]
|
||||
[:span.tool-window-bar-title "Text"]]
|
||||
[:div.tool-window-content
|
||||
[:& tab-container {:on-change-tab #(reset! section %)
|
||||
:selected @section}
|
||||
[:& tab-element {:id :info :title "Info"}
|
||||
[:& info-panel]]
|
||||
|
||||
[:& tab-element {:id :code :title "Code"}
|
||||
[:& code-panel]]]]]]]))
|
||||
|
121
frontend/src/app/main/ui/viewer/handoff/layers_sidebar.cljs
Normal file
121
frontend/src/app/main/ui/viewer/handoff/layers_sidebar.cljs
Normal file
|
@ -0,0 +1,121 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.viewer.handoff.layers-sidebar
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[okulary.core :as l]
|
||||
[app.common.data :as d]
|
||||
[app.common.uuid :as uuid]
|
||||
[app.main.store :as st]
|
||||
[app.util.dom :as dom]
|
||||
[app.main.data.viewer :as dv]
|
||||
[app.main.ui.icons :as i]
|
||||
[app.main.ui.keyboard :as kbd]
|
||||
[app.main.ui.workspace.sidebar.layers :refer [element-icon layer-name frame-wrapper]]))
|
||||
|
||||
(def selected-shapes
|
||||
(l/derived (comp :selected :viewer-local) st/state))
|
||||
|
||||
(def page-ref
|
||||
(l/derived (comp :page :viewer-data) st/state))
|
||||
|
||||
(defn- make-collapsed-iref
|
||||
[id]
|
||||
#(-> (l/in [:viewer-local :collapsed id])
|
||||
(l/derived st/state) ))
|
||||
|
||||
(mf/defc layer-item
|
||||
[{:keys [index item selected objects disable-collapse?] :as props}]
|
||||
(let [id (:id item)
|
||||
selected? (contains? selected id)
|
||||
item-ref (mf/use-ref nil)
|
||||
collapsed-iref (mf/use-memo
|
||||
(mf/deps id)
|
||||
(make-collapsed-iref id))
|
||||
|
||||
expanded? (not (mf/deref collapsed-iref))
|
||||
|
||||
toggle-collapse
|
||||
(fn [event]
|
||||
(dom/stop-propagation event)
|
||||
(if (and expanded? (kbd/shift? event))
|
||||
(st/emit! (dv/collapse-all))
|
||||
(st/emit! (dv/toggle-collapse id))))
|
||||
|
||||
select-shape
|
||||
(fn [event]
|
||||
(dom/prevent-default event)
|
||||
(let [id (:id item)]
|
||||
(st/emit! (dv/select-shape id))
|
||||
#_(cond
|
||||
(or (:blocked item)
|
||||
(:hidden item))
|
||||
nil
|
||||
|
||||
(.-shiftKey event)
|
||||
(st/emit! (dv/select-shape id true))
|
||||
|
||||
(> (count selected) 1)
|
||||
(st/emit! (dv/deselect-all)
|
||||
(dv/select-shape id))
|
||||
:else
|
||||
(st/emit! (dv/deselect-all)
|
||||
(dv/select-shape id)))))
|
||||
]
|
||||
|
||||
(mf/use-effect
|
||||
(mf/deps selected)
|
||||
(fn []
|
||||
(when (and (= (count selected) 1) selected?)
|
||||
(.scrollIntoView (mf/ref-val item-ref) false))))
|
||||
|
||||
[:li {:ref item-ref
|
||||
:class (dom/classnames
|
||||
:component (not (nil? (:component-id item)))
|
||||
:masked (:masked-group? item)
|
||||
:selected selected?)}
|
||||
|
||||
[:div.element-list-body {:class (dom/classnames :selected selected?
|
||||
:icon-layer (= (:type item) :icon))
|
||||
:on-click select-shape}
|
||||
[:& element-icon {:shape item}]
|
||||
[:& layer-name {:shape item}]
|
||||
|
||||
(when (and (not disable-collapse?) (:shapes item))
|
||||
[:span.toggle-content
|
||||
{:on-click toggle-collapse
|
||||
:class (when expanded? "inverse")}
|
||||
i/arrow-slide])]
|
||||
|
||||
(when (and (:shapes item) expanded?)
|
||||
[:ul.element-children
|
||||
(for [[index id] (reverse (d/enumerate (:shapes item)))]
|
||||
(when-let [item (get objects id)]
|
||||
[:& layer-item
|
||||
{:item item
|
||||
:selected selected
|
||||
:index index
|
||||
:objects objects
|
||||
:key (:id item)}]))])]))
|
||||
|
||||
(mf/defc layers-sidebar [{:keys [frame]}]
|
||||
(let [page (mf/deref page-ref)
|
||||
selected (mf/deref selected-shapes)
|
||||
objects (:objects page)]
|
||||
|
||||
[:aside.settings-bar.settings-bar-left
|
||||
[:div.settings-bar-inside
|
||||
[:ul.element-list
|
||||
[:& layer-item
|
||||
{:item frame
|
||||
:selected selected
|
||||
:index 0
|
||||
:objects objects
|
||||
:disable-collapse? true}]]]]))
|
170
frontend/src/app/main/ui/viewer/handoff/render.cljs
Normal file
170
frontend/src/app/main/ui/viewer/handoff/render.cljs
Normal file
|
@ -0,0 +1,170 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.viewer.handoff.render
|
||||
"The main container for a frame in handoff mode"
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[app.util.object :as obj]
|
||||
[app.util.dom :as dom]
|
||||
[app.common.data :as d]
|
||||
[app.common.pages :as cp]
|
||||
[app.common.pages-helpers :as cph]
|
||||
[app.common.geom.matrix :as gmt]
|
||||
[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as geom]
|
||||
[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
[app.main.data.viewer :as dv]
|
||||
[app.main.ui.shapes.filters :as filters]
|
||||
[app.main.ui.shapes.circle :as circle]
|
||||
[app.main.ui.shapes.frame :as frame]
|
||||
[app.main.ui.shapes.group :as group]
|
||||
[app.main.ui.shapes.icon :as icon]
|
||||
[app.main.ui.shapes.image :as image]
|
||||
[app.main.ui.shapes.path :as path]
|
||||
[app.main.ui.shapes.rect :as rect]
|
||||
[app.main.ui.shapes.text :as text]
|
||||
[app.main.ui.viewer.handoff.selection-feedback :refer [selection-feedback]]
|
||||
[app.main.ui.shapes.shape :refer [shape-container]]))
|
||||
|
||||
(declare shape-container-factory)
|
||||
|
||||
(defn handle-hover-shape [{:keys [type id]} hover?]
|
||||
#(when-not (#{:group :frame} type)
|
||||
(do
|
||||
(dom/prevent-default %)
|
||||
(dom/stop-propagation %)
|
||||
(st/emit! (dv/hover-shape id hover?)))))
|
||||
|
||||
(defn select-shape [{:keys [type id]}]
|
||||
#(when-not (#{:group :frame} type)
|
||||
(dom/prevent-default %)
|
||||
(dom/stop-propagation %)
|
||||
(st/emit! (dv/select-shape id))))
|
||||
|
||||
(defn shape-wrapper-factory
|
||||
[component]
|
||||
(mf/fnc shape-wrapper
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (unchecked-get props "childs")
|
||||
frame (unchecked-get props "frame")]
|
||||
|
||||
[:> shape-container {:shape shape
|
||||
:on-mouse-enter (handle-hover-shape shape true)
|
||||
:on-mouse-leave (handle-hover-shape shape false)
|
||||
:on-click (select-shape shape)}
|
||||
[:& component {:shape shape
|
||||
:frame frame
|
||||
:childs childs
|
||||
:is-child-selected? true}]])))
|
||||
|
||||
(defn frame-container-factory
|
||||
[objects]
|
||||
(let [shape-container (shape-container-factory objects)
|
||||
frame-shape (frame/frame-shape shape-container)
|
||||
frame-wrapper (shape-wrapper-factory frame-shape)]
|
||||
(mf/fnc frame-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
shape (geom/transform-shape shape)
|
||||
|
||||
props (-> (obj/new)
|
||||
(obj/merge! props)
|
||||
(obj/merge! #js {:shape shape
|
||||
:childs childs}))]
|
||||
[:> frame-wrapper props]))))
|
||||
|
||||
(defn group-container-factory
|
||||
[objects]
|
||||
(let [shape-container (shape-container-factory objects)
|
||||
group-shape (group/group-shape shape-container)
|
||||
group-wrapper (shape-wrapper-factory group-shape)]
|
||||
(mf/fnc group-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
childs (mapv #(get objects %) (:shapes shape))
|
||||
props (-> (obj/new)
|
||||
(obj/merge! props)
|
||||
(obj/merge! #js {:childs childs}))]
|
||||
[:> group-wrapper props]))))
|
||||
|
||||
(defn shape-container-factory
|
||||
[objects show-interactions?]
|
||||
(let [path-wrapper (shape-wrapper-factory path/path-shape)
|
||||
text-wrapper (shape-wrapper-factory text/text-shape)
|
||||
icon-wrapper (shape-wrapper-factory icon/icon-shape)
|
||||
rect-wrapper (shape-wrapper-factory rect/rect-shape)
|
||||
image-wrapper (shape-wrapper-factory image/image-shape)
|
||||
circle-wrapper (shape-wrapper-factory circle/circle-shape)]
|
||||
(mf/fnc shape-container
|
||||
{::mf/wrap-props false}
|
||||
[props]
|
||||
(let [shape (unchecked-get props "shape")
|
||||
frame (unchecked-get props "frame")
|
||||
group-container (mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(group-container-factory objects))]
|
||||
(when (and shape (not (:hidden shape)))
|
||||
(let [shape (geom/transform-shape frame shape)
|
||||
opts #js {:shape shape
|
||||
:frame frame}]
|
||||
(case (:type shape)
|
||||
:curve [:> path-wrapper opts]
|
||||
:text [:> text-wrapper opts]
|
||||
:icon [:> icon-wrapper opts]
|
||||
:rect [:> rect-wrapper opts]
|
||||
:path [:> path-wrapper opts]
|
||||
:image [:> image-wrapper opts]
|
||||
:circle [:> circle-wrapper opts]
|
||||
:group [:> group-container opts])))))))
|
||||
|
||||
(defn adjust-frame-position [frame-id objects]
|
||||
(let [frame (get objects frame-id)
|
||||
modifier (-> (gpt/point (:x frame) (:y frame))
|
||||
(gpt/negate)
|
||||
(gmt/translate-matrix))
|
||||
|
||||
update-fn #(assoc-in %1 [%2 :modifiers :displacement] modifier)
|
||||
modifier-ids (d/concat [frame-id] (cph/get-children frame-id objects))]
|
||||
(reduce update-fn objects modifier-ids)))
|
||||
|
||||
(defn make-vbox [frame]
|
||||
(str "0 0 " (:width frame 0) " " (:height frame 0)))
|
||||
|
||||
(mf/defc render-frame-svg
|
||||
{::mf/wrap [mf/memo]}
|
||||
[{:keys [objects frame-id zoom] :or {zoom 1} :as props}]
|
||||
|
||||
(let [objects (adjust-frame-position frame-id objects)
|
||||
frame (get objects frame-id)
|
||||
width (* (:width frame) zoom)
|
||||
height (* (:height frame) zoom)
|
||||
vbox (make-vbox frame)
|
||||
render-frame (mf/use-memo
|
||||
(mf/deps objects)
|
||||
#(frame-container-factory objects))]
|
||||
|
||||
[:svg {:view-box vbox
|
||||
:width width
|
||||
:height height
|
||||
:version "1.1"
|
||||
:xmlnsXlink "http://www.w3.org/1999/xlink"
|
||||
:xmlns "http://www.w3.org/2000/svg"}
|
||||
|
||||
[:& render-frame {:shape frame
|
||||
:view-box vbox}]
|
||||
|
||||
[:& selection-feedback {:frame frame}]]))
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||
;; defined by the Mozilla Public License, v. 2.0.
|
||||
;;
|
||||
;; Copyright (c) 2020 UXBOX Labs SL
|
||||
|
||||
(ns app.main.ui.viewer.handoff.selection-feedback
|
||||
(:require
|
||||
[rumext.alpha :as mf]
|
||||
[okulary.core :as l]
|
||||
#_[app.util.object :as obj]
|
||||
#_[app.common.data :as d]
|
||||
#_[app.common.pages :as cp]
|
||||
#_[app.common.pages-helpers :as cph]
|
||||
#_[app.common.geom.matrix :as gmt]
|
||||
#_[app.common.geom.point :as gpt]
|
||||
[app.common.geom.shapes :as gsh]
|
||||
#_[app.main.refs :as refs]
|
||||
[app.main.store :as st]
|
||||
#_[app.main.data.viewer :as dv]
|
||||
#_[app.main.ui.shapes.filters :as filters]
|
||||
#_[app.main.ui.shapes.circle :as circle]
|
||||
#_[app.main.ui.shapes.frame :as frame]
|
||||
#_[app.main.ui.shapes.group :as group]
|
||||
#_[app.main.ui.shapes.icon :as icon]
|
||||
#_[app.main.ui.shapes.image :as image]
|
||||
#_[app.main.ui.shapes.path :as path]
|
||||
#_[app.main.ui.shapes.rect :as rect]
|
||||
#_[app.main.ui.shapes.text :as text]
|
||||
#_[app.main.ui.shapes.shape :refer [shape-container]]))
|
||||
|
||||
(def selection-rect-color-normal "#1FDEA7")
|
||||
(def selection-rect-color-component "#00E0FF")
|
||||
(def selection-rect-width 1)
|
||||
|
||||
#_(def hover-ref
|
||||
(l/derived (l/in [:viewer-local :hover]) st/state))
|
||||
|
||||
(defn make-hover-shapes-iref
|
||||
[]
|
||||
(let [hover->shapes
|
||||
(fn [state]
|
||||
(let [hover (get-in state [:viewer-local :hover])
|
||||
objects (get-in state [:viewer-data :page :objects])
|
||||
resolve-shape #(get objects %)]
|
||||
(map resolve-shape hover)))]
|
||||
#(l/derived hover->shapes st/state)))
|
||||
|
||||
(mf/defc selection-feedback [{:keys [frame]}]
|
||||
(let [hover-shapes-ref (mf/use-memo (make-hover-shapes-iref))
|
||||
hover-shapes (->> (mf/deref hover-shapes-ref)
|
||||
(map #(gsh/translate-to-frame % frame)))]
|
||||
(for [shape hover-shapes]
|
||||
(let [{:keys [x y width height]} (:selrect shape)]
|
||||
[:rect {:x x
|
||||
:y y
|
||||
:width width
|
||||
:height height
|
||||
:fill "transparent"
|
||||
:stroke selection-rect-color-normal
|
||||
:stroke-width selection-rect-width
|
||||
:pointer-events "none"}]))))
|
|
@ -122,7 +122,7 @@
|
|||
(t locale "viewer.header.share.create-link")])]]]]))
|
||||
|
||||
(mf/defc header
|
||||
[{:keys [data index local fullscreen? toggle-fullscreen] :as props}]
|
||||
[{:keys [data index local fullscreen? toggle-fullscreen screen] :as props}]
|
||||
(let [{:keys [project file page frames]} data
|
||||
total (count frames)
|
||||
on-click #(st/emit! dv/toggle-thumbnails-panel)
|
||||
|
@ -141,7 +141,14 @@
|
|||
on-edit #(st/emit! (rt/nav :workspace
|
||||
{:project-id project-id
|
||||
:file-id file-id}
|
||||
{:page-id page-id}))]
|
||||
{:page-id page-id}))
|
||||
|
||||
change-screen
|
||||
(fn [screen]
|
||||
(st/emit!
|
||||
(rt/nav screen
|
||||
{:file-id file-id :page-id page-id}
|
||||
{:index index})))]
|
||||
[:header.viewer-header
|
||||
[:div.main-icon
|
||||
[:a {:on-click on-edit} i/logo-icon]]
|
||||
|
@ -156,6 +163,14 @@
|
|||
[:span.dropdown-button i/arrow-down]
|
||||
[:span.counters (str (inc index) " / " total)]]
|
||||
|
||||
[:div.mode-zone
|
||||
[:button.mode-zone-button {:on-click #(when (not= screen :viewer)
|
||||
(change-screen :viewer))
|
||||
:class (when (= screen :viewer) "active")} i/play]
|
||||
[:button.mode-zone-button {:on-click #(when (not= screen :handoff)
|
||||
(change-screen :handoff))
|
||||
:class (when (= screen :handoff) "active")} i/code]]
|
||||
|
||||
[:div.options-zone
|
||||
[:& interactions-menu {:interactions-mode interactions-mode}]
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@
|
|||
[:span.name (:name frame)]]])
|
||||
|
||||
(mf/defc thumbnails-panel
|
||||
[{:keys [data index] :as props}]
|
||||
[{:keys [data index screen] :as props}]
|
||||
(let [expanded? (mf/use-state false)
|
||||
container (mf/use-ref)
|
||||
page-id (get-in data [:page :id])
|
||||
|
@ -111,7 +111,7 @@
|
|||
on-item-click
|
||||
(fn [event index]
|
||||
(compare-and-set! selected false true)
|
||||
(st/emit! (rt/nav :viewer {:file-id file-id
|
||||
(st/emit! (rt/nav screen {:file-id file-id
|
||||
:page-id page-id} {:index index}))
|
||||
(when @expanded?
|
||||
(on-close)))]
|
||||
|
|
|
@ -331,8 +331,7 @@
|
|||
(mf/defc layers-toolbox
|
||||
{:wrap [mf/memo]}
|
||||
[]
|
||||
(let [locale (mf/deref i18n/locale)
|
||||
page (mf/deref refs/workspace-page)]
|
||||
(let [page (mf/deref refs/workspace-page)]
|
||||
[:div#layers.tool-window
|
||||
[:div.tool-window-bar
|
||||
[:div.tool-window-icon i/layers]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue